vmc 0.4.0.beta.8 → 0.4.0.beta.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/{vmc-ng/LICENSE → LICENSE} +0 -0
  2. data/{vmc-ng/Rakefile → Rakefile} +0 -0
  3. data/bin/vmc +10 -11
  4. data/{vmc-ng/lib → lib}/vmc/cli/app.rb +106 -58
  5. data/{vmc-ng/lib → lib}/vmc/cli/better_help.rb +0 -0
  6. data/{vmc-ng/lib → lib}/vmc/cli/command.rb +14 -0
  7. data/{vmc-ng/lib → lib}/vmc/cli/dots.rb +0 -0
  8. data/{vmc-ng/lib → lib}/vmc/cli/service.rb +0 -0
  9. data/{vmc-ng/lib → lib}/vmc/cli/user.rb +0 -0
  10. data/{vmc-ng/lib → lib}/vmc/cli.rb +88 -2
  11. data/{vmc-ng/lib → lib}/vmc/constants.rb +0 -0
  12. data/{vmc-ng/lib → lib}/vmc/detect.rb +0 -0
  13. data/{vmc-ng/lib → lib}/vmc/errors.rb +0 -0
  14. data/{vmc-ng/lib → lib}/vmc/plugin.rb +0 -0
  15. data/lib/vmc/version.rb +3 -0
  16. data/{vmc-ng/lib → lib}/vmc.rb +0 -0
  17. metadata +41 -273
  18. data/vmc/LICENSE +0 -24
  19. data/vmc/README.md +0 -102
  20. data/vmc/Rakefile +0 -101
  21. data/vmc/bin/vmc +0 -6
  22. data/vmc/caldecott_helper/Gemfile +0 -10
  23. data/vmc/caldecott_helper/Gemfile.lock +0 -48
  24. data/vmc/caldecott_helper/server.rb +0 -43
  25. data/vmc/config/clients.yml +0 -17
  26. data/vmc/config/micro/offline.conf +0 -2
  27. data/vmc/config/micro/paths.yml +0 -22
  28. data/vmc/config/micro/refresh_ip.rb +0 -20
  29. data/vmc/lib/cli/commands/admin.rb +0 -80
  30. data/vmc/lib/cli/commands/apps.rb +0 -1126
  31. data/vmc/lib/cli/commands/base.rb +0 -227
  32. data/vmc/lib/cli/commands/manifest.rb +0 -56
  33. data/vmc/lib/cli/commands/micro.rb +0 -115
  34. data/vmc/lib/cli/commands/misc.rb +0 -129
  35. data/vmc/lib/cli/commands/services.rb +0 -180
  36. data/vmc/lib/cli/commands/user.rb +0 -65
  37. data/vmc/lib/cli/config.rb +0 -173
  38. data/vmc/lib/cli/console_helper.rb +0 -160
  39. data/vmc/lib/cli/core_ext.rb +0 -122
  40. data/vmc/lib/cli/errors.rb +0 -19
  41. data/vmc/lib/cli/frameworks.rb +0 -265
  42. data/vmc/lib/cli/manifest_helper.rb +0 -302
  43. data/vmc/lib/cli/runner.rb +0 -531
  44. data/vmc/lib/cli/services_helper.rb +0 -84
  45. data/vmc/lib/cli/tunnel_helper.rb +0 -332
  46. data/vmc/lib/cli/usage.rb +0 -115
  47. data/vmc/lib/cli/version.rb +0 -7
  48. data/vmc/lib/cli/zip_util.rb +0 -77
  49. data/vmc/lib/cli.rb +0 -47
  50. data/vmc/lib/vmc/client.rb +0 -471
  51. data/vmc/lib/vmc/const.rb +0 -22
  52. data/vmc/lib/vmc/micro/switcher/base.rb +0 -97
  53. data/vmc/lib/vmc/micro/switcher/darwin.rb +0 -19
  54. data/vmc/lib/vmc/micro/switcher/dummy.rb +0 -15
  55. data/vmc/lib/vmc/micro/switcher/linux.rb +0 -16
  56. data/vmc/lib/vmc/micro/switcher/windows.rb +0 -31
  57. data/vmc/lib/vmc/micro/vmrun.rb +0 -158
  58. data/vmc/lib/vmc/micro.rb +0 -56
  59. data/vmc/lib/vmc.rb +0 -3
  60. data/vmc-ng/bin/vmc +0 -14
  61. data/vmc-ng/lib/vmc/version.rb +0 -3
