mirror of https://github.com/docker/cli.git
Merge pull request #1031 from mat007/kubernetes-orchestrator-all
Better stack list experience with Kubernetes and Swarm
This commit is contained in:
commit
5a5b880251
|
@ -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.
|
||||||
|
|
|
@ -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)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"} })
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue