diff --git a/cli/cobra.go b/cli/cobra.go index 1b5809e9e5ce..03cdfcc57337 100644 --- a/cli/cobra.go +++ b/cli/cobra.go @@ -102,7 +102,7 @@ func managementSubCommands(cmd *cobra.Command) []*cobra.Command { var usageTemplate = `Usage: {{- if not .HasSubCommands}} {{.UseLine}}{{end}} -{{- if .HasSubCommands}} {{ .CommandPath}} COMMAND{{end}} +{{- if .HasSubCommands}} {{ .CommandPath}}{{- if .HasAvailableFlags}} [OPTIONS]{{end}} COMMAND{{end}} {{ .Short | trim }} diff --git a/cli/command/cli.go b/cli/command/cli.go index 62a36b20b3ad..c919cbbbe98b 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -166,14 +166,9 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error { if err != nil { return errors.Wrap(err, "Experimental field") } - orchestrator, err := GetOrchestrator(opts.Common.Orchestrator, cli.configFile.Orchestrator) - if err != nil { - return err - } cli.clientInfo = ClientInfo{ DefaultVersion: cli.client.ClientVersion(), HasExperimental: hasExperimental, - Orchestrator: orchestrator, } cli.initializeFromClient() return nil @@ -239,22 +234,6 @@ type ServerInfo struct { type ClientInfo struct { HasExperimental bool 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. diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index 26ead5ddb460..a4b06e699753 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -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) { expected := "password" diff --git a/cli/command/orchestrator.go b/cli/command/orchestrator.go index 7daad75eef2e..5ffd0c0a84f0 100644 --- a/cli/command/orchestrator.go +++ b/cli/command/orchestrator.go @@ -17,10 +17,25 @@ const ( OrchestratorAll = Orchestrator("all") orchestratorUnset = Orchestrator("unset") - defaultOrchestrator = OrchestratorSwarm - envVarDockerOrchestrator = "DOCKER_ORCHESTRATOR" + defaultOrchestrator = OrchestratorSwarm + 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) { switch value { 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. -func GetOrchestrator(flagValue, value string) (Orchestrator, error) { +func GetStackOrchestrator(flagValue, value string) (Orchestrator, error) { // Check flag if o, err := normalize(flagValue); o != orchestratorUnset { return o, err } // Check environment variable - env := os.Getenv(envVarDockerOrchestrator) + env := os.Getenv(envVarDockerStackOrchestrator) if o, err := normalize(env); o != orchestratorUnset { return o, err } diff --git a/cli/command/orchestrator_test.go b/cli/command/orchestrator_test.go new file mode 100644 index 000000000000..01f50acd9248 --- /dev/null +++ b/cli/command/orchestrator_test.go @@ -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))) + }) + } +} diff --git a/cli/command/stack/cmd.go b/cli/command/stack/cmd.go index 06cfc8ee846c..c9feb99b612a 100644 --- a/cli/command/stack/cmd.go +++ b/cli/command/stack/cmd.go @@ -1,50 +1,125 @@ package stack import ( + "errors" "fmt" + "strings" "github.com/docker/cli/cli" "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/pflag" ) 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 func NewStackCommand(dockerCli command.Cli) *cobra.Command { + var opts commonOptions cmd := &cobra.Command{ - Use: "stack", + Use: "stack [OPTIONS]", Short: "Manage Docker stacks", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + 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()), 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( - newDeployCommand(dockerCli), - newListCommand(dockerCli), - newPsCommand(dockerCli), - newRemoveCommand(dockerCli), - newServicesCommand(dockerCli), + newDeployCommand(dockerCli, &opts), + newListCommand(dockerCli, &opts), + newPsCommand(dockerCli, &opts), + newRemoveCommand(dockerCli, &opts), + newServicesCommand(dockerCli, &opts), ) flags := cmd.PersistentFlags() flags.String("kubeconfig", "", "Kubernetes config file") flags.SetAnnotation("kubeconfig", "kubernetes", nil) + flags.String("orchestrator", "", "Orchestrator to use (swarm|kubernetes|all)") return cmd } // NewTopLevelDeployCommand returns a command for `docker deploy` func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command { - cmd := newDeployCommand(dockerCli) + cmd := newDeployCommand(dockerCli, nil) // Remove the aliases at the top level cmd.Aliases = []string{} cmd.Annotations = map[string]string{ "experimental": "", - "swarm": "", "version": "1.25", } 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 +} diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index 38837c6f113a..5ca032d7589b 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/cobra" ) -func newDeployCommand(dockerCli command.Cli) *cobra.Command { +func newDeployCommand(dockerCli command.Cli, common *commonOptions) *cobra.Command { var opts options.Deploy cmd := &cobra.Command{ @@ -20,10 +20,12 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { opts.Namespace = args[0] switch { - case dockerCli.ClientInfo().HasAll(): + case common == nil: // Top level deploy commad + return swarm.RunDeploy(dockerCli, opts) + case common.orchestrator.HasAll(): return errUnsupportedAllOrchestrator - case dockerCli.ClientInfo().HasKubernetes(): - kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) + case common.orchestrator.HasKubernetes(): + kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags(), common.orchestrator)) if err != nil { return err } diff --git a/cli/command/stack/kubernetes/cli.go b/cli/command/stack/kubernetes/cli.go index ca9db8ce6a8f..bcd0b01e5910 100644 --- a/cli/command/stack/kubernetes/cli.go +++ b/cli/command/stack/kubernetes/cli.go @@ -23,13 +23,16 @@ type KubeCli struct { // Options contains resolved parameters to initialize kubernetes clients type Options struct { - Namespace string - Config string + Namespace string + Config string + Orchestrator command.Orchestrator } // NewOptions returns an Options initialized with command line flags -func NewOptions(flags *flag.FlagSet) Options { - var opts Options +func NewOptions(flags *flag.FlagSet, orchestrator command.Orchestrator) Options { + opts := Options{ + Orchestrator: orchestrator, + } if namespace, err := flags.GetString("namespace"); err == nil { opts.Namespace = namespace } @@ -73,7 +76,7 @@ func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) { } cli.clientSet = clientSet - if dockerCli.ClientInfo().HasAll() { + if opts.Orchestrator.HasAll() { if err := cli.checkHostsMatch(); err != nil { return nil, err } diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index da9583e935c9..a0aa3d1cf50e 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -13,16 +13,16 @@ import ( "vbom.ml/util/sortorder" ) -func newListCommand(dockerCli command.Cli) *cobra.Command { +func newListCommand(dockerCli command.Cli, common *commonOptions) *cobra.Command { opts := options.List{} cmd := &cobra.Command{ - Use: "ls", + Use: "ls [OPTIONS]", Aliases: []string{"list"}, Short: "List stacks", Args: cli.NoArgs, 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 } -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{} - if dockerCli.ClientInfo().HasSwarm() { + if orchestrator.HasSwarm() { ss, err := swarm.GetStacks(dockerCli) if err != nil { return err } stacks = append(stacks, ss...) } - if dockerCli.ClientInfo().HasKubernetes() { - kubeCli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) + if orchestrator.HasKubernetes() { + kubeCli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags(), orchestrator)) if err != nil { return err } @@ -55,14 +55,14 @@ func runList(cmd *cobra.Command, dockerCli command.Cli, opts options.List) error } 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 if format == "" || format == formatter.TableFormatKey { format = formatter.SwarmStackTableFormat - if dockerCli.ClientInfo().HasKubernetes() { + if orchestrator.HasKubernetes() { format = formatter.KubernetesStackTableFormat } } diff --git a/cli/command/stack/list_test.go b/cli/command/stack/list_test.go index 2e7ad4df1218..e646fdbea239 100644 --- a/cli/command/stack/list_test.go +++ b/cli/command/stack/list_test.go @@ -4,6 +4,7 @@ import ( "io/ioutil" "testing" + "github.com/docker/cli/cli/command" "github.com/docker/cli/internal/test" // Import builders to get the builder function as package function . "github.com/docker/cli/internal/test/builders" @@ -14,6 +15,10 @@ import ( "gotest.tools/golden" ) +var ( + orchestrator = commonOptions{orchestrator: command.OrchestratorSwarm} +) + func TestListErrors(t *testing.T) { testCases := []struct { args []string @@ -48,7 +53,7 @@ func TestListErrors(t *testing.T) { for _, tc := range testCases { cmd := newListCommand(test.NewFakeCli(&fakeClient{ serviceListFunc: tc.serviceListFunc, - }, test.OrchestratorSwarm)) + }), &orchestrator) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) for key, value := range tc.flags { @@ -69,8 +74,8 @@ func TestListWithFormat(t *testing.T) { }), )}, nil }, - }, test.OrchestratorSwarm) - cmd := newListCommand(cli) + }) + cmd := newListCommand(cli, &orchestrator) cmd.Flags().Set("format", "{{ .Name }}") assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "stack-list-with-format.golden") @@ -86,8 +91,8 @@ func TestListWithoutFormat(t *testing.T) { }), )}, nil }, - }, test.OrchestratorSwarm) - cmd := newListCommand(cli) + }) + cmd := newListCommand(cli, &orchestrator) assert.NilError(t, cmd.Execute()) 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) { return uc.swarmServices, nil }, - }, test.OrchestratorSwarm) - cmd := newListCommand(cli) + }) + cmd := newListCommand(cli, &orchestrator) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), uc.golden) } diff --git a/cli/command/stack/ps.go b/cli/command/stack/ps.go index bb1bed2d1b9f..c1c18d204a15 100644 --- a/cli/command/stack/ps.go +++ b/cli/command/stack/ps.go @@ -10,7 +10,7 @@ import ( "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()} cmd := &cobra.Command{ @@ -20,10 +20,10 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { opts.Namespace = args[0] switch { - case dockerCli.ClientInfo().HasAll(): + case common.orchestrator.HasAll(): return errUnsupportedAllOrchestrator - case dockerCli.ClientInfo().HasKubernetes(): - kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) + case common.orchestrator.HasKubernetes(): + kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags(), common.orchestrator)) if err != nil { return err } diff --git a/cli/command/stack/ps_test.go b/cli/command/stack/ps_test.go index da769d86597d..c192a7a5abce 100644 --- a/cli/command/stack/ps_test.go +++ b/cli/command/stack/ps_test.go @@ -44,7 +44,7 @@ func TestStackPsErrors(t *testing.T) { for _, tc := range testCases { cmd := newPsCommand(test.NewFakeCli(&fakeClient{ taskListFunc: tc.taskListFunc, - })) + }), &orchestrator) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -57,7 +57,7 @@ func TestStackPsEmptyStack(t *testing.T) { return []swarm.Task{}, nil }, }) - cmd := newPsCommand(fakeCli) + cmd := newPsCommand(fakeCli, &orchestrator) cmd.SetArgs([]string{"foo"}) cmd.SetOutput(ioutil.Discard) @@ -71,7 +71,7 @@ func TestStackPsWithQuietOption(t *testing.T) { return []swarm.Task{*Task(TaskID("id-foo"))}, nil }, }) - cmd := newPsCommand(cli) + cmd := newPsCommand(cli, &orchestrator) cmd.SetArgs([]string{"foo"}) cmd.Flags().Set("quiet", "true") assert.NilError(t, cmd.Execute()) @@ -85,7 +85,7 @@ func TestStackPsWithNoTruncOption(t *testing.T) { return []swarm.Task{*Task(TaskID("xn4cypcov06f2w8gsbaf2lst3"))}, nil }, }) - cmd := newPsCommand(cli) + cmd := newPsCommand(cli, &orchestrator) cmd.SetArgs([]string{"foo"}) cmd.Flags().Set("no-trunc", "true") cmd.Flags().Set("format", "{{ .ID }}") @@ -104,7 +104,7 @@ func TestStackPsWithNoResolveOption(t *testing.T) { return *Node(NodeName("node-name-bar")), nil, nil }, }) - cmd := newPsCommand(cli) + cmd := newPsCommand(cli, &orchestrator) cmd.SetArgs([]string{"foo"}) cmd.Flags().Set("no-resolve", "true") cmd.Flags().Set("format", "{{ .Node }}") @@ -118,7 +118,7 @@ func TestStackPsWithFormat(t *testing.T) { return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil }, }) - cmd := newPsCommand(cli) + cmd := newPsCommand(cli, &orchestrator) cmd.SetArgs([]string{"foo"}) cmd.Flags().Set("format", "{{ .Name }}") assert.NilError(t, cmd.Execute()) @@ -134,7 +134,7 @@ func TestStackPsWithConfigFormat(t *testing.T) { cli.SetConfigFile(&configfile.ConfigFile{ TasksFormat: "{{ .Name }}", }) - cmd := newPsCommand(cli) + cmd := newPsCommand(cli, &orchestrator) cmd.SetArgs([]string{"foo"}) assert.NilError(t, cmd.Execute()) 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 }, }) - cmd := newPsCommand(cli) + cmd := newPsCommand(cli, &orchestrator) cmd.SetArgs([]string{"foo"}) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "stack-ps-without-format.golden") diff --git a/cli/command/stack/remove.go b/cli/command/stack/remove.go index 3f34cfc3072e..8738e6eafab7 100644 --- a/cli/command/stack/remove.go +++ b/cli/command/stack/remove.go @@ -9,21 +9,21 @@ import ( "github.com/spf13/cobra" ) -func newRemoveCommand(dockerCli command.Cli) *cobra.Command { +func newRemoveCommand(dockerCli command.Cli, common *commonOptions) *cobra.Command { var opts options.Remove cmd := &cobra.Command{ - Use: "rm STACK [STACK...]", + Use: "rm [OPTIONS] STACK [STACK...]", Aliases: []string{"remove", "down"}, Short: "Remove one or more stacks", Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.Namespaces = args switch { - case dockerCli.ClientInfo().HasAll(): + case common.orchestrator.HasAll(): return errUnsupportedAllOrchestrator - case dockerCli.ClientInfo().HasKubernetes(): - kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) + case common.orchestrator.HasKubernetes(): + kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags(), common.orchestrator)) if err != nil { return err } diff --git a/cli/command/stack/remove_test.go b/cli/command/stack/remove_test.go index 196ebdc67a21..61080fbf3d43 100644 --- a/cli/command/stack/remove_test.go +++ b/cli/command/stack/remove_test.go @@ -43,7 +43,7 @@ func fakeClientForRemoveStackTest(version string) *fakeClient { func TestRemoveStackVersion124DoesNotRemoveConfigsOrSecrets(t *testing.T) { client := fakeClientForRemoveStackTest("1.24") - cmd := newRemoveCommand(test.NewFakeCli(client)) + cmd := newRemoveCommand(test.NewFakeCli(client), &orchestrator) cmd.SetArgs([]string{"foo", "bar"}) assert.NilError(t, cmd.Execute()) @@ -55,7 +55,7 @@ func TestRemoveStackVersion124DoesNotRemoveConfigsOrSecrets(t *testing.T) { func TestRemoveStackVersion125DoesNotRemoveConfigs(t *testing.T) { client := fakeClientForRemoveStackTest("1.25") - cmd := newRemoveCommand(test.NewFakeCli(client)) + cmd := newRemoveCommand(test.NewFakeCli(client), &orchestrator) cmd.SetArgs([]string{"foo", "bar"}) assert.NilError(t, cmd.Execute()) @@ -67,7 +67,7 @@ func TestRemoveStackVersion125DoesNotRemoveConfigs(t *testing.T) { func TestRemoveStackVersion130RemovesEverything(t *testing.T) { client := fakeClientForRemoveStackTest("1.30") - cmd := newRemoveCommand(test.NewFakeCli(client)) + cmd := newRemoveCommand(test.NewFakeCli(client), &orchestrator) cmd.SetArgs([]string{"foo", "bar"}) assert.NilError(t, cmd.Execute()) @@ -98,7 +98,7 @@ func TestRemoveStackSkipEmpty(t *testing.T) { configs: allConfigs, } fakeCli := test.NewFakeCli(fakeClient) - cmd := newRemoveCommand(fakeCli) + cmd := newRemoveCommand(fakeCli, &orchestrator) cmd.SetArgs([]string{"foo", "bar"}) assert.NilError(t, cmd.Execute()) @@ -146,7 +146,7 @@ func TestRemoveContinueAfterError(t *testing.T) { return nil }, } - cmd := newRemoveCommand(test.NewFakeCli(cli)) + cmd := newRemoveCommand(test.NewFakeCli(cli), &orchestrator) cmd.SetOutput(ioutil.Discard) cmd.SetArgs([]string{"foo", "bar"}) diff --git a/cli/command/stack/services.go b/cli/command/stack/services.go index e1fed1a0de34..0379409b31b9 100644 --- a/cli/command/stack/services.go +++ b/cli/command/stack/services.go @@ -10,7 +10,7 @@ import ( "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()} cmd := &cobra.Command{ @@ -20,10 +20,10 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { opts.Namespace = args[0] switch { - case dockerCli.ClientInfo().HasAll(): + case common.orchestrator.HasAll(): return errUnsupportedAllOrchestrator - case dockerCli.ClientInfo().HasKubernetes(): - kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) + case common.orchestrator.HasKubernetes(): + kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags(), common.orchestrator)) if err != nil { return err } diff --git a/cli/command/stack/services_test.go b/cli/command/stack/services_test.go index 2acb91ca73d9..0bc8839b64e7 100644 --- a/cli/command/stack/services_test.go +++ b/cli/command/stack/services_test.go @@ -70,7 +70,7 @@ func TestStackServicesErrors(t *testing.T) { nodeListFunc: tc.nodeListFunc, taskListFunc: tc.taskListFunc, }) - cmd := newServicesCommand(cli) + cmd := newServicesCommand(cli, &orchestrator) cmd.SetArgs(tc.args) for key, value := range tc.flags { cmd.Flags().Set(key, value) @@ -86,7 +86,7 @@ func TestStackServicesEmptyServiceList(t *testing.T) { return []swarm.Service{}, nil }, }) - cmd := newServicesCommand(fakeCli) + cmd := newServicesCommand(fakeCli, &orchestrator) cmd.SetArgs([]string{"foo"}) assert.NilError(t, cmd.Execute()) 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 }, }) - cmd := newServicesCommand(cli) + cmd := newServicesCommand(cli, &orchestrator) cmd.Flags().Set("quiet", "true") cmd.SetArgs([]string{"foo"}) assert.NilError(t, cmd.Execute()) @@ -114,7 +114,7 @@ func TestStackServicesWithFormat(t *testing.T) { }, nil }, }) - cmd := newServicesCommand(cli) + cmd := newServicesCommand(cli, &orchestrator) cmd.SetArgs([]string{"foo"}) cmd.Flags().Set("format", "{{ .Name }}") assert.NilError(t, cmd.Execute()) @@ -132,7 +132,7 @@ func TestStackServicesWithConfigFormat(t *testing.T) { cli.SetConfigFile(&configfile.ConfigFile{ ServicesFormat: "{{ .Name }}", }) - cmd := newServicesCommand(cli) + cmd := newServicesCommand(cli, &orchestrator) cmd.SetArgs([]string{"foo"}) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "stack-services-with-config-format.golden") @@ -155,7 +155,7 @@ func TestStackServicesWithoutFormat(t *testing.T) { )}, nil }, }) - cmd := newServicesCommand(cli) + cmd := newServicesCommand(cli, &orchestrator) cmd.SetArgs([]string{"foo"}) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "stack-services-without-format.golden") diff --git a/cli/command/system/testdata/docker-client-version.golden b/cli/command/system/testdata/docker-client-version.golden index 8d2efad924de..4079d5de0201 100644 --- a/cli/command/system/testdata/docker-client-version.golden +++ b/cli/command/system/testdata/docker-client-version.golden @@ -6,4 +6,3 @@ Client: Built: Wed May 30 22:21:05 2018 OS/Arch: linux/amd64 Experimental: true - Orchestrator: swarm diff --git a/cli/command/system/version.go b/cli/command/system/version.go index 04f060c4dc03..c8a147172052 100644 --- a/cli/command/system/version.go +++ b/cli/command/system/version.go @@ -29,7 +29,6 @@ Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}} Built: {{.BuildTime}} OS/Arch: {{.Os}}/{{.Arch}} Experimental: {{.Experimental}} - Orchestrator: {{.Orchestrator}} {{- end}} {{- if .ServerOK}}{{with .Server}} @@ -78,7 +77,6 @@ type clientVersion struct { Arch string BuildTime string `json:",omitempty"` Experimental bool - Orchestrator string `json:",omitempty"` } type kubernetesVersion struct { @@ -107,7 +105,7 @@ func NewVersionCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() 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) return cmd @@ -128,6 +126,11 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) 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{ Client: clientVersion{ Platform: struct{ Name string }{cli.PlatformName}, @@ -140,14 +143,16 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) error { Os: runtime.GOOS, Arch: runtime.GOARCH, Experimental: dockerCli.ClientInfo().HasExperimental, - Orchestrator: string(dockerCli.ClientInfo().Orchestrator), }, } sv, err := dockerCli.Client().ServerVersion(context.Background()) if err == nil { vd.Server = &sv - kubeVersion := getKubernetesVersion(dockerCli, opts.kubeConfig) + var kubeVersion *kubernetesVersion + if orchestrator.HasKubernetes() { + kubeVersion = getKubernetesVersion(opts.kubeConfig) + } foundEngine := false foundKubernetes := false for _, component := range sv.Components { @@ -225,11 +230,7 @@ func getDetailsOrder(v types.ComponentVersion) []string { return out } -func getKubernetesVersion(dockerCli command.Cli, kubeConfig string) *kubernetesVersion { - if !dockerCli.ClientInfo().HasKubernetes() { - return nil - } - +func getKubernetesVersion(kubeConfig string) *kubernetesVersion { version := kubernetesVersion{ Kubernetes: "Unknown", StackAPI: "Unknown", diff --git a/cli/command/system/version_test.go b/cli/command/system/version_test.go index fdf870d0f558..b8163861b9ed 100644 --- a/cli/command/system/version_test.go +++ b/cli/command/system/version_test.go @@ -11,7 +11,6 @@ import ( "gotest.tools/golden" "github.com/docker/cli/internal/test" - "github.com/docker/docker/api" "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) } -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) { vi := versionInfo{ Client: clientVersion{ @@ -57,7 +42,6 @@ func TestVersionAlign(t *testing.T) { Arch: "amd64", BuildTime: "Wed May 30 22:21:05 2018", 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, is.Equal("", cli.ErrBuffer().String())) } - -func cleanTabs(line string) string { - return strings.Join(strings.Fields(line), " ") -} diff --git a/cli/config/configfile/file.go b/cli/config/configfile/file.go index 383a03228d7a..37e1533f42a8 100644 --- a/cli/config/configfile/file.go +++ b/cli/config/configfile/file.go @@ -46,7 +46,7 @@ type ConfigFile struct { PruneFilters []string `json:"pruneFilters,omitempty"` Proxies map[string]ProxyConfig `json:"proxies,omitempty"` Experimental string `json:"experimental,omitempty"` - Orchestrator string `json:"orchestrator,omitempty"` + StackOrchestrator string `json:"stackOrchestrator,omitempty"` Kubernetes *KubernetesConfig `json:"kubernetes,omitempty"` } diff --git a/cli/flags/common.go b/cli/flags/common.go index 46c3d9d68bb8..3834097c3ad4 100644 --- a/cli/flags/common.go +++ b/cli/flags/common.go @@ -31,13 +31,12 @@ var ( // CommonOptions are options common to both the client and the daemon. type CommonOptions struct { - Debug bool - Hosts []string - Orchestrator string - LogLevel string - TLS bool - TLSVerify bool - TLSOptions *tlsconfig.Options + Debug bool + Hosts []string + LogLevel string + TLS bool + TLSVerify bool + TLSOptions *tlsconfig.Options } // NewCommonOptions returns a new CommonOptions diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index 38a3d063cde6..badc1bcc6c9c 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -51,11 +51,6 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files") 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) setHelpFunc(dockerCli, cmd, flags, opts) @@ -244,13 +239,10 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) { osType := details.ServerInfo().OSType hasExperimental := details.ServerInfo().HasExperimental hasExperimentalCLI := details.ClientInfo().HasExperimental - hasKubernetes := details.ClientInfo().HasKubernetes() cmd.Flags().VisitAll(func(f *pflag.Flag) { hideFeatureFlag(f, hasExperimental, "experimental") hideFeatureFlag(f, hasExperimentalCLI, "experimentalCLI") - hideFeatureFlag(f, hasKubernetes, "kubernetes") - hideFeatureFlag(f, !hasKubernetes, "swarm") // hide flags not supported by the server if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) { f.Hidden = true @@ -266,8 +258,6 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) { for _, subcmd := range cmd.Commands() { hideFeatureSubCommand(subcmd, hasExperimental, "experimental") hideFeatureSubCommand(subcmd, hasExperimentalCLI, "experimentalCLI") - hideFeatureSubCommand(subcmd, hasKubernetes, "kubernetes") - hideFeatureSubCommand(subcmd, !hasKubernetes, "swarm") // hide subcommands not supported by the server if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) { subcmd.Hidden = true @@ -302,7 +292,6 @@ func areFlagsSupported(cmd *cobra.Command, details versionDetails) error { clientVersion := details.Client().ClientVersion() osType := details.ServerInfo().OSType hasExperimental := details.ServerInfo().HasExperimental - hasKubernetes := details.ClientInfo().HasKubernetes() hasExperimentalCLI := details.ClientInfo().HasExperimental errs := []string{} @@ -323,14 +312,6 @@ func areFlagsSupported(cmd *cobra.Command, details versionDetails) error { 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)) } - _, 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 { @@ -345,7 +326,6 @@ func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error { osType := details.ServerInfo().OSType hasExperimental := details.ServerInfo().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` 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 { 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 } diff --git a/docs/reference/commandline/cli.md b/docs/reference/commandline/cli.md index ebb9faae2755..81cd64f28b02 100644 --- a/docs/reference/commandline/cli.md +++ b/docs/reference/commandline/cli.md @@ -66,6 +66,7 @@ by the `docker` command line: * `DOCKER_NOWARN_KERNEL_VERSION` Prevent warnings that your Linux kernel is unsuitable for Docker. * `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_VERIFY` When set Docker uses TLS and verifies the remote. * `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 [**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 the using `CTRL-p CTRL-q` key sequence. This detach key sequence is customizable using the `detachKeys` property. Specify a `` value for the @@ -236,7 +242,8 @@ Following is a sample `config.json` file: "credHelpers": { "awesomereg.example.org": "hip-star", "unicorn.example.com": "vcbait" - } + }, + "stackOrchestrator": "kubernetes" } {% endraw %} ``` diff --git a/docs/reference/commandline/stack.md b/docs/reference/commandline/stack.md index 428df82f296d..cdb5f3ff8433 100644 --- a/docs/reference/commandline/stack.md +++ b/docs/reference/commandline/stack.md @@ -16,18 +16,20 @@ keywords: "stack" # stack ```markdown -Usage: docker stack COMMAND +Usage: docker stack [OPTIONS] COMMAND Manage Docker stacks Options: - --help Print usage + --help Print usage + --kubeconfig string Kubernetes config file + --orchestrator string Orchestrator to use (swarm|kubernetes|all) Commands: deploy Deploy a new stack or update an existing stack ls List stacks ps List the tasks in the stack - rm Remove the stack + rm Remove one or more stacks services List the services in the stack Run 'docker stack COMMAND --help' for more information on a command. diff --git a/docs/reference/commandline/stack_deploy.md b/docs/reference/commandline/stack_deploy.md index 2adbc64e9e9f..8f7687bce863 100644 --- a/docs/reference/commandline/stack_deploy.md +++ b/docs/reference/commandline/stack_deploy.md @@ -27,6 +27,9 @@ Options: --bundle-file string Path to a Distributed Application Bundle file -c, --compose-file strings Path to a Compose file --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 --resolve-image string Query the registry to resolve image digest and supported platforms ("always"|"changed"|"never") (default "always") diff --git a/docs/reference/commandline/stack_ls.md b/docs/reference/commandline/stack_ls.md index 6613577e649a..dd0857ebc960 100644 --- a/docs/reference/commandline/stack_ls.md +++ b/docs/reference/commandline/stack_ls.md @@ -16,7 +16,7 @@ keywords: "stack, ls" # stack ls ```markdown -Usage: docker stack ls +Usage: docker stack ls [OPTIONS] List stacks @@ -24,8 +24,11 @@ Aliases: ls, list Options: - --help Print usage - --format string Pretty-print stacks using a Go template + --help Print usage + --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 diff --git a/docs/reference/commandline/stack_ps.md b/docs/reference/commandline/stack_ps.md index bcdacee04433..bf25c722b2cd 100644 --- a/docs/reference/commandline/stack_ps.md +++ b/docs/reference/commandline/stack_ps.md @@ -21,12 +21,15 @@ Usage: docker stack ps [OPTIONS] STACK List the tasks in the stack Options: - -f, --filter filter Filter output based on conditions provided - --format string Pretty-print tasks using a Go template - --help Print usage - --no-resolve Do not map IDs to Names - --no-trunc Do not truncate output - -q, --quiet Only display task IDs + -f, --filter filter Filter output based on conditions provided + --format string Pretty-print tasks using a Go template + --help Print usage + --kubeconfig string Kubernetes config file + --namespace string Kubernetes namespace to use + --no-resolve Do not map IDs to Names + --no-trunc Do not truncate output + --orchestrator string Orchestrator to use (swarm|kubernetes|all) + -q, --quiet Only display task IDs ``` ## Description diff --git a/docs/reference/commandline/stack_rm.md b/docs/reference/commandline/stack_rm.md index e51037c09041..b9d48a345ae1 100644 --- a/docs/reference/commandline/stack_rm.md +++ b/docs/reference/commandline/stack_rm.md @@ -16,7 +16,7 @@ keywords: "stack, rm, remove, down" # stack rm ```markdown -Usage: docker stack rm STACK [STACK...] +Usage: docker stack rm [OPTIONS] STACK [STACK...] Remove one or more stacks @@ -24,7 +24,10 @@ Aliases: rm, remove, down 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 diff --git a/docs/reference/commandline/stack_services.md b/docs/reference/commandline/stack_services.md index 5cb3c110b661..2f60df426739 100644 --- a/docs/reference/commandline/stack_services.md +++ b/docs/reference/commandline/stack_services.md @@ -2,7 +2,6 @@ title: "stack services" description: "The stack services command description and usage" keywords: "stack, services" -advisory: "experimental" --- -# stack services (experimental) +# stack services ```markdown Usage: docker stack services [OPTIONS] STACK @@ -22,10 +21,13 @@ Usage: docker stack services [OPTIONS] STACK List the services in the stack Options: - -f, --filter filter Filter output based on conditions provided - --format string Pretty-print services using a Go template - --help Print usage - -q, --quiet Only display IDs + -f, --filter filter Filter output based on conditions provided + --format string Pretty-print services using a Go template + --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 ``` ## Description diff --git a/docs/reference/commandline/version.md b/docs/reference/commandline/version.md index aa720fa583f3..c138052b9c94 100644 --- a/docs/reference/commandline/version.md +++ b/docs/reference/commandline/version.md @@ -21,8 +21,9 @@ Usage: docker version [OPTIONS] Show the Docker version information Options: - -f, --format string Format the output using the given Go template - --help Print usage + -f, --format string Format the output using the given Go template + --help Print usage + --kubeconfig string Kubernetes config file ``` ## Description diff --git a/e2e/stack/deploy_test.go b/e2e/stack/deploy_test.go index a0e7d28608f4..959405c68d90 100644 --- a/e2e/stack/deploy_test.go +++ b/e2e/stack/deploy_test.go @@ -30,10 +30,10 @@ func testDeployWithNamedResources(t *testing.T, orchestrator string) { stackname := fmt.Sprintf("test-stack-deploy-with-names-%s", orchestrator) composefile := golden.Path("stack-with-named-resources.yml") - result := icmd.RunCommand("docker", "--orchestrator", orchestrator, - "stack", "deploy", "-c", composefile, stackname) - defer icmd.RunCommand("docker", "--orchestrator", orchestrator, - "stack", "rm", stackname) + result := icmd.RunCommand("docker", "stack", "deploy", + "-c", composefile, stackname, "--orchestrator", orchestrator) + defer icmd.RunCommand("docker", "stack", "rm", + "--orchestrator", orchestrator, stackname) result.Assert(t, icmd.Success) stdout := strings.Split(result.Stdout(), "\n") diff --git a/e2e/stack/remove_test.go b/e2e/stack/remove_test.go index 27300b1cc25b..6c5f57981786 100644 --- a/e2e/stack/remove_test.go +++ b/e2e/stack/remove_test.go @@ -29,8 +29,8 @@ func testRemove(t *testing.T, orchestrator string) { stackname := "test-stack-remove-" + orchestrator deployFullStack(t, orchestrator, stackname) defer cleanupFullStack(t, orchestrator, stackname) - result := icmd.RunCommand("docker", "--orchestrator", orchestrator, - "stack", "rm", stackname) + result := icmd.RunCommand("docker", "stack", "rm", + stackname, "--orchestrator", orchestrator) result.Assert(t, icmd.Expected{Err: icmd.None}) golden.Assert(t, result.Stdout(), 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) { // TODO: this stack should have full options not minimal options - result := icmd.RunCommand("docker", "--orchestrator", orchestrator, - "stack", "deploy", "--compose-file=./testdata/full-stack.yml", stackname) + result := icmd.RunCommand("docker", "stack", "deploy", + "--compose-file=./testdata/full-stack.yml", stackname, "--orchestrator", orchestrator) result.Assert(t, icmd.Success) 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 { 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 strings.Contains(result.Stderr(), "not found") { 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 { 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 if orchestrator == "swarm" { args = append(args, "-f=desired-state=running") diff --git a/internal/test/cli.go b/internal/test/cli.go index fdb88072945e..17fab645ea24 100644 --- a/internal/test/cli.go +++ b/internal/test/cli.go @@ -167,18 +167,3 @@ func (c *FakeCli) ContentTrustEnabled() bool { func EnableContentTrust(c *FakeCli) { c.contentTrust = true } - -// OrchestratorSwarm sets a command.ClientInfo with Swarm orchestrator -func OrchestratorSwarm(c *FakeCli) { - c.SetClientInfo(func() command.ClientInfo { return command.ClientInfo{Orchestrator: "swarm"} }) -} - -// OrchestratorKubernetes sets a command.ClientInfo with Kubernetes orchestrator -func OrchestratorKubernetes(c *FakeCli) { - c.SetClientInfo(func() command.ClientInfo { return command.ClientInfo{Orchestrator: "kubernetes"} }) -} - -// OrchestratorAll sets a command.ClientInfo with all orchestrator -func OrchestratorAll(c *FakeCli) { - c.SetClientInfo(func() command.ClientInfo { return command.ClientInfo{Orchestrator: "all"} }) -}