Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add kubernetes support to docker/cli #721

Merged
merged 17 commits into from
Jan 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 9 additions & 0 deletions cli/command/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type Cli interface {
ServerInfo() ServerInfo
ClientInfo() ClientInfo
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
DefaultVersion() string
}

// DockerCli is an instance the docker command line client.
Expand Down Expand Up @@ -135,9 +136,11 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
if err != nil {
return errors.Wrap(err, "Experimental field")
}
orchestrator := GetOrchestrator(hasExperimental, opts.Common.Orchestrator, cli.configFile.Orchestrator)
cli.clientInfo = ClientInfo{
DefaultVersion: cli.client.ClientVersion(),
HasExperimental: hasExperimental,
Orchestrator: orchestrator,
}
cli.initializeFromClient()
return nil
Expand Down Expand Up @@ -203,6 +206,12 @@ 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.HasExperimental && c.Orchestrator == OrchestratorKubernetes
}

// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
Expand Down
104 changes: 104 additions & 0 deletions cli/command/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,110 @@ 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
}{
{
doc: "default",
configfile: `{
"experimental": "enabled"
}`,
expectedOrchestrator: "swarm",
expectedKubernetes: false,
},
{
doc: "kubernetesIsExperimental",
configfile: `{
"experimental": "disabled",
"orchestrator": "kubernetes"
}`,
envOrchestrator: "kubernetes",
flagOrchestrator: "kubernetes",
expectedOrchestrator: "swarm",
expectedKubernetes: false,
},
{
doc: "kubernetesConfigFile",
configfile: `{
"experimental": "enabled",
"orchestrator": "kubernetes"
}`,
expectedOrchestrator: "kubernetes",
expectedKubernetes: true,
},
{
doc: "kubernetesEnv",
configfile: `{
"experimental": "enabled"
}`,
envOrchestrator: "kubernetes",
expectedOrchestrator: "kubernetes",
expectedKubernetes: true,
},
{
doc: "kubernetesFlag",
configfile: `{
"experimental": "enabled"
}`,
flagOrchestrator: "kubernetes",
expectedOrchestrator: "kubernetes",
expectedKubernetes: true,
},
{
doc: "envOverridesConfigFile",
configfile: `{
"experimental": "enabled",
"orchestrator": "kubernetes"
}`,
envOrchestrator: "swarm",
expectedOrchestrator: "swarm",
expectedKubernetes: false,
},
{
doc: "flagOverridesEnv",
configfile: `{
"experimental": "enabled"
}`,
envOrchestrator: "kubernetes",
flagOrchestrator: "swarm",
expectedOrchestrator: "swarm",
expectedKubernetes: false,
},
}

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 patchEnvVariable(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.NoError(t, err)
assert.Equal(t, testcase.expectedKubernetes, cli.ClientInfo().HasKubernetes())
assert.Equal(t, testcase.expectedOrchestrator, string(cli.ClientInfo().Orchestrator))
})
}
}

