Merge pull request #1137 from silvin-lubecki/stack-orchestrator

Fix broken swarm commands with Kubernetes defined as orchestrator
This commit is contained in:
Andrew Hsu 2018-06-22 13:56:42 -07:00 committed by GitHub
commit 151990de62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 368 additions and 317 deletions

View File

@ -102,7 +102,7 @@ func managementSubCommands(cmd *cobra.Command) []*cobra.Command {
var usageTemplate = `Usage: var usageTemplate = `Usage:
{{- if not .HasSubCommands}} {{.UseLine}}{{end}} {{- if not .HasSubCommands}} {{.UseLine}}{{end}}
{{- if .HasSubCommands}} {{ .CommandPath}} COMMAND{{end}} {{- if .HasSubCommands}} {{ .CommandPath}}{{- if .HasAvailableFlags}} [OPTIONS]{{end}} COMMAND{{end}}
{{ .Short | trim }} {{ .Short | trim }}

View File

@ -166,14 +166,9 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
if err != nil { if err != nil {
return errors.Wrap(err, "Experimental field") return errors.Wrap(err, "Experimental field")
} }
orchestrator, err := GetOrchestrator(opts.Common.Orchestrator, cli.configFile.Orchestrator)
if err != nil {
return err
}
cli.clientInfo = ClientInfo{ cli.clientInfo = ClientInfo{
DefaultVersion: cli.client.ClientVersion(), DefaultVersion: cli.client.ClientVersion(),
HasExperimental: hasExperimental, HasExperimental: hasExperimental,
Orchestrator: orchestrator,
} }
cli.initializeFromClient() cli.initializeFromClient()
return nil return nil
@ -239,22 +234,6 @@ type ServerInfo struct {
type ClientInfo struct { type ClientInfo struct {
HasExperimental bool HasExperimental bool
DefaultVersion string DefaultVersion string
Orchestrator Orchestrator
}
// HasKubernetes checks if kubernetes orchestrator is enabled
func (c ClientInfo) HasKubernetes() bool {
return 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

@ -161,110 +161,6 @@ func TestExperimentalCLI(t *testing.T) {
} }
} }
func TestOrchestratorSwitch(t *testing.T) {
defaultVersion := "v0.00"
var testcases = []struct {
doc string
configfile string
envOrchestrator string
flagOrchestrator string
expectedOrchestrator string
expectedKubernetes bool
expectedSwarm bool
}{
{
doc: "default",
configfile: `{
}`,
expectedOrchestrator: "swarm",
expectedKubernetes: false,
expectedSwarm: true,
},
{
doc: "kubernetesConfigFile",
configfile: `{
"orchestrator": "kubernetes"
}`,
expectedOrchestrator: "kubernetes",
expectedKubernetes: true,
expectedSwarm: false,
},
{
doc: "kubernetesEnv",
configfile: `{
}`,
envOrchestrator: "kubernetes",
expectedOrchestrator: "kubernetes",
expectedKubernetes: true,
expectedSwarm: false,
},
{
doc: "kubernetesFlag",
configfile: `{
}`,
flagOrchestrator: "kubernetes",
expectedOrchestrator: "kubernetes",
expectedKubernetes: true,
expectedSwarm: false,
},
{
doc: "allOrchestratorFlag",
configfile: `{
}`,
flagOrchestrator: "all",
expectedOrchestrator: "all",
expectedKubernetes: true,
expectedSwarm: true,
},
{
doc: "envOverridesConfigFile",
configfile: `{
"orchestrator": "kubernetes"
}`,
envOrchestrator: "swarm",
expectedOrchestrator: "swarm",
expectedKubernetes: false,
expectedSwarm: true,
},
{
doc: "flagOverridesEnv",
configfile: `{
}`,
envOrchestrator: "kubernetes",
flagOrchestrator: "swarm",
expectedOrchestrator: "swarm",
expectedKubernetes: false,
expectedSwarm: true,
},
}
for _, testcase := range testcases {
t.Run(testcase.doc, func(t *testing.T) {
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
defer dir.Remove()
apiclient := &fakeClient{
version: defaultVersion,
}
if testcase.envOrchestrator != "" {
defer env.Patch(t, "DOCKER_ORCHESTRATOR", testcase.envOrchestrator)()
}
cli := &DockerCli{client: apiclient, err: os.Stderr}
cliconfig.SetDir(dir.Path())
options := flags.NewClientOptions()
if testcase.flagOrchestrator != "" {
options.Common.Orchestrator = testcase.flagOrchestrator
}
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)))
})
}
}
func TestGetClientWithPassword(t *testing.T) { func TestGetClientWithPassword(t *testing.T) {
expected := "password" expected := "password"

View File

@ -18,9 +18,24 @@ const (
orchestratorUnset = Orchestrator("unset") orchestratorUnset = Orchestrator("unset")
defaultOrchestrator = OrchestratorSwarm defaultOrchestrator = OrchestratorSwarm
envVarDockerOrchestrator = "DOCKER_ORCHESTRATOR" envVarDockerStackOrchestrator = "DOCKER_STACK_ORCHESTRATOR"
) )
// HasKubernetes returns true if defined orchestrator has Kubernetes capabilities.
func (o Orchestrator) HasKubernetes() bool {
return o == OrchestratorKubernetes || o == OrchestratorAll
}
// HasSwarm returns true if defined orchestrator has Swarm capabilities.
func (o Orchestrator) HasSwarm() bool {
return o == OrchestratorSwarm || o == OrchestratorAll
}
// HasAll returns true if defined orchestrator has both Swarm and Kubernetes capabilities.
func (o Orchestrator) HasAll() bool {
return o == OrchestratorAll
}
func normalize(value string) (Orchestrator, error) { func normalize(value string) (Orchestrator, error) {
switch value { switch value {
case "kubernetes": case "kubernetes":
@ -36,15 +51,15 @@ func normalize(value string) (Orchestrator, error) {
} }
} }
// GetOrchestrator checks DOCKER_ORCHESTRATOR environment variable and configuration file // GetStackOrchestrator checks DOCKER_STACK_ORCHESTRATOR environment variable and configuration file
// orchestrator value and returns user defined Orchestrator. // orchestrator value and returns user defined Orchestrator.
func GetOrchestrator(flagValue, value string) (Orchestrator, error) { func GetStackOrchestrator(flagValue, value string) (Orchestrator, error) {
// Check flag // Check flag
if o, err := normalize(flagValue); o != orchestratorUnset { if o, err := normalize(flagValue); o != orchestratorUnset {
return o, err return o, err
} }
// Check environment variable // Check environment variable
env := os.Getenv(envVarDockerOrchestrator) env := os.Getenv(envVarDockerStackOrchestrator)
if o, err := normalize(env); o != orchestratorUnset { if o, err := normalize(env); o != orchestratorUnset {
return o, err return o, err
} }

View File

@ -0,0 +1,117 @@
package command
import (
"os"
"testing"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/flags"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/env"
"gotest.tools/fs"
)
func TestOrchestratorSwitch(t *testing.T) {
defaultVersion := "v0.00"
var testcases = []struct {
doc string
configfile string
envOrchestrator string
flagOrchestrator string
expectedOrchestrator string
expectedKubernetes bool
expectedSwarm bool
}{
{
doc: "default",
configfile: `{
}`,
expectedOrchestrator: "swarm",
expectedKubernetes: false,
expectedSwarm: true,
},
{
doc: "kubernetesConfigFile",
configfile: `{
"stackOrchestrator": "kubernetes"
}`,
expectedOrchestrator: "kubernetes",
expectedKubernetes: true,
expectedSwarm: false,
},
{
doc: "kubernetesEnv",
configfile: `{
}`,
envOrchestrator: "kubernetes",
expectedOrchestrator: "kubernetes",
expectedKubernetes: true,
expectedSwarm: false,
},
{
doc: "kubernetesFlag",
configfile: `{
}`,
flagOrchestrator: "kubernetes",
expectedOrchestrator: "kubernetes",
expectedKubernetes: true,
expectedSwarm: false,
},
{
doc: "allOrchestratorFlag",
configfile: `{
}`,
flagOrchestrator: "all",
expectedOrchestrator: "all",
expectedKubernetes: true,
expectedSwarm: true,
},
{
doc: "envOverridesConfigFile",
configfile: `{
"stackOrchestrator": "kubernetes"
}`,
envOrchestrator: "swarm",
expectedOrchestrator: "swarm",
expectedKubernetes: false,
expectedSwarm: true,
},
{
doc: "flagOverridesEnv",
configfile: `{
}`,
envOrchestrator: "kubernetes",
flagOrchestrator: "swarm",
expectedOrchestrator: "swarm",
expectedKubernetes: false,
expectedSwarm: true,
},
}
for _, testcase := range testcases {
t.Run(testcase.doc, func(t *testing.T) {
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
defer dir.Remove()
apiclient := &fakeClient{
version: defaultVersion,
}
if testcase.envOrchestrator != "" {
defer env.Patch(t, "DOCKER_STACK_ORCHESTRATOR", testcase.envOrchestrator)()
}
cli := &DockerCli{client: apiclient, err: os.Stderr}
cliconfig.SetDir(dir.Path())
options := flags.NewClientOptions()
err := cli.Initialize(options)
assert.NilError(t, err)
orchestrator, err := GetStackOrchestrator(testcase.flagOrchestrator, cli.ConfigFile().StackOrchestrator)
assert.NilError(t, err)
assert.Check(t, is.Equal(testcase.expectedKubernetes, orchestrator.HasKubernetes()))
assert.Check(t, is.Equal(testcase.expectedSwarm, orchestrator.HasSwarm()))
assert.Check(t, is.Equal(testcase.expectedOrchestrator, string(orchestrator)))
})
}
}

View File

@ -1,50 +1,125 @@
package stack package stack
import ( import (
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
) )
var errUnsupportedAllOrchestrator = fmt.Errorf(`no orchestrator specified: use either "kubernetes" or "swarm"`) var errUnsupportedAllOrchestrator = fmt.Errorf(`no orchestrator specified: use either "kubernetes" or "swarm"`)
type commonOptions struct {
orchestrator command.Orchestrator
}
// 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 {
var opts commonOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "stack", Use: "stack [OPTIONS]",
Short: "Manage Docker stacks", Short: "Manage Docker stacks",
Args: cli.NoArgs, Args: cli.NoArgs,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
orchestrator, err := getOrchestrator(dockerCli.ConfigFile(), cmd)
if err != nil {
return err
}
opts.orchestrator = orchestrator
hideFlag(cmd, orchestrator)
return checkSupportedFlag(cmd, orchestrator)
},
RunE: command.ShowHelp(dockerCli.Err()), RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{ Annotations: map[string]string{
"kubernetes": "",
"swarm": "",
"version": "1.25", "version": "1.25",
}, },
} }
defaultHelpFunc := cmd.HelpFunc()
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
config := cliconfig.LoadDefaultConfigFile(dockerCli.Err()) // dockerCli is not yet initialized, but we only need config file here
o, err := getOrchestrator(config, cmd)
if err != nil {
fmt.Fprint(dockerCli.Err(), err)
return
}
hideFlag(cmd, o)
defaultHelpFunc(cmd, args)
})
cmd.AddCommand( cmd.AddCommand(
newDeployCommand(dockerCli), newDeployCommand(dockerCli, &opts),
newListCommand(dockerCli), newListCommand(dockerCli, &opts),
newPsCommand(dockerCli), newPsCommand(dockerCli, &opts),
newRemoveCommand(dockerCli), newRemoveCommand(dockerCli, &opts),
newServicesCommand(dockerCli), newServicesCommand(dockerCli, &opts),
) )
flags := cmd.PersistentFlags() flags := cmd.PersistentFlags()
flags.String("kubeconfig", "", "Kubernetes config file") flags.String("kubeconfig", "", "Kubernetes config file")
flags.SetAnnotation("kubeconfig", "kubernetes", nil) flags.SetAnnotation("kubeconfig", "kubernetes", nil)
flags.String("orchestrator", "", "Orchestrator to use (swarm|kubernetes|all)")
return cmd return cmd
} }
// NewTopLevelDeployCommand returns a command for `docker deploy` // NewTopLevelDeployCommand returns a command for `docker deploy`
func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command { func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
cmd := newDeployCommand(dockerCli) cmd := newDeployCommand(dockerCli, nil)
// Remove the aliases at the top level // Remove the aliases at the top level
cmd.Aliases = []string{} cmd.Aliases = []string{}
cmd.Annotations = map[string]string{ cmd.Annotations = map[string]string{
"experimental": "", "experimental": "",
"swarm": "",
"version": "1.25", "version": "1.25",
} }
return cmd return cmd
} }
func getOrchestrator(config *configfile.ConfigFile, cmd *cobra.Command) (command.Orchestrator, error) {
var orchestratorFlag string
if o, err := cmd.Flags().GetString("orchestrator"); err == nil {
orchestratorFlag = o
}
return command.GetStackOrchestrator(orchestratorFlag, config.StackOrchestrator)
}
func hideFlag(cmd *cobra.Command, orchestrator command.Orchestrator) {
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if _, ok := f.Annotations["kubernetes"]; ok && !orchestrator.HasKubernetes() {
f.Hidden = true
}
if _, ok := f.Annotations["swarm"]; ok && !orchestrator.HasSwarm() {
f.Hidden = true
}
})
for _, subcmd := range cmd.Commands() {
hideFlag(subcmd, orchestrator)
}
}
func checkSupportedFlag(cmd *cobra.Command, orchestrator command.Orchestrator) error {
errs := []string{}
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if !f.Changed {
return
}
if _, ok := f.Annotations["kubernetes"]; ok && !orchestrator.HasKubernetes() {
errs = append(errs, fmt.Sprintf(`"--%s" is only supported on a Docker cli with kubernetes features enabled`, f.Name))
}
if _, ok := f.Annotations["swarm"]; ok && !orchestrator.HasSwarm() {
errs = append(errs, fmt.Sprintf(`"--%s" is only supported on a Docker cli with swarm features enabled`, f.Name))
}
})
for _, subcmd := range cmd.Commands() {
if err := checkSupportedFlag(subcmd, orchestrator); err != nil {
errs = append(errs, err.Error())
}
}
if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
}
return nil
}

