forked from pkallberg/cx
/
stacks.go
345 lines (324 loc) · 9.79 KB
/
stacks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
package main
import (
"io"
"os"
"sort"
"strings"
"text/tabwriter"
"github.com/cloud66/cloud66"
"github.com/cloud66/cli"
)
var cmdStacks = &Command{
Name: "stacks",
Build: buildStacks,
Short: "commands to work with stacks",
}
func buildStacks() cli.Command {
base := buildBasicCommand()
base.Subcommands = []cli.Command{
cli.Command{
Name: "list",
Usage: "lists all stacks",
Flags: []cli.Flag{
cli.StringFlag{
Name: "environment,e",
Usage: "full or partial environment name",
},
},
Action: runStacks,
Description: `Lists stacks. Shows the stack name, environment, and last deploy time.
You can use multiple names at the same time.
Examples:
$ cx stacks list
mystack production Jan 2 12:34
mystack staging Feb 2 12:34
mystack-2 development Jan 2 12:35
$ cx stacks list mystack-2
mystack-2 development Jan 2 12:34
$ cx stacks list mystack -e staging
mystack staging Feb 2 12:34
`,
},
cli.Command{
Name: "create",
Usage: "creates new docker stack",
Flags: []cli.Flag{
cli.StringFlag{
Name: "name,n",
Usage: "New docker stack name.",
},
cli.StringFlag{
Name: "environment,e",
Usage: "New docker stack environment.",
},
cli.StringFlag{
Name: "service_yaml,s",
Usage: "File containing your service definition.",
},
cli.StringFlag{
Name: "manifest_yaml,m",
Usage: "File containing your manifest definition (optional)",
},
},
Action: runCreateStack,
Description: `Creates a new docker stack.
Examples:
$ cx stacks create --name my_docker_stack --environment production --service_yaml service.yml --manifest_yaml manifest.yml
`,
},
cli.Command{
Name: "redeploy",
Usage: "redeploys a stack",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "y",
Usage: "answer yes to confirmations",
},
cli.StringFlag{
Name: "git-ref",
Usage: "[classic stacks] git reference",
},
cli.StringSliceFlag{
Name: "service",
Usage: "[docker stacks] service name (and optional colon separated reference) to include in the deploy. Repeatable for multiple services",
Value: &cli.StringSlice{},
},
cli.BoolFlag{
Name: "listen",
Usage: "show stack deployment progress and log output",
},
cli.StringFlag{
Name: "environment,e",
Usage: "full or partial environment name",
},
cli.StringFlag{
Name: "stack,s",
Usage: "full or partial stack name. This can be omitted if the current directory is a stack directory",
},
},
Action: runRedeploy,
Description: `Enqueues redeployment of the stack.
If the stack is already building, another build will be enqueued and performed immediately
after the current one is finished.
-y answers yes to confirmation question if the stack is production.
--git-ref will redeploy the specific branch, tag or hash git reference [classic stacks]
--service is a repeateable option to deploy only the specified service(s). Including a reference (separated by a colon) will attempt to deploy that particular reference for that service [docker stacks]
`,
},
cli.Command{
Name: "restart",
Action: runRestart,
Flags: basicFlags(),
Usage: "restarts all components of a stack",
Description: `This will send a restart method to all stack components. This means different things for different components.
For a web server, it means a restart of nginx. For an application server, this might be a restart of the workers like Unicorn.
For more information on restart command, please refer to help.cloud66.com
`,
},
cli.Command{
Name: "clear-caches",
Action: runClearCaches,
Flags: []cli.Flag{
cli.StringFlag{
Name: "environment,e",
Usage: "full or partial environment name",
},
cli.StringFlag{
Name: "stack,s",
Usage: "full or partial stack name. This can be omitted if the current directory is a stack directory",
},
},
Usage: "clears all existing stack code caches",
Description: `Clears all existing code caches.
For improved performance, volatile code caches exist for your stack.
It is possible for a those volatile caches to become invalid if you switch branches, change git repository URL, or rebase or force a commit.
Since switching branch or changing git repository URL is done via the Cloud 66 interface, your volatile caches will automatically be purged.
However, rebasing or forcing a commit doesn't have any association with Cloud 66, so this command can be used to purge the exising volatile caches.
`},
cli.Command{
Name: "listen",
Action: runListen,
Usage: "tails all deployment logs",
Flags: []cli.Flag{
cli.StringFlag{
Name: "environment,e",
Usage: "full or partial environment name",
},
cli.StringFlag{
Name: "stack,s",
Usage: "full or partial stack name. This can be omitted if the current directory is a stack directory",
},
},
Description: `This acts as a log tail for deployment of a stack so you don't have to follow the deployment on the web.
Examples:
$ cx stacks listen
$ cx stacks listen -s mystack
`},
cli.Command{
Name: "configure",
Usage: "list, download and upload of configuration files",
Subcommands: []cli.Command{
cli.Command{
Name: "list",
Action: runStackConfigureFileList,
Usage: "list of all versions of a configuration file",
Flags: []cli.Flag{
cli.StringFlag{
Name: "file,f",
Usage: "supported values are: service.yml , manifest.yml",
},
cli.StringFlag{
Name: "environment,e",
Usage: "full or partial environment name",
},
cli.StringFlag{
Name: "stack,s",
Usage: "full or partial stack name. This can be omitted if the current directory is a stack directory",
},
},
Description: `This acts list of all versions of configuration file.
`},
cli.Command{
Name: "download",
Action: runStackConfigureDownloadFile,
Usage: "download a configuration file",
Flags: []cli.Flag{
cli.StringFlag{
Name: "file,f",
Usage: "supported values are: service.yml , manifest.yml",
},
cli.StringFlag{
Name: "version,v",
Usage: "full or partial file version (optional)",
},
cli.StringFlag{
Name: "output,o",
Usage: "full path of output file (optional)",
},
cli.StringFlag{
Name: "environment,e",
Usage: "full or partial environment name",
},
cli.StringFlag{
Name: "stack,s",
Usage: "full or partial stack name. This can be omitted if the current directory is a stack directory",
},
},
Description: `download service.yml and manifest.yml.
`},
cli.Command{
Name: "upload",
Action: runStackConfigureUploadFile,
Usage: "uploading new version of configuration file",
Flags: []cli.Flag{
cli.StringFlag{
Name: "file,f",
Usage: "supported values are: service.yml , manifest.yml",
},
cli.StringFlag{
Name: "comments,c",
Usage: "a brief description of your changes",
},
cli.StringFlag{
Name: "environment,e",
Usage: "full or partial environment name",
},
cli.StringFlag{
Name: "stack,s",
Usage: "full or partial stack name. This can be omitted if the current directory is a stack directory",
},
},
Description: `upload new service.yml or manifest.yml.
`},
},
Description: `
Examples:
$ cx stacks configure list -f service.yml -s mystack
$ cx stacks configure download -f manifest.yml -s mystack
$ cx stacks configure download -f service.yml -o /tmp/my_stack_servive.yml -s mystack
$ cx stacks configure download -f manifest.yml -v f345 -s mystack
$ cx stacks configure upload /tmp/mystack_edited_service.yml -f service.yml -s mystack --comments "new service added"
`},
}
return base
}
func runStacks(c *cli.Context) {
w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
defer w.Flush()
var stacks []cloud66.Stack
names := c.Args()
flagForcedEnvironment := c.String("environment")
if len(names) == 0 {
var err error
stacks, err = client.StackListWithFilter(func(item interface{}) bool {
if flagForcedEnvironment == "" {
return true
}
return strings.HasPrefix(strings.ToLower(item.(cloud66.Stack).Environment), strings.ToLower(flagForcedEnvironment))
})
must(err)
} else {
stackch := make(chan *cloud66.Stack, len(names))
errch := make(chan error, len(names))
for _, name := range names {
if name == "" {
stackch <- nil
} else {
go func(stackname string) {
if stack, err := client.StackInfoWithEnvironment(stackname, flagForcedEnvironment); err != nil {
errch <- err
} else {
stackch <- stack
}
}(name)
}
}
for _ = range names {
select {
case err := <-errch:
printFatal(err.Error())
case stack := <-stackch:
if stack != nil {
stacks = append(stacks, *stack)
}
}
}
}
printStackList(w, stacks)
}
func printStackList(w io.Writer, stacks []cloud66.Stack) {
sort.Sort(stacksByName(stacks))
for _, a := range stacks {
if a.Name != "" {
listStack(w, a)
}
}
}
func listStack(w io.Writer, a cloud66.Stack) {
t := a.CreatedAt
if a.LastActivity != nil {
t = *a.LastActivity
}
listRec(w,
a.Name,
a.Environment,
a.Status(),
prettyTime{t},
)
}
func basicFlags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "environment,e",
Usage: "full or partial environment name",
},
cli.StringFlag{
Name: "stack,s",
Usage: "full or partial stack name. This can be omitted if the current directory is a stack directory",
},
}
}
type stacksByName []cloud66.Stack
func (a stacksByName) Len() int { return len(a) }
func (a stacksByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a stacksByName) Less(i, j int) bool { return a[i].Name < a[j].Name }