vmc 0.0.8 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/LICENSE +8 -3
  2. data/README.md +83 -0
  3. data/Rakefile +11 -65
  4. data/bin/vmc +3 -2
  5. data/lib/cli/commands/admin.rb +57 -0
  6. data/lib/cli/commands/apps.rb +828 -0
  7. data/lib/cli/commands/base.rb +56 -0
  8. data/lib/cli/commands/misc.rb +99 -0
  9. data/lib/cli/commands/services.rb +84 -0
  10. data/lib/cli/commands/user.rb +60 -0
  11. data/lib/cli/config.rb +109 -0
  12. data/lib/cli/core_ext.rb +119 -0
  13. data/lib/cli/errors.rb +19 -0
  14. data/lib/cli/frameworks.rb +97 -0
  15. data/lib/cli/runner.rb +437 -0
  16. data/lib/cli/services_helper.rb +74 -0
  17. data/lib/cli/usage.rb +94 -0
  18. data/lib/cli/version.rb +5 -0
  19. data/lib/cli/zip_util.rb +61 -0
  20. data/lib/cli.rb +30 -0
  21. data/lib/vmc/client.rb +415 -0
  22. data/lib/vmc/const.rb +19 -0
  23. data/lib/vmc.rb +2 -1589
  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/login_fail.txt +9 -0
  35. data/spec/assets/login_success.txt +9 -0
  36. data/spec/assets/sample_token.txt +1 -0
  37. data/spec/assets/service_already_exists.txt +9 -0
  38. data/spec/assets/service_listings.txt +9 -0
  39. data/spec/assets/service_not_found.txt +9 -0
  40. data/spec/assets/user_info.txt +9 -0
  41. data/spec/spec_helper.rb +11 -0
  42. data/spec/unit/cli_opts_spec.rb +73 -0
  43. data/spec/unit/client_spec.rb +284 -0
  44. metadata +114 -71
  45. data/README +0 -58
  46. data/lib/parse.rb +0 -719
  47. data/lib/vmc_base.rb +0 -205
  48. data/vendor/gems/httpclient/VERSION +0 -1
  49. data/vendor/gems/httpclient/lib/http-access2/cookie.rb +0 -1
  50. data/vendor/gems/httpclient/lib/http-access2/http.rb +0 -1
  51. data/vendor/gems/httpclient/lib/http-access2.rb +0 -53
  52. data/vendor/gems/httpclient/lib/httpclient/auth.rb +0 -522
  53. data/vendor/gems/httpclient/lib/httpclient/cacert.p7s +0 -1579
  54. data/vendor/gems/httpclient/lib/httpclient/cacert_sha1.p7s +0 -1579
  55. data/vendor/gems/httpclient/lib/httpclient/connection.rb +0 -84
  56. data/vendor/gems/httpclient/lib/httpclient/cookie.rb +0 -562
  57. data/vendor/gems/httpclient/lib/httpclient/http.rb +0 -867
  58. data/vendor/gems/httpclient/lib/httpclient/session.rb +0 -864
  59. data/vendor/gems/httpclient/lib/httpclient/ssl_config.rb +0 -417
  60. data/vendor/gems/httpclient/lib/httpclient/timeout.rb +0 -136
  61. data/vendor/gems/httpclient/lib/httpclient/util.rb +0 -86
  62. data/vendor/gems/httpclient/lib/httpclient.rb +0 -1020
  63. data/vendor/gems/httpclient/lib/tags +0 -908
data/lib/vmc.rb CHANGED
@@ -1,1590 +1,3 @@
1
- # Copyright 2010, VMware, Inc. Licensed under the
2
- # MIT license, please see the LICENSE file. All rights reserved
1
+ module VMC; end
3
2
 
4
- require 'rubygems'
5
- require 'pp'
6
- require 'vmc_base'
7
- require 'highline/import'
8
-
9
- module VMC
10
-
11
- class Client < VMC::BaseClient
12
-
13
- VERSION = "0.0.7"
14
-
15
- attr_reader :host, :base_uri, :droplets_uri, :services_uri, :resources_uri, :token, :args
16
-
17
- attr_accessor :puser
18
-
19
- def setup_target_uris
20
- get_host_target
21
- @base_uri = "http://#{@host}"
22
- @droplets_uri = "#{base_uri}/apps"
23
- @services_uri = "#{base_uri}/services"
24
- @resources_uri = "#{base_uri}/resources"
25
- end
26
-
27
- def check_target
28
- get_token # Just load it if its around
29
- @check = HTTPClient.get "#{base_uri}/info", nil, auth_hdr
30
- error "\nERROR: Unable to contact target server: [#{@base_uri}]\n\n" if @check.status != 200
31
- #display "\n[#{@base_uri}]\n\n"
32
- check_puser if @puser
33
- rescue
34
- error "\nERROR: Unable to contact target server: [#{@base_uri}]\n\n"
35
- end
36
-
37
- def set_puser(puser)
38
- @puser = puser
39
- end
40
-
41
- def check_puser
42
- info_json = JSON.parse(@check.content)
43
- username = info_json['user']
44
- error "ERROR: Proxying to #{@puser} failed." unless (username && (username.downcase == @puser.downcase))
45
- end
46
-
47
- # ------- commands --------
48
-
49
- # show version string
50
- def version
51
- display VERSION
52
- exit 0
53
- end
54
-
55
- # Define a new cloud controller target
56
- def target(host=nil)
57
- unless host
58
- display "\n[#{@base_uri}]\n\n"
59
- return
60
- end
61
-
62
- target_host = host.gsub(/^http(s*):\/\//i, '')
63
- try_host = "http://#{target_host}/info"
64
- target_host_display = "http://#{target_host}"
65
- display ''
66
- begin
67
- response = HTTPClient.get try_host
68
- if response.status != 200
69
- display "New Target host is not valid: '#{target_host_display}'"
70
- show_response = ask "Would you like see the response [yN]? "
71
- display response.content if show_response.upcase == 'Y'
72
- exit(1)
73
- end
74
- response_json = JSON.parse(response.content)
75
- error "New Target host is not valid: '#{target_host_display}'" unless
76
- response_json['name'] && response_json['version'] && response_json['support'] && response_json['description']
77
- rescue => e
78
- display e
79
- error "New Target host is not valid: '#{target_host_display}'"
80
- end
81
- write_host_target(target_host)
82
- display "Succesfully targeted to [#{target_host_display}]\n\n"
83
- rescue => e
84
- error "Problem executing command, #{e}"
85
- end
86
-
87
- # get information about this cloud
88
- def info
89
- info_json = JSON.parse(@check.content)
90
-
91
- display "\n#{info_json['description']}"
92
- display "For support visit #{info_json['support']}"
93
- display ""
94
- display "Target: #{@base_uri} (v#{info_json['version']})"
95
- display "Client: v#{VERSION}"
96
- if info_json['user']
97
- display ''
98
- display "User: #{info_json['user']}"
99
- end
100
- if usage = info_json['usage'] and limits = info_json['limits']
101
- tmem = pretty_size(limits['memory']*1024*1024)
102
- mem = pretty_size(usage['memory']*1024*1024)
103
- tser = limits['services']
104
- ser = usage['services']
105
- tapps = limits['apps'] || 0
106
- apps = usage['apps'] || 0
107
-
108
- display "Usage: Memory (#{mem} of #{tmem} total)"
109
- display " Services (#{ser} of #{tser} total)"
110
- display " Apps (#{apps} of #{tapps} total)" if limits['apps']
111
- end
112
- display ''
113
- rescue => e
114
- error "Problem executing command, #{e}"
115
- end
116
-
117
- # register me as a user of this cloud
118
- def register(email=nil,password=nil)
119
- if not email and not password
120
- puts "Register your account with an email account and password."
121
- email = ask("Email: ")
122
- password = ask("Password: ") {|q| q.echo = '*'}
123
- password2 = ask("Verify Password: ") {|q| q.echo = '*'}
124
- error "Passwords did not match, try again" if password != password2
125
- end
126
- get_token # Just load it if its around
127
- register_internal(@base_uri, email, password, auth_hdr)
128
- display "Registration completed"
129
- # do an autologin also to setup token, avoiding login on next VMC command.
130
- login_save_token(email, password) unless @token
131
- rescue => e
132
- error "Problem registering, #{e}"
133
- end
134
-
135
- def login(email=nil,password=nil)
136
- if email and not password
137
- tries = 0
138
- begin
139
- password = ask("Password: ") {|q| q.echo = '*'}
140
- login_save_token(email, password)
141
- display ""
142
- rescue => e
143
- display "Problem with login, #{e}, try again or register for an account."
144
- retry if (tries += 1) < 3
145
- exit 1
146
- end
147
- elsif !(email && password)
148
- tries = 0
149
- begin
150
- email = ask("Email: ")
151
- password = ask("Password: ") {|q| q.echo = '*'}
152
- login_save_token(email, password)
153
- display ""
154
- rescue => e
155
- display "Problem with login, #{e}, try again or register for an account."
156
- retry if (tries += 1) < 3
157
- exit 1
158
- end
159
- else
160
- begin
161
- login_save_token(email, password)
162
- display ""
163
- rescue => e
164
- error "Problem with login, #{e}, try again or register for an account."
165
- end
166
- end
167
- end
168
-
169
- def logout
170
- FileUtils.rm_f(token_file)
171
- display "Succesfully logged out."
172
- end
173
-
174
- # Change the current users passwd
175
- def passwd(password=nil)
176
- check_for_token
177
- user_info = JSON.parse(@check.content)
178
- email = user_info['user']
179
- puts "Changing password for #{email}\n\n"
180
- if !password
181
- password = ask("Password: ") {|q| q.echo = '*'}
182
- password2 = ask("Verify Password: ") {|q| q.echo = '*'}
183
- error "Passwords did not match, try again" if password != password2
184
- end
185
-
186
- response = get_user_internal(@base_uri, email, auth_hdr)
187
- user_info = JSON.parse(response.content)
188
- user_info['password'] = password
189
- change_passwd_internal(@base_uri, user_info, auth_hdr)
190
-
191
- display "Password succesfully changed."
192
- # do an autologin also to setup token, avoiding login on next VMC command.
193
- # only if not proxying
194
- login_save_token(email, password) unless @puser
195
- rescue => e
196
- error "Problem changing password, #{e}"
197
- end
198
-
199
-
200
- def login_save_token(email, password)
201
- # TODO: We should be url encoding email
202
- @token = login_internal(@base_uri, email, password)
203
- write_token
204
- end
205
-
206
- def target_file
207
- "#{ENV['HOME']}/.vmc_target"
208
- end
209
-
210
- def get_host_target
211
- return if @host
212
- if File.exists? target_file
213
- @host = File.read(target_file).strip!
214
- ha = @host.split('.')
215
- ha.shift
216
- @suggest_url = ha.join('.')
217
- @suggest_url = 'b29.me' if @suggest_url.empty?
218
- else
219
- @host = 'api.b29.me'
220
- @suggest_url = 'b29.me'
221
- end
222
- end
223
-
224
- def write_host_target(target_host)
225
- File.open(target_file, 'w+') { |f| f.puts target_host }
226
- FileUtils.chmod 0600, target_file
227
- end
228
-
229
- def token_file
230
- "#{ENV['HOME']}/.vmc_token"
231
- end
232
-
233
- def instance_file
234
- "#{ENV['HOME']}/.vmc_instances"
235
- end
236
-
237
- def get_token
238
- return @token if @token
239
- if File.exists?(token_file)
240
- contents = File.read(token_file).strip
241
- begin
242
- tokens = JSON.parse(contents)
243
- @token = tokens[@base_uri]
244
- rescue
245
- @token = contents
246
- write_token
247
- end
248
- end
249
- end
250
-
251
- def check_for_token
252
- return if get_token
253
- display "Please Login:\n\n"
254
- login
255
- check_for_token
256
- end
257
-
258
- def write_token
259
- contents = File.exists?(token_file) ? File.read(token_file).strip : nil
260
- tokens = {}
261
-
262
- if contents
263
- begin
264
- tokens = JSON.parse(contents)
265
- rescue
266
- end
267
- end
268
-
269
- tokens[@base_uri] = @token
270
-
271
- File.open(token_file, 'w+') { |f| f.write(tokens.to_json) }
272
- FileUtils.chmod 0600, token_file
273
- end
274
-
275
- def auth_hdr
276
- auth = { 'AUTHORIZATION' => "#{@token}" }
277
- auth['PROXY-USER'] = @puser if @puser
278
- return auth
279
- end
280
-
281
- def display_services_banner
282
- #display "#{'Name'.ljust(15)} #{'Service'.ljust(10)} #{'Version'.ljust(10)} #{'Plans'}"
283
- #display "#{'----'.ljust(15)} #{'-------'.ljust(10)} #{'-------'.ljust(10)} #{'-----'}"
284
- display "#{'Name'.ljust(15)} #{'Service'.ljust(10)}"
285
- display "#{'----'.ljust(15)} #{'-------'.ljust(10)}"
286
-
287
- end
288
-
289
- def list_services(service_names, banner)
290
- services = []
291
- service_names.each { |service_name|
292
- sn = URI.escape("#{services_uri}/#{service_name}")
293
- response = HTTPClient.get(sn, nil, auth_hdr)
294
- error "Problem getting services list" if response.status != 200
295
- services << JSON.parse(response.content)
296
- }
297
-
298
- display(banner)
299
-
300
- if services.empty?
301
- display "None provisioned yet."
302
- else
303
- display_services_banner
304
- services.each { |h|
305
- display "#{h['name'].to_s.ljust(15)} ", false
306
- display "#{h['vendor'].ljust(10)} "
307
- #display "#{h['vendor'].ljust(10)} ", false
308
- #display "#{h['version'].ljust(10)} ", false
309
- #display "#{h['tier']}"
310
- }
311
- end
312
- end
313
-
314
- def app_list_services(appname=nil)
315
- error "Application name required\nvmc app services <appname>" unless appname
316
-
317
- response = HTTPClient.get "#{droplets_uri}/#{appname}", nil, auth_hdr
318
- error "Application does not exist." if response.status == 404
319
- return '' if response.status != 200
320
- service_names = JSON.parse(response.content)['services']
321
- service_names.empty? ? "None" : service_names.join(", ")
322
- end
323
-
324
- def user_list_services_helper
325
- service_names = get_user_service_names
326
- #list_services(service_names, "Provisioned Services:\n\n")
327
- list_services(service_names, "\n======= Provisioned Services ========\n\n")
328
- end
329
-
330
- def get_user_service_names
331
- service_names = []
332
- response = HTTPClient.get "#{services_uri}", nil, auth_hdr
333
- response_json = JSON.parse(response.content)
334
- response_json.each { |service_desc| service_names << service_desc['name'] }
335
- service_names
336
- end
337
-
338
- def services
339
- check_for_token
340
- response = HTTPClient.get "#{base_uri}/info/services", nil, auth_hdr
341
- handle_response(response)
342
- display "\n========== System Services ==========\n\n"
343
- services = JSON.parse(response.content)
344
- display_services_directory_banner
345
- services.each { |service_type, value|
346
- value.each { |vendor, version|
347
- version.each { |version_str, service|
348
- display "#{vendor.ljust(10)} ", false
349
- display "#{version_str.ljust(5)} ", false
350
- display "#{service['description']}"
351
- }
352
- }
353
- }
354
- display ""
355
- user_list_services_helper
356
- display ""
357
- rescue => e
358
- error "Problem executing command, #{e}"
359
- end
360
-
361
- def bind_service(service_name, appname)
362
- display "Creating new service binding to '#{service_name}' for '#{appname}'."
363
-
364
- # Now get the app and update it with the provisioned service
365
- response = get_app_internal(droplets_uri, appname, auth_hdr)
366
- appinfo = JSON.parse(response.content)
367
- provisioned_service = appinfo['services']
368
- provisioned_service = [] unless provisioned_service
369
- provisioned_service << service_name
370
- appinfo['services'] = provisioned_service
371
- response = update_app_state_internal droplets_uri, appname, appinfo, auth_hdr
372
- error "Problem updating application with new services" if response.status >= 400
373
- display "Application '#{appname}' updated."
374
- restart appname if appinfo['state'] == 'STARTED'
375
- rescue => e
376
- error "Problem executing command, #{e}"
377
- end
378
-
379
- def unbind_service(service_name, appname)
380
- app_remove_service(appname, service_name)
381
- end
382
-
383
- def create_service(service, opts={})
384
- check_for_token
385
- response = HTTPClient.get "#{base_uri}/info/services", nil, auth_hdr
386
- handle_response(response)
387
- services = JSON.parse(response.content)
388
- types = []
389
- services.each {|k,v| types << v }
390
- selection = nil
391
- types.each { |t| selection = t if t[service] }
392
- if ! selection
393
- options = []
394
- types.each {|t| options << t.keys }
395
- options.flatten!
396
- error("Service type must be one of: #{options.join(',')}") unless options.empty?
397
- error("No selectable services at this time") if options.empty?
398
- else
399
- info = selection[service][selection[service].keys.first]
400
- service_name = opts[:name] || "#{service}-#{fast_uuid[0..6]}"
401
- service_type = info['type']
402
- service_vendor = info['vendor']
403
- service_tier = 'free'
404
- service_version = info['version']
405
-
406
- services = {
407
- :name => service_name,
408
- :type => service_type,
409
- :vendor => service_vendor,
410
- :tier => service_tier,
411
- :version => service_version,
412
- #:options => option_values
413
- }
414
-
415
- check_for_token
416
- response = add_service_internal @services_uri, services, auth_hdr
417
- handle_response(response)
418
- display "Service '#{service_name}' provisioned."
419
-
420
- if appname = opts[:bind]
421
- display "Creating new service binding to '#{service_name}' for '#{appname}'."
422
-
423
- # Now get the app and update it with the provisioned service
424
- response = get_app_internal(droplets_uri, appname, auth_hdr)
425
- handle_response(response)
426
- appinfo = JSON.parse(response.content)
427
- provisioned_service = appinfo['services']
428
- provisioned_service = [] unless provisioned_service
429
- provisioned_service << service_name
430
- appinfo['services'] = provisioned_service
431
- response = update_app_state_internal droplets_uri, appname, appinfo, auth_hdr
432
- handle_response(response)
433
- display "Application '#{appname}' updated."
434
- restart appname if appinfo['state'] == 'STARTED'
435
- end
436
- end
437
- rescue => e
438
- error "Problem executing command, #{e}"
439
- end
440
-
441
- def delete_service(service)
442
- check_for_token
443
- user_remove_service_helper(service)
444
- end
445
-
446
- def user_remove_service_helper(service_name=nil)
447
- available_services = get_user_service_names
448
- error "No services to delete" if available_services.empty?
449
-
450
- unless service_name
451
- choose do |menu|
452
- #menu.header = "The following services are provisioned"
453
- menu.prompt = 'Please select the service you wish to delete: '
454
- menu.select_by = :index
455
- available_services.each { |key| menu.choice(key) { service_name = key } }
456
- end
457
- end
458
-
459
- error "Error: Invalid service" unless available_services.include? service_name
460
-
461
- remove_service_internal(services_uri, service_name, auth_hdr)
462
- display("Successfully removed service: #{service_name}")
463
- end
464
-
465
- def user_add_service_helper(service_name_prefix, opts={})
466
- service_type = opts[:type]
467
- service_vendor = opts[:vendor]
468
- service_version = opts[:version]
469
- service_tier = opts[:tier]
470
- service_name = opts[:name]
471
- service_size = opts[:size]
472
- response = HTTPClient.get "#{base_uri}/info/services", nil, auth_hdr
473
- handle_response(response)
474
-
475
- last_print = :none
476
-
477
- services = JSON.parse(response.content)
478
-
479
- available_services = {}
480
- services.each do |type, vendors|
481
- vendors.each do |vendor, versions|
482
- versions.each do |version, entry|
483
- tiers = entry["tiers"]
484
- if tiers.size > 1
485
- tier = tiers.has_key?("free") ? "free" : tiers.keys.first
486
- entry["tiers"] = {tier => tiers[tier]}
487
- end
488
- available_services[vendor] = {:type => type, :version => version} if (service_type.nil? or service_type == type)
489
- end
490
- end
491
- end
492
-
493
- return nil if available_services.empty?
494
-
495
- service_type_hash = nil
496
- service_vendor = nil if (service_vendor and service_vendor.empty?)
497
- unless service_vendor
498
-
499
- if available_services.length == 1
500
- service_vendor = available_services.keys.first
501
- service_type_hash = services[available_services[service_vendor][:type]]
502
-
503
- service_type = available_services[service_vendor][:type]
504
- say("Single #{service_type} service available: #{service_vendor}")
505
- last_print = :auto
506
- else
507
- puts "" if last_print == :auto
508
- choose do |menu|
509
- menu.header = "The following #{service_type} services are available"
510
- menu.prompt = 'Please select one you wish to provision: '
511
- menu.select_by = :index
512
-
513
- available_services.each_key do |key|
514
- menu.choice(key) do
515
- service_type = available_services[key][:type]
516
- service_type_hash = services[service_type]
517
- service_vendor = key
518
- end
519
- end
520
- end
521
- last_print = :menu
522
- end
523
- end
524
- error "Could not find vendor '#{service_vendor}' for #{service_type} services." unless service_type_hash[service_vendor]
525
-
526
- service_vendor_hash = service_type_hash[service_vendor]
527
-
528
- unless service_version
529
- puts "" if last_print == :menu
530
- if service_vendor_hash.length == 1
531
- service_version = service_vendor_hash.first[0]
532
- say("Single version available: #{service_version}")
533
- last_print = :auto
534
- else
535
- puts "" if last_print == :auto
536
- choose do |menu|
537
- menu.header = "The following #{service_vendor} #{service_type} versions are available"
538
- menu.prompt = 'Please select one you wish to provision: '
539
- menu.select_by = :index
540
-
541
- service_vendor_hash.each_key do |key|
542
- menu.choice(key) { service_version = key }
543
- end
544
- end
545
- last_print = :menu
546
- end
547
- end
548
- error "Could not find version '#{service_version}' for #{service_vendor} #{service_type}." unless service_vendor_hash[service_version]
549
-
550
- service_version_hash = service_vendor_hash[service_version]
551
- service_tier = service_version_hash['tiers'].keys.first
552
-
553
- service_tier_hash = service_version_hash['tiers'][service_tier]
554
- options = service_tier_hash['options']
555
-
556
- option_values = {}
557
- if options
558
- options.each do |k, v|
559
-
560
- if service_size
561
- option_values['size'] = service_size
562
- else
563
- puts "" if last_print == :menu
564
- if v['type'] == 'value'
565
- puts "" if last_print == :auto
566
- choose do |menu|
567
- menu.prompt = "#{k} (#{v['description']}): "
568
- menu.select_by = :index
569
-
570
-
571
- v['values'].each do |option|
572
- option_value = option
573
- menu.choice("#{option}") { option_values[k] = option_value }
574
- end
575
- end
576
- last_print = :menu
577
- end
578
- end
579
- end
580
- end
581
-
582
- default_service_name = "#{service_vendor}-#{fast_uuid[0..6]}"
583
- if service_name.nil?
584
- puts ""
585
- service_name = ask("Specify the name of the service [#{default_service_name}]: ")
586
- service_name = default_service_name if service_name.empty?
587
- end
588
-
589
- services = {
590
- :name => service_name,
591
- :type => service_type,
592
- :vendor => service_vendor,
593
- :tier => service_tier,
594
- :version => service_version,
595
- :options => option_values
596
- }
597
-
598
- check_for_token
599
- response = add_service_internal @services_uri, services, auth_hdr
600
- handle_response(response)
601
- display "Service '#{service_vendor}' provisioned."
602
-
603
- service_name
604
- end
605
-
606
-
607
- def app_add_service(opts={})
608
- service_type = opts[:service]
609
- service_vendor = opts[:vendor]
610
- service_version = opts[:version]
611
- service_tier = opts[:tier]
612
- appname = opts[:appname]
613
-
614
- error "Application name required\nvmc apps add-service <appname> [service] [vendor] [version] [tier]" unless appname
615
-
616
- app_add_service_helper(appname, service_type, service_vendor, service_version, service_tier)
617
-
618
- @appname = appname
619
- restart
620
- end
621
-
622
- def app_add_service_helper(appname, service_type=nil, service_vendor=nil, service_version=nil, service_tier=nil)
623
- check_for_token
624
-
625
- # If we have a single arg, let's assume its a service name..
626
- if service_type && !service_vendor && !service_version && !service_tier
627
- service_name = service_type
628
- service_type = nil
629
- end
630
-
631
- service_names = get_user_service_names
632
-
633
- if service_name && (service_names.empty? || !service_names.include?(service_name))
634
- service_name = nil
635
- end
636
-
637
- if !service_names.empty? && !service_name
638
- list_services(get_user_service_names, "The following services have been provisioned for you:")
639
- display ''
640
- use_existing = ask "Would you like to use one of these [yN]? "
641
- if use_existing.upcase == 'Y'
642
- begin
643
- service_name = ask "Which service (name)? "
644
- if !service_names.include?(service_name)
645
- display "Incorrect service name, please type again"
646
- service_name = nil
647
- end
648
- end while !service_name
649
- end
650
- end
651
-
652
- if !service_name
653
- display "Let's provision a new service"
654
- opts = {
655
- :type => service_type,
656
- :vendor => service_vendor,
657
- :version => service_version,
658
- :tier => service_tier
659
- }
660
- service_name = user_add_service_helper(appname, opts)
661
- end
662
-
663
- unless service_name
664
- display "No services available to provision"
665
- # Should we ask if they want to continue?
666
- return
667
- end
668
-
669
- display "Creating new service binding to '#{service_name}' for '#{appname}'."
670
-
671
- # Now get the app and update it with the provisioned service
672
- response = get_app_internal(droplets_uri, appname, auth_hdr)
673
- appinfo = JSON.parse(response.content)
674
- provisioned_service = appinfo['services']
675
- provisioned_service = [] unless provisioned_service
676
- provisioned_service << service_name
677
- appinfo['services'] = provisioned_service
678
- response = update_app_state_internal droplets_uri, appname, appinfo, auth_hdr
679
- handle_response(response)
680
- display "Application '#{appname}' updated."
681
- end
682
-
683
- def format_price(amount, type, *rest)
684
- case type
685
- when 'flat'
686
- return "$#{amount}/#{rest[0]}"
687
- when 'metered'
688
- raise "Not Implemented"
689
- end
690
- end
691
-
692
- def app_remove_service(appname=nil, service_name=nil)
693
- check_for_token
694
-
695
- app_response = get_app_internal(droplets_uri, appname, auth_hdr)
696
- appinfo = JSON.parse(app_response.content)
697
- provisioned_service = appinfo['services']
698
- provisioned_service = [] unless provisioned_service
699
- provisioned_service.delete(service_name)
700
- appinfo['services'] = provisioned_service
701
- response = update_app_state_internal droplets_uri, appname, appinfo, auth_hdr
702
- handle_response(response)
703
- display "Application '#{appname}' updated"
704
- display "Service #{service_name} removed."
705
- @appname = appname
706
- restart
707
- rescue => e
708
- error "Problem executing command, #{e}"
709
- end
710
-
711
- def list(first_arg=nil)
712
- apps(first_arg)
713
- end
714
-
715
- def apps(first_arg=nil, opts={})
716
- check_for_token
717
-
718
- case first_arg
719
- when "list" then list_apps
720
- when nil then list_apps
721
- when 'add-service' then app_add_service(opts)
722
- when 'remove-service' then app_remove_service(opts[:appname], opts[:service_name])
723
- when 'list-services' then app_list_services(opts[:appname])
724
- else "Incorrect option #{first_arg}. Must be 'list' (default), 'services', 'add-service', or 'remove-service"
725
- end
726
- end
727
-
728
- def health(d)
729
- return 'N/A' unless (d and d['state'])
730
-
731
- healthy_instances = d['runningInstances']
732
- expected_instance = d['instances']
733
- health = nil
734
-
735
- if d['state'] == "STARTED" && expected_instance > 0 && healthy_instances
736
- health = format("%.3f", healthy_instances.to_f / expected_instance).to_f
737
- end
738
-
739
- if health
740
- if health == 1.0
741
- health = "RUNNING"
742
- else
743
- health = "#{(health * 100).round}%"
744
- end
745
- else
746
- if d['state'] == 'STOPPED'
747
- health = 'STOPPED'
748
- else
749
- health = 'N/A'
750
- end
751
- end
752
-
753
- health
754
- end
755
-
756
- def list_apps
757
- droplets_full = get_apps_internal @droplets_uri, auth_hdr
758
- if droplets_full.empty?
759
- display "No applications available."
760
- return
761
- end
762
-
763
- display "#{'APPNAME'.ljust 15} #{'HEALTH'.ljust 8} #{'INSTANCES'.ljust 10} #{'SERVICES'.ljust 25} #{'URL'.ljust 15}\n"
764
- display "#{'-------'.ljust 15} #{'------'.ljust 8} #{'---------'.ljust 10} #{'--------'.ljust 25} #{'---'.ljust 15}\n"
765
- droplets_full.each { |d|
766
- display "#{d['name'].ljust 15} ", false
767
- display "#{health(d).ljust 8} ", false
768
- display "#{d['instances'].to_s.ljust 10} ", false
769
- display "#{d['services'].join(", ").ljust(25)} ", false
770
- #display "#{app_list_services(d['name']).ljust(25)} ", false
771
- display "#{d['uris'].join(', ').ljust(15)}"
772
- }
773
- rescue => e
774
- error "Problem executing command, #{e}"
775
- end
776
-
777
- # opts = {:instances => [optional], :exec => [optional],
778
- # :ignore_framework => [true/false/optional],
779
- # :noframework => [true/false/optional],
780
- # :appname => [optional],
781
- # :url => [optional]}
782
-
783
- def push(opts={})
784
- instances = opts.delete(:instances) || 1
785
- exec = opts.delete(:exec) || 'thin start'
786
- ignore_framework = opts.delete(:noframework)
787
- no_start = opts[:no_start]
788
-
789
- path = opts[:path] || '.'
790
- appname = opts[:appname]
791
- url = opts[:url]
792
- memswitch = opts[:mem]
793
-
794
- check_for_token
795
-
796
- # check if we have hit our app limit
797
- check_app_limit
798
-
799
- # check memsize here for capacity
800
- check_has_capacity_for(mem_choice_to_quota(memswitch)) if memswitch
801
-
802
- unless (appname && url)
803
- unless opts[:path]
804
- proceed = ask("Would you like to deploy from the current directory? [Yn]: ")
805
- error "Push aborted" if proceed == 'n' || proceed == 'N'
806
- end
807
- appname = ask("Application Name: ") unless appname
808
- error "Push aborted: Application Name required." if appname.empty?
809
- url = ask("Application Deployed URL: '#{appname}.#{@suggest_url}'? ") unless url
810
- url = "#{appname}.#{@suggest_url}" if url.empty?
811
- end
812
-
813
- response = get_app_internal(@droplets_uri, appname, auth_hdr)
814
- error "Application #{appname} already exists, use update or delete." if response.status == 200
815
-
816
- Dir.chdir(path) do
817
- # Detect the appropriate framework.
818
- framework = "http://b20nine.com/unknown"
819
- mem = '256M'
820
- unless ignore_framework
821
- if File.exist?('config/environment.rb')
822
- display "Rails application detected."
823
- framework = "rails/1.0"
824
- elsif Dir.glob('*.war').first
825
- opt_war_file = Dir.glob('*.war').first
826
- display "Java war file found, detecting framework..."
827
-
828
- entries = []
829
- Zip::ZipFile.foreach(opt_war_file) { |zentry| entries << zentry }
830
- contents = entries.join("\n")
831
-
832
- if contents =~ /WEB-INF\/grails-app/
833
- display "Grails application detected."
834
- framework = "grails/1.0"
835
- mem = '512M'
836
- elsif contents =~ /WEB-INF\/classes\/org\/springframework/
837
- display "SpringSource application detected."
838
- framework = "spring_web/1.0"
839
- mem = '512M'
840
- elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
841
- display "SpringSource application detected."
842
- framework = "spring_web/1.0"
843
- mem = '512M'
844
- else
845
- display "Unknown J2EE Web Application"
846
- framework = "spring_web/1.0"
847
- end
848
- elsif File.exist?('web.config')
849
- display "ASP.NET application detected."
850
- framework = "asp_web/1.0"
851
- elsif !Dir.glob('*.rb').empty?
852
- matched_file = nil
853
- Dir.glob('*.rb').each do |fname|
854
- next if matched_file
855
- File.open(fname, 'r') do |f|
856
- str = f.read # This might want to be limited
857
- matched_file = fname if (str && str.match(/^\s*require\s*'sinatra'/i))
858
- end
859
- end
860
- if matched_file && !File.exist?('config.ru')
861
- display "Simple Sinatra application detected in #{matched_file}."
862
- exec = "ruby #{matched_file}"
863
- end
864
- mem = '128M'
865
- elsif !Dir.glob('*.js').empty?
866
- # Fixme, make other files work too..
867
- if File.exist?('app.js')
868
- display "Node.js application detected."
869
- framework = "nodejs/1.0"
870
- mem = '64M'
871
- end
872
- end
873
- end
874
-
875
- mem = memswitch if memswitch
876
- unless memswitch
877
- choose do |menu|
878
- menu.layout = :one_line
879
- menu.prompt = "Memory Reservation [Default:#{mem}] "
880
- menu.default = mem
881
- mem_choices.each { |choice| menu.choice(choice) { mem = choice } }
882
- end
883
- end
884
-
885
- # Set to MB number
886
- mem_quota = mem_choice_to_quota(mem)
887
-
888
- # check memsize here for capacity
889
- check_has_capacity_for(mem_quota)
890
-
891
- manifest = {
892
- :name => "#{appname}",
893
- :staging => {
894
- :model => framework,
895
- :stack => exec
896
- },
897
- :uris => [url],
898
- :instances => instances,
899
- :resources => {
900
- :memory => mem_quota
901
- },
902
- }
903
-
904
- display "Uploading Application Information."
905
-
906
- # Send the manifest to the cloud controller
907
- response = create_app_internal(@droplets_uri, manifest, auth_hdr)
908
- handle_response(response)
909
-
910
- # Provision database here if needed.
911
- if framework_needs_db?(framework)
912
- proceed = ask("This framework usually needs a database, would you like to provision it? [Yn]: ")
913
- if proceed != 'n' && proceed != 'N'
914
- app_add_service_helper(appname, 'database', '')
915
- provisioned_db = true
916
- end
917
- end
918
-
919
- # Stage and upload the app bits.
920
- display "Uploading Application."
921
- upload_size = upload_app_bits(@resources_uri, @droplets_uri, appname, auth_hdr, opt_war_file, provisioned_db)
922
- if upload_size > 1024*1024
923
- upload_size = (upload_size/(1024.0*1024.0)).round.to_s + 'M'
924
- elsif upload_size > 0
925
- upload_size = (upload_size/1024.0).round.to_s + 'K'
926
- end
927
- display "\nUploaded Application '#{appname}' (#{upload_size})."
928
-
929
- display "Push completed."
930
- @push = true
931
- @appname = appname
932
- start unless no_start
933
- end
934
- rescue => e
935
- error "Problem executing command, #{e}"
936
- end
937
-
938
- def delete(appname=nil, force=false)
939
- error "Application name required, vmc delete <appname || --all> [--force]." unless appname
940
-
941
- check_for_token
942
-
943
- # wildcard behavior
944
- apps = []
945
- if appname == '--all'
946
- droplets_full = get_apps_internal @droplets_uri, auth_hdr
947
- apps = droplets_full.collect { |d| "#{d['name']}" }
948
- apps.each { |app| delete_app(app, force) }
949
- else
950
- delete_app(appname, force)
951
- end
952
- rescue => e
953
- error "Problem executing command, #{e}"
954
- end
955
-
956
- def delete_app(appname, force)
957
- response = get_app_internal @droplets_uri, appname, auth_hdr
958
- if response.status != 200
959
- display "Application '#{appname}' does not exist."
960
- return
961
- end
962
- appinfo = JSON.parse(response.content)
963
- services_to_delete = []
964
- app_services = appinfo['services']
965
- app_services.each { |service|
966
- del_service = 'Y' if force
967
- del_service = ask("Application '#{appname}' uses '#{service}' service, would you like to delete it? [Yn]: ") unless force
968
- services_to_delete << service unless (del_service == 'n' || del_service == 'N')
969
- }
970
- delete_app_internal @droplets_uri, appname, services_to_delete, auth_hdr
971
- services_to_delete.each { |s| display "Service '#{s}' deleted."}
972
- display "Application '#{appname}' deleted."
973
- end
974
-
975
- def stop(appname=nil)
976
- appname ||= @appname
977
- error "Application name required, vmc stop <appname>." unless appname
978
-
979
- check_for_token
980
- response = get_app_internal @droplets_uri, appname, auth_hdr
981
- if response.status != 200
982
- display "Application '#{appname}' does not exist, use push first."
983
- return
984
- end
985
- appinfo = JSON.parse(response.content)
986
- appinfo['state'] = 'STOPPED'
987
- #display JSON.pretty_generate(appinfo)
988
-
989
- hdrs = auth_hdr.merge({'content-type' => 'application/json'})
990
- update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
991
- display "Application '#{appname}' stopped."
992
- rescue => e
993
- error "Problem executing command, #{e}"
994
- end
995
-
996
- def start(appname=nil)
997
- appname ||= @appname
998
- error "Application name required, vmc start <appname>." unless appname
999
-
1000
- check_for_token
1001
- response = get_app_internal @droplets_uri, appname, auth_hdr
1002
- if response.status != 200
1003
- display"Application #{appname} does not exist, use push first."
1004
- return
1005
- end
1006
- appinfo = JSON.parse(response.content)
1007
- if (appinfo['state'] == 'STARTED')
1008
- display "Application '#{appname}' is already running."
1009
- return
1010
- end
1011
-
1012
- appinfo['state'] = 'STARTED'
1013
-
1014
- response = update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
1015
- handle_response(response)
1016
-
1017
- count = 0
1018
- log_lines_displayed = 0
1019
- failed = false
1020
- display "Trying to start Application: '#{appname}'."
1021
- loop do
1022
- display ".", false unless count > 19
1023
- sleep 0.5
1024
- begin
1025
- if app_started_properly(appname, count > 6)
1026
- display ''
1027
- break;
1028
- elsif !crashes(appname, false).empty?
1029
- # Check for the existance of crashes
1030
- display "\n\nERROR - Application '#{appname}' failed to start, logs information below.\n\n"
1031
- grab_crash_logs(appname, '0', true)
1032
- if @push
1033
- display ''
1034
- should_delete = ask "Should I delete the application? (y/n)? "
1035
- delete_app(appname, false) if should_delete == 'Y' || should_delete == 'y'
1036
- end
1037
- failed = true
1038
- break
1039
- elsif count > 19
1040
- log_lines_displayed = grab_startup_tail(appname, log_lines_displayed)
1041
- end
1042
- rescue => e
1043
- display e.message, false
1044
- end
1045
- count += 1
1046
- if count > 600 # 5 minutes
1047
- error "\n\nApplication is taking too long to start, check your logs"
1048
- break
1049
- end
1050
- end
1051
-
1052
- display("Application '#{appname}' started.") unless failed
1053
- rescue => e
1054
- error "Problem executing command, #{e}"
1055
- end
1056
-
1057
- def restart(appname=nil)
1058
- appname ||= @appname
1059
- error "Application name required, vmc start <appname>." unless appname
1060
-
1061
- stop(appname)
1062
- start(appname)
1063
- rescue => e
1064
- error "Problem executing command, #{e}"
1065
- end
1066
-
1067
- def mem(appname, memsize=nil)
1068
- check_for_token
1069
-
1070
- response = get_app_internal @droplets_uri, appname, auth_hdr
1071
- handle_response(response)
1072
-
1073
- appinfo = JSON.parse(response.content)
1074
-
1075
- mem = current_mem = mem_quota_to_choice(appinfo['resources']['memory'])
1076
-
1077
- if ! memsize
1078
- choose do |menu|
1079
- menu.layout = :one_line
1080
- menu.prompt = "Update Memory Reservation? [Current:#{current_mem}] "
1081
- menu.default = current_mem
1082
- mem_choices.each { |choice| menu.choice(choice) { memsize = choice } }
1083
- end
1084
- end
1085
-
1086
- memsize = mem_choice_to_quota(memsize)
1087
- mem = mem_choice_to_quota(mem)
1088
-
1089
- # check memsize here for capacity
1090
- check_has_capacity_for(memsize - mem)
1091
-
1092
- mem = memsize
1093
-
1094
- if (mem != current_mem)
1095
- appinfo['resources']['memory'] = mem
1096
- response = update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
1097
- handle_response(response)
1098
- display "Updated memory reservation to '#{mem_quota_to_choice(mem)}'."
1099
- restart appname if appinfo['state'] == 'STARTED'
1100
- end
1101
- rescue => e
1102
- error "Problem executing command, #{e}"
1103
- end
1104
-
1105
- def update(appname=nil, canary=false)
1106
- error "Application name required, vmc update <appname>." unless appname
1107
-
1108
- check_for_token
1109
-
1110
- response = get_app_internal @droplets_uri, appname, auth_hdr
1111
- if response.status != 200
1112
- display "Application '#{appname}' does not exist, use push first."
1113
- return
1114
- end
1115
- appinfo = JSON.parse(response.content)
1116
-
1117
- display "Uploading Application."
1118
- upload_app_bits(@resources_uri, @droplets_uri, appname, auth_hdr, Dir.glob('*.war').first)
1119
-
1120
- unless canary
1121
- @appname = appname
1122
- display "Application updated."
1123
- restart appname if appinfo['state'] == 'STARTED'
1124
- return
1125
- end
1126
-
1127
- response = update_app_internal @droplets_uri, appname, auth_hdr
1128
- handle_response(response)
1129
-
1130
- last_state = 'NONE'
1131
- begin
1132
- response = get_update_app_status @droplets_uri, appname, auth_hdr
1133
-
1134
- update_info = JSON.parse(response.content)
1135
- update_state = update_info['state']
1136
- if update_state != last_state
1137
- display('') unless last_state == 'NONE'
1138
- display("#{update_state.ljust(15)}", false)
1139
- else
1140
- display('.', false)
1141
- end
1142
-
1143
- if update_state == 'SUCCEEDED' || update_state == 'CANARY_FAILED'
1144
- display('')
1145
- if update_state == 'CANARY_FAILED' && update_info['canary']
1146
- begin
1147
- map = File.open(instance_file, 'r') { |f| JSON.parse(f.read) }
1148
- rescue
1149
- map = {}
1150
- end
1151
-
1152
- map["#{appname}-canary"] = update_info['canary']
1153
-
1154
- File.open(instance_file, 'w') {|f| f.write(map.to_json)}
1155
- display("Debug the canary using 'vmc files #{appname} --instance #{appname}-canary'")
1156
- end
1157
- break
1158
- else
1159
- last_state = update_state
1160
- end
1161
- sleep(0.5)
1162
- end while true
1163
- rescue => e
1164
- error "Problem executing command, #{e}"
1165
- end
1166
-
1167
- def change_instances(appinfo, appname, instances)
1168
- match = instances.match(/([+-])?\d+/)
1169
- error "Invalid number of instances '#{instances}', vmc instances <appname> <num>." unless match
1170
-
1171
- instances = instances.to_i
1172
- current_instances = appinfo['instances']
1173
- new_instances = match.captures[0] ? current_instances + instances : instances
1174
- error "There must be at least 1 instance." if new_instances < 1
1175
-
1176
- if current_instances == new_instances
1177
- display "Application '#{appname}' is already running #{new_instances} instance#{'s' if new_instances > 1}."
1178
- return
1179
- end
1180
-
1181
- appinfo['instances'] = new_instances
1182
- response = update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
1183
-
1184
- handle_response(response)
1185
-
1186
- display "Scaled '#{appname}' #{new_instances > current_instances ? 'up' : 'down'} to " +
1187
- "#{new_instances} instance#{'s' if new_instances > 1}."
1188
- end
1189
-
1190
- def get_instances(appname)
1191
- instances_info_envelope = get_app_instances_internal(@droplets_uri, appname, auth_hdr)
1192
-
1193
- # Empty array is returned if there are no instances running.
1194
- error "No running instances for '#{appname}'" if instances_info_envelope.is_a?(Array)
1195
-
1196
- instances_info = instances_info_envelope['instances']
1197
- display "#{'Index'.ljust 5} #{'State'.ljust 15} #{'Since'.ljust 20}\n"
1198
- display "#{'--'.ljust 5} #{'--------'.ljust 15} #{'-------------'.ljust 20}\n"
1199
-
1200
- instances_info.each {|entry| entry[0] = entry[0].to_i}
1201
- instances_info = instances_info.sort {|a,b| a['index'] - b['index']}
1202
- instances_info.each do |entry|
1203
- display "#{entry['index'].to_s.ljust 5} #{entry['state'].ljust 15} #{Time.at(entry['since']).strftime("%m/%d/%Y %I:%M%p").ljust 20}\n"
1204
- end
1205
- end
1206
-
1207
- def instances(appname=nil, instances=nil)
1208
- appname ||= @appname
1209
- error "Application name required, vmc instances <appname> [num | delta]." unless appname
1210
-
1211
- check_for_token
1212
- response = get_app_internal @droplets_uri, appname, auth_hdr
1213
- handle_response(response)
1214
- appinfo = JSON.parse(response.content)
1215
-
1216
- if instances
1217
- change_instances(appinfo, appname, instances) if instances
1218
- elsif
1219
- get_instances(appname)
1220
- end
1221
-
1222
- rescue => e
1223
- error "Problem executing command, #{e}"
1224
- end
1225
-
1226
- def crashes(appname=nil, print_results=true)
1227
- error "Application name required, vmc crashes <appname>." unless appname
1228
-
1229
- check_for_token
1230
- response = get_app_crashes_internal @droplets_uri, appname, auth_hdr
1231
- if response.status == 404
1232
- display"Application #{appname} does not exist, use push first."
1233
- return []
1234
- elsif response.status != 200
1235
- display"Could not fetch application crashes."
1236
- return []
1237
- end
1238
- crashes = JSON.parse(response.content)['crashes']
1239
-
1240
- instance_map = {}
1241
-
1242
- if print_results
1243
- display "#{'Name'.ljust 10} #{'Id'.ljust 40} #{'Since'.ljust 20}\n"
1244
- display "#{'--'.ljust 10} #{'--------'.ljust 40} #{'-------------'.ljust 20}\n"
1245
- end
1246
-
1247
- counter = 1
1248
-
1249
- crashes = crashes.to_a.sort {|a,b| a['since'] - b['since']}
1250
- crashes.each do |crash|
1251
- name = "#{appname}-#{counter}"
1252
- display "#{name.ljust 10} #{crash['instance'].ljust 40} #{Time.at(crash['since']).strftime("%m/%d/%Y %I:%M%p").ljust 20}\n" if print_results
1253
- instance_map[name] = crash['instance']
1254
- counter +=1
1255
- end
1256
-
1257
- File.open(instance_file, 'w') {|f| f.write(instance_map.to_json)}
1258
- crashes
1259
- rescue => e
1260
- error "Problem executing command, #{e}"
1261
- end
1262
-
1263
- def map(appname=nil, url=nil)
1264
- error "Application name and url required, vmc map <appname> <url>." unless appname && url
1265
-
1266
- check_for_token
1267
- response = get_app_internal @droplets_uri, appname, auth_hdr
1268
- error "Application #{appname} does not exist, use push first." if response.status != 200
1269
-
1270
- appinfo = JSON.parse(response.content)
1271
-
1272
- appinfo['uris'] << url
1273
-
1274
- #display JSON.pretty_generate(appinfo)
1275
- response = update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
1276
- handle_response(response)
1277
- display "Map completed."
1278
- rescue => e
1279
- error "Problem executing command, #{e}"
1280
- end
1281
-
1282
- def unmap(appname=nil, url=nil)
1283
- error "Application name and url required, vmc unmap <appname> <url>." unless appname && url
1284
-
1285
- check_for_token
1286
- response = get_app_internal @droplets_uri, appname, auth_hdr
1287
- handle_response(response)
1288
-
1289
- url = url.gsub(/^http(s*):\/\//i, '')
1290
- appinfo = JSON.parse(response.content)
1291
-
1292
- if appinfo['uris'].delete(url) == nil
1293
- error "Error: You can only unmap a previously registered URL."
1294
- end
1295
-
1296
- #display JSON.pretty_generate(appinfo)
1297
- update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
1298
-
1299
- display "Unmap completed."
1300
- rescue => e
1301
- error "Problem executing command, #{e}"
1302
- end
1303
-
1304
- def files(appname=nil, path=nil, instance=nil)
1305
- path ||= '/'
1306
- error "Application name required, vmc files <appname> <pathinfo> [--instance <instance>]." unless appname
1307
- check_for_token
1308
- response = get_app_internal @droplets_uri, appname, auth_hdr
1309
- if response.status != 200
1310
- display"Application #{appname} does not exist, use push first."
1311
- return
1312
- end
1313
- instance ||= '0'
1314
-
1315
- begin
1316
- map = File.open(instance_file) {|f| JSON.parse(f.read)}
1317
- instance = map[instance] if map[instance]
1318
- rescue
1319
- end
1320
-
1321
- response = get_app_files_internal @droplets_uri, appname, instance, path, auth_hdr
1322
- if response.status != 200 && response.status == 400
1323
- error "Information not available, either pathinfo is incorrect or instance index is out of bounds."
1324
- end
1325
- display response.content
1326
- rescue => e
1327
- error "Problem executing command, #{e}"
1328
- end
1329
-
1330
- def log_file_paths
1331
- %w[logs/stderr.log logs/stdout.log logs/startup.log]
1332
- end
1333
-
1334
- def grab_logs(appname, instance)
1335
- display "No information available" and return unless appname
1336
- instance ||= '0'
1337
- log_file_paths.each do |path|
1338
- response = get_app_files_internal @droplets_uri, appname, instance, path, auth_hdr
1339
- unless response.status != 200 || response.content.empty?
1340
- display "==== #{path} ====\n\n"
1341
- display response.content
1342
- display ''
1343
- end
1344
- end
1345
- end
1346
-
1347
- def grab_crash_logs(appname, instance, was_staged=false)
1348
- display "No information available" and return unless appname
1349
-
1350
- # stage crash info
1351
- crashes(appname, false) unless was_staged
1352
-
1353
- instance ||= '0'
1354
- begin
1355
- map = File.open(instance_file) {|f| JSON.parse(f.read)}
1356
- instance = map[instance] if map[instance]
1357
- rescue
1358
- end
1359
-
1360
- ['/logs/err.log', 'logs/stderr.log', 'logs/stdout.log', 'logs/startup.log'].each do |path|
1361
- response = get_app_files_internal @droplets_uri, appname, instance, path, auth_hdr
1362
- unless response.status != 200 || response.content.empty?
1363
- display "==== #{path} ====\n\n"
1364
- display response.content
1365
- display ''
1366
- end
1367
- end
1368
- end
1369
-
1370
- def grab_startup_tail(appname, since = 0)
1371
- new_lines = 0
1372
- path = "logs/startup.log"
1373
- response = get_app_files_internal @droplets_uri, appname, '0', path, auth_hdr
1374
- unless response.status != 200 || response.content.empty?
1375
- display "\n==== displaying startup log ====\n\n" if since == 0
1376
- response_lines = response.content.split("\n")
1377
- lines = response_lines.size
1378
- tail = response_lines[since, lines] || []
1379
- new_lines = tail.size
1380
- display tail.join("\n") if new_lines > 0
1381
- end
1382
- since + new_lines
1383
- end
1384
-
1385
- def app_started_properly(appname, error_on_health)
1386
- check_for_token
1387
- response = get_app_internal @droplets_uri, appname, auth_hdr
1388
- if response.status != 200
1389
- error "\nApplication '#{appname}'s state is undetermined, not enough information available at this time."
1390
- return
1391
- end
1392
-
1393
- droplet = JSON.parse(response.content)
1394
-
1395
- case health(droplet)
1396
- when 'N/A'
1397
- # Health manager not running.
1398
- error "\nApplication '#{appname}'s state is undetermined, not enough information available at this time." if error_on_health
1399
- return false
1400
- when 'RUNNING'
1401
- return true
1402
- else
1403
- return false
1404
- end
1405
- end
1406
-
1407
- # Get the current logged in user
1408
- def user
1409
- info_json = JSON.parse(@check.content)
1410
- username = info_json['user'] || 'N/A'
1411
- display "[#{username}]"
1412
- end
1413
-
1414
- def display_services_directory_banner
1415
- display "#{'Service'.ljust(10)} #{'Ver'.ljust(5)} #{'Description'}"
1416
- display "#{'-------'.ljust(10)} #{'---'.ljust(5)} #{'-----------'}"
1417
- end
1418
-
1419
- # Get stats for application
1420
- def stats(appname=nil)
1421
- error "Application name required, vmc stats <appname>." unless appname
1422
- check_for_token
1423
- response = get_app_internal @droplets_uri, appname, auth_hdr
1424
- if response.status != 200
1425
- display"Application #{appname} does not exist, use push first."
1426
- return
1427
- end
1428
- response = get_app_stats_internal @droplets_uri, appname, auth_hdr
1429
- if response.status != 200 && response.status == 400
1430
- error "Information not available, is instance index out of bounds?"
1431
- end
1432
-
1433
- display " #{'Instance '.ljust(10)} #{'CPU (Cores)'.ljust(15)} #{'Memory (limit)'.ljust(15)} #{'Disk (limit)'.ljust(15)} #{'Uptime '.ljust(5)}"
1434
- display " #{'---------'.ljust(10)} #{'-----------'.ljust(15)} #{'--------------'.ljust(15)} #{'------------'.ljust(15)} #{'------ '.ljust(5)}"
1435
-
1436
- stats = JSON.parse(response.content).to_a
1437
-
1438
- stats.each {|entry| entry[0] = entry[0].to_i}
1439
- stats = stats.sort {|a,b| a[0] - b[0]}
1440
- stats.each do |entry|
1441
- index, index_entry = entry
1442
- stat = index_entry['stats']
1443
- next unless stat
1444
- hp = "#{stat['host']}:#{stat['port']}"
1445
- uptime = uptime_string(stat['uptime'])
1446
- usage = stat['usage']
1447
- if usage
1448
- cpu = usage['cpu']
1449
- mem = (usage['mem'] * 1024) # mem comes in K's
1450
- disk = usage['disk']
1451
- end
1452
-
1453
- mem_quota = stat['mem_quota']
1454
- disk_quota = stat['disk_quota']
1455
-
1456
- mem = "#{pretty_size(mem)} (#{pretty_size(mem_quota, 0)})"
1457
- disk = "#{pretty_size(disk)} (#{pretty_size(disk_quota, 0)})"
1458
- cpu = cpu ? cpu.to_s : 'NA'
1459
- cpu = "#{cpu}% (#{stat['cores']})"
1460
-
1461
- display " #{index.to_s.ljust(10)} #{cpu.ljust(15)} #{mem.ljust(15)} #{disk.ljust(15)} #{uptime.ljust(5)}"
1462
-
1463
- end
1464
- rescue => e
1465
- error "Problem executing command, #{e}"
1466
- end
1467
-
1468
-
1469
- ##################################################################
1470
- # Non vmc Commands
1471
- ##################################################################
1472
-
1473
- def handle_response(response)
1474
- return unless response
1475
- return if (response and response.status < 400)
1476
- error "Error: Unknown error in response from server." unless response.content
1477
- begin
1478
- error_json = JSON.parse(response.content)
1479
- error "Error: #{error_json['description']}"
1480
- rescue
1481
- response.content ||= "System error has occurred."
1482
- error "Error: #{response.content}"
1483
- end
1484
- end
1485
-
1486
- def framework_needs_db?(framework)
1487
- return true if (framework == 'rails/1.0')
1488
- return true if (framework == 'grails/1.0')
1489
- return true if (framework == 'spring_web/1.0')
1490
- return true if (framework == "asp_web/1.0")
1491
- return false
1492
- end
1493
-
1494
- def uptime_string(delta)
1495
- num_seconds = delta.to_i
1496
- days = num_seconds / (60 * 60 * 24);
1497
- num_seconds -= days * (60 * 60 * 24);
1498
- hours = num_seconds / (60 * 60);
1499
- num_seconds -= hours * (60 * 60);
1500
- minutes = num_seconds / 60;
1501
- num_seconds -= minutes * 60;
1502
- "#{days}d:#{hours}h:#{minutes}m:#{num_seconds}s"
1503
- end
1504
-
1505
- def pretty_size(size, prec=1)
1506
- return 'NA' unless size
1507
- return "#{size}B" if size < 1024
1508
- return sprintf("%.#{prec}fK", size/1024.0) if size < (1024*1024)
1509
- return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)
1510
- return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
1511
- end
1512
-
1513
- def display(msg, nl=true)
1514
- if nl
1515
- puts(msg)
1516
- else
1517
- print(msg)
1518
- STDOUT.flush
1519
- end
1520
- end
1521
-
1522
- def error(msg)
1523
- STDERR.puts(msg)
1524
- STDERR.puts('')
1525
- exit 1
1526
- end
1527
-
1528
- def check_app_limit
1529
- info_json = JSON.parse(@check.content)
1530
- usage = info_json['usage']
1531
- limits = info_json['limits']
1532
- return unless usage and limits and limits['apps']
1533
- if limits['apps'] == usage['apps']
1534
- display "Not enough capacity for operation."
1535
- tapps = limits['apps'] || 0
1536
- apps = usage['apps'] || 0
1537
- error "Current Usage: (#{apps} of #{tapps} total apps already in use)"
1538
- end
1539
- end
1540
-
1541
- def check_has_capacity_for(mem_wanted)
1542
- info_json = JSON.parse(@check.content)
1543
- usage = info_json['usage']
1544
- limits = info_json['limits']
1545
- return unless usage and limits
1546
- available_for_use = limits['memory'].to_i - usage['memory'].to_i
1547
- if mem_wanted > available_for_use
1548
- info_json = JSON.parse(@check.content)
1549
- tmem = pretty_size(limits['memory']*1024*1024)
1550
- mem = pretty_size(usage['memory']*1024*1024)
1551
- display "Not enough capacity for operation."
1552
- available = pretty_size(available_for_use * 1024 * 1024)
1553
- error "Current Usage: (#{mem} of #{tmem} total, #{available} available for use)"
1554
- end
1555
- end
1556
-
1557
- def mem_choices
1558
- default = ['64M', '128M', '256M', '512M', '1G', '2G']
1559
- info_json = JSON.parse(@check.content)
1560
-
1561
- return default unless info_json
1562
- return default unless (usage = info_json['usage'] and limits = info_json['limits'])
1563
-
1564
- available_for_use = limits['memory'].to_i - usage['memory'].to_i
1565
- check_has_capacity_for(64) if available_for_use < 64
1566
- return ['64M'] if available_for_use < 128
1567
- return ['64M', '128M'] if available_for_use < 256
1568
- return ['64M', '128M', '256M'] if available_for_use < 512
1569
- return ['64M', '128M', '256M', '512M'] if available_for_use < 1024
1570
- return ['64M', '128M', '256M', '512M', '1G'] if available_for_use < 2048
1571
- return ['64M', '128M', '256M', '512M', '1G', '2G']
1572
- end
1573
-
1574
- def mem_choice_to_quota(mem_choice)
1575
- (mem_choice =~ /(\d+)M/i) ? mem_quota = $1.to_i : mem_quota = mem_choice.to_i * 1024
1576
- mem_quota
1577
- end
1578
-
1579
- def mem_quota_to_choice(mem)
1580
- if mem < 1024
1581
- mem_choice = "#{mem}M"
1582
- else
1583
- mem_choice = "#{(mem/1024).to_i}G"
1584
- end
1585
- mem_choice
1586
- end
1587
-
1588
- end
1589
-
1590
- end
3
+ require 'vmc/client'