View File

@ -9,7 +9,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newDeployCommand(dockerCli command.Cli) *cobra.Command { func newDeployCommand(dockerCli command.Cli, common *commonOptions) *cobra.Command {
var opts options.Deploy var opts options.Deploy
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -20,10 +20,12 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0] opts.Namespace = args[0]
switch { switch {
case dockerCli.ClientInfo().HasAll(): case common == nil: // Top level deploy commad
return swarm.RunDeploy(dockerCli, opts)
case common.orchestrator.HasAll():
return errUnsupportedAllOrchestrator return errUnsupportedAllOrchestrator
case dockerCli.ClientInfo().HasKubernetes(): case common.orchestrator.HasKubernetes():
kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags(), common.orchestrator))
if err != nil { if err != nil {
return err return err
} }

View File

@ -25,11 +25,14 @@ type KubeCli struct {
type Options struct { type Options struct {
Namespace string Namespace string
Config string Config string
Orchestrator command.Orchestrator
} }
// NewOptions returns an Options initialized with command line flags // NewOptions returns an Options initialized with command line flags
func NewOptions(flags *flag.FlagSet) Options { func NewOptions(flags *flag.FlagSet, orchestrator command.Orchestrator) Options {
var opts Options opts := Options{
Orchestrator: orchestrator,
}
if namespace, err := flags.GetString("namespace"); err == nil { if namespace, err := flags.GetString("namespace"); err == nil {
opts.Namespace = namespace opts.Namespace = namespace
} }
@ -73,7 +76,7 @@ func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) {
} }
cli.clientSet = clientSet cli.clientSet = clientSet
if dockerCli.ClientInfo().HasAll() { if opts.Orchestrator.HasAll() {
if err := cli.checkHostsMatch(); err != nil { if err := cli.checkHostsMatch(); err != nil {
return nil, err return nil, err
} }

View File

@ -13,16 +13,16 @@ import (
"vbom.ml/util/sortorder" "vbom.ml/util/sortorder"
) )
func newListCommand(dockerCli command.Cli) *cobra.Command { func newListCommand(dockerCli command.Cli, common *commonOptions) *cobra.Command {
opts := options.List{} opts := options.List{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "ls", Use: "ls [OPTIONS]",
Aliases: []string{"list"}, Aliases: []string{"list"},
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 {
return runList(cmd, dockerCli, opts) return runList(cmd, dockerCli, opts, common.orchestrator)
}, },
} }
@ -35,17 +35,17 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
return cmd return cmd
} }
func runList(cmd *cobra.Command, dockerCli command.Cli, opts options.List) error { func runList(cmd *cobra.Command, dockerCli command.Cli, opts options.List, orchestrator command.Orchestrator) error {
stacks := []*formatter.Stack{} stacks := []*formatter.Stack{}
if dockerCli.ClientInfo().HasSwarm() { if orchestrator.HasSwarm() {
ss, err := swarm.GetStacks(dockerCli) ss, err := swarm.GetStacks(dockerCli)
if err != nil { if err != nil {
return err return err
} }
stacks = append(stacks, ss...) stacks = append(stacks, ss...)
} }
if dockerCli.ClientInfo().HasKubernetes() { if orchestrator.HasKubernetes() {
kubeCli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) kubeCli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags(), orchestrator))
if err != nil { if err != nil {
return err return err
} }
@ -55,14 +55,14 @@ func runList(cmd *cobra.Command, dockerCli command.Cli, opts options.List) error
} }
stacks = append(stacks, ss...) stacks = append(stacks, ss...)
} }
return format(dockerCli, opts, stacks) return format(dockerCli, opts, orchestrator, stacks)
} }
func format(dockerCli command.Cli, opts options.List, stacks []*formatter.Stack) error { func format(dockerCli command.Cli, opts options.List, orchestrator command.Orchestrator, stacks []*formatter.Stack) error {
format := opts.Format format := opts.Format
if format == "" || format == formatter.TableFormatKey { if format == "" || format == formatter.TableFormatKey {
format = formatter.SwarmStackTableFormat format = formatter.SwarmStackTableFormat
if dockerCli.ClientInfo().HasKubernetes() { if orchestrator.HasKubernetes() {
format = formatter.KubernetesStackTableFormat format = formatter.KubernetesStackTableFormat
} }
} }

View File

@ -4,6 +4,7 @@ import (
"io/ioutil" "io/ioutil"
"testing" "testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test"
// Import builders to get the builder function as package function // Import builders to get the builder function as package function
. "github.com/docker/cli/internal/test/builders" . "github.com/docker/cli/internal/test/builders"
@ -14,6 +15,10 @@ import (
"gotest.tools/golden" "gotest.tools/golden"
) )
var (
orchestrator = commonOptions{orchestrator: command.OrchestratorSwarm}
)
func TestListErrors(t *testing.T) { func TestListErrors(t *testing.T) {
testCases := []struct { testCases := []struct {
args []string args []string
@ -48,7 +53,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)) }), &orchestrator)
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 {
@ -69,8 +74,8 @@ func TestListWithFormat(t *testing.T) {
}), }),
)}, nil )}, nil
}, },
}, test.OrchestratorSwarm) })
cmd := newListCommand(cli) cmd := newListCommand(cli, &orchestrator)
cmd.Flags().Set("format", "{{ .Name }}") cmd.Flags().Set("format", "{{ .Name }}")
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-list-with-format.golden") golden.Assert(t, cli.OutBuffer().String(), "stack-list-with-format.golden")
@ -86,8 +91,8 @@ func TestListWithoutFormat(t *testing.T) {
}), }),
)}, nil )}, nil
}, },
}, test.OrchestratorSwarm) })
cmd := newListCommand(cli) cmd := newListCommand(cli, &orchestrator)
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")
} }
@ -139,8 +144,8 @@ 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, &orchestrator)
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

@ -10,7 +10,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newPsCommand(dockerCli command.Cli) *cobra.Command { func newPsCommand(dockerCli command.Cli, common *commonOptions) *cobra.Command {
opts := options.PS{Filter: cliopts.NewFilterOpt()} opts := options.PS{Filter: cliopts.NewFilterOpt()}
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -20,10 +20,10 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0] opts.Namespace = args[0]
switch { switch {
case dockerCli.ClientInfo().HasAll(): case common.orchestrator.HasAll():
return errUnsupportedAllOrchestrator return errUnsupportedAllOrchestrator
case dockerCli.ClientInfo().HasKubernetes(): case common.orchestrator.HasKubernetes():
kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags(), common.orchestrator))
if err != nil { if err != nil {
return err return err
} }

View File

@ -44,7 +44,7 @@ func TestStackPsErrors(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
cmd := newPsCommand(test.NewFakeCli(&fakeClient{ cmd := newPsCommand(test.NewFakeCli(&fakeClient{
taskListFunc: tc.taskListFunc, taskListFunc: tc.taskListFunc,
})) }), &orchestrator)
cmd.SetArgs(tc.args) cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError) assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -57,7 +57,7 @@ func TestStackPsEmptyStack(t *testing.T) {
return []swarm.Task{}, nil return []swarm.Task{}, nil
}, },
}) })
cmd := newPsCommand(fakeCli) cmd := newPsCommand(fakeCli, &orchestrator)
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -71,7 +71,7 @@ func TestStackPsWithQuietOption(t *testing.T) {
return []swarm.Task{*Task(TaskID("id-foo"))}, nil return []swarm.Task{*Task(TaskID("id-foo"))}, nil
}, },
}) })
cmd := newPsCommand(cli) cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("quiet", "true") cmd.Flags().Set("quiet", "true")
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -85,7 +85,7 @@ func TestStackPsWithNoTruncOption(t *testing.T) {
return []swarm.Task{*Task(TaskID("xn4cypcov06f2w8gsbaf2lst3"))}, nil return []swarm.Task{*Task(TaskID("xn4cypcov06f2w8gsbaf2lst3"))}, nil
}, },
}) })
cmd := newPsCommand(cli) cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("no-trunc", "true") cmd.Flags().Set("no-trunc", "true")
cmd.Flags().Set("format", "{{ .ID }}") cmd.Flags().Set("format", "{{ .ID }}")
@ -104,7 +104,7 @@ func TestStackPsWithNoResolveOption(t *testing.T) {
return *Node(NodeName("node-name-bar")), nil, nil return *Node(NodeName("node-name-bar")), nil, nil
}, },
}) })
cmd := newPsCommand(cli) cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("no-resolve", "true") cmd.Flags().Set("no-resolve", "true")
cmd.Flags().Set("format", "{{ .Node }}") cmd.Flags().Set("format", "{{ .Node }}")
@ -118,7 +118,7 @@ func TestStackPsWithFormat(t *testing.T) {
return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil
}, },
}) })
cmd := newPsCommand(cli) cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("format", "{{ .Name }}") cmd.Flags().Set("format", "{{ .Name }}")
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -134,7 +134,7 @@ func TestStackPsWithConfigFormat(t *testing.T) {
cli.SetConfigFile(&configfile.ConfigFile{ cli.SetConfigFile(&configfile.ConfigFile{
TasksFormat: "{{ .Name }}", TasksFormat: "{{ .Name }}",
}) })
cmd := newPsCommand(cli) cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-config-format.golden") golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-config-format.golden")
@ -156,7 +156,7 @@ func TestStackPsWithoutFormat(t *testing.T) {
return *Node(NodeName("node-name-bar")), nil, nil return *Node(NodeName("node-name-bar")), nil, nil
}, },
}) })
cmd := newPsCommand(cli) cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-ps-without-format.golden") golden.Assert(t, cli.OutBuffer().String(), "stack-ps-without-format.golden")

View File

@ -9,21 +9,21 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newRemoveCommand(dockerCli command.Cli) *cobra.Command { func newRemoveCommand(dockerCli command.Cli, common *commonOptions) *cobra.Command {
var opts options.Remove var opts options.Remove
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "rm STACK [STACK...]", Use: "rm [OPTIONS] STACK [STACK...]",
Aliases: []string{"remove", "down"}, Aliases: []string{"remove", "down"},
Short: "Remove one or more stacks", Short: "Remove one or more stacks",
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
switch { switch {
case dockerCli.ClientInfo().HasAll(): case common.orchestrator.HasAll():
return errUnsupportedAllOrchestrator return errUnsupportedAllOrchestrator
case dockerCli.ClientInfo().HasKubernetes(): case common.orchestrator.HasKubernetes():
kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags(), common.orchestrator))
if err != nil { if err != nil {
return err return err
} }

View File

@ -43,7 +43,7 @@ func fakeClientForRemoveStackTest(version string) *fakeClient {
func TestRemoveStackVersion124DoesNotRemoveConfigsOrSecrets(t *testing.T) { func TestRemoveStackVersion124DoesNotRemoveConfigsOrSecrets(t *testing.T) {
client := fakeClientForRemoveStackTest("1.24") client := fakeClientForRemoveStackTest("1.24")
cmd := newRemoveCommand(test.NewFakeCli(client)) cmd := newRemoveCommand(test.NewFakeCli(client), &orchestrator)
cmd.SetArgs([]string{"foo", "bar"}) cmd.SetArgs([]string{"foo", "bar"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -55,7 +55,7 @@ func TestRemoveStackVersion124DoesNotRemoveConfigsOrSecrets(t *testing.T) {
func TestRemoveStackVersion125DoesNotRemoveConfigs(t *testing.T) { func TestRemoveStackVersion125DoesNotRemoveConfigs(t *testing.T) {
client := fakeClientForRemoveStackTest("1.25") client := fakeClientForRemoveStackTest("1.25")
cmd := newRemoveCommand(test.NewFakeCli(client)) cmd := newRemoveCommand(test.NewFakeCli(client), &orchestrator)
cmd.SetArgs([]string{"foo", "bar"}) cmd.SetArgs([]string{"foo", "bar"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -67,7 +67,7 @@ func TestRemoveStackVersion125DoesNotRemoveConfigs(t *testing.T) {
func TestRemoveStackVersion130RemovesEverything(t *testing.T) { func TestRemoveStackVersion130RemovesEverything(t *testing.T) {
client := fakeClientForRemoveStackTest("1.30") client := fakeClientForRemoveStackTest("1.30")
cmd := newRemoveCommand(test.NewFakeCli(client)) cmd := newRemoveCommand(test.NewFakeCli(client), &orchestrator)
cmd.SetArgs([]string{"foo", "bar"}) cmd.SetArgs([]string{"foo", "bar"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -98,7 +98,7 @@ func TestRemoveStackSkipEmpty(t *testing.T) {
configs: allConfigs, configs: allConfigs,
} }
fakeCli := test.NewFakeCli(fakeClient) fakeCli := test.NewFakeCli(fakeClient)
cmd := newRemoveCommand(fakeCli) cmd := newRemoveCommand(fakeCli, &orchestrator)
cmd.SetArgs([]string{"foo", "bar"}) cmd.SetArgs([]string{"foo", "bar"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -146,7 +146,7 @@ func TestRemoveContinueAfterError(t *testing.T) {
return nil return nil
}, },
} }
cmd := newRemoveCommand(test.NewFakeCli(cli)) cmd := newRemoveCommand(test.NewFakeCli(cli), &orchestrator)
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"foo", "bar"}) cmd.SetArgs([]string{"foo", "bar"})

View File

@ -10,7 +10,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newServicesCommand(dockerCli command.Cli) *cobra.Command { func newServicesCommand(dockerCli command.Cli, common *commonOptions) *cobra.Command {
opts := options.Services{Filter: cliopts.NewFilterOpt()} opts := options.Services{Filter: cliopts.NewFilterOpt()}
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -20,10 +20,10 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0] opts.Namespace = args[0]
switch { switch {
case dockerCli.ClientInfo().HasAll(): case common.orchestrator.HasAll():
return errUnsupportedAllOrchestrator return errUnsupportedAllOrchestrator
case dockerCli.ClientInfo().HasKubernetes(): case common.orchestrator.HasKubernetes():
kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags(), common.orchestrator))
if err != nil { if err != nil {
return err return err
} }

View File

@ -70,7 +70,7 @@ func TestStackServicesErrors(t *testing.T) {
nodeListFunc: tc.nodeListFunc, nodeListFunc: tc.nodeListFunc,
taskListFunc: tc.taskListFunc, taskListFunc: tc.taskListFunc,
}) })
cmd := newServicesCommand(cli) cmd := newServicesCommand(cli, &orchestrator)
cmd.SetArgs(tc.args) cmd.SetArgs(tc.args)
for key, value := range tc.flags { for key, value := range tc.flags {
cmd.Flags().Set(key, value) cmd.Flags().Set(key, value)
@ -86,7 +86,7 @@ func TestStackServicesEmptyServiceList(t *testing.T) {
return []swarm.Service{}, nil return []swarm.Service{}, nil
}, },
}) })
cmd := newServicesCommand(fakeCli) cmd := newServicesCommand(fakeCli, &orchestrator)
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
assert.Check(t, is.Equal("", fakeCli.OutBuffer().String())) assert.Check(t, is.Equal("", fakeCli.OutBuffer().String()))
@ -99,7 +99,7 @@ func TestStackServicesWithQuietOption(t *testing.T) {
return []swarm.Service{*Service(ServiceID("id-foo"))}, nil return []swarm.Service{*Service(ServiceID("id-foo"))}, nil
}, },
}) })
cmd := newServicesCommand(cli) cmd := newServicesCommand(cli, &orchestrator)
cmd.Flags().Set("quiet", "true") cmd.Flags().Set("quiet", "true")
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -114,7 +114,7 @@ func TestStackServicesWithFormat(t *testing.T) {
}, nil }, nil
}, },
}) })
cmd := newServicesCommand(cli) cmd := newServicesCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("format", "{{ .Name }}") cmd.Flags().Set("format", "{{ .Name }}")
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -132,7 +132,7 @@ func TestStackServicesWithConfigFormat(t *testing.T) {
cli.SetConfigFile(&configfile.ConfigFile{ cli.SetConfigFile(&configfile.ConfigFile{
ServicesFormat: "{{ .Name }}", ServicesFormat: "{{ .Name }}",
}) })
cmd := newServicesCommand(cli) cmd := newServicesCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-services-with-config-format.golden") golden.Assert(t, cli.OutBuffer().String(), "stack-services-with-config-format.golden")
@ -155,7 +155,7 @@ func TestStackServicesWithoutFormat(t *testing.T) {
)}, nil )}, nil
}, },
}) })
cmd := newServicesCommand(cli) cmd := newServicesCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"}) cmd.SetArgs([]string{"foo"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-services-without-format.golden") golden.Assert(t, cli.OutBuffer().String(), "stack-services-without-format.golden")

