af 0.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. data/LICENSE +24 -0
  2. data/README.md +92 -0
  3. data/Rakefile +17 -0
  4. data/bin/af +6 -0
  5. data/lib/cli.rb +30 -0
  6. data/lib/cli/commands/admin.rb +77 -0
  7. data/lib/cli/commands/apps.rb +940 -0
  8. data/lib/cli/commands/base.rb +79 -0
  9. data/lib/cli/commands/misc.rb +128 -0
  10. data/lib/cli/commands/services.rb +86 -0
  11. data/lib/cli/commands/user.rb +60 -0
  12. data/lib/cli/config.rb +110 -0
  13. data/lib/cli/core_ext.rb +119 -0
  14. data/lib/cli/errors.rb +19 -0
  15. data/lib/cli/frameworks.rb +109 -0
  16. data/lib/cli/runner.rb +490 -0
  17. data/lib/cli/services_helper.rb +78 -0
  18. data/lib/cli/usage.rb +104 -0
  19. data/lib/cli/version.rb +7 -0
  20. data/lib/cli/zip_util.rb +77 -0
  21. data/lib/vmc.rb +3 -0
  22. data/lib/vmc/client.rb +451 -0
  23. data/lib/vmc/const.rb +21 -0
  24. data/spec/assets/app_info.txt +9 -0
  25. data/spec/assets/app_listings.txt +9 -0
  26. data/spec/assets/bad_create_app.txt +9 -0
  27. data/spec/assets/delete_app.txt +9 -0
  28. data/spec/assets/global_service_listings.txt +9 -0
  29. data/spec/assets/good_create_app.txt +9 -0
  30. data/spec/assets/good_create_service.txt +9 -0
  31. data/spec/assets/info_authenticated.txt +27 -0
  32. data/spec/assets/info_return.txt +15 -0
  33. data/spec/assets/info_return_bad.txt +16 -0
  34. data/spec/assets/list_users.txt +13 -0
  35. data/spec/assets/login_fail.txt +9 -0
  36. data/spec/assets/login_success.txt +9 -0
  37. data/spec/assets/sample_token.txt +1 -0
  38. data/spec/assets/service_already_exists.txt +9 -0
  39. data/spec/assets/service_listings.txt +9 -0
  40. data/spec/assets/service_not_found.txt +9 -0
  41. data/spec/assets/user_info.txt +9 -0
  42. data/spec/spec_helper.rb +11 -0
  43. data/spec/unit/cli_opts_spec.rb +68 -0
  44. data/spec/unit/client_spec.rb +332 -0
  45. metadata +221 -0
