Scope orchestration selection to stack commands only

* Renaming DOCKER_ORCHESTRATOR to DOCKER_STACK_ORCHESTRATOR
* Renaming config file option "orchestrator" to "stackOrchestrator"
* "--orchestrator" flag is no more global but local to stack command and subcommands
* Cleaning all global orchestrator code
* Replicating Hidden flags in help and Supported flags from root command to stack command

Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
This commit is contained in:
Silvin Lubecki 2018-06-20 14:48:50 +02:00 committed by Sebastiaan van Stijn
parent 8de0753869
commit 71272dd203
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
24 changed files with 329 additions and 278 deletions

View File

@ -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.

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) {
expected := "password"

View File

@ -18,9 +18,24 @@ const (
orchestratorUnset = Orchestrator("unset")
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) {
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
}

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
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",
Short: "Manage Docker stacks",
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()),
Annotations: map[string]string{
"kubernetes": "",
"swarm": "",
"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
}

View File

@ -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
}

View File

@ -25,11 +25,14 @@ type KubeCli struct {
type Options struct {
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
}

View File

@ -13,7 +13,7 @@ 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{
@ -22,7 +22,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
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
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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")

View File

@ -9,7 +9,7 @@ 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{
@ -20,10 +20,10 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
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
}

View File

@ -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"})

View File

@ -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
}

View File

@ -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")

View File

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

View File

@ -29,7 +29,7 @@ Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
Built: {{.BuildTime}}
OS/Arch: {{.Os}}/{{.Arch}}
Experimental: {{.Experimental}}
Orchestrator: {{.Orchestrator}}
Stack Orchestrator: {{.StackOrchestrator}}
{{- end}}
{{- if .ServerOK}}{{with .Server}}
@ -78,7 +78,7 @@ type clientVersion struct {
Arch string
BuildTime string `json:",omitempty"`
Experimental bool
Orchestrator string `json:",omitempty"`
StackOrchestrator string `json:",omitempty"`
}
type kubernetesVersion struct {
@ -128,6 +128,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 +145,17 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) error {
Os: runtime.GOOS,
Arch: runtime.GOARCH,
Experimental: dockerCli.ClientInfo().HasExperimental,
Orchestrator: string(dockerCli.ClientInfo().Orchestrator),
StackOrchestrator: string(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 +233,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",

View File

@ -39,7 +39,7 @@ func fakeServerVersion(_ context.Context) (types.Version, error) {
}
func TestVersionWithOrchestrator(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{serverVersion: fakeServerVersion}, test.OrchestratorSwarm)
cli := test.NewFakeCli(&fakeClient{serverVersion: fakeServerVersion})
cmd := NewVersionCommand(cli)
assert.NilError(t, cmd.Execute())
assert.Check(t, is.Contains(cleanTabs(cli.OutBuffer().String()), "Orchestrator: swarm"))
@ -57,7 +57,7 @@ func TestVersionAlign(t *testing.T) {
Arch: "amd64",
BuildTime: "Wed May 30 22:21:05 2018",
Experimental: true,
Orchestrator: "swarm",
StackOrchestrator: "swarm",
},
}

View File

@ -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"`
}

View File

@ -33,7 +33,6 @@ var (
type CommonOptions struct {
Debug bool
Hosts []string
Orchestrator string
LogLevel string
TLS 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")
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
}

View File

@ -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")

View File

@ -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")

View File

@ -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"} })
}