View File

@ -6,4 +6,3 @@ Client:
Built: Wed May 30 22:21:05 2018 Built: Wed May 30 22:21:05 2018
OS/Arch: linux/amd64 OS/Arch: linux/amd64
Experimental: true Experimental: true
Orchestrator: swarm

View File

@ -29,7 +29,6 @@ Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
Built: {{.BuildTime}} Built: {{.BuildTime}}
OS/Arch: {{.Os}}/{{.Arch}} OS/Arch: {{.Os}}/{{.Arch}}
Experimental: {{.Experimental}} Experimental: {{.Experimental}}
Orchestrator: {{.Orchestrator}}
{{- end}} {{- end}}
{{- if .ServerOK}}{{with .Server}} {{- if .ServerOK}}{{with .Server}}
@ -78,7 +77,6 @@ type clientVersion struct {
Arch string Arch string
BuildTime string `json:",omitempty"` BuildTime string `json:",omitempty"`
Experimental bool Experimental bool
Orchestrator string `json:",omitempty"`
} }
type kubernetesVersion struct { type kubernetesVersion struct {
@ -107,7 +105,7 @@ func NewVersionCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags() flags := cmd.Flags()
flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template") flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
flags.StringVarP(&opts.kubeConfig, "kubeconfig", "k", "", "Kubernetes config file") flags.StringVar(&opts.kubeConfig, "kubeconfig", "", "Kubernetes config file")
flags.SetAnnotation("kubeconfig", "kubernetes", nil) flags.SetAnnotation("kubeconfig", "kubernetes", nil)
return cmd return cmd
@ -128,6 +126,11 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) error {
return cli.StatusError{StatusCode: 64, Status: err.Error()} return cli.StatusError{StatusCode: 64, Status: err.Error()}
} }
orchestrator, err := command.GetStackOrchestrator("", dockerCli.ConfigFile().StackOrchestrator)
if err != nil {
return cli.StatusError{StatusCode: 64, Status: err.Error()}
}
vd := versionInfo{ vd := versionInfo{
Client: clientVersion{ Client: clientVersion{
Platform: struct{ Name string }{cli.PlatformName}, Platform: struct{ Name string }{cli.PlatformName},
@ -140,14 +143,16 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) error {
Os: runtime.GOOS, Os: runtime.GOOS,
Arch: runtime.GOARCH, Arch: runtime.GOARCH,
Experimental: dockerCli.ClientInfo().HasExperimental, Experimental: dockerCli.ClientInfo().HasExperimental,
Orchestrator: string(dockerCli.ClientInfo().Orchestrator),
}, },
} }
sv, err := dockerCli.Client().ServerVersion(context.Background()) sv, err := dockerCli.Client().ServerVersion(context.Background())
if err == nil { if err == nil {
vd.Server = &sv vd.Server = &sv
kubeVersion := getKubernetesVersion(dockerCli, opts.kubeConfig) var kubeVersion *kubernetesVersion
if orchestrator.HasKubernetes() {
kubeVersion = getKubernetesVersion(opts.kubeConfig)
}
foundEngine := false foundEngine := false
foundKubernetes := false foundKubernetes := false
for _, component := range sv.Components { for _, component := range sv.Components {
@ -225,11 +230,7 @@ func getDetailsOrder(v types.ComponentVersion) []string {
return out return out
} }
func getKubernetesVersion(dockerCli command.Cli, kubeConfig string) *kubernetesVersion { func getKubernetesVersion(kubeConfig string) *kubernetesVersion {
if !dockerCli.ClientInfo().HasKubernetes() {
return nil
}
version := kubernetesVersion{ version := kubernetesVersion{
Kubernetes: "Unknown", Kubernetes: "Unknown",
StackAPI: "Unknown", StackAPI: "Unknown",

View File

@ -11,7 +11,6 @@ import (
"gotest.tools/golden" "gotest.tools/golden"
"github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
) )
@ -31,20 +30,6 @@ func TestVersionWithoutServer(t *testing.T) {
assert.Assert(t, !strings.Contains(out, "Server:"), "actual: %s", out) assert.Assert(t, !strings.Contains(out, "Server:"), "actual: %s", out)
} }
func fakeServerVersion(_ context.Context) (types.Version, error) {
return types.Version{
Version: "docker-dev",
APIVersion: api.DefaultVersion,
}, nil
}
func TestVersionWithOrchestrator(t *testing.T) {
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"))
}
func TestVersionAlign(t *testing.T) { func TestVersionAlign(t *testing.T) {
vi := versionInfo{ vi := versionInfo{
Client: clientVersion{ Client: clientVersion{
@ -57,7 +42,6 @@ func TestVersionAlign(t *testing.T) {
Arch: "amd64", Arch: "amd64",
BuildTime: "Wed May 30 22:21:05 2018", BuildTime: "Wed May 30 22:21:05 2018",
Experimental: true, Experimental: true,
Orchestrator: "swarm",
}, },
} }
@ -68,7 +52,3 @@ func TestVersionAlign(t *testing.T) {
assert.Check(t, golden.String(cli.OutBuffer().String(), "docker-client-version.golden")) assert.Check(t, golden.String(cli.OutBuffer().String(), "docker-client-version.golden"))
assert.Check(t, is.Equal("", cli.ErrBuffer().String())) assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
} }
func cleanTabs(line string) string {
return strings.Join(strings.Fields(line), " ")
}

View File

@ -46,7 +46,7 @@ type ConfigFile struct {
PruneFilters []string `json:"pruneFilters,omitempty"` PruneFilters []string `json:"pruneFilters,omitempty"`
Proxies map[string]ProxyConfig `json:"proxies,omitempty"` Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
Experimental string `json:"experimental,omitempty"` Experimental string `json:"experimental,omitempty"`
Orchestrator string `json:"orchestrator,omitempty"` StackOrchestrator string `json:"stackOrchestrator,omitempty"`
Kubernetes *KubernetesConfig `json:"kubernetes,omitempty"` Kubernetes *KubernetesConfig `json:"kubernetes,omitempty"`
} }

View File

@ -33,7 +33,6 @@ var (
type CommonOptions struct { type CommonOptions struct {
Debug bool Debug bool
Hosts []string Hosts []string
Orchestrator string
LogLevel string LogLevel string
TLS bool TLS bool
TLSVerify bool TLSVerify bool

View File

@ -51,11 +51,6 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files") flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files")
opts.Common.InstallFlags(flags) opts.Common.InstallFlags(flags)
// Install persistent flags
persistentFlags := cmd.PersistentFlags()
persistentFlags.StringVar(&opts.Common.Orchestrator, "orchestrator", "", "Orchestrator to use (swarm|kubernetes|all)")
persistentFlags.SetAnnotation("orchestrator", "top-level", []string{"version", "stack"})
setFlagErrorFunc(dockerCli, cmd, flags, opts) setFlagErrorFunc(dockerCli, cmd, flags, opts)
setHelpFunc(dockerCli, cmd, flags, opts) setHelpFunc(dockerCli, cmd, flags, opts)
@ -244,13 +239,10 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
osType := details.ServerInfo().OSType osType := details.ServerInfo().OSType
hasExperimental := details.ServerInfo().HasExperimental hasExperimental := details.ServerInfo().HasExperimental
hasExperimentalCLI := details.ClientInfo().HasExperimental hasExperimentalCLI := details.ClientInfo().HasExperimental
hasKubernetes := details.ClientInfo().HasKubernetes()
cmd.Flags().VisitAll(func(f *pflag.Flag) { cmd.Flags().VisitAll(func(f *pflag.Flag) {
hideFeatureFlag(f, hasExperimental, "experimental") hideFeatureFlag(f, hasExperimental, "experimental")
hideFeatureFlag(f, hasExperimentalCLI, "experimentalCLI") hideFeatureFlag(f, hasExperimentalCLI, "experimentalCLI")
hideFeatureFlag(f, hasKubernetes, "kubernetes")
hideFeatureFlag(f, !hasKubernetes, "swarm")
// hide flags not supported by the server // hide flags not supported by the server
if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) { if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
f.Hidden = true f.Hidden = true
@ -266,8 +258,6 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
for _, subcmd := range cmd.Commands() { for _, subcmd := range cmd.Commands() {
hideFeatureSubCommand(subcmd, hasExperimental, "experimental") hideFeatureSubCommand(subcmd, hasExperimental, "experimental")
hideFeatureSubCommand(subcmd, hasExperimentalCLI, "experimentalCLI") hideFeatureSubCommand(subcmd, hasExperimentalCLI, "experimentalCLI")
hideFeatureSubCommand(subcmd, hasKubernetes, "kubernetes")
hideFeatureSubCommand(subcmd, !hasKubernetes, "swarm")
// hide subcommands not supported by the server // hide subcommands not supported by the server
if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) { if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
subcmd.Hidden = true subcmd.Hidden = true
@ -302,7 +292,6 @@ func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
clientVersion := details.Client().ClientVersion() clientVersion := details.Client().ClientVersion()
osType := details.ServerInfo().OSType osType := details.ServerInfo().OSType
hasExperimental := details.ServerInfo().HasExperimental hasExperimental := details.ServerInfo().HasExperimental
hasKubernetes := details.ClientInfo().HasKubernetes()
hasExperimentalCLI := details.ClientInfo().HasExperimental hasExperimentalCLI := details.ClientInfo().HasExperimental
errs := []string{} errs := []string{}
@ -323,14 +312,6 @@ func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI { if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
errs = append(errs, fmt.Sprintf("\"--%s\" is on a Docker cli with experimental cli features enabled", f.Name)) errs = append(errs, fmt.Sprintf("\"--%s\" is on a Docker cli with experimental cli features enabled", f.Name))
} }
_, isKubernetesAnnotated := f.Annotations["kubernetes"]
_, isSwarmAnnotated := f.Annotations["swarm"]
if isKubernetesAnnotated && !isSwarmAnnotated && !hasKubernetes {
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker cli with kubernetes features enabled", f.Name))
}
if isSwarmAnnotated && !isKubernetesAnnotated && hasKubernetes {
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker cli with swarm features enabled", f.Name))
}
} }
}) })
if len(errs) > 0 { if len(errs) > 0 {
@ -345,7 +326,6 @@ func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error {
osType := details.ServerInfo().OSType osType := details.ServerInfo().OSType
hasExperimental := details.ServerInfo().HasExperimental hasExperimental := details.ServerInfo().HasExperimental
hasExperimentalCLI := details.ClientInfo().HasExperimental hasExperimentalCLI := details.ClientInfo().HasExperimental
hasKubernetes := details.ClientInfo().HasKubernetes()
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack` // Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
for curr := cmd; curr != nil; curr = curr.Parent() { for curr := cmd; curr != nil; curr = curr.Parent() {
@ -361,15 +341,6 @@ func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error {
if _, ok := curr.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI { if _, ok := curr.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
return fmt.Errorf("%s is only supported on a Docker cli with experimental cli features enabled", cmd.CommandPath()) return fmt.Errorf("%s is only supported on a Docker cli with experimental cli features enabled", cmd.CommandPath())
} }
_, isKubernetesAnnotated := curr.Annotations["kubernetes"]
_, isSwarmAnnotated := curr.Annotations["swarm"]
if isKubernetesAnnotated && !isSwarmAnnotated && !hasKubernetes {
return fmt.Errorf("%s is only supported on a Docker cli with kubernetes features enabled", cmd.CommandPath())
}
if isSwarmAnnotated && !isKubernetesAnnotated && hasKubernetes {
return fmt.Errorf("%s is only supported on a Docker cli with swarm features enabled", cmd.CommandPath())
}
} }
return nil return nil
} }

View File

@ -66,6 +66,7 @@ by the `docker` command line:
* `DOCKER_NOWARN_KERNEL_VERSION` Prevent warnings that your Linux kernel is * `DOCKER_NOWARN_KERNEL_VERSION` Prevent warnings that your Linux kernel is
unsuitable for Docker. unsuitable for Docker.
* `DOCKER_RAMDISK` If set this will disable 'pivot_root'. * `DOCKER_RAMDISK` If set this will disable 'pivot_root'.
* `DOCKER_STACK_ORCHESTRATOR` Configure the default orchestrator to use when using `docker stack` management commands.
* `DOCKER_TLS` When set Docker uses TLS. * `DOCKER_TLS` When set Docker uses TLS.
* `DOCKER_TLS_VERIFY` When set Docker uses TLS and verifies the remote. * `DOCKER_TLS_VERIFY` When set Docker uses TLS and verifies the remote.
* `DOCKER_CONTENT_TRUST` When set Docker uses notary to sign and verify images. * `DOCKER_CONTENT_TRUST` When set Docker uses notary to sign and verify images.
@ -196,6 +197,11 @@ credentials for specific registries. If this property is set, the binary
for a specific registry. For more information, see the for a specific registry. For more information, see the
[**Credential helpers** section in the `docker login` documentation](login.md#credential-helpers) [**Credential helpers** section in the `docker login` documentation](login.md#credential-helpers)
The property `stackOrchestrator` specifies the default orchestrator to use when
running `docker stack` management commands. Valid values are `"swarm"`,
`"kubernetes"`, and `"all"`. This property can be overridden with the
`DOCKER_STACK_ORCHESTRATOR` environment variable, or the `--orchestrator` flag.
Once attached to a container, users detach from it and leave it running using Once attached to a container, users detach from it and leave it running using
the using `CTRL-p CTRL-q` key sequence. This detach key sequence is customizable the using `CTRL-p CTRL-q` key sequence. This detach key sequence is customizable
using the `detachKeys` property. Specify a `<sequence>` value for the using the `detachKeys` property. Specify a `<sequence>` value for the
@ -236,7 +242,8 @@ Following is a sample `config.json` file:
"credHelpers": { "credHelpers": {
"awesomereg.example.org": "hip-star", "awesomereg.example.org": "hip-star",
"unicorn.example.com": "vcbait" "unicorn.example.com": "vcbait"
} },
"stackOrchestrator": "kubernetes"
} }
{% endraw %} {% endraw %}
``` ```

View File

@ -16,18 +16,20 @@ keywords: "stack"
# stack # stack
```markdown ```markdown
Usage: docker stack COMMAND Usage: docker stack [OPTIONS] COMMAND
Manage Docker stacks Manage Docker stacks
Options: Options:
--help Print usage --help Print usage
--kubeconfig string Kubernetes config file
--orchestrator string Orchestrator to use (swarm|kubernetes|all)
Commands: Commands:
deploy Deploy a new stack or update an existing stack deploy Deploy a new stack or update an existing stack
ls List stacks ls List stacks
ps List the tasks in the stack ps List the tasks in the stack
rm Remove the stack rm Remove one or more stacks
services List the services in the stack services List the services in the stack
Run 'docker stack COMMAND --help' for more information on a command. Run 'docker stack COMMAND --help' for more information on a command.

View File

@ -27,6 +27,9 @@ Options:
--bundle-file string Path to a Distributed Application Bundle file --bundle-file string Path to a Distributed Application Bundle file
-c, --compose-file strings Path to a Compose file -c, --compose-file strings Path to a Compose file
--help Print usage --help Print usage
--kubeconfig string Kubernetes config file
--namespace string Kubernetes namespace to use
--orchestrator string Orchestrator to use (swarm|kubernetes|all)
--prune Prune services that are no longer referenced --prune Prune services that are no longer referenced
--resolve-image string Query the registry to resolve image digest and supported platforms --resolve-image string Query the registry to resolve image digest and supported platforms
("always"|"changed"|"never") (default "always") ("always"|"changed"|"never") (default "always")

View File

@ -16,7 +16,7 @@ keywords: "stack, ls"
# stack ls # stack ls
```markdown ```markdown
Usage: docker stack ls Usage: docker stack ls [OPTIONS]
List stacks List stacks
@ -26,6 +26,9 @@ Aliases:
Options: Options:
--help Print usage --help Print usage
--format string Pretty-print stacks using a Go template --format string Pretty-print stacks using a Go template
--kubeconfig string Kubernetes config file
--namespace string Kubernetes namespace to use
--orchestrator string Orchestrator to use (swarm|kubernetes|all)
``` ```
## Description ## Description

View File

@ -24,8 +24,11 @@ Options:
-f, --filter filter Filter output based on conditions provided -f, --filter filter Filter output based on conditions provided
--format string Pretty-print tasks using a Go template --format string Pretty-print tasks using a Go template
--help Print usage --help Print usage
--kubeconfig string Kubernetes config file
--namespace string Kubernetes namespace to use
--no-resolve Do not map IDs to Names --no-resolve Do not map IDs to Names
--no-trunc Do not truncate output --no-trunc Do not truncate output
--orchestrator string Orchestrator to use (swarm|kubernetes|all)
-q, --quiet Only display task IDs -q, --quiet Only display task IDs
``` ```

View File

@ -16,7 +16,7 @@ keywords: "stack, rm, remove, down"
# stack rm # stack rm
```markdown ```markdown
Usage: docker stack rm STACK [STACK...] Usage: docker stack rm [OPTIONS] STACK [STACK...]
Remove one or more stacks Remove one or more stacks
@ -25,6 +25,9 @@ Aliases:
Options: Options:
--help Print usage --help Print usage
--kubeconfig string Kubernetes config file
--namespace string Kubernetes namespace to use
--orchestrator string Orchestrator to use (swarm|kubernetes|all)
``` ```
## Description ## Description

View File

@ -2,7 +2,6 @@
title: "stack services" title: "stack services"
description: "The stack services command description and usage" description: "The stack services command description and usage"
keywords: "stack, services" keywords: "stack, services"
advisory: "experimental"
--- ---
<!-- This file is maintained within the docker/cli GitHub <!-- This file is maintained within the docker/cli GitHub
@ -14,7 +13,7 @@ advisory: "experimental"
will be rejected. will be rejected.
--> -->
# stack services (experimental) # stack services
```markdown ```markdown
Usage: docker stack services [OPTIONS] STACK Usage: docker stack services [OPTIONS] STACK
@ -25,6 +24,9 @@ Options:
-f, --filter filter Filter output based on conditions provided -f, --filter filter Filter output based on conditions provided
--format string Pretty-print services using a Go template --format string Pretty-print services using a Go template
--help Print usage --help Print usage
--kubeconfig string Kubernetes config file
--namespace string Kubernetes namespace to use
--orchestrator string Orchestrator to use (swarm|kubernetes|all)
-q, --quiet Only display IDs -q, --quiet Only display IDs
``` ```

View File

@ -23,6 +23,7 @@ Show the Docker version information
Options: Options:
-f, --format string Format the output using the given Go template -f, --format string Format the output using the given Go template
--help Print usage --help Print usage
--kubeconfig string Kubernetes config file
``` ```
## Description ## Description

View File

@ -30,10 +30,10 @@ func testDeployWithNamedResources(t *testing.T, orchestrator string) {
stackname := fmt.Sprintf("test-stack-deploy-with-names-%s", orchestrator) stackname := fmt.Sprintf("test-stack-deploy-with-names-%s", orchestrator)
composefile := golden.Path("stack-with-named-resources.yml") composefile := golden.Path("stack-with-named-resources.yml")
result := icmd.RunCommand("docker", "--orchestrator", orchestrator, result := icmd.RunCommand("docker", "stack", "deploy",
"stack", "deploy", "-c", composefile, stackname) "-c", composefile, stackname, "--orchestrator", orchestrator)
defer icmd.RunCommand("docker", "--orchestrator", orchestrator, defer icmd.RunCommand("docker", "stack", "rm",
"stack", "rm", stackname) "--orchestrator", orchestrator, stackname)
result.Assert(t, icmd.Success) result.Assert(t, icmd.Success)
stdout := strings.Split(result.Stdout(), "\n") stdout := strings.Split(result.Stdout(), "\n")

View File

@ -29,8 +29,8 @@ func testRemove(t *testing.T, orchestrator string) {
stackname := "test-stack-remove-" + orchestrator stackname := "test-stack-remove-" + orchestrator
deployFullStack(t, orchestrator, stackname) deployFullStack(t, orchestrator, stackname)
defer cleanupFullStack(t, orchestrator, stackname) defer cleanupFullStack(t, orchestrator, stackname)
result := icmd.RunCommand("docker", "--orchestrator", orchestrator, result := icmd.RunCommand("docker", "stack", "rm",
"stack", "rm", stackname) stackname, "--orchestrator", orchestrator)
result.Assert(t, icmd.Expected{Err: icmd.None}) result.Assert(t, icmd.Expected{Err: icmd.None})
golden.Assert(t, result.Stdout(), golden.Assert(t, result.Stdout(),
fmt.Sprintf("stack-remove-%s-success.golden", orchestrator)) fmt.Sprintf("stack-remove-%s-success.golden", orchestrator))
@ -38,8 +38,8 @@ func testRemove(t *testing.T, orchestrator string) {
func deployFullStack(t *testing.T, orchestrator, stackname string) { func deployFullStack(t *testing.T, orchestrator, stackname string) {
// TODO: this stack should have full options not minimal options // TODO: this stack should have full options not minimal options
result := icmd.RunCommand("docker", "--orchestrator", orchestrator, result := icmd.RunCommand("docker", "stack", "deploy",
"stack", "deploy", "--compose-file=./testdata/full-stack.yml", stackname) "--compose-file=./testdata/full-stack.yml", stackname, "--orchestrator", orchestrator)
result.Assert(t, icmd.Success) result.Assert(t, icmd.Success)
poll.WaitOn(t, taskCount(orchestrator, stackname, 2), pollSettings) poll.WaitOn(t, taskCount(orchestrator, stackname, 2), pollSettings)
@ -53,7 +53,7 @@ func cleanupFullStack(t *testing.T, orchestrator, stackname string) {
func stackRm(orchestrator, stackname string) func(t poll.LogT) poll.Result { func stackRm(orchestrator, stackname string) func(t poll.LogT) poll.Result {
return func(poll.LogT) poll.Result { return func(poll.LogT) poll.Result {
result := icmd.RunCommand("docker", "--orchestrator", orchestrator, "stack", "rm", stackname) result := icmd.RunCommand("docker", "stack", "rm", stackname, "--orchestrator", orchestrator)
if result.Error != nil { if result.Error != nil {
if strings.Contains(result.Stderr(), "not found") { if strings.Contains(result.Stderr(), "not found") {
return poll.Success() return poll.Success()
@ -66,7 +66,7 @@ func stackRm(orchestrator, stackname string) func(t poll.LogT) poll.Result {
func taskCount(orchestrator, stackname string, expected int) func(t poll.LogT) poll.Result { func taskCount(orchestrator, stackname string, expected int) func(t poll.LogT) poll.Result {
return func(poll.LogT) poll.Result { return func(poll.LogT) poll.Result {
args := []string{"--orchestrator", orchestrator, "stack", "ps", stackname} args := []string{"stack", "ps", stackname, "--orchestrator", orchestrator}
// FIXME(chris-crone): remove when we support filtering by desired-state on kubernetes // FIXME(chris-crone): remove when we support filtering by desired-state on kubernetes
if orchestrator == "swarm" { if orchestrator == "swarm" {
args = append(args, "-f=desired-state=running") args = append(args, "-f=desired-state=running")

View File

@ -167,18 +167,3 @@ 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"} })
}