Skip to content

Commit 987aabb

Browse files
committed
Refactor stack command
- Define command and subcommands only once - Use annotations for k8s or swarm specific flags or subcommands Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent a31b1c8 commit 987aabb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+759
-578
lines changed

cli/command/cli.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type Cli interface {
4242
SetIn(in *InStream)
4343
ConfigFile() *configfile.ConfigFile
4444
ServerInfo() ServerInfo
45+
ClientInfo() ClientInfo
4546
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
4647
}
4748

@@ -55,6 +56,7 @@ type DockerCli struct {
5556
client client.APIClient
5657
defaultVersion string
5758
server ServerInfo
59+
clientInfo ClientInfo
5860
}
5961

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

112+
// ClientInfo returns the client details
113+
func (cli *DockerCli) ClientInfo() ClientInfo {
114+
return cli.clientInfo
115+
}
116+
110117
// Initialize the dockerCli runs initialization that must happen after command
111118
// line flags are parsed.
112119
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
@@ -125,6 +132,8 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
125132
if err != nil {
126133
return err
127134
}
135+
orchestrator := GetOrchestrator(cli.configFile.Orchestrator)
136+
cli.clientInfo = ClientInfo{HasKubernetes: orchestrator == OrchestratorKubernetes}
128137
cli.initializeFromClient()
129138
return nil
130139
}
@@ -176,6 +185,11 @@ type ServerInfo struct {
176185
OSType string
177186
}
178187

188+
// ClientInfo store details about the supported features of the client
189+
type ClientInfo struct {
190+
HasKubernetes bool
191+
}
192+
179193
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
180194
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
181195
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}

cli/command/orchestrator.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@ package command
33
import (
44
"os"
55
"strings"
6-
7-
cliconfig "github.com/docker/cli/cli/config"
86
)
97

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

