vmc 0.4.0.beta.6 → 0.4.0.beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. data/bin/vmc +11 -10
  2. data/{LICENSE → vmc-ng/LICENSE} +0 -0
  3. data/{Rakefile → vmc-ng/Rakefile} +0 -0
  4. data/vmc-ng/bin/vmc +14 -0
  5. data/{lib → vmc-ng/lib}/vmc.rb +0 -0
  6. data/{lib → vmc-ng/lib}/vmc/cli.rb +0 -0
  7. data/{lib → vmc-ng/lib}/vmc/cli/app.rb +2 -2
  8. data/{lib → vmc-ng/lib}/vmc/cli/better_help.rb +0 -0
  9. data/{lib → vmc-ng/lib}/vmc/cli/command.rb +2 -2
  10. data/{lib → vmc-ng/lib}/vmc/cli/dots.rb +3 -1
  11. data/{lib → vmc-ng/lib}/vmc/cli/service.rb +0 -0
  12. data/{lib → vmc-ng/lib}/vmc/cli/user.rb +0 -0
  13. data/{lib → vmc-ng/lib}/vmc/constants.rb +0 -0
  14. data/{lib → vmc-ng/lib}/vmc/detect.rb +0 -0
  15. data/{lib → vmc-ng/lib}/vmc/errors.rb +0 -0
  16. data/{lib → vmc-ng/lib}/vmc/plugin.rb +0 -0
  17. data/vmc-ng/lib/vmc/version.rb +3 -0
  18. data/vmc/LICENSE +24 -0
  19. data/vmc/README.md +102 -0
  20. data/vmc/Rakefile +101 -0
  21. data/vmc/bin/vmc +6 -0
  22. data/vmc/caldecott_helper/Gemfile +10 -0
  23. data/vmc/caldecott_helper/Gemfile.lock +48 -0
  24. data/vmc/caldecott_helper/server.rb +43 -0
  25. data/vmc/config/clients.yml +17 -0
  26. data/vmc/config/micro/offline.conf +2 -0
  27. data/vmc/config/micro/paths.yml +22 -0
  28. data/vmc/config/micro/refresh_ip.rb +20 -0
  29. data/vmc/lib/cli.rb +47 -0
  30. data/vmc/lib/cli/commands/admin.rb +80 -0
  31. data/vmc/lib/cli/commands/apps.rb +1126 -0
  32. data/vmc/lib/cli/commands/base.rb +227 -0
  33. data/vmc/lib/cli/commands/manifest.rb +56 -0
  34. data/vmc/lib/cli/commands/micro.rb +115 -0
  35. data/vmc/lib/cli/commands/misc.rb +129 -0
  36. data/vmc/lib/cli/commands/services.rb +180 -0
  37. data/vmc/lib/cli/commands/user.rb +65 -0
  38. data/vmc/lib/cli/config.rb +173 -0
  39. data/vmc/lib/cli/console_helper.rb +160 -0
  40. data/vmc/lib/cli/core_ext.rb +122 -0
  41. data/vmc/lib/cli/errors.rb +19 -0
  42. data/vmc/lib/cli/frameworks.rb +265 -0
  43. data/vmc/lib/cli/manifest_helper.rb +302 -0
  44. data/vmc/lib/cli/runner.rb +531 -0
  45. data/vmc/lib/cli/services_helper.rb +84 -0
  46. data/vmc/lib/cli/tunnel_helper.rb +332 -0
  47. data/vmc/lib/cli/usage.rb +115 -0
  48. data/vmc/lib/cli/version.rb +7 -0
  49. data/vmc/lib/cli/zip_util.rb +77 -0
  50. data/vmc/lib/vmc.rb +3 -0
  51. data/vmc/lib/vmc/client.rb +471 -0
  52. data/vmc/lib/vmc/const.rb +22 -0
  53. data/vmc/lib/vmc/micro.rb +56 -0
  54. data/vmc/lib/vmc/micro/switcher/base.rb +97 -0
  55. data/vmc/lib/vmc/micro/switcher/darwin.rb +19 -0
  56. data/vmc/lib/vmc/micro/switcher/dummy.rb +15 -0
  57. data/vmc/lib/vmc/micro/switcher/linux.rb +16 -0
  58. data/vmc/lib/vmc/micro/switcher/windows.rb +31 -0
  59. data/vmc/lib/vmc/micro/vmrun.rb +158 -0
  60. metadata +266 -34
  61. data/lib/vmc/version.rb +0 -3