@@ -0,0 +1,119 @@
1
+ module VMCExtensions
2
+
3
+ def say(message)
4
+ VMC::Cli::Config.output.puts(message) if VMC::Cli::Config.output
5
+ end
6
+
7
+ def header(message, filler = '-')
8
+ say "\n"
9
+ say message
10
+ say filler.to_s * message.size
11
+ end
12
+
13
+ def banner(message)
14
+ say "\n"
15
+ say message
16
+ end
17
+
18
+ def display(message, nl=true)
19
+ if nl
20
+ say message
21
+ else
22
+ if VMC::Cli::Config.output
23
+ VMC::Cli::Config.output.print(message)
24
+ VMC::Cli::Config.output.flush
25
+ end
26
+ end
27
+ end
28
+
29
+ def clear(size=80)
30
+ return unless VMC::Cli::Config.output
31
+ VMC::Cli::Config.output.print("\r")
32
+ VMC::Cli::Config.output.print(" " * size)
33
+ VMC::Cli::Config.output.print("\r")
34
+ #VMC::Cli::Config.output.flush
35
+ end
36
+
37
+ def err(message, prefix='Error: ')
38
+ raise VMC::Cli::CliExit, "#{prefix}#{message}"
39
+ end
40
+
41
+ def quit(message = nil)
42
+ raise VMC::Cli::GracefulExit, message
43
+ end
44
+
45
+ def blank?
46
+ self.to_s.blank?
47
+ end
48
+
49
+ def uptime_string(delta)
50
+ num_seconds = delta.to_i
51
+ days = num_seconds / (60 * 60 * 24);
52
+ num_seconds -= days * (60 * 60 * 24);
53
+ hours = num_seconds / (60 * 60);
54
+ num_seconds -= hours * (60 * 60);
55
+ minutes = num_seconds / 60;
56
+ num_seconds -= minutes * 60;
57
+ "#{days}d:#{hours}h:#{minutes}m:#{num_seconds}s"
58
+ end
59
+
60
+ def pretty_size(size, prec=1)
61
+ return 'NA' unless size
62
+ return "#{size}B" if size < 1024
63
+ return sprintf("%.#{prec}fK", size/1024.0) if size < (1024*1024)
64
+ return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)
65
+ return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
66
+ end
67
+
68
+ end
69
+
70
+ module VMCStringExtensions
71
+
72
+ def red
73
+ colorize("\e[0m\e[31m")
74
+ end
75
+
76
+ def green
77
+ colorize("\e[0m\e[32m")
78
+ end
79
+
80
+ def yellow
81
+ colorize("\e[0m\e[33m")
82
+ end
83
+
84
+ def bold
85
+ colorize("\e[0m\e[1m")
86
+ end
87
+
88
+ def colorize(color_code)
89
+ if VMC::Cli::Config.colorize
90
+ "#{color_code}#{self}\e[0m"
91
+ else
92
+ self
93
+ end
94
+ end
95
+
96
+ def blank?
97
+ self =~ /^\s*$/
98
+ end
99
+
100
+ def truncate(limit = 30)
101
+ return "" if self.blank?
102
+ etc = "..."
103
+ stripped = self.strip[0..limit]
104
+ if stripped.length > limit
105
+ stripped.gsub(/\s+?(\S+)?$/, "") + etc
106
+ else
107
+ stripped
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ class Object
114
+ include VMCExtensions
115
+ end
116
+
117
+ class String
118
+ include VMCStringExtensions
119
+ end
@@ -0,0 +1,19 @@
1
+ module VMC::Cli
2
+
3
+ class CliError < StandardError
4
+ def self.error_code(code = nil)
5
+ define_method(:error_code) { code }
6
+ end
7
+ end
8
+
9
+ class UnknownCommand < CliError; error_code(100); end
10
+ class TargetMissing < CliError; error_code(102); end
11
+ class TargetInaccessible < CliError; error_code(103); end
12
+
13
+ class TargetError < CliError; error_code(201); end
14
+ class AuthError < TargetError; error_code(202); end
15
+
16
+ class CliExit < CliError; error_code(400); end
17
+ class GracefulExit < CliExit; error_code(401); end
18
+
19
+ end
@@ -0,0 +1,109 @@
1
+ module VMC::Cli
2
+
3
+ class Framework
4
+
5
+ DEFAULT_FRAMEWORK = "http://b20nine.com/unknown"
6
+ DEFAULT_MEM = '256M'
7
+
8
+ FRAMEWORKS = {
9
+ 'Rails' => ['rails3', { :mem => '256M', :description => 'Rails Application'}],
10
+ 'Spring' => ['spring', { :mem => '512M', :description => 'Java SpringSource Spring Application'}],
11
+ 'Grails' => ['grails', { :mem => '512M', :description => 'Java SpringSource Grails Application'}],
12
+ 'Lift' => ['lift', { :mem => '512M', :description => 'Scala Lift Application'}],
13
+ 'Roo' => ['spring', { :mem => '512M', :description => 'Java SpringSource Roo Application'}],
14
+ 'JavaWeb' => ['spring', { :mem => '512M', :description => 'Java Web Application'}],
15
+ 'Sinatra' => ['sinatra', { :mem => '128M', :description => 'Sinatra Application'}],
16
+ 'Node' => ['node', { :mem => '64M', :description => 'Node.js Application'}],
17
+ 'PHP' => ['php', { :mem => '128M', :description => 'PHP Application'}],
18
+ 'Erlang/OTP Rebar' => ['otp_rebar', { :mem => '64M', :description => 'Erlang/OTP Rebar Application'}]
19
+ }
20
+
21
+ class << self
22
+
23
+ def known_frameworks
24
+ FRAMEWORKS.keys
25
+ end
26
+
27
+ def lookup(name)
28
+ return Framework.new(*FRAMEWORKS[name])
29
+ end
30
+
31
+ def detect(path)
32
+ Dir.chdir(path) do
33
+
34
+ # Rails
35
+ if File.exist?('config/environment.rb')
36
+ return Framework.lookup('Rails')
37
+
38
+ # Java
39
+ elsif Dir.glob('*.war').first
40
+ war_file = Dir.glob('*.war').first
41
+ contents = ZipUtil.entry_lines(war_file)
42
+
43
+ # Spring/Lift Variations
44
+ if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/
45
+ return Framework.lookup('Grails')
46
+ elsif contents =~ /WEB-INF\/lib\/lift-webkit.*\.jar/
47
+ return Framework.lookup('Lift')
48
+ elsif contents =~ /WEB-INF\/classes\/org\/springframework/
49
+ return Framework.lookup('Spring')
50
+ elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
51
+ return Framework.lookup('Spring')
52
+ else
53
+ return Framework.lookup('JavaWeb')
54
+ end
55
+
56
+ # Simple Ruby Apps
57
+ elsif !Dir.glob('*.rb').empty?
58
+ matched_file = nil
59
+ Dir.glob('*.rb').each do |fname|
60
+ next if matched_file
61
+ File.open(fname, 'r') do |f|
62
+ str = f.read # This might want to be limited
63
+ matched_file = fname if (str && str.match(/^\s*require[\s\(]*['"]sinatra['"]/))
64
+ end
65
+ end
66
+ if matched_file
67
+ f = Framework.lookup('Sinatra')
68
+ f.exec = "ruby #{matched_file}"
69
+ return f
70
+ end
71
+
72
+ # Node.js
73
+ elsif !Dir.glob('*.js').empty?
74
+ if File.exist?('server.js') || File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js')
75
+ return Framework.lookup('Node')
76
+ end
77
+
78
+ # PHP
79
+ elsif !Dir.glob('*.php').empty?
80
+ return Framework.lookup('PHP')
81
+
82
+ # Erlang/OTP using Rebar
83
+ elsif !Dir.glob('releases/*/*.rel').empty? && !Dir.glob('releases/*/*.boot').empty?
84
+ return Framework.lookup('Erlang/OTP Rebar')
85
+ end
86
+ end
87
+ nil
88
+ end
89
+
90
+ end
91
+
92
+ attr_reader :name, :description, :memory
93
+ attr_accessor :exec
94
+
95
+ alias :mem :memory
96
+
97
+ def initialize(framework=nil, opts={})
98
+ @name = framework || DEFAULT_FRAMEWORK
99
+ @memory = opts[:mem] || DEFAULT_MEM
100
+ @description = opts[:description] || 'Unknown Application Type'
101
+ @exec = opts[:exec]
102
+ end
103
+
104
+ def to_s
105
+ description
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1,490 @@
1
+
2
+ require 'optparse'
3
+
4
+ require File.dirname(__FILE__) + '/usage'
5
+
6
+ class VMC::Cli::Runner
7
+
8
+ attr_reader :namespace
9
+ attr_reader :action
10
+ attr_reader :args
11
+ attr_reader :options
12
+
13
+ def self.run(args)
14
+ new(args).run
15
+ end
16
+
17
+ def initialize(args=[])
18
+ @args = args
19
+ @options = { :colorize => true }
20
+ @exit_status = true
21
+ end
22
+
23
+ # Collect all the available options for all commands
24
+ # Some duplicates exists to capture all scenarios
25
+ def parse_options!
26
+ opts_parser = OptionParser.new do |opts|
27
+ opts.banner = "\nAvailable options:\n\n"
28
+
29
+ opts.on('--email EMAIL') { |email| @options[:email] = email }
30
+ opts.on('--user EMAIL') { |email| @options[:email] = email }
31
+ opts.on('--passwd PASS') { |pass| @options[:password] = pass }
32
+ opts.on('--pass PASS') { |pass| @options[:password] = pass }
33
+ opts.on('--password PASS') { |pass| @options[:password] = pass }
34
+ opts.on('--app NAME') { |name| @options[:name] = name }
35
+ opts.on('--name NAME') { |name| @options[:name] = name }
36
+ opts.on('--bind BIND') { |bind| @options[:bind] = bind }
37
+ opts.on('--instance INST') { |inst| @options[:instance] = inst }
38
+ opts.on('--instances INST') { |inst| @options[:instances] = inst }
39
+ opts.on('--url URL') { |url| @options[:url] = url }
40
+ opts.on('--mem MEM') { |mem| @options[:mem] = mem }
41
+ opts.on('--path PATH') { |path| @options[:path] = path }
42
+ opts.on('--no-start') { @options[:nostart] = true }
43
+ opts.on('--nostart') { @options[:nostart] = true }
44
+ opts.on('--force') { @options[:force] = true }
45
+ opts.on('--all') { @options[:all] = true }
46
+
47
+ # generic tracing and debugging
48
+ opts.on('-t [TKEY]') { |tkey| @options[:trace] = tkey || true }
49
+ opts.on('--trace [TKEY]') { |tkey| @options[:trace] = tkey || true }
50
+
51
+ opts.on('-q', '--quiet') { @options[:quiet] = true }
52
+
53
+ # Don't use builtin zip
54
+ opts.on('--no-zip') { @options[:nozip] = true }
55
+ opts.on('--nozip') { @options[:nozip] = true }
56
+
57
+ opts.on('--no-resources') { @options[:noresources] = true }
58
+ opts.on('--noresources') { @options[:noresources] = true }
59
+
60
+ opts.on('--no-color') { @options[:colorize] = false }
61
+ opts.on('--verbose') { @options[:verbose] = true }
62
+
63
+ opts.on('-n','--no-prompt') { @options[:noprompts] = true }
64
+ opts.on('--noprompt') { @options[:noprompts] = true }
65
+ opts.on('--non-interactive') { @options[:noprompts] = true }
66
+
67
+ opts.on('--prefix') { @options[:prefixlogs] = true }
68
+ opts.on('--prefix-logs') { @options[:prefixlogs] = true }
69
+ opts.on('--prefixlogs') { @options[:prefixlogs] = true }
70
+
71
+ opts.on('--json') { @options[:json] = true }
72
+
73
+ opts.on('-v', '--version') { set_cmd(:misc, :version) }
74
+ opts.on('-h', '--help') { puts "#{command_usage}\n"; exit }
75
+
76
+ opts.on('--runtime RUNTIME') { |rt| @options[:runtime] = rt }
77
+
78
+ # deprecated
79
+ opts.on('--exec EXEC') { |exec| @options[:exec] = exec }
80
+ opts.on('--noframework') { @options[:noframework] = true }
81
+ opts.on('--canary') { @options[:canary] = true }
82
+
83
+ # Proxying for another user, requires admin privileges
84
+ opts.on('-u PROXY') { |proxy| @options[:proxy] = proxy }
85
+
86
+ opts.on_tail('--options') { puts "#{opts}\n"; exit }
87
+ end
88
+ instances_delta_arg = check_instances_delta!
89
+ @args = opts_parser.parse!(@args)
90
+ @args.concat instances_delta_arg
91
+ convert_options!
92
+ self
93
+ end
94
+
95
+ def check_instances_delta!
96
+ return unless @args
97
+ instance_args = @args.select { |arg| /^[-]\d+$/ =~ arg } || []
98
+ @args.delete_if { |arg| instance_args.include? arg}
99
+ instance_args
100
+ end
101
+
102
+ def display_help
103
+ puts command_usage
104
+ exit
105
+ end
106
+
107
+ def convert_options!
108
+ # make sure certain options are valid and in correct form.
109
+ @options[:instances] = Integer(@options[:instances]) if @options[:instances]
110
+ end
111
+
112
+ def set_cmd(namespace, action, args_range=0)
113
+ return if @help_only
114
+ unless args_range == "*" || args_range.is_a?(Range)
115
+ args_range = (args_range.to_i..args_range.to_i)
116
+ end
117
+
118
+ if args_range == "*" || args_range.include?(@args.size)
119
+ @namespace = namespace
120
+ @action = action
121
+ else
122
+ @exit_status = false
123
+ if @args.size > args_range.last
124
+ usage_error("Too many arguments for [#{action}]: %s" % [ @args[args_range.last..-1].map{|a| "'#{a}'"}.join(', ') ])
125
+ else
126
+ usage_error("Not enough arguments for [#{action}]")
127
+ end
128
+ end
129
+ end
130
+
131
+ def parse_command!
132
+ # just return if already set, happends with -v, -h
133
+ return if @namespace && @action
134
+
135
+ verb = @args.shift
136
+ case verb
137
+
138
+ when 'version'
139
+ usage('vmc version')
140
+ set_cmd(:misc, :version)
141
+
142
+ when 'target'
143
+ usage('vmc target [url] [--url]')
144
+ if @args.size == 1
145
+ set_cmd(:misc, :set_target, 1)
146
+ else
147
+ set_cmd(:misc, :target)
148
+ end
149
+
150
+ when 'targets'
151
+ usage('vmc targets')
152
+ set_cmd(:misc, :targets)
153
+
154
+ when 'tokens'
155
+ usage('vmc tokens')
156
+ set_cmd(:misc, :tokens)
157
+
158
+ when 'info'
159
+ usage('vmc info')
160
+ set_cmd(:misc, :info)
161
+
162
+ when 'runtimes'
163
+ usage('vmc runtimes')
164
+ set_cmd(:misc, :runtimes)
165
+
166
+ when 'frameworks'
167
+ usage('vmc frameworks')
168
+ set_cmd(:misc, :frameworks)
169
+
170
+ when 'user'
171
+ usage('vmc user')
172
+ set_cmd(:user, :info)
173
+
174
+ when 'login'
175
+ usage('vmc login [email] [--email EMAIL] [--passwd PASS]')
176
+ if @args.size == 1
177
+ set_cmd(:user, :login, 1)
178
+ else
179
+ set_cmd(:user, :login)
180
+ end
181
+
182
+ when 'logout'
183
+ usage('vmc logout')
184
+ set_cmd(:user, :logout)
185
+
186
+ when 'passwd'
187
+ usage('vmc passwd')
188
+ if @args.size == 1
189
+ set_cmd(:user, :change_password, 1)
190
+ else
191
+ set_cmd(:user, :change_password)
192
+ end
193
+
194
+ when 'add-user', 'add_user', 'create_user', 'create-user', 'register'
195
+ usage('vmc add-user [user] [--email EMAIL] [--passwd PASS]')
196
+ if @args.size == 1
197
+ set_cmd(:admin, :add_user, 1)
198
+ else
199
+ set_cmd(:admin, :add_user)
200
+ end
201
+
202
+ when 'delete-user', 'delete_user', 'unregister'
203
+ usage('vmc delete-user <user>')
204
+ set_cmd(:admin, :delete_user, 1)
205
+
206
+ when 'users'
207
+ usage('vmc users')
208
+ set_cmd(:admin, :users)
209
+
210
+ when 'apps'
211
+ usage('vmc apps')
212
+ set_cmd(:apps, :apps)
213
+
214
+ when 'list'
215
+ usage('vmc list')
216
+ set_cmd(:apps, :list)
217
+
218
+ when 'start'
219
+ usage('vmc start <appname>')
220
+ set_cmd(:apps, :start, 1)
221
+
222
+ when 'stop'
223
+ usage('vmc stop <appname>')
224
+ set_cmd(:apps, :stop, 1)
225
+
226
+ when 'restart'
227
+ usage('vmc restart <appname>')
228
+ set_cmd(:apps, :restart, 1)
229
+
230
+ when 'rename'
231
+ usage('vmc rename <appname> <newname>')
232
+ set_cmd(:apps, :rename, 2)
233
+
234
+ when 'mem'
235
+ usage('vmc mem <appname> [memsize]')
236
+ if @args.size == 2
237
+ set_cmd(:apps, :mem, 2)
238
+ else
239
+ set_cmd(:apps, :mem, 1)
240
+ end
241
+
242
+ when 'stats'
243
+ usage('vmc stats <appname>')
244
+ set_cmd(:apps, :stats, 1)
245
+
246
+ when 'map'
247
+ usage('vmc map <appname> <url>')
248
+ set_cmd(:apps, :map, 2)
249
+
250
+ when 'unmap'
251
+ usage('vmc unmap <appname> <url>')
252
+ set_cmd(:apps, :unmap, 2)
253
+
254
+ when 'delete'
255
+ usage('vmc delete <appname>')
256
+ if @options[:all] && @args.size == 0
257
+ set_cmd(:apps, :delete)
258
+ else
259
+ set_cmd(:apps, :delete, 1)
260
+ end
261
+
262
+ when 'files'
263
+ usage('vmc files <appname> [path] [--instance N] [--all] [--prefix]')
264
+ if @args.size == 1
265
+ set_cmd(:apps, :files, 1)
266
+ else
267
+ set_cmd(:apps, :files, 2)
268
+ end
269
+
270
+ when 'logs'
271
+ usage('vmc logs <appname> [--instance N] [--all] [--prefix]')
272
+ set_cmd(:apps, :logs, 1)
273
+
274
+ when 'instances', 'scale'
275
+ if @args.size == 1
276
+ usage('vmc instances <appname>')
277
+ set_cmd(:apps, :instances, 1)
278
+ else
279
+ usage('vmc instances <appname> <num|delta>')
280
+ set_cmd(:apps, :instances, 2)
281
+ end
282
+
283
+ when 'crashes'
284
+ usage('vmc crashes <appname>')
285
+ set_cmd(:apps, :crashes, 1)
286
+
287
+ when 'crashlogs'
288
+ usage('vmc crashlogs <appname>')
289
+ set_cmd(:apps, :crashlogs, 1)
290
+
291
+ when 'push'
292
+ usage('vmc push [appname] [--path PATH] [--url URL] [--instances N] [--mem] [--runtime RUNTIME] [--no-start]')
293
+ if @args.size == 1
294
+ set_cmd(:apps, :push, 1)
295
+ else
296
+ set_cmd(:apps, :push, 0)
297
+ end
298
+
299
+ when 'update'
300
+ usage('vmc update <appname> [--path PATH]')
301
+ set_cmd(:apps, :update, 1)
302
+
303
+ when 'services'
304
+ usage('vmc services')
305
+ set_cmd(:services, :services)
306
+
307
+ when 'env'
308
+ usage('vmc env <appname>')
309
+ set_cmd(:apps, :environment, 1)
310
+
311
+ when 'env-add'
312
+ usage('vmc env-add <appname> <variable[=]value>')
313
+ if @args.size == 2
314
+ set_cmd(:apps, :environment_add, 2)
315
+ elsif @args.size == 3
316
+ set_cmd(:apps, :environment_add, 3)
317
+ end
318
+
319
+ when 'env-del'
320
+ usage('vmc env-del <appname> <variable>')
321
+ set_cmd(:apps, :environment_del, 2)
322
+
323
+ when 'create-service', 'create_service'
324
+ usage('vmc create-service [service] [servicename] [appname] [--name servicename] [--bind appname]')
325
+ set_cmd(:services, :create_service) if @args.size == 0
326
+ set_cmd(:services, :create_service, 1) if @args.size == 1
327
+ set_cmd(:services, :create_service, 2) if @args.size == 2
328
+ set_cmd(:services, :create_service, 3) if @args.size == 3
329
+
330
+ when 'delete-service', 'delete_service'
331
+ usage('vmc delete-service <service>')
332
+ if @args.size == 1
333
+ set_cmd(:services, :delete_service, 1)
334
+ else
335
+ set_cmd(:services, :delete_service)
336
+ end
337
+
338
+ when 'bind-service', 'bind_service'
339
+ usage('vmc bind-service <servicename> <appname>')
340
+ set_cmd(:services, :bind_service, 2)
341
+
342
+ when 'unbind-service', 'unbind_service'
343
+ usage('vmc unbind-service <servicename> <appname>')
344
+ set_cmd(:services, :unbind_service, 2)
345
+
346
+ when 'clone-services'
347
+ usage('vmc clone-services <src-app> <dest-app>')
348
+ set_cmd(:services, :clone_services, 2)
349
+
350
+ when 'aliases'
351
+ usage('vmc aliases')
352
+ set_cmd(:misc, :aliases)
353
+
354
+ when 'alias'
355
+ usage('vmc alias <alias[=]command>')
356
+ if @args.size == 1
357
+ set_cmd(:misc, :alias, 1)
358
+ elsif @args.size == 2
359
+ set_cmd(:misc, :alias, 2)
360
+ end
361
+
362
+ when 'unalias'
363
+ usage('vmc unalias <alias>')
364
+ set_cmd(:misc, :unalias, 1)
365
+
366
+ when 'help'
367
+ display_help if @args.size == 0
368
+ @help_only = true
369
+ parse_command!
370
+
371
+ when 'usage'
372
+ display basic_usage
373
+ exit(true)
374
+
375
+ when 'options'
376
+ # Simulate --options
377
+ @args = @args.unshift('--options')
378
+ parse_options!
379
+
380
+ else
381
+ if verb
382
+ display "vmc: Unknown command [#{verb}]"
383
+ display basic_usage
384
+ exit(false)
385
+ end
386
+ end
387
+ end
388
+
389
+ def process_aliases!
390
+ return if @args.empty?
391
+ aliases = VMC::Cli::Config.aliases
392
+ aliases.each_pair do |k,v|
393
+ if @args[0] == k
394
+ display "[#{@args[0]} aliased to #{aliases.invert[key]}]" if @options[:verbose]
395
+ @args[0] = v
396
+ break;
397
+ end
398
+ end
399
+ end
400
+
401
+ def usage(msg = nil)
402
+ @usage = msg if msg
403
+ @usage
404
+ end
405
+
406
+ def usage_error(msg = nil)
407
+ @usage_error = msg if msg
408
+ @usage_error
409
+ end
410
+
411
+ def run
412
+
413
+ trap('TERM') { print "\nTerminated\n"; exit(false)}
414
+
415
+ parse_options!
416
+
417
+ @options[:colorize] = false unless STDOUT.tty?
418
+
419
+ VMC::Cli::Config.colorize = @options.delete(:colorize)
420
+ VMC::Cli::Config.nozip = @options.delete(:nozip)
421
+ VMC::Cli::Config.trace = @options.delete(:trace)
422
+ VMC::Cli::Config.output ||= STDOUT unless @options[:quiet]
423
+
424
+ process_aliases!
425
+ parse_command!
426
+
427
+ if @namespace && @action
428
+ eval("VMC::Cli::Command::#{@namespace.to_s.capitalize}").new(@options).send(@action.to_sym, *@args)
429
+ elsif @help_only || @usage
430
+ display_usage
431
+ else
432
+ display basic_usage
433
+ exit(false)
434
+ end
435
+
436
+ rescue OptionParser::InvalidOption => e
437
+ rescue OptionParser::AmbiguousOption => e
438
+ puts(e.message.red)
439
+ puts("\n")
440
+ puts(basic_usage)
441
+ @exit_status = false
442
+ rescue VMC::Client::AuthError => e
443
+ if VMC::Cli::Config.auth_token.nil?
444
+ puts "Login Required".red
445
+ else
446
+ puts "Not Authorized".red
447
+ end
448
+ @exit_status = false
449
+ rescue VMC::Client::TargetError, VMC::Client::NotFound, VMC::Client::BadTarget => e
450
+ puts e.message.red
451
+ @exit_status = false
452
+ rescue VMC::Client::HTTPException => e
453
+ puts e.message.red
454
+ @exit_status = false
455
+ rescue VMC::Cli::GracefulExit => e
456
+ # Redirected commands end up generating this exception (kind of goto)
457
+ rescue VMC::Cli::CliExit => e
458
+ puts e.message.red
459
+ @exit_status = false
460
+ rescue VMC::Cli::CliError => e
461
+ say("Error #{e.error_code}: #{e.message}".red)
462
+ @exit_status = false
463
+ rescue SystemExit => e
464
+ @exit_status = e.success?
465
+ rescue SyntaxError => e
466
+ puts e.message.red
467
+ puts e.backtrace
468
+ @exit_status = false
469
+ rescue Interrupt => e
470
+ say("\nInterrupted".red)
471
+ @exit_status = false
472
+ rescue => e
473
+ puts e.message.red
474
+ puts e.backtrace
475
+ @exit_status = false
476
+ ensure
477
+ say("\n")
478
+ @exit_status == true if @exit_status.nil?
479
+ if @options[:verbose]
480
+ if @exit_status
481
+ puts "[#{@namespace}:#{@action}] SUCCEEDED".green
482
+ else
483
+ puts "[#{@namespace}:#{@action}] FAILED".red
484
+ end
485
+ say("\n")
486
+ end
487
+ exit(@exit_status)
488
+ end
489
+
490
+ end