@@ -1,1126 +0,0 @@
1
- require 'digest/sha1'
2
- require 'fileutils'
3
- require 'pathname'
4
- require 'tempfile'
5
- require 'tmpdir'
6
- require 'set'
7
- require "uuidtools"
8
- require 'socket'
9
-
10
- module VMC::Cli::Command
11
-
12
- class Apps < Base
13
- include VMC::Cli::ServicesHelper
14
- include VMC::Cli::ManifestHelper
15
- include VMC::Cli::TunnelHelper
16
- include VMC::Cli::ConsoleHelper
17
-
18
- def list
19
- apps = client.apps
20
- apps.sort! {|a, b| a[:name] <=> b[:name] }
21
- return display JSON.pretty_generate(apps || []) if @options[:json]
22
-
23
- display "\n"
24
- return display "No Applications" if apps.nil? || apps.empty?
25
-
26
- apps_table = table do |t|
27
- t.headings = 'Application', '# ', 'Health', 'URLS', 'Services'
28
- apps.each do |app|
29
- t << [app[:name], app[:instances], health(app), app[:uris].join(', '), app[:services].join(', ')]
30
- end
31
- end
32
- display apps_table
33
- end
34
-
35
- alias :apps :list
36
-
37
- SLEEP_TIME = 1
38
- LINE_LENGTH = 80
39
-
40
- # Numerators are in secs
41
- TICKER_TICKS = 25/SLEEP_TIME
42
- HEALTH_TICKS = 5/SLEEP_TIME
43
- TAIL_TICKS = 45/SLEEP_TIME
44
- GIVEUP_TICKS = 120/SLEEP_TIME
45
-
46
- def info(what, default=nil)
47
- @options[what] || (@app_info && @app_info[what.to_s]) || default
48
- end
49
-
50
- def console(appname, interactive=true)
51
- unless defined? Caldecott
52
- display "To use `vmc rails-console', you must first install Caldecott:"
53
- display ""
54
- display "\tgem install caldecott"
55
- display ""
56
- display "Note that you'll need a C compiler. If you're on OS X, Xcode"
57
- display "will provide one. If you're on Windows, try DevKit."
58
- display ""
59
- display "This manual step will be removed in the future."
60
- display ""
61
- err "Caldecott is not installed."
62
- end
63
-
64
- #Make sure there is a console we can connect to first
65
- conn_info = console_connection_info appname
66
-
67
- port = pick_tunnel_port(@options[:port] || 20000)
68
-
69
- raise VMC::Client::AuthError unless client.logged_in?
70
-
71
- if not tunnel_pushed?
72
- display "Deploying tunnel application '#{tunnel_appname}'."
73
- auth = UUIDTools::UUID.random_create.to_s
74
- push_caldecott(auth)
75
- start_caldecott
76
- else
77
- auth = tunnel_auth
78
- end
79
-
80
- if not tunnel_healthy?(auth)
81
- display "Redeploying tunnel application '#{tunnel_appname}'."
82
- # We don't expect caldecott not to be running, so take the
83
- # most aggressive restart method.. delete/re-push
84
- client.delete_app(tunnel_appname)
85
- invalidate_tunnel_app_info
86
- push_caldecott(auth)
87
- start_caldecott
88
- end
89
-
90
- start_tunnel(port, conn_info, auth)
91
- wait_for_tunnel_start(port)
92
- start_local_console(port, appname) if interactive
93
- port
94
- end
95
-
96
- def start(appname=nil, push=false)
97
- if appname
98
- do_start(appname, push)
99
- else
100
- each_app do |name|
101
- do_start(name, push)
102
- end
103
- end
104
- end
105
-
106
- def stop(appname=nil)
107
- if appname
108
- do_stop(appname)
109
- else
110
- reversed = []
111
- each_app do |name|
112
- reversed.unshift name
113
- end
114
-
115
- reversed.each do |name|
116
- do_stop(name)
117
- end
118
- end
119
- end
120
-
121
- def restart(appname=nil)
122
- stop(appname)
123
- start(appname)
124
- end
125
-
126
- def mem(appname, memsize=nil)
127
- app = client.app_info(appname)
128
- mem = current_mem = mem_quota_to_choice(app[:resources][:memory])
129
- memsize = normalize_mem(memsize) if memsize
130
-
131
- memsize ||= ask(
132
- "Update Memory Reservation?",
133
- :default => current_mem,
134
- :choices => mem_choices
135
- )
136
-
137
- mem = mem_choice_to_quota(mem)
138
- memsize = mem_choice_to_quota(memsize)
139
- current_mem = mem_choice_to_quota(current_mem)
140
-
141
- display "Updating Memory Reservation to #{mem_quota_to_choice(memsize)}: ", false
142
-
143
- # check memsize here for capacity
144
- check_has_capacity_for((memsize - mem) * app[:instances])
145
-
146
- mem = memsize
147
-
148
- if (mem != current_mem)
149
- app[:resources][:memory] = mem
150
- client.update_app(appname, app)
151
- display 'OK'.green
152
- restart appname if app[:state] == 'STARTED'
153
- else
154
- display 'OK'.green
155
- end
156
- end
157
-
158
- def map(appname, url)
159
- app = client.app_info(appname)
160
- uris = app[:uris] || []
161
- uris << url
162
- app[:uris] = uris
163
- client.update_app(appname, app)
164
- display "Successfully mapped url".green
165
- end
166
-
167
- def unmap(appname, url)
168
- app = client.app_info(appname)
169
- uris = app[:uris] || []
170
- url = url.gsub(/^http(s*):\/\//i, '')
171
- deleted = uris.delete(url)
172
- err "Invalid url" unless deleted
173
- app[:uris] = uris
174
- client.update_app(appname, app)
175
- display "Successfully unmapped url".green
176
- end
177
-
178
- def delete(appname=nil)
179
- force = @options[:force]
180
- if @options[:all]
181
- if no_prompt || force || ask("Delete ALL applications?", :default => false)
182
- apps = client.apps
183
- apps.each { |app| delete_app(app[:name], force) }
184
- end
185
- else
186
- err 'No valid appname given' unless appname
187
- delete_app(appname, force)
188
- end
189
- end
190
-
191
- def files(appname, path='/')
192
- return all_files(appname, path) if @options[:all] && !@options[:instance]
193
- instance = @options[:instance] || '0'
194
- content = client.app_files(appname, path, instance)
195
- display content
196
- rescue VMC::Client::NotFound, VMC::Client::TargetError
197
- err 'No such file or directory'
198
- end
199
-
200
- def logs(appname)
201
- # Check if we have an app before progressing further
202
- client.app_info(appname)
203
- return grab_all_logs(appname) if @options[:all] && !@options[:instance]
204
- instance = @options[:instance] || '0'
205
- grab_logs(appname, instance)
206
- end
207
-
208
- def crashes(appname, print_results=true, since=0)
209
- crashed = client.app_crashes(appname)[:crashes]
210
- crashed.delete_if { |c| c[:since] < since }
211
- instance_map = {}
212
-
213
- # return display JSON.pretty_generate(apps) if @options[:json]
214
-
215
-
216
- counter = 0
217
- crashed = crashed.to_a.sort { |a,b| a[:since] - b[:since] }
218
- crashed_table = table do |t|
219
- t.headings = 'Name', 'Instance ID', 'Crashed Time'
220
- crashed.each do |crash|
221
- name = "#{appname}-#{counter += 1}"
222
- instance_map[name] = crash[:instance]
223
- t << [name, crash[:instance], Time.at(crash[:since]).strftime("%m/%d/%Y %I:%M%p")]
224
- end
225
- end
226
-
227
- VMC::Cli::Config.store_instances(instance_map)
228
-
229
- if @options[:json]
230
- return display JSON.pretty_generate(crashed)
231
- elsif print_results
232
- display "\n"
233
- if crashed.empty?
234
- display "No crashed instances for [#{appname}]" if print_results
235
- else
236
- display crashed_table if print_results
237
- end
238
- end
239
-
240
- crashed
241
- end
242
-
243
- def crashlogs(appname)
244
- instance = @options[:instance] || '0'
245
- grab_crash_logs(appname, instance)
246
- end
247
-
248
- def instances(appname, num=nil)
249
- if num
250
- change_instances(appname, num)
251
- else
252
- get_instances(appname)
253
- end
254
- end
255
-
256
- def stats(appname=nil)
257
- if appname
258
- display "\n", false
259
- do_stats(appname)
260
- else
261
- each_app do |n|
262
- display "\n#{n}:"
263
- do_stats(n)
264
- end
265
- end
266
- end
267
-
268
- def update(appname=nil)
269
- if appname
270
- app = client.app_info(appname)
271
- if @options[:canary]
272
- display "[--canary] is deprecated and will be removed in a future version".yellow
273
- end
274
- upload_app_bits(appname, @path)
275
- restart appname if app[:state] == 'STARTED'
276
- else
277
- each_app do |name|
278
- display "Updating application '#{name}'..."
279
-
280
- app = client.app_info(name)
281
- upload_app_bits(name, @application)
282
- restart name if app[:state] == 'STARTED'
283
- end
284
- end
285
- end
286
-
287
- def push(appname=nil)
288
- unless no_prompt || @options[:path]
289
- proceed = ask(
290
- 'Would you like to deploy from the current directory?',
291
- :default => true
292
- )
293
-
294
- unless proceed
295
- @path = ask('Deployment path')
296
- end
297
- end
298
-
299
- pushed = false
300
- each_app(false) do |name|
301
- display "Pushing application '#{name}'..." if name
302
- do_push(name)
303
- pushed = true
304
- end
305
-
306
- unless pushed
307
- @application = @path
308
- do_push(appname)
309
- end
310
- end
311
-
312
- def environment(appname)
313
- app = client.app_info(appname)
314
- env = app[:env] || []
315
- return display JSON.pretty_generate(env) if @options[:json]
316
- return display "No Environment Variables" if env.empty?
317
- etable = table do |t|
318
- t.headings = 'Variable', 'Value'
319
- env.each do |e|
320
- k,v = e.split('=', 2)
321
- t << [k, v]
322
- end
323
- end
324
- display "\n"
325
- display etable
326
- end
327
-
328
- def environment_add(appname, k, v=nil)
329
- app = client.app_info(appname)
330
- env = app[:env] || []
331
- k,v = k.split('=', 2) unless v
332
- env << "#{k}=#{v}"
333
- display "Adding Environment Variable [#{k}=#{v}]: ", false
334
- app[:env] = env
335
- client.update_app(appname, app)
336
- display 'OK'.green
337
- restart appname if app[:state] == 'STARTED'
338
- end
339
-
340
- def environment_del(appname, variable)
341
- app = client.app_info(appname)
342
- env = app[:env] || []
343
- deleted_env = nil
344
- env.each do |e|
345
- k,v = e.split('=')
346
- if (k == variable)
347
- deleted_env = e
348
- break;
349
- end
350
- end
351
- display "Deleting Environment Variable [#{variable}]: ", false
352
- if deleted_env
353
- env.delete(deleted_env)
354
- app[:env] = env
355
- client.update_app(appname, app)
356
- display 'OK'.green
357
- restart appname if app[:state] == 'STARTED'
358
- else
359
- display 'OK'.green
360
- end
361
- end
362
-
363
- private
364
-
365
- def app_exists?(appname)
366
- app_info = client.app_info(appname)
367
- app_info != nil
368
- rescue VMC::Client::NotFound
369
- false
370
- end
371
-
372
- def check_deploy_directory(path)
373
- err 'Deployment path does not exist' unless File.exists? path
374
- return if File.expand_path(Dir.tmpdir) != File.expand_path(path)
375
- err "Can't deploy applications from staging directory: [#{Dir.tmpdir}]"
376
- end
377
-
378
- def check_unreachable_links(path)
379
- files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
380
-
381
- pwd = Pathname.pwd
382
-
383
- abspath = File.expand_path(path)
384
- unreachable = []
385
- files.each do |f|
386
- file = Pathname.new(f)
387
- if file.symlink? && !file.realpath.to_s.start_with?(abspath)
388
- unreachable << file.relative_path_from(pwd)
389
- end
390
- end
391
-
392
- unless unreachable.empty?
393
- root = Pathname.new(path).relative_path_from(pwd)
394
- err "Can't deploy application containing links '#{unreachable}' that reach outside its root '#{root}'"
395
- end
396
- end
397
-
398
- def find_sockets(path)
399
- files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
400
- files && files.select { |f| File.socket? f }
401
- end
402
-
403
- def upload_app_bits(appname, path)
404
- display 'Uploading Application:'
405
-
406
- upload_file, file = "#{Dir.tmpdir}/#{appname}.zip", nil
407
- FileUtils.rm_f(upload_file)
408
-
409
- explode_dir = "#{Dir.tmpdir}/.vmc_#{appname}_files"
410
- FileUtils.rm_rf(explode_dir) # Make sure we didn't have anything left over..
411
-
412
- if path =~ /\.(war|zip)$/
413
- #single file that needs unpacking
414
- VMC::Cli::ZipUtil.unpack(path, explode_dir)
415
- elsif !File.directory? path
416
- #single file that doesn't need unpacking
417
- FileUtils.mkdir(explode_dir)
418
- FileUtils.cp(path,explode_dir)
419
- else
420
- Dir.chdir(path) do
421
- # Stage the app appropriately and do the appropriate fingerprinting, etc.
422
- if war_file = Dir.glob('*.war').first
423
- VMC::Cli::ZipUtil.unpack(war_file, explode_dir)
424
- elsif zip_file = Dir.glob('*.zip').first
425
- VMC::Cli::ZipUtil.unpack(zip_file, explode_dir)
426
- else
427
- check_unreachable_links(path)
428
- FileUtils.mkdir(explode_dir)
429
-
430
- files = Dir.glob('{*,.[^\.]*}')
431
-
432
- # Do not process .git files
433
- files.delete('.git') if files
434
-
435
- FileUtils.cp_r(files, explode_dir)
436
-
437
- find_sockets(explode_dir).each do |s|
438
- File.delete s
439
- end
440
- end
441
- end
442
- end
443
-
444
- # Send the resource list to the cloudcontroller, the response will tell us what it already has..
445
- unless @options[:noresources]
446
- display ' Checking for available resources: ', false
447
- fingerprints = []
448
- total_size = 0
449
- resource_files = Dir.glob("#{explode_dir}/**/*", File::FNM_DOTMATCH)
450
- resource_files.each do |filename|
451
- next if (File.directory?(filename) || !File.exists?(filename))
452
- fingerprints << {
453
- :size => File.size(filename),
454
- :sha1 => Digest::SHA1.file(filename).hexdigest,
455
- :fn => filename
456
- }
457
- total_size += File.size(filename)
458
- end
459
-
460
- # Check to see if the resource check is worth the round trip
461
- if (total_size > (64*1024)) # 64k for now
462
- # Send resource fingerprints to the cloud controller
463
- appcloud_resources = client.check_resources(fingerprints)
464
- end
465
- display 'OK'.green
466
-
467
- if appcloud_resources
468
- display ' Processing resources: ', false
469
- # We can then delete what we do not need to send.
470
- appcloud_resources.each do |resource|
471
- FileUtils.rm_f resource[:fn]
472
- # adjust filenames sans the explode_dir prefix
473
- resource[:fn].sub!("#{explode_dir}/", '')
474
- end
475
- display 'OK'.green
476
- end
477
-
478
- end
479
-
480
- # If no resource needs to be sent, add an empty file to ensure we have
481
- # a multi-part request that is expected by nginx fronting the CC.
482
- if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
483
- Dir.chdir(explode_dir) do
484
- File.new(".__empty__", "w")
485
- end
486
- end
487
- # Perform Packing of the upload bits here.
488
- display ' Packing application: ', false
489
- VMC::Cli::ZipUtil.pack(explode_dir, upload_file)
490
- display 'OK'.green
491
-
492
- upload_size = File.size(upload_file);
493
- if upload_size > 1024*1024
494
- upload_size = (upload_size/(1024.0*1024.0)).round.to_s + 'M'
495
- elsif upload_size > 0
496
- upload_size = (upload_size/1024.0).round.to_s + 'K'
497
- else
498
- upload_size = '0K'
499
- end
500
-
501
- upload_str = " Uploading (#{upload_size}): "
502
- display upload_str, false
503
-
504
- FileWithPercentOutput.display_str = upload_str
505
- FileWithPercentOutput.upload_size = File.size(upload_file);
506
- file = FileWithPercentOutput.open(upload_file, 'rb')
507
-
508
- client.upload_app(appname, file, appcloud_resources)
509
- display 'OK'.green if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
510
-
511
- display 'Push Status: ', false
512
- display 'OK'.green
513
-
514
- ensure
515
- # Cleanup if we created an exploded directory.
516
- FileUtils.rm_f(upload_file) if upload_file
517
- FileUtils.rm_rf(explode_dir) if explode_dir
518
- end
519
-
520
- def check_app_limit
521
- usage = client_info[:usage]
522
- limits = client_info[:limits]
523
- return unless usage and limits and limits[:apps]
524
- if limits[:apps] == usage[:apps]
525
- display "Not enough capacity for operation.".red
526
- tapps = limits[:apps] || 0
527
- apps = usage[:apps] || 0
528
- err "Current Usage: (#{apps} of #{tapps} total apps already in use)"
529
- end
530
- end
531
-
532
- def check_has_capacity_for(mem_wanted)
533
- usage = client_info[:usage]
534
- limits = client_info[:limits]
535
- return unless usage and limits
536
- available_for_use = limits[:memory].to_i - usage[:memory].to_i
537
- if mem_wanted > available_for_use
538
- tmem = pretty_size(limits[:memory]*1024*1024)
539
- mem = pretty_size(usage[:memory]*1024*1024)
540
- display "Not enough capacity for operation.".yellow
541
- available = pretty_size(available_for_use * 1024 * 1024)
542
- err "Current Usage: (#{mem} of #{tmem} total, #{available} available for use)"
543
- end
544
- end
545
-
546
- def mem_choices
547
- default = ['64M', '128M', '256M', '512M', '1G', '2G']
548
-
549
- return default unless client_info
550
- return default unless (usage = client_info[:usage] and limits = client_info[:limits])
551
-
552
- available_for_use = limits[:memory].to_i - usage[:memory].to_i
553
- check_has_capacity_for(64) if available_for_use < 64
554
- return ['64M'] if available_for_use < 128
555
- return ['64M', '128M'] if available_for_use < 256
556
- return ['64M', '128M', '256M'] if available_for_use < 512
557
- return ['64M', '128M', '256M', '512M'] if available_for_use < 1024
558
- return ['64M', '128M', '256M', '512M', '1G'] if available_for_use < 2048
559
- return ['64M', '128M', '256M', '512M', '1G', '2G']
560
- end
561
-
562
- def normalize_mem(mem)
563
- return mem if /K|G|M/i =~ mem
564
- "#{mem}M"
565
- end
566
-
567
- def mem_choice_to_quota(mem_choice)
568
- (mem_choice =~ /(\d+)M/i) ? mem_quota = $1.to_i : mem_quota = mem_choice.to_i * 1024
569
- mem_quota
570
- end
571
-
572
- def mem_quota_to_choice(mem)
573
- if mem < 1024
574
- mem_choice = "#{mem}M"
575
- else
576
- mem_choice = "#{(mem/1024).to_i}G"
577
- end
578
- mem_choice
579
- end
580
-
581
- def get_instances(appname)
582
- instances_info_envelope = client.app_instances(appname)
583
- # Empty array is returned if there are no instances running.
584
- instances_info_envelope = {} if instances_info_envelope.is_a?(Array)
585
-
586
- instances_info = instances_info_envelope[:instances] || []
587
- instances_info = instances_info.sort {|a,b| a[:index] - b[:index]}
588
-
589
- return display JSON.pretty_generate(instances_info) if @options[:json]
590
-
591
- return display "No running instances for [#{appname}]".yellow if instances_info.empty?
592
-
593
- instances_table = table do |t|
594
- show_debug = instances_info.any? { |e| e[:debug_port] }
595
-
596
- headings = ['Index', 'State', 'Start Time']
597
- headings << 'Debug IP' if show_debug
598
- headings << 'Debug Port' if show_debug
599
-
600
- t.headings = headings
601
-
602
- instances_info.each do |entry|
603
- row = [entry[:index], entry[:state], Time.at(entry[:since]).strftime("%m/%d/%Y %I:%M%p")]
604
- row << entry[:debug_ip] if show_debug
605
- row << entry[:debug_port] if show_debug
606
- t << row
607
- end
608
- end
609
- display "\n"
610
- display instances_table
611
- end
612
-
613
- def change_instances(appname, instances)
614
- app = client.app_info(appname)
615
-
616
- match = instances.match(/([+-])?\d+/)
617
- err "Invalid number of instances '#{instances}'" unless match
618
-
619
- instances = instances.to_i
620
- current_instances = app[:instances]
621
- new_instances = match.captures[0] ? current_instances + instances : instances
622
- err "There must be at least 1 instance." if new_instances < 1
623
-
624
- if current_instances == new_instances
625
- display "Application [#{appname}] is already running #{new_instances} instance#{'s' if new_instances > 1}.".yellow
626
- return
627
- end
628
-
629
- up_or_down = new_instances > current_instances ? 'up' : 'down'
630
- display "Scaling Application instances #{up_or_down} to #{new_instances}: ", false
631
- app[:instances] = new_instances
632
- client.update_app(appname, app)
633
- display 'OK'.green
634
- end
635
-
636
- def health(d)
637
- return 'N/A' unless (d and d[:state])
638
- return 'STOPPED' if d[:state] == 'STOPPED'
639
-
640
- healthy_instances = d[:runningInstances]
641
- expected_instance = d[:instances]
642
- health = nil
643
-
644
- if d[:state] == "STARTED" && expected_instance > 0 && healthy_instances
645
- health = format("%.3f", healthy_instances.to_f / expected_instance).to_f
646
- end
647
-
648
- return 'RUNNING' if health && health == 1.0
649
- return "#{(health * 100).round}%" if health
650
- return 'N/A'
651
- end
652
-
653
- def app_started_properly(appname, error_on_health)
654
- app = client.app_info(appname)
655
- case health(app)
656
- when 'N/A'
657
- # Health manager not running.
658
- err "\nApplication '#{appname}'s state is undetermined, not enough information available." if error_on_health
659
- return false
660
- when 'RUNNING'
661
- return true
662
- else
663
- if app[:meta][:debug] == "suspend"
664
- display "\nApplication [#{appname}] has started in a mode that is waiting for you to trigger startup."
665
- return true
666
- else
667
- return false
668
- end
669
- end
670
- end
671
-
672
- def display_logfile(path, content, instance='0', banner=nil)
673
- banner ||= "====> #{path} <====\n\n"
674
-
675
- unless content.empty?
676
- display banner
677
- prefix = "[#{instance}: #{path}] -".bold if @options[:prefixlogs]
678
- unless prefix
679
- display content
680
- else
681
- lines = content.split("\n")
682
- lines.each { |line| display "#{prefix} #{line}"}
683
- end
684
- display ''
685
- end
686
- end
687
-
688
- def grab_all_logs(appname)
689
- instances_info_envelope = client.app_instances(appname)
690
- return if instances_info_envelope.is_a?(Array)
691
- instances_info = instances_info_envelope[:instances] || []
692
- instances_info.each do |entry|
693
- grab_logs(appname, entry[:index])
694
- end
695
- end
696
-
697
- def grab_logs(appname, instance)
698
- files_under(appname, instance, "/logs").each do |path|
699
- begin
700
- content = client.app_files(appname, path, instance)
701
- display_logfile(path, content, instance)
702
- rescue VMC::Client::NotFound, VMC::Client::TargetError
703
- end
704
- end
705
- end
706
-
707
- def files_under(appname, instance, path)
708
- client.app_files(appname, path, instance).split("\n").collect do |l|
709
- "#{path}/#{l.split[0]}"
710
- end
711
- rescue VMC::Client::NotFound, VMC::Client::TargetError
712
- []
713
- end
714
-
715
- def grab_crash_logs(appname, instance, was_staged=false)
716
- # stage crash info
717
- crashes(appname, false) unless was_staged
718
-
719
- instance ||= '0'
720
- map = VMC::Cli::Config.instances
721
- instance = map[instance] if map[instance]
722
-
723
- (files_under(appname, instance, "/logs") +
724
- files_under(appname, instance, "/app/logs") +
725
- files_under(appname, instance, "/app/log")).each do |path|
726
- content = client.app_files(appname, path, instance)
727
- display_logfile(path, content, instance)
728
- end
729
- end
730
-
731
- def grab_startup_tail(appname, since = 0)
732
- new_lines = 0
733
- path = "logs/startup.log"
734
- content = client.app_files(appname, path)
735
- if content && !content.empty?
736
- display "\n==== displaying startup log ====\n\n" if since == 0
737
- response_lines = content.split("\n")
738
- lines = response_lines.size
739
- tail = response_lines[since, lines] || []
740
- new_lines = tail.size
741
- display tail.join("\n") if new_lines > 0
742
- end
743
- since + new_lines
744
- rescue VMC::Client::NotFound, VMC::Client::TargetError
745
- 0
746
- end
747
-
748
- def provisioned_services_apps_hash
749
- apps = client.apps
750
- services_apps_hash = {}
751
- apps.each {|app|
752
- app[:services].each { |svc|
753
- svc_apps = services_apps_hash[svc]
754
- unless svc_apps
755
- svc_apps = Set.new
756
- services_apps_hash[svc] = svc_apps
757
- end
758
- svc_apps.add(app[:name])
759
- } unless app[:services] == nil
760
- }
761
- services_apps_hash
762
- end
763
-
764
- def delete_app(appname, force)
765
- app = client.app_info(appname)
766
- services_to_delete = []
767
- app_services = app[:services]
768
- services_apps_hash = provisioned_services_apps_hash
769
- app_services.each { |service|
770
- del_service = force && no_prompt
771
- unless no_prompt || force
772
- del_service = ask(
773
- "Provisioned service [#{service}] detected, would you like to delete it?",
774
- :default => false
775
- )
776
-
777
- if del_service
778
- apps_using_service = services_apps_hash[service].reject!{ |app| app == appname}
779
- if apps_using_service.size > 0
780
- del_service = ask(
781
- "Provisioned service [#{service}] is also used by #{apps_using_service.size == 1 ? "app" : "apps"} #{apps_using_service.entries}, are you sure you want to delete it?",
782
- :default => false
783
- )
784
- end
785
- end
786
- end
787
- services_to_delete << service if del_service
788
- }
789
-
790
- display "Deleting application [#{appname}]: ", false
791
- client.delete_app(appname)
792
- display 'OK'.green
793
-
794
- services_to_delete.each do |s|
795
- delete_service_banner(s)
796
- end
797
- end
798
-
799
- def do_start(appname, push=false)
800
- app = client.app_info(appname)
801
- return display "Application '#{appname}' could not be found".red if app.nil?
802
- return display "Application '#{appname}' already started".yellow if app[:state] == 'STARTED'
803
-
804
-
805
-
806
- if @options[:debug]
807
- runtimes = client.runtimes_info
808
- return display "Cannot get runtime information." unless runtimes
809
-
810
- runtime = runtimes[app[:staging][:stack].to_sym]
811
- return display "Unknown runtime." unless runtime
812
-
813
- unless runtime[:debug_modes] and runtime[:debug_modes].include? @options[:debug]
814
- modes = runtime[:debug_modes] || []
815
-
816
- display "\nApplication '#{appname}' cannot start in '#{@options[:debug]}' mode"
817
-
818
- if push
819
- display "Try 'vmc start' with one of the following modes: #{modes.inspect}"
820
- else
821
- display "Available modes: #{modes.inspect}"
822
- end
823
-
824
- return
825
- end
826
- end
827
-
828
- banner = "Staging Application '#{appname}': "
829
- display banner, false
830
-
831
- t = Thread.new do
832
- count = 0
833
- while count < TAIL_TICKS do
834
- display '.', false
835
- sleep SLEEP_TIME
836
- count += 1
837
- end
838
- end
839
-
840
- app[:state] = 'STARTED'
841
- app[:debug] = @options[:debug]
842
- app[:console] = VMC::Cli::Framework.lookup_by_framework(app[:staging][:model]).console
843
- client.update_app(appname, app)
844
-
845
- Thread.kill(t)
846
- clear(LINE_LENGTH)
847
- display "#{banner}#{'OK'.green}"
848
-
849
- banner = "Starting Application '#{appname}': "
850
- display banner, false
851
-
852
- count = log_lines_displayed = 0
853
- failed = false
854
- start_time = Time.now.to_i
855
-
856
- loop do
857
- display '.', false unless count > TICKER_TICKS
858
- sleep SLEEP_TIME
859
-
860
- break if app_started_properly(appname, count > HEALTH_TICKS)
861
-
862
- if !crashes(appname, false, start_time).empty?
863
- # Check for the existance of crashes
864
- display "\nError: Application [#{appname}] failed to start, logs information below.\n".red
865
- grab_crash_logs(appname, '0', true)
866
- if push and !no_prompt
867
- display "\n"
868
- delete_app(appname, false) if ask "Delete the application?", :default => true
869
- end
870
- failed = true
871
- break
872
- elsif count > TAIL_TICKS
873
- log_lines_displayed = grab_startup_tail(appname, log_lines_displayed)
874
- end
875
-
876
- count += 1
877
- if count > GIVEUP_TICKS # 2 minutes
878
- display "\nApplication is taking too long to start, check your logs".yellow
879
- break
880
- end
881
- end
882
- exit(false) if failed
883
- clear(LINE_LENGTH)
884
- display "#{banner}#{'OK'.green}"
885
- end
886
-
887
- def do_stop(appname)
888
- app = client.app_info(appname)
889
- return display "Application '#{appname}' already stopped".yellow if app[:state] == 'STOPPED'
890
- display "Stopping Application '#{appname}': ", false
891
- app[:state] = 'STOPPED'
892
- client.update_app(appname, app)
893
- display 'OK'.green
894
- end
895
-
896
- def do_push(appname=nil)
897
- unless @app_info || no_prompt
898
- @manifest = { "applications" => { @path => { "name" => appname } } }
899
-
900
- interact
901
-
902
- if ask("Would you like to save this configuration?", :default => false)
903
- save_manifest
904
- end
905
-
906
- resolve_manifest(@manifest)
907
-
908
- @app_info = @manifest["applications"][@path]
909
- end
910
-
911
- instances = info(:instances, 1)
912
- exec = info(:exec, 'thin start')
913
-
914
- ignore_framework = @options[:noframework]
915
- no_start = @options[:nostart]
916
-
917
- appname ||= info(:name)
918
- url = info(:url) || info(:urls)
919
- mem, memswitch = nil, info(:mem)
920
- memswitch = normalize_mem(memswitch) if memswitch
921
- command = info(:command)
922
- runtime = info(:runtime)
923
-
924
- # Check app existing upfront if we have appname
925
- app_checked = false
926
- if appname
927
- err "Application '#{appname}' already exists, use update" if app_exists?(appname)
928
- app_checked = true
929
- else
930
- raise VMC::Client::AuthError unless client.logged_in?
931
- end
932
-
933
- # check if we have hit our app limit
934
- check_app_limit
935
- # check memsize here for capacity
936
- if memswitch && !no_start
937
- check_has_capacity_for(mem_choice_to_quota(memswitch) * instances)
938
- end
939
-
940
- appname ||= ask("Application Name") unless no_prompt
941
- err "Application Name required." if appname.nil? || appname.empty?
942
-
943
- check_deploy_directory(@application)
944
-
945
- if !app_checked and app_exists?(appname)
946
- err "Application '#{appname}' already exists, use update or delete."
947
- end
948
-
949
- if ignore_framework
950
- framework = VMC::Cli::Framework.new
951
- elsif f = info(:framework)
952
- info = Hash[f["info"].collect { |k, v| [k.to_sym, v] }]
953
-
954
- framework = VMC::Cli::Framework.create(f["name"], info)
955
- exec = framework.exec if framework && framework.exec
956
- else
957
- framework = detect_framework(prompt_ok)
958
- end
959
-
960
- err "Application Type undetermined for path '#{@application}'" unless framework
961
-
962
- if not runtime
963
- default_runtime = framework.default_runtime @application
964
- runtime = detect_runtime(default_runtime, !no_prompt) if framework.prompt_for_runtime?
965
- end
966
- command = ask("Start Command") if !command && framework.require_start_command?
967
-
968
- default_url = "None"
969
- default_url = "#{appname}.#{VMC::Cli::Config.suggest_url}" if framework.require_url?
970
-
971
-
972
- unless no_prompt || url || !framework.require_url?
973
- url = ask(
974
- "Application Deployed URL",
975
- :default => default_url
976
- )
977
-
978
- # common error case is for prompted users to answer y or Y or yes or
979
- # YES to this ask() resulting in an unintended URL of y. Special case
980
- # this common error
981
- url = nil if YES_SET.member? url
982
- end
983
- url = nil if url == "None"
984
- default_url = nil if default_url == "None"
985
- url ||= default_url
986
-
987
- if memswitch
988
- mem = memswitch
989
- elsif prompt_ok
990
- mem = ask("Memory Reservation",
991
- :default => framework.memory(runtime),
992
- :choices => mem_choices)
993
- else
994
- mem = framework.memory runtime
995
- end
996
-
997
- # Set to MB number
998
- mem_quota = mem_choice_to_quota(mem)
999
-
1000
- # check memsize here for capacity
1001
- check_has_capacity_for(mem_quota * instances) unless no_start
1002
-
1003
- display 'Creating Application: ', false
1004
-
1005
- manifest = {
1006
- :name => "#{appname}",
1007
- :staging => {
1008
- :framework => framework.name,
1009
- :runtime => runtime
1010
- },
1011
- :uris => Array(url),
1012
- :instances => instances,
1013
- :resources => {
1014
- :memory => mem_quota
1015
- }
1016
- }
1017
- manifest[:staging][:command] = command if command
1018
-
1019
- # Send the manifest to the cloud controller
1020
- client.create_app(appname, manifest)
1021
- display 'OK'.green
1022
-
1023
-
1024
- existing = Set.new(client.services.collect { |s| s[:name] })
1025
-
1026
- if @app_info && services = @app_info["services"]
1027
- services.each do |name, info|
1028
- unless existing.include? name
1029
- create_service_banner(info["type"], name, true)
1030
- end
1031
-
1032
- bind_service_banner(name, appname)
1033
- end
1034
- end
1035
-
1036
- # Stage and upload the app bits.
1037
- upload_app_bits(appname, @application)
1038
-
1039
- start(appname, true) unless no_start
1040
- end
1041
-
1042
- def do_stats(appname)
1043
- stats = client.app_stats(appname)
1044
- return display JSON.pretty_generate(stats) if @options[:json]
1045
-
1046
- stats_table = table do |t|
1047
- t.headings = 'Instance', 'CPU (Cores)', 'Memory (limit)', 'Disk (limit)', 'Uptime'
1048
- stats.each do |entry|
1049
- index = entry[:instance]
1050
- stat = entry[:stats]
1051
- hp = "#{stat[:host]}:#{stat[:port]}"
1052
- uptime = uptime_string(stat[:uptime])
1053
- usage = stat[:usage]
1054
- if usage
1055
- cpu = usage[:cpu]
1056
- mem = (usage[:mem] * 1024) # mem comes in K's
1057
- disk = usage[:disk]
1058
- end
1059
- mem_quota = stat[:mem_quota]
1060
- disk_quota = stat[:disk_quota]
1061
- mem = "#{pretty_size(mem)} (#{pretty_size(mem_quota, 0)})"
1062
- disk = "#{pretty_size(disk)} (#{pretty_size(disk_quota, 0)})"
1063
- cpu = cpu ? cpu.to_s : 'NA'
1064
- cpu = "#{cpu}% (#{stat[:cores]})"
1065
- t << [index, cpu, mem, disk, uptime]
1066
- end
1067
- end
1068
-
1069
- if stats.empty?
1070
- display "No running instances for [#{appname}]".yellow
1071
- else
1072
- display stats_table
1073
- end
1074
- end
1075
-
1076
- def all_files(appname, path)
1077
- instances_info_envelope = client.app_instances(appname)
1078
- return if instances_info_envelope.is_a?(Array)
1079
- instances_info = instances_info_envelope[:instances] || []
1080
- instances_info.each do |entry|
1081
- begin
1082
- content = client.app_files(appname, path, entry[:index])
1083
- display_logfile(
1084
- path,
1085
- content,
1086
- entry[:index],
1087
- "====> [#{entry[:index]}: #{path}] <====\n".bold
1088
- )
1089
- rescue VMC::Client::NotFound, VMC::Client::TargetError
1090
- end
1091
- end
1092
- end
1093
- end
1094
-
1095
- class FileWithPercentOutput < ::File
1096
- class << self
1097
- attr_accessor :display_str, :upload_size
1098
- end
1099
-
1100
- def update_display(rsize)
1101
- @read ||= 0
1102
- @read += rsize
1103
- p = (@read * 100 / FileWithPercentOutput.upload_size).to_i
1104
- unless VMC::Cli::Config.output.nil? || !STDOUT.tty?
1105
- clear(FileWithPercentOutput.display_str.size + 5)
1106
- VMC::Cli::Config.output.print("#{FileWithPercentOutput.display_str} #{p}%")
1107
- VMC::Cli::Config.output.flush
1108
- end
1109
- end
1110
-
1111
- def read(*args)
1112
- result = super(*args)
1113
- if result && result.size > 0
1114
- update_display(result.size)
1115
- else
1116
- unless VMC::Cli::Config.output.nil? || !STDOUT.tty?
1117
- clear(FileWithPercentOutput.display_str.size + 5)
1118
- VMC::Cli::Config.output.print(FileWithPercentOutput.display_str)
1119
- display('OK'.green)
1120
- end
1121
- end
1122
- result
1123
- end
1124
- end
1125
-
1126
- end