@@ -0,0 +1,17 @@
1
+ redis:
2
+ redis-cli: -h ${host} -p ${port} -a ${password}
3
+
4
+ mysql:
5
+ mysql: --protocol=TCP --host=${host} --port=${port} --user=${user} --password=${password} ${name}
6
+ mysqldump: --protocol=TCP --host=${host} --port=${port} --user=${user} --password=${password} ${name} > ${Output file}
7
+
8
+ mongodb:
9
+ mongo: --host ${host} --port ${port} -u ${user} -p ${password} ${name}
10
+ mongodump: --host ${host} --port ${port} -u ${user} -p ${password} --db ${name}
11
+ mongorestore: --host ${host} --port ${port} -u ${user} -p ${password} --db ${name} ${Directory or filename to restore from}
12
+
13
+ postgresql:
14
+ psql:
15
+ command: -h ${host} -p ${port} -d ${name} -U ${user} -w
16
+ environment:
17
+ - PGPASSWORD='${password}'
@@ -0,0 +1,2 @@
1
+ no-resolv
2
+ log-queries
@@ -0,0 +1,22 @@
1
+ darwin:
2
+ vmrun:
3
+ - "/Applications/VMware Fusion.app/Contents/Library/"
4
+ - "/Applications/Fusion.app/Contents/Library/"
5
+ vmx:
6
+ - "~/Documents/Virtual Machines.localized/"
7
+ - "~/Documents/Virtual Machines/"
8
+ - "~/Desktop/"
9
+
10
+ linux:
11
+ vmrun:
12
+ - "/usr/bin/"
13
+ vmx:
14
+ - "~/"
15
+
16
+ windows:
17
+ vmrun:
18
+ - "c:\\Program Files (x86)\\"
19
+ - "c:\\Program Files\\"
20
+ vmx:
21
+ - "~\\Documents\\"
22
+ - "~\\Desktop\\"
@@ -0,0 +1,20 @@
1
+ #!/var/vcap/bosh/bin/ruby
2
+ require 'socket'
3
+
4
+ A_ROOT_SERVER = '198.41.0.4'
5
+
6
+ begin
7
+ retries ||= 0
8
+ route ||= A_ROOT_SERVER
9
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
10
+ ip_address = UDPSocket.open {|s| s.connect(route, 1); s.addr.last }
11
+ rescue Errno::ENETUNREACH
12
+ # happens on boot when dhcp hasn't completed when we get here
13
+ sleep 3
14
+ retries += 1
15
+ retry if retries < 10
16
+ ensure
17
+ Socket.do_not_reverse_lookup = orig
18
+ end
19
+
20
+ File.open("/tmp/ip.txt", 'w') { |file| file.write(ip_address) }
data/vmc/lib/cli.rb ADDED
@@ -0,0 +1,47 @@
1
+ require "rbconfig"
2
+
3
+ ROOT = File.expand_path(File.dirname(__FILE__))
4
+ WINDOWS = !!(RbConfig::CONFIG['host_os'] =~ /mingw|mswin32|cygwin/)
5
+
6
+ module VMC
7
+ autoload :Client, "#{ROOT}/vmc/client"
8
+ autoload :Micro, "#{ROOT}/vmc/micro"
9
+
10
+ module Micro
11
+ module Switcher
12
+ autoload :Base, "#{ROOT}/vmc/micro/switcher/base"
13
+ autoload :Darwin, "#{ROOT}/vmc/micro/switcher/darwin"
14
+ autoload :Dummy, "#{ROOT}/vmc/micro/switcher/dummy"
15
+ autoload :Linux, "#{ROOT}/vmc/micro/switcher/linux"
16
+ autoload :Windows, "#{ROOT}/vmc/micro/switcher/windows"
17
+ end
18
+ autoload :VMrun, "#{ROOT}/vmc/micro/vmrun"
19
+ end
20
+
21
+ module Cli
22
+ autoload :Config, "#{ROOT}/cli/config"
23
+ autoload :Framework, "#{ROOT}/cli/frameworks"
24
+ autoload :Runner, "#{ROOT}/cli/runner"
25
+ autoload :ZipUtil, "#{ROOT}/cli/zip_util"
26
+ autoload :ServicesHelper, "#{ROOT}/cli/services_helper"
27
+ autoload :TunnelHelper, "#{ROOT}/cli/tunnel_helper"
28
+ autoload :ManifestHelper, "#{ROOT}/cli/manifest_helper"
29
+ autoload :ConsoleHelper, "#{ROOT}/cli/console_helper"
30
+
31
+ module Command
32
+ autoload :Base, "#{ROOT}/cli/commands/base"
33
+ autoload :Admin, "#{ROOT}/cli/commands/admin"
34
+ autoload :Apps, "#{ROOT}/cli/commands/apps"
35
+ autoload :Micro, "#{ROOT}/cli/commands/micro"
36
+ autoload :Misc, "#{ROOT}/cli/commands/misc"
37
+ autoload :Services, "#{ROOT}/cli/commands/services"
38
+ autoload :User, "#{ROOT}/cli/commands/user"
39
+ autoload :Manifest, "#{ROOT}/cli/commands/manifest"
40
+ end
41
+
42
+ end
43
+ end
44
+
45
+ require "#{ROOT}/cli/version"
46
+ require "#{ROOT}/cli/core_ext"
47
+ require "#{ROOT}/cli/errors"
@@ -0,0 +1,80 @@
1
+ module VMC::Cli::Command
2
+
3
+ class Admin < Base
4
+
5
+ def list_users
6
+ users = client.users
7
+ users.sort! {|a, b| a[:email] <=> b[:email] }
8
+ return display JSON.pretty_generate(users || []) if @options[:json]
9
+
10
+ display "\n"
11
+ return display "No Users" if users.nil? || users.empty?
12
+
13
+ users_table = table do |t|
14
+ t.headings = 'Email', 'Admin', 'Apps'
15
+ users.each do |user|
16
+ t << [user[:email], user[:admin], user[:apps].map {|x| x[:name]}.join(', ')]
17
+ end
18
+ end
19
+ display users_table
20
+ end
21
+
22
+ alias :users :list_users
23
+
24
+ def add_user(email=nil)
25
+ email ||= @options[:email]
26
+ email ||= ask("Email") unless no_prompt
27
+ password = @options[:password]
28
+ unless no_prompt || password
29
+ password = ask("Password", :echo => "*")
30
+ password2 = ask("Verify Password", :echo => "*")
31
+ err "Passwords did not match, try again" if password != password2
32
+ end
33
+ err "Need a valid email" unless email
34
+ err "Need a password" unless password
35
+ display 'Creating New User: ', false
36
+ client.add_user(email, password)
37
+ display 'OK'.green
38
+
39
+ # if we are not logged in for the current target, log in as the new user
40
+ return unless VMC::Cli::Config.auth_token.nil?
41
+ @options[:password] = password
42
+ cmd = User.new(@options)
43
+ cmd.login(email)
44
+ end
45
+
46
+ def delete_user(user_email)
47
+ # Check to make sure all apps and services are deleted before deleting the user
48
+ # implicit proxying
49
+
50
+ client.proxy_for(user_email)
51
+ @options[:proxy] = user_email
52
+ apps = client.apps
53
+
54
+ if (apps && !apps.empty?)
55
+ unless no_prompt
56
+ proceed = ask(
57
+ "\nDeployed applications and associated services will be DELETED, continue?",
58
+ :default => false
59
+ )
60
+ err "Aborted" unless proceed
61
+ end
62
+ cmd = Apps.new(@options.merge({ :force => true }))
63
+ apps.each { |app| cmd.delete(app[:name]) }
64
+ end
65
+
66
+ services = client.services
67
+ if (services && !services.empty?)
68
+ cmd = Services.new(@options)
69
+ services.each { |s| cmd.delete_service(s[:name])}
70
+ end
71
+
72
+ display 'Deleting User: ', false
73
+ client.proxy = nil
74
+ client.delete_user(user_email)
75
+ display 'OK'.green
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,1126 @@
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