Merge pull request #1031 from mat007/kubernetes-orchestrator-all

Better stack list experience with Kubernetes and Swarm
This commit is contained in:
Vincent Demeester 2018-05-15 17:26:41 +02:00 committed by GitHub
commit 5a5b880251
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 196 additions and 101 deletions

View File

@ -244,7 +244,17 @@ type ClientInfo struct {
// HasKubernetes checks if kubernetes orchestrator is enabled // HasKubernetes checks if kubernetes orchestrator is enabled
func (c ClientInfo) HasKubernetes() bool { 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. // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.

View File

@ -171,6 +171,7 @@ func TestOrchestratorSwitch(t *testing.T) {
flagOrchestrator string flagOrchestrator string
expectedOrchestrator string expectedOrchestrator string
expectedKubernetes bool expectedKubernetes bool
expectedSwarm bool
}{ }{
{ {
doc: "default", doc: "default",
@ -179,6 +180,7 @@ func TestOrchestratorSwitch(t *testing.T) {
}`, }`,
expectedOrchestrator: "swarm", expectedOrchestrator: "swarm",
expectedKubernetes: false, expectedKubernetes: false,
expectedSwarm: true,
}, },
{ {
doc: "kubernetesIsExperimental", doc: "kubernetesIsExperimental",
@ -190,6 +192,7 @@ func TestOrchestratorSwitch(t *testing.T) {
flagOrchestrator: "kubernetes", flagOrchestrator: "kubernetes",
expectedOrchestrator: "swarm", expectedOrchestrator: "swarm",
expectedKubernetes: false, expectedKubernetes: false,
expectedSwarm: true,
}, },
{ {
doc: "kubernetesConfigFile", doc: "kubernetesConfigFile",
@ -199,6 +202,7 @@ func TestOrchestratorSwitch(t *testing.T) {
}`, }`,
expectedOrchestrator: "kubernetes", expectedOrchestrator: "kubernetes",
expectedKubernetes: true, expectedKubernetes: true,
expectedSwarm: false,
}, },
{ {
doc: "kubernetesEnv", doc: "kubernetesEnv",
@ -208,6 +212,7 @@ func TestOrchestratorSwitch(t *testing.T) {
envOrchestrator: "kubernetes", envOrchestrator: "kubernetes",
expectedOrchestrator: "kubernetes", expectedOrchestrator: "kubernetes",
expectedKubernetes: true, expectedKubernetes: true,
expectedSwarm: false,
}, },
{ {
doc: "kubernetesFlag", doc: "kubernetesFlag",
@ -217,6 +222,17 @@ func TestOrchestratorSwitch(t *testing.T) {
flagOrchestrator: "kubernetes", flagOrchestrator: "kubernetes",
expectedOrchestrator: "kubernetes", expectedOrchestrator: "kubernetes",
expectedKubernetes: true, expectedKubernetes: true,
expectedSwarm: false,
},
{
doc: "allOrchestratorFlag",
configfile: `{
"experimental": "enabled"
}`,
flagOrchestrator: "all",
expectedOrchestrator: "all",
expectedKubernetes: true,
expectedSwarm: true,
}, },
{ {
doc: "envOverridesConfigFile", doc: "envOverridesConfigFile",
@ -227,6 +243,7 @@ func TestOrchestratorSwitch(t *testing.T) {
envOrchestrator: "swarm", envOrchestrator: "swarm",
expectedOrchestrator: "swarm", expectedOrchestrator: "swarm",
expectedKubernetes: false, expectedKubernetes: false,
expectedSwarm: true,
}, },
{ {
doc: "flagOverridesEnv", doc: "flagOverridesEnv",
@ -237,6 +254,7 @@ func TestOrchestratorSwitch(t *testing.T) {
flagOrchestrator: "swarm", flagOrchestrator: "swarm",
expectedOrchestrator: "swarm", expectedOrchestrator: "swarm",
expectedKubernetes: false, expectedKubernetes: false,
expectedSwarm: true,
}, },
} }
@ -260,6 +278,7 @@ func TestOrchestratorSwitch(t *testing.T) {
err := cli.Initialize(options) err := cli.Initialize(options)
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(testcase.expectedKubernetes, cli.ClientInfo().HasKubernetes())) 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))) assert.Check(t, is.Equal(testcase.expectedOrchestrator, string(cli.ClientInfo().Orchestrator)))
}) })
} }

