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

Sign up to get free protection for your applications and to get access to all the features.
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