From 0d7bb8adaaf98335d260f73b179fea304e51ba66 Mon Sep 17 00:00:00 2001 From: Mathieu Champlon Date: Wed, 25 Apr 2018 14:19:34 +0200 Subject: [PATCH 1/3] Fix typo in golden file name Signed-off-by: Mathieu Champlon --- e2e/stack/deploy_test.go | 2 +- ...-deploy-with-nanes.golden => stack-deploy-with-names.golden} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename e2e/stack/testdata/{stack-deploy-with-nanes.golden => stack-deploy-with-names.golden} (100%) diff --git a/e2e/stack/deploy_test.go b/e2e/stack/deploy_test.go index 086af3ea1620..27790acd462a 100644 --- a/e2e/stack/deploy_test.go +++ b/e2e/stack/deploy_test.go @@ -19,7 +19,7 @@ func TestDeployWithNamedResources(t *testing.T) { result.Assert(t, icmd.Success) stdout := strings.Split(result.Stdout(), "\n") - expected := strings.Split(string(golden.Get(t, "stack-deploy-with-nanes.golden")), "\n") + expected := strings.Split(string(golden.Get(t, "stack-deploy-with-names.golden")), "\n") sort.Strings(stdout) sort.Strings(expected) assert.DeepEqual(t, stdout, expected) diff --git a/e2e/stack/testdata/stack-deploy-with-nanes.golden b/e2e/stack/testdata/stack-deploy-with-names.golden similarity index 100% rename from e2e/stack/testdata/stack-deploy-with-nanes.golden rename to e2e/stack/testdata/stack-deploy-with-names.golden From 4d947de2927ab35a734a0c816a01f54744d3d429 Mon Sep 17 00:00:00 2001 From: Mathieu Champlon Date: Wed, 25 Apr 2018 14:24:46 +0200 Subject: [PATCH 2/3] 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 344fddd61ee6..29c7af2129c7 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 36d6f098ec72..e5aa0f908dcf 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 b15c8f41bf93..b8224edb182f 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 5fe079583642..5e4fc2e95423 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 a21640d054e9..9aedcedab52d 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 cc4b3ef9812a..155134e34867 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 fc96fd679105..ae94c4bd1fb9 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 b3a9bdc3b796..c3d827ccda7f 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 7c003bda8089..96cb9a7a1efb 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 25df2a5b615f..00242507d7b4 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 c05d3e51c109..c218d99522ae 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 76c18432324d..c0c19d0a56ba 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 ea2182563f2a..fccfcfb2e305 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 6a9e79078c24..9c37aed648ef 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 17fab645ea24..fdb88072945e 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"} }) +} From 84241cc393a36848076c2d3bd61fa023858fb16b Mon Sep 17 00:00:00 2001 From: Mathieu Champlon Date: Thu, 26 Apr 2018 11:13:14 +0200 Subject: [PATCH 3/3] Support multiple namespaces for docker stack ls Signed-off-by: Mathieu Champlon --- cli/command/stack/cmd.go | 3 -- cli/command/stack/deploy.go | 1 + cli/command/stack/kubernetes/cli.go | 8 +++++- cli/command/stack/kubernetes/list.go | 41 ++++++++++++++++++++++++++-- cli/command/stack/list.go | 19 ++++++++----- cli/command/stack/options/opts.go | 1 + cli/command/stack/ps.go | 2 +- cli/command/stack/remove.go | 2 ++ cli/command/stack/services.go | 2 +- 9 files changed, 64 insertions(+), 15 deletions(-) diff --git a/cli/command/stack/cmd.go b/cli/command/stack/cmd.go index 5e4fc2e95423..dddafc37fef4 100644 --- a/cli/command/stack/cmd.go +++ b/cli/command/stack/cmd.go @@ -31,9 +31,6 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command { newServicesCommand(dockerCli), ) flags := cmd.PersistentFlags() - flags.String("namespace", "", "Kubernetes namespace to use") - flags.SetAnnotation("namespace", "kubernetes", nil) - flags.SetAnnotation("namespace", "experimentalCLI", nil) flags.String("kubeconfig", "", "Kubernetes config file") flags.SetAnnotation("kubeconfig", "kubernetes", nil) flags.SetAnnotation("kubeconfig", "experimentalCLI", nil) diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index 9aedcedab52d..38837c6f113a 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -49,5 +49,6 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command { `Query the registry to resolve image digest and supported platforms ("`+swarm.ResolveImageAlways+`"|"`+swarm.ResolveImageChanged+`"|"`+swarm.ResolveImageNever+`")`) flags.SetAnnotation("resolve-image", "version", []string{"1.30"}) flags.SetAnnotation("resolve-image", "swarm", nil) + kubernetes.AddNamespaceFlag(flags) return cmd } diff --git a/cli/command/stack/kubernetes/cli.go b/cli/command/stack/kubernetes/cli.go index 9749a41e91a5..018dd05e21f4 100644 --- a/cli/command/stack/kubernetes/cli.go +++ b/cli/command/stack/kubernetes/cli.go @@ -34,9 +34,15 @@ func NewOptions(flags *flag.FlagSet) Options { return opts } +// AddNamespaceFlag adds the namespace flag to the given flag set +func AddNamespaceFlag(flags *flag.FlagSet) { + flags.String("namespace", "", "Kubernetes namespace to use") + flags.SetAnnotation("namespace", "kubernetes", nil) + flags.SetAnnotation("namespace", "experimentalCLI", nil) +} + // WrapCli wraps command.Cli with kubernetes specifics func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) { - var err error cli := &KubeCli{ Cli: dockerCli, } diff --git a/cli/command/stack/kubernetes/list.go b/cli/command/stack/kubernetes/list.go index 155134e34867..27b2a81b7caf 100644 --- a/cli/command/stack/kubernetes/list.go +++ b/cli/command/stack/kubernetes/list.go @@ -1,13 +1,25 @@ package kubernetes import ( + "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/stack/options" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// GetStacks lists the kubernetes stacks. -func GetStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) { +// GetStacks lists the kubernetes stacks +func GetStacks(dockerCli command.Cli, opts options.List, kopts Options) ([]*formatter.Stack, error) { + kubeCli, err := WrapCli(dockerCli, kopts) + if err != nil { + return nil, err + } + if opts.AllNamespaces || len(opts.Namespaces) == 0 { + return getStacks(kubeCli, opts) + } + return getStacksWithNamespaces(kubeCli, opts) +} + +func getStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) { composeClient, err := kubeCli.composeClient() if err != nil { return nil, err @@ -31,3 +43,28 @@ func GetStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) } return formattedStacks, nil } + +func getStacksWithNamespaces(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) { + stacks := []*formatter.Stack{} + for _, namespace := range removeDuplicates(opts.Namespaces) { + kubeCli.kubeNamespace = namespace + ss, err := getStacks(kubeCli, opts) + if err != nil { + return nil, err + } + stacks = append(stacks, ss...) + } + return stacks, nil +} + +func removeDuplicates(namespaces []string) []string { + found := make(map[string]bool) + results := namespaces[:0] + for _, n := range namespaces { + if !found[n] { + results = append(results, n) + found[n] = true + } + } + return results +} diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index ae94c4bd1fb9..26737ec13295 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -28,7 +28,10 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() 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.StringSliceVar(&opts.Namespaces, "namespace", []string{}, "Kubernetes namespaces to use") + flags.SetAnnotation("namespace", "kubernetes", nil) + flags.SetAnnotation("namespace", "experimentalCLI", nil) + flags.BoolVarP(&opts.AllNamespaces, "all-namespaces", "", false, "List stacks from all Kubernetes namespaces") flags.SetAnnotation("all-namespaces", "kubernetes", nil) flags.SetAnnotation("all-namespaces", "experimentalCLI", nil) return cmd @@ -44,16 +47,16 @@ func runList(cmd *cobra.Command, dockerCli command.Cli, opts options.List) error 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) + ss, err := kubernetes.GetStacks(dockerCli, opts, kubernetes.NewOptions(cmd.Flags())) if err != nil { return err } stacks = append(stacks, ss...) } + return format(dockerCli, opts, stacks) +} + +func format(dockerCli command.Cli, opts options.List, stacks []*formatter.Stack) error { format := opts.Format if format == "" || format == formatter.TableFormatKey { format = formatter.SwarmStackTableFormat @@ -66,7 +69,9 @@ func runList(cmd *cobra.Command, dockerCli command.Cli, opts options.List) error Format: formatter.Format(format), } sort.Slice(stacks, func(i, j int) bool { - return sortorder.NaturalLess(stacks[i].Name, stacks[j].Name) + return sortorder.NaturalLess(stacks[i].Name, stacks[j].Name) || + !sortorder.NaturalLess(stacks[j].Name, stacks[i].Name) && + sortorder.NaturalLess(stacks[j].Namespace, stacks[i].Namespace) }) return formatter.StackWrite(stackCtx, stacks) } diff --git a/cli/command/stack/options/opts.go b/cli/command/stack/options/opts.go index f620fe54a9d5..afcecd9961ef 100644 --- a/cli/command/stack/options/opts.go +++ b/cli/command/stack/options/opts.go @@ -16,6 +16,7 @@ type Deploy struct { type List struct { Format string AllNamespaces bool + Namespaces []string } // PS holds docker stack ps options diff --git a/cli/command/stack/ps.go b/cli/command/stack/ps.go index 96cb9a7a1efb..be446c1f31ca 100644 --- a/cli/command/stack/ps.go +++ b/cli/command/stack/ps.go @@ -40,6 +40,6 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command { flags.SetAnnotation("filter", "swarm", nil) flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display task IDs") flags.StringVar(&opts.Format, "format", "", "Pretty-print tasks using a Go template") - + kubernetes.AddNamespaceFlag(flags) return cmd } diff --git a/cli/command/stack/remove.go b/cli/command/stack/remove.go index 00242507d7b4..3f34cfc3072e 100644 --- a/cli/command/stack/remove.go +++ b/cli/command/stack/remove.go @@ -33,5 +33,7 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command { } }, } + flags := cmd.Flags() + kubernetes.AddNamespaceFlag(flags) return cmd } diff --git a/cli/command/stack/services.go b/cli/command/stack/services.go index c218d99522ae..09e9310e8f7e 100644 --- a/cli/command/stack/services.go +++ b/cli/command/stack/services.go @@ -38,6 +38,6 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command { flags.StringVar(&opts.Format, "format", "", "Pretty-print services using a Go template") flags.VarP(&opts.Filter, "filter", "f", "Filter output based on conditions provided") flags.SetAnnotation("filter", "swarm", nil) - + kubernetes.AddNamespaceFlag(flags) return cmd }