View File

@ -13,6 +13,8 @@ const (
OrchestratorKubernetes = Orchestrator("kubernetes") OrchestratorKubernetes = Orchestrator("kubernetes")
// OrchestratorSwarm orchestrator // OrchestratorSwarm orchestrator
OrchestratorSwarm = Orchestrator("swarm") OrchestratorSwarm = Orchestrator("swarm")
// OrchestratorAll orchestrator
OrchestratorAll = Orchestrator("all")
orchestratorUnset = Orchestrator("unset") orchestratorUnset = Orchestrator("unset")
defaultOrchestrator = OrchestratorSwarm defaultOrchestrator = OrchestratorSwarm
@ -27,8 +29,10 @@ func normalize(value string) (Orchestrator, error) {
return OrchestratorSwarm, nil return OrchestratorSwarm, nil
case "": case "":
return orchestratorUnset, nil return orchestratorUnset, nil
case "all":
return OrchestratorAll, nil
default: 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)
} }
} }

View File

@ -1,11 +1,15 @@
package stack package stack
import ( import (
"fmt"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var errUnsupportedAllOrchestrator = fmt.Errorf(`no orchestrator specified: use either "kubernetes" or "swarm"`)
// NewStackCommand returns a cobra command for `stack` subcommands // NewStackCommand returns a cobra command for `stack` subcommands
func NewStackCommand(dockerCli command.Cli) *cobra.Command { func NewStackCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -27,9 +31,6 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
newServicesCommand(dockerCli), newServicesCommand(dockerCli),
) )
flags := cmd.PersistentFlags() 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.String("kubeconfig", "", "Kubernetes config file")
flags.SetAnnotation("kubeconfig", "kubernetes", nil) flags.SetAnnotation("kubeconfig", "kubernetes", nil)
flags.SetAnnotation("kubeconfig", "experimentalCLI", nil) flags.SetAnnotation("kubeconfig", "experimentalCLI", nil)

View File

@ -19,14 +19,18 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.ExactArgs(1), Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0] 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())) kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags()))
if err != nil { if err != nil {
return err return err
} }
return kubernetes.RunDeploy(kli, opts) return kubernetes.RunDeploy(kli, opts)
default:
return swarm.RunDeploy(dockerCli, opts)
} }
return swarm.RunDeploy(dockerCli, opts)
}, },
} }
@ -45,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+`")`) `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", "version", []string{"1.30"})
flags.SetAnnotation("resolve-image", "swarm", nil) flags.SetAnnotation("resolve-image", "swarm", nil)
kubernetes.AddNamespaceFlag(flags)
return cmd return cmd
} }

View File

@ -34,9 +34,15 @@ func NewOptions(flags *flag.FlagSet) Options {
return opts 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 // WrapCli wraps command.Cli with kubernetes specifics
func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) { func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) {
var err error
cli := &KubeCli{ cli := &KubeCli{
Cli: dockerCli, Cli: dockerCli,
} }

View File

@ -1,44 +1,30 @@
package kubernetes package kubernetes
import ( import (
"sort" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/cli/command/stack/options"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"vbom.ml/util/sortorder"
) )
// RunList is the kubernetes implementation of docker stack ls // GetStacks lists the kubernetes stacks
func RunList(dockerCli *KubeCli, opts options.List) error { func GetStacks(dockerCli command.Cli, opts options.List, kopts Options) ([]*formatter.Stack, error) {
stacks, err := getStacks(dockerCli, opts.AllNamespaces) kubeCli, err := WrapCli(dockerCli, kopts)
if err != nil { if err != nil {
return err return nil, err
} }
format := opts.Format if opts.AllNamespaces || len(opts.Namespaces) == 0 {
if format == "" || format == formatter.TableFormatKey { return getStacks(kubeCli, opts)
format = formatter.KubernetesStackTableFormat
} }
stackCtx := formatter.Context{ return getStacksWithNamespaces(kubeCli, opts)
Output: dockerCli.Out(),
Format: formatter.Format(format),
}
sort.Sort(byName(stacks))
return formatter.StackWrite(stackCtx, stacks)
} }
type byName []*formatter.Stack func getStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) {
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) {
composeClient, err := kubeCli.composeClient() composeClient, err := kubeCli.composeClient()
if err != nil { if err != nil {
return nil, err return nil, err
} }
stackSvc, err := composeClient.Stacks(allNamespaces) stackSvc, err := composeClient.Stacks(opts.AllNamespaces)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -57,3 +43,28 @@ func getStacks(kubeCli *KubeCli, allNamespaces bool) ([]*formatter.Stack, error)
} }
return formattedStacks, nil 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
}

View File

@ -1,12 +1,16 @@
package stack package stack
import ( import (
"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/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"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"vbom.ml/util/sortorder"
) )
func newListCommand(dockerCli command.Cli) *cobra.Command { func newListCommand(dockerCli command.Cli) *cobra.Command {
@ -18,20 +22,56 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
Short: "List stacks", Short: "List stacks",
Args: cli.NoArgs, Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if dockerCli.ClientInfo().HasKubernetes() { return runList(cmd, dockerCli, opts)
kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags()))
if err != nil {
return err
}
return kubernetes.RunList(kli, opts)
}
return swarm.RunList(dockerCli, opts)
}, },
} }
flags := cmd.Flags() flags := cmd.Flags()
flags.StringVar(&opts.Format, "format", "", "Pretty-print stacks using a Go template") 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", "kubernetes", nil)
flags.SetAnnotation("all-namespaces", "experimentalCLI", nil)
return cmd 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() {
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
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) ||
!sortorder.NaturalLess(stacks[j].Name, stacks[i].Name) &&
sortorder.NaturalLess(stacks[j].Namespace, stacks[i].Namespace)
})
return formatter.StackWrite(stackCtx, stacks)
}

View File

@ -48,7 +48,7 @@ func TestListErrors(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
cmd := newListCommand(test.NewFakeCli(&fakeClient{ cmd := newListCommand(test.NewFakeCli(&fakeClient{
serviceListFunc: tc.serviceListFunc, serviceListFunc: tc.serviceListFunc,
})) }, test.OrchestratorSwarm))
cmd.SetArgs(tc.args) cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
for key, value := range tc.flags { for key, value := range tc.flags {
@ -59,16 +59,17 @@ func TestListErrors(t *testing.T) {
} }
func TestListWithFormat(t *testing.T) { func TestListWithFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{ cli := test.NewFakeCli(
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { &fakeClient{
return []swarm.Service{ serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
*Service( return []swarm.Service{
ServiceLabels(map[string]string{ *Service(
"com.docker.stack.namespace": "service-name-foo", ServiceLabels(map[string]string{
}), "com.docker.stack.namespace": "service-name-foo",
)}, nil }),
}, )}, nil
}) },
}, test.OrchestratorSwarm)
cmd := newListCommand(cli) cmd := newListCommand(cli)
cmd.Flags().Set("format", "{{ .Name }}") cmd.Flags().Set("format", "{{ .Name }}")
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -85,7 +86,7 @@ func TestListWithoutFormat(t *testing.T) {
}), }),
)}, nil )}, nil
}, },
}) }, test.OrchestratorSwarm)
cmd := newListCommand(cli) cmd := newListCommand(cli)
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-list-without-format.golden") 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) { serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return uc.swarmServices, nil return uc.swarmServices, nil
}, },
}) }, test.OrchestratorSwarm)
cmd := newListCommand(cli) cmd := newListCommand(cli)
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), uc.golden) golden.Assert(t, cli.OutBuffer().String(), uc.golden)

View File

@ -16,6 +16,7 @@ type Deploy struct {
type List struct { type List struct {
Format string Format string
AllNamespaces bool AllNamespaces bool
Namespaces []string
} }
// PS holds docker stack ps options // PS holds docker stack ps options

View File

@ -19,14 +19,18 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.ExactArgs(1), Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0] 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())) kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags()))
if err != nil { if err != nil {
return err return err
} }
return kubernetes.RunPS(kli, opts) return kubernetes.RunPS(kli, opts)
default:
return swarm.RunPS(dockerCli, opts)
} }
return swarm.RunPS(dockerCli, opts)
}, },
} }
flags := cmd.Flags() flags := cmd.Flags()
@ -36,6 +40,6 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command {
flags.SetAnnotation("filter", "swarm", nil) flags.SetAnnotation("filter", "swarm", nil)
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display task IDs") flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display task IDs")
flags.StringVar(&opts.Format, "format", "", "Pretty-print tasks using a Go template") flags.StringVar(&opts.Format, "format", "", "Pretty-print tasks using a Go template")
kubernetes.AddNamespaceFlag(flags)
return cmd return cmd
} }

View File

@ -19,15 +19,21 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMinArgs(1), Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespaces = args 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())) kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags()))
if err != nil { if err != nil {
return err return err
} }
return kubernetes.RunRemove(kli, opts) return kubernetes.RunRemove(kli, opts)
default:
return swarm.RunRemove(dockerCli, opts)
} }
return swarm.RunRemove(dockerCli, opts)
}, },
} }
flags := cmd.Flags()
kubernetes.AddNamespaceFlag(flags)
return cmd return cmd
} }

View File

@ -19,14 +19,18 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.ExactArgs(1), Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0] 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())) kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags()))
if err != nil { if err != nil {
return err return err
} }
return kubernetes.RunServices(kli, opts) return kubernetes.RunServices(kli, opts)
default:
return swarm.RunServices(dockerCli, opts)
} }
return swarm.RunServices(dockerCli, opts)
}, },
} }
flags := cmd.Flags() flags := cmd.Flags()
@ -34,6 +38,6 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVar(&opts.Format, "format", "", "Pretty-print services using a Go template") 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.VarP(&opts.Filter, "filter", "f", "Filter output based on conditions provided")
flags.SetAnnotation("filter", "swarm", nil) flags.SetAnnotation("filter", "swarm", nil)
kubernetes.AddNamespaceFlag(flags)
return cmd return cmd
} }

View File

@ -2,48 +2,18 @@ package swarm
import ( import (
"context" "context"
"sort"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter" "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/cli/cli/compose/convert"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pkg/errors" "github.com/pkg/errors"
"vbom.ml/util/sortorder"
) )
// RunList is the swarm implementation of docker stack ls // GetStacks lists the swarm stacks.
func RunList(dockerCli command.Cli, opts options.List) error { func GetStacks(dockerCli command.Cli) ([]*formatter.Stack, error) {
client := dockerCli.Client() services, err := dockerCli.Client().ServiceList(
ctx := context.Background() 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,
types.ServiceListOptions{Filters: getAllStacksFilter()}) types.ServiceListOptions{Filters: getAllStacksFilter()})
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -6,7 +6,6 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/docker/cli/cli/command"
"github.com/gotestyourself/gotestyourself/assert" "github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "github.com/gotestyourself/gotestyourself/assert/cmp"
@ -39,8 +38,7 @@ func fakeServerVersion(_ context.Context) (types.Version, error) {
} }
func TestVersionWithOrchestrator(t *testing.T) { func TestVersionWithOrchestrator(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{serverVersion: fakeServerVersion}) cli := test.NewFakeCli(&fakeClient{serverVersion: fakeServerVersion}, test.OrchestratorSwarm)
cli.SetClientInfo(func() command.ClientInfo { return command.ClientInfo{Orchestrator: "swarm"} })
cmd := NewVersionCommand(cli) cmd := NewVersionCommand(cli)
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
assert.Check(t, is.Contains(cleanTabs(cli.OutBuffer().String()), "Orchestrator: swarm")) assert.Check(t, is.Contains(cleanTabs(cli.OutBuffer().String()), "Orchestrator: swarm"))

View File

@ -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.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.TLS, "tls", dockerTLS, "Use TLS; implied by --tlsverify")
flags.BoolVar(&commonOpts.TLSVerify, FlagTLSVerify, dockerTLSVerify, "Use TLS and verify the remote") 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) flags.SetAnnotation("orchestrator", "experimentalCLI", nil)
// TODO use flag flags.String("identity"}, "i", "", "Path to libtrust key file") // TODO use flag flags.String("identity"}, "i", "", "Path to libtrust key file")

View File

@ -19,7 +19,7 @@ func TestDeployWithNamedResources(t *testing.T) {
result.Assert(t, icmd.Success) result.Assert(t, icmd.Success)
stdout := strings.Split(result.Stdout(), "\n") 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(stdout)
sort.Strings(expected) sort.Strings(expected)
assert.DeepEqual(t, stdout, expected) assert.DeepEqual(t, stdout, expected)

View File

@ -167,3 +167,18 @@ func (c *FakeCli) ContentTrustEnabled() bool {
func EnableContentTrust(c *FakeCli) { func EnableContentTrust(c *FakeCli) {
c.contentTrust = true 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"} })
}