Skip to content

Commit

Permalink
Refactor stack command
Browse files Browse the repository at this point in the history
- Define command and subcommands only once
- Use annotations for k8s or swarm specific flags or subcommands

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
  • Loading branch information
vdemeester committed Dec 4, 2017
1 parent a31b1c8 commit 987aabb
Show file tree
Hide file tree
Showing 49 changed files with 759 additions and 578 deletions.
14 changes: 14 additions & 0 deletions cli/command/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type Cli interface {
SetIn(in *InStream)
ConfigFile() *configfile.ConfigFile
ServerInfo() ServerInfo
ClientInfo() ClientInfo
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
}

Expand All @@ -55,6 +56,7 @@ type DockerCli struct {
client client.APIClient
defaultVersion string
server ServerInfo
clientInfo ClientInfo
}

// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
Expand Down Expand Up @@ -107,6 +109,11 @@ func (cli *DockerCli) ServerInfo() ServerInfo {
return cli.server
}

// ClientInfo returns the client details
func (cli *DockerCli) ClientInfo() ClientInfo {
return cli.clientInfo
}

// Initialize the dockerCli runs initialization that must happen after command
// line flags are parsed.
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
Expand All @@ -125,6 +132,8 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
if err != nil {
return err
}
orchestrator := GetOrchestrator(cli.configFile.Orchestrator)
cli.clientInfo = ClientInfo{HasKubernetes: orchestrator == OrchestratorKubernetes}
cli.initializeFromClient()
return nil
}
Expand Down Expand Up @@ -176,6 +185,11 @@ type ServerInfo struct {
OSType string
}

// ClientInfo store details about the supported features of the client
type ClientInfo struct {
HasKubernetes bool
}

// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
Expand Down
16 changes: 6 additions & 10 deletions cli/command/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@ package command
import (
"os"
"strings"

cliconfig "github.com/docker/cli/cli/config"
)

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

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

Expand All @@ -34,17 +32,15 @@ func normalize(flag string) Orchestrator {

// GetOrchestrator checks DOCKER_ORCHESTRATOR environment variable and configuration file
// orchestrator value and returns user defined Orchestrator.
func GetOrchestrator(dockerCli Cli) Orchestrator {
func GetOrchestrator(orchestrator string) Orchestrator {
// Check environment variable
env := os.Getenv(dockerOrchestrator)
if o := normalize(env); o != orchestratorUnset {
return o
}
// Check config file
if configFile := cliconfig.LoadDefaultConfigFile(dockerCli.Err()); configFile != nil {
if o := normalize(configFile.Orchestrator); o != orchestratorUnset {
return o
}
// Check specified orchestrator
if o := normalize(orchestrator); o != orchestratorUnset {
return o
}

// Nothing set, use default orchestrator
Expand Down
239 changes: 239 additions & 0 deletions cli/command/stack/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package stack

import (
"strings"

"github.com/docker/cli/cli/compose/convert"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)

type fakeClient struct {
client.Client

version string

services []string
networks []string
secrets []string
configs []string

removedServices []string
removedNetworks []string
removedSecrets []string
removedConfigs []string

serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
networkListFunc func(options types.NetworkListOptions) ([]types.NetworkResource, error)
secretListFunc func(options types.SecretListOptions) ([]swarm.Secret, error)
configListFunc func(options types.ConfigListOptions) ([]swarm.Config, error)
nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error)
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error)

serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error)

serviceRemoveFunc func(serviceID string) error
networkRemoveFunc func(networkID string) error
secretRemoveFunc func(secretID string) error
configRemoveFunc func(configID string) error
}

func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
return types.Version{
Version: "docker-dev",
APIVersion: api.DefaultVersion,
}, nil
}

func (cli *fakeClient) ClientVersion() string {
return cli.version
}

func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
if cli.serviceListFunc != nil {
return cli.serviceListFunc(options)
}

namespace := namespaceFromFilters(options.Filters)
servicesList := []swarm.Service{}
for _, name := range cli.services {
if belongToNamespace(name, namespace) {
servicesList = append(servicesList, serviceFromName(name))
}
}
return servicesList, nil
}

func (cli *fakeClient) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
if cli.networkListFunc != nil {
return cli.networkListFunc(options)
}

namespace := namespaceFromFilters(options.Filters)
networksList := []types.NetworkResource{}
for _, name := range cli.networks {
if belongToNamespace(name, namespace) {
networksList = append(networksList, networkFromName(name))
}
}
return networksList, nil
}

func (cli *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
if cli.secretListFunc != nil {
return cli.secretListFunc(options)
}

namespace := namespaceFromFilters(options.Filters)
secretsList := []swarm.Secret{}
for _, name := range cli.secrets {
if belongToNamespace(name, namespace) {
secretsList = append(secretsList, secretFromName(name))
}
}
return secretsList, nil
}

