-
Notifications
You must be signed in to change notification settings - Fork 43
/
deis.go
267 lines (229 loc) · 7.2 KB
/
deis.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
package main
import (
"fmt"
"io"
"os"
"os/exec"
"strings"
"syscall"
"github.com/deis/workflow-cli/cli"
"github.com/deis/workflow-cli/cmd"
"github.com/deis/workflow-cli/parser"
docopt "github.com/docopt/docopt-go"
)
const extensionPrefix = "deis-"
// main exits with the return value of Command(os.Args[1:]), deferring all logic to
// a func we can test.
func main() {
os.Exit(Command(os.Args[1:], os.Stdout, os.Stderr, os.Stdin))
}
// Command routes deis commands to their proper parser.
func Command(argv []string, wOut io.Writer, wErr io.Writer, wIn io.Reader) int {
usage := `
The Deis command-line client issues API calls to a Deis controller.
Usage: deis <command> [<args>...]
Options:
-h --help
display help information
-v --version
display client version
-c --config=<config>
path to configuration file. Equivalent to
setting $DEIS_PROFILE. Defaults to ~/.deis/config.json.
If value is not a filepath, will assume location ~/.deis/client.json
Auth commands, use 'deis help auth' to learn more::
register register a new user with a controller
login login to a controller
logout logout from the current controller
Subcommands, use 'deis help [subcommand]' to learn more::
apps manage applications used to provide services
autoscale manage autoscale for applications
builds manage builds created using 'git push'
certs manage SSL endpoints for an app
config manage environment variables that define app config
domains manage and assign domain names to your applications
git manage git for applications
healthchecks manage healthchecks for applications
keys manage ssh keys used for 'git push' deployments
labels manage labels of application
limits manage resource limits for your application
perms manage permissions for applications
ps manage processes inside an app container
registry manage private registry information for your application
releases manage releases of an application
routing manage routability of an application
maintenance manage maintenance mode of an application
tags manage tags for application containers
tls manage TLS settings for applications
users manage users
version display client version
whitelist manage whitelisted addresses of an application
Shortcut commands, use 'deis shortcuts' to see all::
create create a new application
destroy destroy an application
info view information about the current app
logs view aggregated log info for the app
open open a URL to the app in a browser
pull imports an image and deploys as a new release
run run a command in an ephemeral app container
scale scale processes by type (web=2, worker=1)
Use 'git push deis master' to deploy to an application.
`
// Reorganize some command line flags and commands.
command, argv := parseArgs(argv)
// Give docopt an optional final false arg so it doesn't call os.Exit().
_, err := docopt.Parse(usage, []string{command}, false, "", true, false)
if err != nil {
fmt.Fprintln(wErr, err)
return 1
}
if len(argv) == 0 {
fmt.Fprintln(wErr, "Usage: deis <command> [<args>...]")
return 1
}
configFlag := getConfigFlag(argv)
// Don't pass down config flag to parser because it isn't defined there.
argv = removeConfigFlag(argv)
cmdr := cmd.DeisCmd{ConfigFile: configFlag, WOut: wOut, WErr: wErr, WIn: wIn}
// Dispatch the command, passing the argv through so subcommands can
// re-parse it according to their usage strings.
switch command {
case "apps":
err = parser.Apps(argv, &cmdr)
case "auth":
err = parser.Auth(argv, &cmdr)
case "autoscale":
err = parser.Autoscale(argv, &cmdr)
case "builds":
err = parser.Builds(argv, &cmdr)
case "certs":
err = parser.Certs(argv, &cmdr)
case "config":
err = parser.Config(argv, &cmdr)
case "domains":
err = parser.Domains(argv, &cmdr)
case "git":
err = parser.Git(argv, &cmdr)
case "healthchecks":
err = parser.Healthchecks(argv, &cmdr)
case "help":
fmt.Fprint(os.Stdout, usage)
return 0
case "keys":
err = parser.Keys(argv, &cmdr)
case "labels":
err = parser.Labels(argv, &cmdr)
case "limits":
err = parser.Limits(argv, &cmdr)
case "perms":
err = parser.Perms(argv, &cmdr)
case "ps":
err = parser.Ps(argv, &cmdr)
case "registry":
err = parser.Registry(argv, &cmdr)
case "releases":
err = parser.Releases(argv, &cmdr)
case "routing":
err = parser.Routing(argv, &cmdr)
case "maintenance":
err = parser.Maintenance(argv, &cmdr)
case "shortcuts":
err = parser.Shortcuts(argv, &cmdr)
case "tags":
err = parser.Tags(argv, &cmdr)
case "tls":
err = parser.TLS(argv, &cmdr)
case "users":
err = parser.Users(argv, &cmdr)
case "version":
err = parser.Version(argv, &cmdr)
case "whitelist":
err = parser.Whitelist(argv, &cmdr)
default:
env := os.Environ()
binary, err := exec.LookPath(extensionPrefix + command)
if err != nil {
parser.PrintUsage(&cmdr)
return 1
}
cmdArgv := prepareCmdArgs(command, argv)
err = syscall.Exec(binary, cmdArgv, env)
if err != nil {
parser.PrintUsage(&cmdr)
return 1
}
}
if err != nil {
fmt.Fprintf(wErr, "Error: %v\n", err)
return 1
}
return 0
}
func removeConfigFlag(argv []string) []string {
var kept []string
for i, arg := range argv {
if arg == "-c" || strings.HasPrefix(arg, "--config=") {
continue
// If the previous option is -c, remove the argument as well
} else if i != 0 && argv[i-1] == "-c" {
continue
}
kept = append(kept, arg)
}
return kept
}
func getConfigFlag(argv []string) string {
for i, arg := range argv {
if strings.HasPrefix(arg, "--config=") {
return strings.TrimPrefix(arg, "--config=")
} else if i != 0 && argv[i-1] == "-c" {
return arg
}
}
return ""
}
// parseArgs returns the provided args with "--help" as the last arg if need be,
// expands shortcuts and formats commands to be properly routed.
func parseArgs(argv []string) (string, []string) {
if len(argv) == 1 {
if argv[0] == "--help" || argv[0] == "-h" {
// rearrange "deis --help" as "deis help"
argv[0] = "help"
} else if argv[0] == "--version" || argv[0] == "-v" {
// rearrange "deis --version" as "deis version"
argv[0] = "version"
}
}
if len(argv) > 1 {
// Rearrange "deis help <command>" to "deis <command> --help".
if argv[0] == "help" || argv[0] == "--help" || argv[0] == "-h" {
argv = append(argv[1:], "--help")
}
}
if len(argv) > 0 {
argv[0] = replaceShortcut(argv[0])
index := strings.Index(argv[0], ":")
if index != -1 {
command := argv[0]
return command[:index], argv
}
return argv[0], argv
}
return "", argv
}
// split original command and pass its first element in arguments
func prepareCmdArgs(command string, argv []string) []string {
cmdArgv := []string{extensionPrefix + command}
cmdSplit := strings.Split(argv[0], command+":")
if len(cmdSplit) > 1 {
cmdArgv = append(cmdArgv, cmdSplit[1])
}
return append(cmdArgv, argv[1:]...)
}
func replaceShortcut(command string) string {
expandedCommand := cli.Shortcuts[command]
if expandedCommand == "" {
return command
}
return expandedCommand
}