func TestGetClientWithPassword(t *testing.T) {
expected := "password"

Expand Down
13 changes: 8 additions & 5 deletions cli/command/config/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import (
// NewConfigCommand returns a cobra command for `config` subcommands
func NewConfigCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Manage Docker configs",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"version": "1.30"},
Use: "config",
Short: "Manage Docker configs",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{
"version": "1.30",
"swarm": "",
},
}
cmd.AddCommand(
newConfigListCommand(dockerCli),
Expand Down
13 changes: 8 additions & 5 deletions cli/command/node/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ import (
// NewNodeCommand returns a cobra command for `node` subcommands
func NewNodeCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "node",
Short: "Manage Swarm nodes",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"version": "1.24"},
Use: "node",
Short: "Manage Swarm nodes",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{
"version": "1.24",
"swarm": "",
},
}
cmd.AddCommand(
newDemoteCommand(dockerCli),
Expand Down
59 changes: 59 additions & 0 deletions cli/command/orchestrator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package command

import (
"fmt"
"os"
)

// Orchestrator type acts as an enum describing supported orchestrators.
type Orchestrator string

const (
// OrchestratorKubernetes orchestrator
OrchestratorKubernetes = Orchestrator("kubernetes")
// OrchestratorSwarm orchestrator
OrchestratorSwarm = Orchestrator("swarm")
orchestratorUnset = Orchestrator("unset")

defaultOrchestrator = OrchestratorSwarm
envVarDockerOrchestrator = "DOCKER_ORCHESTRATOR"
)

func normalize(flag string) Orchestrator {
switch flag {
case "kubernetes", "k8s":
return OrchestratorKubernetes
case "swarm", "swarmkit":
return OrchestratorSwarm
default:
return orchestratorUnset
}
}

// GetOrchestrator checks DOCKER_ORCHESTRATOR environment variable and configuration file
// orchestrator value and returns user defined Orchestrator.
func GetOrchestrator(isExperimental bool, flagValue, value string) Orchestrator {
// Non experimental CLI has kubernetes disabled
if !isExperimental {
return defaultOrchestrator
}
// Check flag
if o := normalize(flagValue); o != orchestratorUnset {
return o
}
// Check environment variable
env := os.Getenv(envVarDockerOrchestrator)
if o := normalize(env); o != orchestratorUnset {
return o
}
// Check specified orchestrator
if o := normalize(value); o != orchestratorUnset {
return o
}

if value != "" {
fmt.Fprintf(os.Stderr, "Specified orchestrator %q is invalid. Please use either kubernetes or swarm\n", value)
}
// Nothing set, use default orchestrator
return defaultOrchestrator
}
13 changes: 8 additions & 5 deletions cli/command/secret/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import (
// NewSecretCommand returns a cobra command for `secret` subcommands
func NewSecretCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "secret",
Short: "Manage Docker secrets",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"version": "1.25"},
Use: "secret",
Short: "Manage Docker secrets",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{
"version": "1.25",
"swarm": "",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the plans with this annotation's value? Are we planning to do (e.g.) "kubernetes": "1.9" (only available starting from a specific kubernetes version)? If not, would it make sense to use "orchestrator": "swarm" for this? (thinking out loud).

edit: we do the same for "experimental", so I guess this is ok (version-check for k8s may still be something to consider using if we expect features to become available only from a certain version of k8s)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also add the new annotations to the YAML generator for the documentation, so that they can be used there to add badges/labels in the documentation; https://github.com/docker/cli/blob/master/docs/yaml/yaml.go

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am preparing a compose controller version negociation in a followup. I will consider the kubernetes version too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done for the YAML generator.

},
}
cmd.AddCommand(
newSecretListCommand(dockerCli),
Expand Down
13 changes: 8 additions & 5 deletions cli/command/service/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import (
// NewServiceCommand returns a cobra command for `service` subcommands
func NewServiceCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "service",
Short: "Manage services",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"version": "1.24"},
Use: "service",
Short: "Manage services",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{
"version": "1.24",
"swarm": "",
},
}
cmd.AddCommand(
newCreateCommand(dockerCli),
Expand Down
9 changes: 8 additions & 1 deletion cli/command/stack/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
cmd.AddCommand(
newDeployCommand(dockerCli),
newListCommand(dockerCli),
newPsCommand(dockerCli),
newRemoveCommand(dockerCli),
newServicesCommand(dockerCli),
newPsCommand(dockerCli),
)
flags := cmd.PersistentFlags()
flags.String("namespace", "default", "Kubernetes namespace to use")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd probably be cleaner to have those flags in a stackOptions struct

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't do that one, given how cobra works, we can just use PersistentFlags().Changed("namespace") for those.

flags.SetAnnotation("namespace", "kubernetes", nil)
flags.SetAnnotation("namespace", "experimentalCLI", nil)
flags.String("kubeconfig", "", "Kubernetes config file")
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
flags.SetAnnotation("kubeconfig", "experimentalCLI", nil)
return cmd
}

Expand Down
Loading