1311
const (
14-
// Kubernetes orchestrator
12+
// OrchestratorKubernetes orchestrator
1513
OrchestratorKubernetes = Orchestrator("kubernetes")
16-
// Swarm orchestrator
14+
// OrchestratorSwarm orchestrator
1715
OrchestratorSwarm = Orchestrator("swarm")
1816
orchestratorUnset = Orchestrator("unset")
1917

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

3533
// GetOrchestrator checks DOCKER_ORCHESTRATOR environment variable and configuration file
3634
// orchestrator value and returns user defined Orchestrator.
37-
func GetOrchestrator(dockerCli Cli) Orchestrator {
35+
func GetOrchestrator(orchestrator string) Orchestrator {
3836
// Check environment variable
3937
env := os.Getenv(dockerOrchestrator)
4038
if o := normalize(env); o != orchestratorUnset {
4139
return o
4240
}
43-
// Check config file
44-
if configFile := cliconfig.LoadDefaultConfigFile(dockerCli.Err()); configFile != nil {
45-
if o := normalize(configFile.Orchestrator); o != orchestratorUnset {
46-
return o
47-
}
41+
// Check specified orchestrator
42+
if o := normalize(orchestrator); o != orchestratorUnset {
43+
return o
4844
}
4945

5046
// Nothing set, use default orchestrator

cli/command/stack/client_test.go

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package stack
2+
3+
import (
4+
"strings"
5+
6+
"github.com/docker/cli/cli/compose/convert"
7+
"github.com/docker/docker/api"
8+
"github.com/docker/docker/api/types"
9+
"github.com/docker/docker/api/types/filters"
10+
"github.com/docker/docker/api/types/swarm"
11+
"github.com/docker/docker/client"
12+
"golang.org/x/net/context"
13+
)
14+
15+
type fakeClient struct {
16+
client.Client
17+
18+
version string
19+
20+
services []string
21+
networks []string
22+
secrets []string
23+
configs []string
24+
25+
removedServices []string
26+
removedNetworks []string
27+
removedSecrets []string
28+
removedConfigs []string
29+
30+
serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
31+
networkListFunc func(options types.NetworkListOptions) ([]types.NetworkResource, error)
32+
secretListFunc func(options types.SecretListOptions) ([]swarm.Secret, error)
33+
configListFunc func(options types.ConfigListOptions) ([]swarm.Config, error)
34+
nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error)
35+
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
36+
nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error)
37+
38+
serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error)
39+
40+
serviceRemoveFunc func(serviceID string) error
41+
networkRemoveFunc func(networkID string) error
42+
secretRemoveFunc func(secretID string) error
43+
configRemoveFunc func(configID string) error
44+
}
45+
46+
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
47+
return types.Version{
48+
Version: "docker-dev",
49+
APIVersion: api.DefaultVersion,
50+
}, nil
51+
}
52+
53+
func (cli *fakeClient) ClientVersion() string {
54+
return cli.version
55+
}
56+
57+
func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
58+
if cli.serviceListFunc != nil {
59+
return cli.serviceListFunc(options)
60+
}
61+
62+
namespace := namespaceFromFilters(options.Filters)
63+
servicesList := []swarm.Service{}
64+
for _, name := range cli.services {
65+
if belongToNamespace(name, namespace) {
66+
servicesList = append(servicesList, serviceFromName(name))
67+
}
68+
}
69+
return servicesList, nil
70+
}
71+
72+
func (cli *fakeClient) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
73+
if cli.networkListFunc != nil {
74+
return cli.networkListFunc(options)
75+
}
76+
77+
namespace := namespaceFromFilters(options.Filters)
78+
networksList := []types.NetworkResource{}
79+
for _, name := range cli.networks {
80+
if belongToNamespace(name, namespace) {
81+
networksList = append(networksList, networkFromName(name))
82+
}
83+
}
84+
return networksList, nil
85+
}
86+
87+
func (cli *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
88+
if cli.secretListFunc != nil {
89+
return cli.secretListFunc(options)
90+
}
91+
92+
namespace := namespaceFromFilters(options.Filters)
93+
secretsList := []swarm.Secret{}
94+
for _, name := range cli.secrets {
95+
if belongToNamespace(name, namespace) {
96+
secretsList = append(secretsList, secretFromName(name))
97+
}
98+
}
99+
return secretsList, nil
100+
}
101+
102+
func (cli *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
103+
if cli.configListFunc != nil {
104+
return cli.configListFunc(options)
105+
}
106+
107+
namespace := namespaceFromFilters(options.Filters)
108+
configsList := []swarm.Config{}
109+
for _, name := range cli.configs {
110+
if belongToNamespace(name, namespace) {
111+
configsList = append(configsList, configFromName(name))
112+
}
113+
}
114+
return configsList, nil
115+
}
116+
117+
func (cli *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
118+
if cli.taskListFunc != nil {
119+
return cli.taskListFunc(options)
120+
}
121+
return []swarm.Task{}, nil
122+
}
123+
124+
func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
125+
if cli.nodeListFunc != nil {
126+
return cli.nodeListFunc(options)
127+
}
128+
return []swarm.Node{}, nil
129+
}
130+
131+
func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) {
132+
if cli.nodeInspectWithRaw != nil {
133+
return cli.nodeInspectWithRaw(ref)
134+
}
135+
return swarm.Node{}, nil, nil
136+
}
137+
138+
func (cli *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
139+
if cli.serviceUpdateFunc != nil {
140+
return cli.serviceUpdateFunc(serviceID, version, service, options)
141+
}
142+
143+
return types.ServiceUpdateResponse{}, nil
144+
}
145+
146+
func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error {
147+
if cli.serviceRemoveFunc != nil {
148+
return cli.serviceRemoveFunc(serviceID)
149+
}
150+
151+
cli.removedServices = append(cli.removedServices, serviceID)
152+
return nil
153+
}
154+
155+
func (cli *fakeClient) NetworkRemove(ctx context.Context, networkID string) error {
156+
if cli.networkRemoveFunc != nil {
157+
return cli.networkRemoveFunc(networkID)
158+
}
159+
160+
cli.removedNetworks = append(cli.removedNetworks, networkID)
161+
return nil
162+
}
163+
164+
func (cli *fakeClient) SecretRemove(ctx context.Context, secretID string) error {
165+
if cli.secretRemoveFunc != nil {
166+
return cli.secretRemoveFunc(secretID)
167+
}
168+
169+
cli.removedSecrets = append(cli.removedSecrets, secretID)
170+
return nil
171+
}
172+
173+
func (cli *fakeClient) ConfigRemove(ctx context.Context, configID string) error {
174+
if cli.configRemoveFunc != nil {
175+
return cli.configRemoveFunc(configID)
176+
}
177+
178+
cli.removedConfigs = append(cli.removedConfigs, configID)
179+
return nil
180+
}
181+
182+
func serviceFromName(name string) swarm.Service {
183+
return swarm.Service{
184+
ID: "ID-" + name,
185+
Spec: swarm.ServiceSpec{
186+
Annotations: swarm.Annotations{Name: name},
187+
},
188+
}
189+
}
190+
191+
func networkFromName(name string) types.NetworkResource {
192+
return types.NetworkResource{
193+
ID: "ID-" + name,
194+
Name: name,
195+
}
196+
}
197+
198+
func secretFromName(name string) swarm.Secret {
199+
return swarm.Secret{
200+
ID: "ID-" + name,
201+
Spec: swarm.SecretSpec{
202+
Annotations: swarm.Annotations{Name: name},
203+
},
204+
}
205+
}
206+
207+
func configFromName(name string) swarm.Config {
208+
return swarm.Config{
209+
ID: "ID-" + name,
210+
Spec: swarm.ConfigSpec{
211+
Annotations: swarm.Annotations{Name: name},
212+
},
213+
}
214+
}
215+
216+
func namespaceFromFilters(filters filters.Args) string {
217+
label := filters.Get("label")[0]
218+
return strings.TrimPrefix(label, convert.LabelNamespace+"=")
219+
}
220+
221+
func belongToNamespace(id, namespace string) bool {
222+
return strings.HasPrefix(id, namespace+"_")
223+
}
224+
225+
func objectName(namespace, name string) string {
226+
return namespace + "_" + name
227+
}
228+
229+
func objectID(name string) string {
230+
return "ID-" + name
231+
}
232+
233+
func buildObjectIDs(objectNames []string) []string {
234+
IDs := make([]string, len(objectNames))
235+
for i, name := range objectNames {
236+
IDs[i] = objectID(name)
237+
}
238+
return IDs
239+
}

