diff --git a/cli/command/stack/cmd.go b/cli/command/stack/cmd.go index 5e4fc2e954..dddafc37fe 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 9aedcedab5..38837c6f11 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 9749a41e91..018dd05e21 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 155134e348..27b2a81b7c 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 ae94c4bd1f..26737ec132 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 f620fe54a9..afcecd9961 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 96cb9a7a1e..be446c1f31 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 00242507d7..3f34cfc307 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 c218d99522..09e9310e8f 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 }