func (cli *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
if cli.configListFunc != nil {
return cli.configListFunc(options)
}

namespace := namespaceFromFilters(options.Filters)
configsList := []swarm.Config{}
for _, name := range cli.configs {
if belongToNamespace(name, namespace) {
configsList = append(configsList, configFromName(name))
}
}
return configsList, nil
}

func (cli *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
if cli.taskListFunc != nil {
return cli.taskListFunc(options)
}
return []swarm.Task{}, nil
}

func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
if cli.nodeListFunc != nil {
return cli.nodeListFunc(options)
}
return []swarm.Node{}, nil
}

func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) {
if cli.nodeInspectWithRaw != nil {
return cli.nodeInspectWithRaw(ref)
}
return swarm.Node{}, nil, nil
}

func (cli *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
if cli.serviceUpdateFunc != nil {
return cli.serviceUpdateFunc(serviceID, version, service, options)
}

return types.ServiceUpdateResponse{}, nil
}

func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error {
if cli.serviceRemoveFunc != nil {
return cli.serviceRemoveFunc(serviceID)
}

cli.removedServices = append(cli.removedServices, serviceID)
return nil
}

func (cli *fakeClient) NetworkRemove(ctx context.Context, networkID string) error {
if cli.networkRemoveFunc != nil {
return cli.networkRemoveFunc(networkID)
}

cli.removedNetworks = append(cli.removedNetworks, networkID)
return nil
}

func (cli *fakeClient) SecretRemove(ctx context.Context, secretID string) error {
if cli.secretRemoveFunc != nil {
return cli.secretRemoveFunc(secretID)
}

cli.removedSecrets = append(cli.removedSecrets, secretID)
return nil
}

func (cli *fakeClient) ConfigRemove(ctx context.Context, configID string) error {
if cli.configRemoveFunc != nil {
return cli.configRemoveFunc(configID)
}

cli.removedConfigs = append(cli.removedConfigs, configID)
return nil
}

func serviceFromName(name string) swarm.Service {
return swarm.Service{
ID: "ID-" + name,
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: name},
},
}
}

func networkFromName(name string) types.NetworkResource {
return types.NetworkResource{
ID: "ID-" + name,
Name: name,
}
}

func secretFromName(name string) swarm.Secret {
return swarm.Secret{
ID: "ID-" + name,
Spec: swarm.SecretSpec{
Annotations: swarm.Annotations{Name: name},
},
}
}

func configFromName(name string) swarm.Config {
return swarm.Config{
ID: "ID-" + name,
Spec: swarm.ConfigSpec{
Annotations: swarm.Annotations{Name: name},
},
}
}

func namespaceFromFilters(filters filters.Args) string {
label := filters.Get("label")[0]
return strings.TrimPrefix(label, convert.LabelNamespace+"=")
}

func belongToNamespace(id, namespace string) bool {
return strings.HasPrefix(id, namespace+"_")
}

func objectName(namespace, name string) string {
return namespace + "_" + name
}

func objectID(name string) string {
return "ID-" + name
}

func buildObjectIDs(objectNames []string) []string {
IDs := make([]string, len(objectNames))
for i, name := range objectNames {
IDs[i] = objectID(name)
}
return IDs
}
30 changes: 13 additions & 17 deletions cli/command/stack/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package stack
import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/kubernetes"
"github.com/docker/cli/cli/command/stack/swarm"
"github.com/spf13/cobra"
)

Expand All @@ -17,26 +15,24 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"version": "1.25"},
}
switch command.GetOrchestrator(dockerCli) {
case command.OrchestratorKubernetes:
kubernetes.AddStackCommands(cmd, dockerCli)
case command.OrchestratorSwarm:
swarm.AddStackCommands(cmd, dockerCli)
}
cmd.AddCommand(
newDeployCommand(dockerCli),
newListCommand(dockerCli),
newPsCommand(dockerCli),
newRemoveCommand(dockerCli),
newServicesCommand(dockerCli),
)
flags := cmd.PersistentFlags()
flags.String("namespace", "default", "Kubernetes namespace to use")
flags.SetAnnotation("namespace", "kubernetes", nil)
flags.String("kubeconfig", "", "Kubernetes config file")
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
return cmd
}

// NewTopLevelDeployCommand returns a command for `docker deploy`
func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
var cmd *cobra.Command
switch command.GetOrchestrator(dockerCli) {
case command.OrchestratorKubernetes:
cmd = kubernetes.NewTopLevelDeployCommand(dockerCli)
case command.OrchestratorSwarm:
cmd = swarm.NewTopLevelDeployCommand(dockerCli)
default:
cmd = swarm.NewTopLevelDeployCommand(dockerCli)
}
cmd := newDeployCommand(dockerCli)
// Remove the aliases at the top level
cmd.Aliases = []string{}
cmd.Annotations = map[string]string{"experimental": "", "version": "1.25"}
Expand Down
Loading

0 comments on commit 987aabb

Please sign in to comment.