cli/command/stack/cmd.go

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package stack
33
import (
44
"github.com/docker/cli/cli"
55
"github.com/docker/cli/cli/command"
6-
"github.com/docker/cli/cli/command/stack/kubernetes"
7-
"github.com/docker/cli/cli/command/stack/swarm"
86
"github.com/spf13/cobra"
97
)
108

@@ -17,26 +15,24 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
1715
RunE: command.ShowHelp(dockerCli.Err()),
1816
Annotations: map[string]string{"version": "1.25"},
1917
}
20-
switch command.GetOrchestrator(dockerCli) {
21-
case command.OrchestratorKubernetes:
22-
kubernetes.AddStackCommands(cmd, dockerCli)
23-
case command.OrchestratorSwarm:
24-
swarm.AddStackCommands(cmd, dockerCli)
25-
}
18+
cmd.AddCommand(
19+
newDeployCommand(dockerCli),
20+
newListCommand(dockerCli),
21+
newPsCommand(dockerCli),
22+
newRemoveCommand(dockerCli),
23+
newServicesCommand(dockerCli),
24+
)
25+
flags := cmd.PersistentFlags()
26+
flags.String("namespace", "default", "Kubernetes namespace to use")
27+
flags.SetAnnotation("namespace", "kubernetes", nil)
28+
flags.String("kubeconfig", "", "Kubernetes config file")
29+
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
2630
return cmd
2731
}
2832

2933
// NewTopLevelDeployCommand returns a command for `docker deploy`
3034
func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
31-
var cmd *cobra.Command
32-
switch command.GetOrchestrator(dockerCli) {
33-
case command.OrchestratorKubernetes:
34-
cmd = kubernetes.NewTopLevelDeployCommand(dockerCli)
35-
case command.OrchestratorSwarm:
36-
cmd = swarm.NewTopLevelDeployCommand(dockerCli)
37-
default:
38-
cmd = swarm.NewTopLevelDeployCommand(dockerCli)
39-
}
35+
cmd := newDeployCommand(dockerCli)
4036
// Remove the aliases at the top level
4137
cmd.Aliases = []string{}
4238
cmd.Annotations = map[string]string{"experimental": "", "version": "1.25"}

0 commit comments

Comments
 (0)