vmc 0.3.23 → 0.4.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/bin/vmc +11 -2
  2. data/vmc-ng/LICENSE +746 -0
  3. data/vmc-ng/Rakefile +11 -0
  4. data/vmc-ng/bin/vmc +14 -0
  5. data/vmc-ng/lib/vmc.rb +2 -0
  6. data/vmc-ng/lib/vmc/cli.rb +327 -0
  7. data/vmc-ng/lib/vmc/cli/app.rb +622 -0
  8. data/vmc-ng/lib/vmc/cli/better_help.rb +193 -0
  9. data/vmc-ng/lib/vmc/cli/command.rb +523 -0
  10. data/vmc-ng/lib/vmc/cli/dots.rb +133 -0
  11. data/vmc-ng/lib/vmc/cli/service.rb +122 -0
  12. data/vmc-ng/lib/vmc/cli/user.rb +72 -0
  13. data/vmc-ng/lib/vmc/constants.rb +10 -0
  14. data/vmc-ng/lib/vmc/detect.rb +64 -0
  15. data/vmc-ng/lib/vmc/errors.rb +17 -0
  16. data/vmc-ng/lib/vmc/plugin.rb +40 -0
  17. data/vmc-ng/lib/vmc/version.rb +3 -0
  18. data/{LICENSE → vmc/LICENSE} +0 -0
  19. data/{README.md → vmc/README.md} +1 -5
  20. data/{Rakefile → vmc/Rakefile} +0 -0
  21. data/vmc/bin/vmc +6 -0
  22. data/{caldecott_helper → vmc/caldecott_helper}/Gemfile +0 -0
  23. data/{caldecott_helper → vmc/caldecott_helper}/Gemfile.lock +0 -0
  24. data/{caldecott_helper → vmc/caldecott_helper}/server.rb +0 -0
  25. data/{config → vmc/config}/clients.yml +0 -0
  26. data/{config → vmc/config}/micro/offline.conf +0 -0
  27. data/{config → vmc/config}/micro/paths.yml +0 -0
  28. data/{config → vmc/config}/micro/refresh_ip.rb +0 -0
  29. data/{lib → vmc/lib}/cli.rb +0 -0
  30. data/{lib → vmc/lib}/cli/commands/admin.rb +0 -0
  31. data/{lib → vmc/lib}/cli/commands/apps.rb +0 -0
  32. data/{lib → vmc/lib}/cli/commands/base.rb +0 -0
  33. data/{lib → vmc/lib}/cli/commands/manifest.rb +0 -0
  34. data/{lib → vmc/lib}/cli/commands/micro.rb +0 -0
  35. data/{lib → vmc/lib}/cli/commands/misc.rb +0 -0
  36. data/{lib → vmc/lib}/cli/commands/services.rb +0 -0
  37. data/{lib → vmc/lib}/cli/commands/user.rb +2 -6
  38. data/{lib → vmc/lib}/cli/config.rb +0 -0
  39. data/{lib → vmc/lib}/cli/console_helper.rb +4 -14
  40. data/{lib → vmc/lib}/cli/core_ext.rb +0 -0
  41. data/{lib → vmc/lib}/cli/errors.rb +0 -0
  42. data/{lib → vmc/lib}/cli/frameworks.rb +1 -1
  43. data/{lib → vmc/lib}/cli/manifest_helper.rb +0 -0
  44. data/{lib → vmc/lib}/cli/runner.rb +0 -0
  45. data/{lib → vmc/lib}/cli/services_helper.rb +0 -0
  46. data/{lib → vmc/lib}/cli/tunnel_helper.rb +0 -0
  47. data/{lib → vmc/lib}/cli/usage.rb +0 -0
  48. data/{lib → vmc/lib}/cli/version.rb +1 -1
  49. data/{lib → vmc/lib}/cli/zip_util.rb +0 -0
  50. data/{lib → vmc/lib}/vmc.rb +0 -0
  51. data/{lib → vmc/lib}/vmc/client.rb +0 -0
  52. data/{lib → vmc/lib}/vmc/const.rb +0 -0
  53. data/{lib → vmc/lib}/vmc/micro.rb +0 -0
  54. data/{lib → vmc/lib}/vmc/micro/switcher/base.rb +0 -0
  55. data/{lib → vmc/lib}/vmc/micro/switcher/darwin.rb +0 -0
  56. data/{lib → vmc/lib}/vmc/micro/switcher/dummy.rb +0 -0
  57. data/{lib → vmc/lib}/vmc/micro/switcher/linux.rb +0 -0
  58. data/{lib → vmc/lib}/vmc/micro/switcher/windows.rb +0 -0
  59. data/{lib → vmc/lib}/vmc/micro/vmrun.rb +1 -11
  60. metadata +177 -93
@@ -0,0 +1,11 @@
1
+ require "bundler"
2
+
3
+ if Gem::Version.new(Bundler::VERSION) > Gem::Version.new("1.0.12")
4
+ require "bundler/gem_tasks"
5
+ end
6
+
7
+ task :default => :spec
8
+
9
+ task :spec do
10
+
11
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: ft=ruby
3
+
4
+ require "rubygems"
5
+ require "thor"
6
+
7
+ require "vmc"
8
+ require "vmc/plugin"
9
+
10
+ VMC::Plugin.load_all
11
+
12
+ $exit_status = 0
13
+ VMC::CLI.start(ARGV)
14
+ exit($exit_status)
@@ -0,0 +1,2 @@
1
+ require "vmc/version"
2
+ require "vmc/cli"
@@ -0,0 +1,327 @@
1
+ require "vmc/cli/command"
2
+
3
+ VMC::Command.groups(
4
+ [:start, "Getting Started"],
5
+ [:apps, "Applications",
6
+ [:manage, "Management"],
7
+ [:info, "Information"]],
8
+ [:services, "Services",
9
+ [:manage, "Management"]],
10
+ [:admin, "Administration",
11
+ [:user, "User Management"]])
12
+
13
+ require "vmc/cli/app"
14
+ require "vmc/cli/service"
15
+ require "vmc/cli/user"
16
+
17
+ module VMC
18
+ class CLI < App # subclass App since we operate on Apps by default
19
+ desc "service SUBCOMMAND ...ARGS", "Service management"
20
+ subcommand "service", Service
21
+
22
+ desc "user SUBCOMMAND ...ARGS", "User management"
23
+ subcommand "user", User
24
+
25
+ desc "info", "Display information on the current target, user, etc."
26
+ group :start
27
+ flag :runtimes, :default => false
28
+ flag :services, :default => false
29
+ flag :frameworks, :default => false
30
+ def info
31
+ info =
32
+ with_progress("Getting target information") do
33
+ client.info
34
+ end
35
+
36
+ authorized = !!info["frameworks"]
37
+
38
+ if input(:runtimes)
39
+ raise NotAuthorized unless authorized
40
+
41
+ runtimes = {}
42
+ info["frameworks"].each do |_, f|
43
+ f["runtimes"].each do |r|
44
+ runtimes[r["name"]] = r
45
+ end
46
+ end
47
+
48
+ runtimes = runtimes.values.sort_by { |x| x["name"] }
49
+
50
+ if simple_output?
51
+ runtimes.each do |r|
52
+ puts r["name"]
53
+ end
54
+ return
55
+ end
56
+
57
+ runtimes.each do |r|
58
+ puts ""
59
+ puts "#{c(r["name"], :blue)}:"
60
+ puts " version: #{b(r["version"])}"
61
+ puts " description: #{b(r["description"])}"
62
+ end
63
+
64
+ return
65
+ end
66
+
67
+ if input(:services)
68
+ raise NotAuthorized unless authorized
69
+
70
+ services = {}
71
+ client.system_services.each do |_, svcs|
72
+ svcs.each do |name, versions|
73
+ services[name] = versions.values
74
+ end
75
+ end
76
+
77
+ if simple_output?
78
+ services.each do |name, _|
79
+ puts name
80
+ end
81
+
82
+ return
83
+ end
84
+
85
+ services.each do |name, versions|
86
+ puts ""
87
+ puts "#{c(name, :blue)}:"
88
+ puts " versions: #{versions.collect { |v| v["version"] }.join ", "}"
89
+ puts " description: #{versions[0]["description"]}"
90
+ puts " type: #{versions[0]["type"]}"
91
+ end
92
+
93
+ return
94
+ end
95
+
96
+ if input(:frameworks)
97
+ raise NotAuthorized unless authorized
98
+
99
+ puts "" unless simple_output?
100
+
101
+ info["frameworks"].each do |name, _|
102
+ puts name
103
+ end
104
+
105
+ return
106
+ end
107
+
108
+ puts ""
109
+
110
+ puts info["description"]
111
+ puts ""
112
+ puts "target: #{b(client.target)}"
113
+ puts " version: #{info["version"]}"
114
+ puts " support: #{info["support"]}"
115
+
116
+ if info["user"]
117
+ puts ""
118
+ puts "user: #{b(info["user"])}"
119
+ puts " usage:"
120
+
121
+ limits = info["limits"]
122
+ info["usage"].each do |k, v|
123
+ m = limits[k]
124
+ if k == "memory"
125
+ puts " #{k}: #{usage(v * 1024 * 1024, m * 1024 * 1024)}"
126
+ else
127
+ puts " #{k}: #{b(v)} of #{b(m)} limit"
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ desc "target [URL]", "Set or display the current target cloud"
134
+ group :start
135
+ def target(url = nil)
136
+ if url.nil?
137
+ display_target
138
+ return
139
+ end
140
+
141
+ target = sane_target_url(url)
142
+ display = c(target.sub(/https?:\/\//, ""), :blue)
143
+ with_progress("Setting target to #{display}") do
144
+ unless force?
145
+ # check that the target is valid
146
+ CFoundry::Client.new(target).info
147
+ end
148
+
149
+ set_target(target)
150
+ end
151
+ end
152
+
153
+ desc "login [EMAIL]", "Authenticate with the target"
154
+ group :start
155
+ flag(:email) {
156
+ ask("Email")
157
+ }
158
+ flag(:password)
159
+ # TODO: implement new authentication scheme
160
+ def login(email = nil)
161
+ unless simple_output?
162
+ display_target
163
+ puts ""
164
+ end
165
+
166
+ email ||= input(:email)
167
+ password = input(:password)
168
+
169
+ authenticated = false
170
+ failed = false
171
+ until authenticated
172
+ unless force?
173
+ if failed || !password
174
+ password = ask("Password", :echo => "*", :forget => true)
175
+ end
176
+ end
177
+
178
+ with_progress("Authenticating") do |s|
179
+ begin
180
+ save_token(client.login(email, password))
181
+ authenticated = true
182
+ rescue CFoundry::Denied
183
+ return if force?
184
+
185
+ s.fail do
186
+ failed = true
187
+ end
188
+ end
189
+ end
190
+ end
191
+ ensure
192
+ $exit_status = 1 if not authenticated
193
+ end
194
+
195
+ desc "logout", "Log out from the target"
196
+ group :start
197
+ def logout
198
+ with_progress("Logging out") do
199
+ remove_token
200
+ end
201
+ end
202
+
203
+ desc "register [EMAIL]", "Create a user and log in"
204
+ group :start, :hidden => true
205
+ flag(:email) {
206
+ ask("Email")
207
+ }
208
+ flag(:password) {
209
+ ask("Password", :echo => "*", :forget => true)
210
+ }
211
+ flag(:no_login, :type => :boolean)
212
+ def register(email = nil)
213
+ unless simple_output?
214
+ puts "Target: #{c(client_target, :blue)}"
215
+ puts ""
216
+ end
217
+
218
+ email ||= input(:email)
219
+ password = input(:password)
220
+
221
+ with_progress("Creating user") do
222
+ client.register(email, password)
223
+ end
224
+
225
+ unless input(:skip_login)
226
+ with_progress("Logging in") do
227
+ save_token(client.login(email, password))
228
+ end
229
+ end
230
+ end
231
+
232
+ desc "apps", "List your applications"
233
+ group :apps
234
+ def apps
235
+ apps =
236
+ with_progress("Getting applications") do
237
+ client.apps
238
+ end
239
+
240
+ if apps.empty? and !simple_output?
241
+ puts ""
242
+ puts "No applications."
243
+ return
244
+ end
245
+
246
+ apps.each.with_index do |a, num|
247
+ display_app(a)
248
+ end
249
+ end
250
+
251
+ desc "services", "List your services"
252
+ group :services
253
+ def services
254
+ services =
255
+ with_progress("Getting services") do
256
+ client.services
257
+ end
258
+
259
+ puts "" unless simple_output?
260
+
261
+ if services.empty? and !simple_output?
262
+ puts "No services."
263
+ end
264
+
265
+ services.each do |s|
266
+ display_service(s)
267
+ end
268
+ end
269
+
270
+ desc "users", "List all users"
271
+ group :admin, :hidden => true
272
+ def users
273
+ users =
274
+ with_progress("Getting users") do
275
+ client.users
276
+ end
277
+
278
+ users.each do |u|
279
+ display_user(u)
280
+ end
281
+ end
282
+
283
+ desc "help [COMMAND]", "usage instructions"
284
+ flag :all, :default => false
285
+ group :start
286
+ def help(task = nil)
287
+ if task
288
+ self.class.task_help(@shell, task)
289
+ else
290
+ unless input(:all)
291
+ puts "Showing basic command set. Pass --all to list all commands."
292
+ puts ""
293
+ end
294
+
295
+ self.class.print_help_groups(input(:all))
296
+ end
297
+ end
298
+
299
+ private
300
+
301
+ def display_service(s)
302
+ if simple_output?
303
+ puts s.name
304
+ else
305
+ puts "#{c(s.name, :blue)}: #{s.vendor} v#{s.version}"
306
+ end
307
+ end
308
+
309
+ def display_user(u)
310
+ if simple_output?
311
+ puts u.email
312
+ else
313
+ puts ""
314
+ puts "#{c(u.email, :blue)}:"
315
+ puts " admin?: #{c(u.admin?, u.admin? ? :green : :red)}"
316
+ end
317
+ end
318
+
319
+ def display_target
320
+ if simple_output?
321
+ puts client.target
322
+ else
323
+ puts "Target: #{c(client.target, :blue)}"
324
+ end
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,622 @@
1
+ require "vmc/cli/command"
2
+ require "vmc/detect"
3
+
4
+ module VMC
5
+ class App < Command
6
+ MEM_CHOICES = ["64M", "128M", "256M", "512M"]
7
+
8
+ desc "push [NAME]", "Push an application, syncing changes if it exists"
9
+ group :apps, :manage
10
+ flag(:name) { ask("Name") }
11
+ flag(:path) {
12
+ ask("Push from...", :default => ".")
13
+ }
14
+ flag(:url) { |name, target|
15
+ ask("URL", :default => "#{name}.#{target}")
16
+ }
17
+ flag(:memory) {
18
+ ask("Memory Limit",
19
+ :choices => MEM_CHOICES,
20
+
21
+ # TODO: base this on framework choice
22
+ :default => "64M")
23
+ }
24
+ flag(:instances) {
25
+ ask("Instances", :default => 1)
26
+ }
27
+ flag(:framework) { |choices, default|
28
+ ask("Framework", :choices => choices, :default => default)
29
+ }
30
+ flag(:runtime) { |choices|
31
+ ask("Runtime", :choices => choices)
32
+ }
33
+ flag(:start, :default => true)
34
+ flag(:restart, :default => true)
35
+ def push(name = nil)
36
+ path = File.expand_path(input(:path))
37
+
38
+ name ||= input(:name)
39
+
40
+ detector = Detector.new(client, path)
41
+ frameworks = detector.all_frameworks
42
+ detected, default = detector.frameworks
43
+
44
+ app = client.app(name)
45
+
46
+ if app.exists?
47
+ upload_app(app, path)
48
+ restart(app.name) if input(:restart)
49
+ return
50
+ end
51
+
52
+ app.total_instances = input(:instances)
53
+
54
+ domain = client.target.sub(/^https?:\/\/api\.(.+)\/?/, '\1')
55
+ app.urls = [input(:url, name, domain)]
56
+
57
+ framework = input(:framework, ["other"] + detected.keys, default)
58
+ if framework == "other"
59
+ forget(:framework)
60
+ framework = input(:framework, frameworks.keys)
61
+ end
62
+
63
+ framework_runtimes =
64
+ frameworks[framework]["runtimes"].collect do |k|
65
+ "#{k["name"]} (#{k["description"]})"
66
+ end
67
+
68
+ # TODO: include descriptions
69
+ runtime = input(:runtime, framework_runtimes).split.first
70
+
71
+ app.framework = framework
72
+ app.runtime = runtime
73
+
74
+ app.memory = megabytes(input(:memory))
75
+
76
+ with_progress("Creating #{c(name, :blue)}") do
77
+ app.create!
78
+ end
79
+
80
+ begin
81
+ upload_app(app, path)
82
+ rescue
83
+ err "Upload failed. Try again with 'vmc push'."
84
+ raise
85
+ end
86
+
87
+ start(name) if input(:start)
88
+ end
89
+
90
+ desc "start APPS...", "Start an application"
91
+ group :apps, :manage
92
+ flag :name
93
+ flag :debug_mode
94
+ def start(*names)
95
+ if name = input(:name)
96
+ names = [name]
97
+ end
98
+
99
+ fail "No applications given." if names.empty?
100
+
101
+ names.each do |name|
102
+ app = client.app(name)
103
+
104
+ fail "Unknown application." unless app.exists?
105
+
106
+ switch_mode(app, input(:debug_mode))
107
+
108
+ with_progress("Starting #{c(name, :blue)}") do |s|
109
+ if app.running?
110
+ s.skip do
111
+ err "Already started."
112
+ end
113
+ end
114
+
115
+ app.start!
116
+ end
117
+
118
+ check_application(app)
119
+
120
+ if app.debug_mode && !simple_output?
121
+ puts ""
122
+ instances(name)
123
+ end
124
+ end
125
+ end
126
+
127
+ desc "stop APPS...", "Stop an application"
128
+ group :apps, :manage
129
+ flag :name
130
+ def stop(*names)
131
+ if name = input(:name)
132
+ names = [name]
133
+ end
134
+
135
+ fail "No applications given." if names.empty?
136
+
137
+ names.each do |name|
138
+ with_progress("Stopping #{c(name, :blue)}") do |s|
139
+ app = client.app(name)
140
+
141
+ unless app.exists?
142
+ s.fail do
143
+ err "Unknown application."
144
+ end
145
+ end
146
+
147
+ if app.stopped?
148
+ s.skip do
149
+ err "Application is not running."
150
+ end
151
+ end
152
+
153
+ app.stop!
154
+ end
155
+ end
156
+ end
157
+
158
+ desc "restart APPS...", "Stop and start an application"
159
+ group :apps, :manage
160
+ flag :name
161
+ flag :debug_mode
162
+ def restart(*names)
163
+ stop(*names)
164
+ start(*names)
165
+ end
166
+
167
+ desc "delete APPS...", "Delete an application"
168
+ group :apps, :manage
169
+ flag :name
170
+ flag(:really) { |name, color|
171
+ force? || ask("Really delete #{c(name, color)}?", :default => false)
172
+ }
173
+ flag(:name) { |names|
174
+ ask("Delete which application?", :choices => names)
175
+ }
176
+ flag(:all, :default => false)
177
+ def delete(*names)
178
+ if input(:all)
179
+ return unless input(:really, "ALL APPS", :red)
180
+
181
+ with_progress("Deleting all applications") do
182
+ client.apps.collect(&:delete!)
183
+ end
184
+
185
+ return
186
+ end
187
+
188
+ if names.empty?
189
+ apps = client.apps
190
+ fail "No applications." if apps.empty?
191
+
192
+ names = [input(:name, apps.collect(&:name))]
193
+ end
194
+
195
+ names.each do |name|
196
+ really = input(:really, name, :blue)
197
+
198
+ forget(:really)
199
+
200
+ next unless really
201
+
202
+ with_progress("Deleting #{c(name, :blue)}") do
203
+ client.app(name).delete!
204
+ end
205
+ end
206
+ end
207
+
208
+ desc "instances APPS...", "List an app's instances"
209
+ group :apps, :info, :hidden => true
210
+ flag :name
211
+ def instances(*names)
212
+ if name = input(:name)
213
+ names = [name]
214
+ end
215
+
216
+ fail "No applications given." if names.empty?
217
+
218
+ names.each do |name|
219
+ instances =
220
+ with_progress("Getting instances for #{c(name, :blue)}") do
221
+ client.app(name).instances
222
+ end
223
+
224
+ instances.each do |i|
225
+ if simple_output?
226
+ puts i.index
227
+ else
228
+ puts ""
229
+ display_instance(i)
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ desc "scale APP", "Update the instances/memory limit for an application"
236
+ group :apps, :info, :hidden => true
237
+ flag(:instances, :type => :numeric) { |default|
238
+ ask("Instances", :default => default)
239
+ }
240
+ flag(:memory) { |default|
241
+ ask("Memory Limit",
242
+ :default => human_size(default * 1024 * 1024, 0),
243
+ :choices => MEM_CHOICES)
244
+ }
245
+ def scale(name)
246
+ app = client.app(name)
247
+
248
+ instances = passed_value(:instances)
249
+ memory = passed_value(:memory)
250
+
251
+ unless instances || memory
252
+ instances = input(:instances, app.total_instances)
253
+ memory = input(:memory, app.memory)
254
+ end
255
+
256
+ with_progress("Scaling #{c(name, :blue)}") do
257
+ app.total_instances = instances.to_i if instances
258
+ app.memory = megabytes(memory) if memory
259
+ app.update!
260
+ end
261
+ end
262
+
263
+ desc "logs APP", "Print out an app's logs"
264
+ group :apps, :info, :hidden => true
265
+ flag(:instance, :type => :numeric, :default => 0)
266
+ flag(:all, :default => false)
267
+ def logs(name)
268
+ app = client.app(name)
269
+ fail "Unknown application." unless app.exists?
270
+
271
+ instances =
272
+ if input(:all)
273
+ app.instances
274
+ else
275
+ app.instances.select { |i| i.index == input(:instance) }
276
+ end
277
+
278
+ if instances.empty?
279
+ if input(:all)
280
+ fail "No instances found."
281
+ else
282
+ fail "Instance #{name} \##{input(:instance)} not found."
283
+ end
284
+ end
285
+
286
+ instances.each do |i|
287
+ logs =
288
+ with_progress(
289
+ "Getting logs for " +
290
+ c(name, :blue) + " " +
291
+ c("\##{i.index}", :yellow)) do
292
+ i.files("logs")
293
+ end
294
+
295
+ puts "" unless simple_output?
296
+
297
+ logs.each do |log|
298
+ body =
299
+ with_progress("Reading " + b(log.join("/"))) do
300
+ i.file(*log)
301
+ end
302
+
303
+ puts body
304
+ puts "" unless body.empty?
305
+ end
306
+ end
307
+ end
308
+
309
+ desc "file APP [PATH]", "Print out an app's file contents"
310
+ group :apps, :info, :hidden => true
311
+ def file(name, path = "/")
312
+ file =
313
+ with_progress("Getting file contents") do
314
+ client.app(name).file(*path.split("/"))
315
+ end
316
+
317
+ puts "" unless simple_output?
318
+
319
+ print file
320
+ end
321
+
322
+ desc "files APP [PATH]", "Examine an app's files"
323
+ group :apps, :info, :hidden => true
324
+ def files(name, path = "/")
325
+ files =
326
+ with_progress("Getting file listing") do
327
+ client.app(name).files(*path.split("/"))
328
+ end
329
+
330
+ puts "" unless simple_output?
331
+
332
+ files.each do |file|
333
+ puts file
334
+ end
335
+ end
336
+
337
+ desc "health ...APPS", "Get application health"
338
+ group :apps, :info, :hidden => true
339
+ flag :name
340
+ def health(*names)
341
+ if name = input(:name)
342
+ names = [name]
343
+ end
344
+
345
+ apps =
346
+ with_progress("Getting application health") do
347
+ names.collect do |n|
348
+ [n, app_status(client.app(n))]
349
+ end
350
+ end
351
+
352
+ apps.each do |name, status|
353
+ unless simple_output?
354
+ puts ""
355
+ print "#{c(name, :blue)}: "
356
+ end
357
+
358
+ puts status
359
+ end
360
+ end
361
+
362
+ desc "stats APP", "Display application instance status"
363
+ group :apps, :info, :hidden => true
364
+ def stats(name)
365
+ stats =
366
+ with_progress("Getting stats") do
367
+ client.app(name).stats
368
+ end
369
+
370
+ stats.sort_by { |k, _| k }.each do |idx, info|
371
+ stats = info["stats"]
372
+ usage = stats["usage"]
373
+ puts ""
374
+ puts "instance #{c("#" + idx, :blue)}:"
375
+ print " cpu: #{percentage(usage["cpu"])} of"
376
+ puts " #{b(stats["cores"])} cores"
377
+ puts " memory: #{usage(usage["mem"] * 1024, stats["mem_quota"])}"
378
+ puts " disk: #{usage(usage["disk"], stats["disk_quota"])}"
379
+ end
380
+ end
381
+
382
+ desc "update", "DEPRECATED", :hide => true
383
+ def update(*args)
384
+ fail "The 'update' command is no longer used; use 'push' instead."
385
+ end
386
+
387
+ class URL < Command
388
+ desc "map APP URL", "Add a URL mapping for an app"
389
+ group :apps, :info, :hidden => true
390
+ def map(name, url)
391
+ simple = url.sub(/^https?:\/\/(.*)\/?/i, '\1')
392
+
393
+ with_progress("Updating #{c(name, :blue)}") do
394
+ app = client.app(name)
395
+ app.urls << simple
396
+ app.update!
397
+ end
398
+ end
399
+
400
+ desc "unmap APP URL", "Remove a URL mapping from an app"
401
+ group :apps, :info, :hidden => true
402
+ def unmap(name, url)
403
+ simple = url.sub(/^https?:\/\/(.*)\/?/i, '\1')
404
+
405
+ app = client.app(name)
406
+ fail "Unknown application." unless app.exists?
407
+
408
+ with_progress("Updating #{c(name, :blue)}") do |s|
409
+ unless app.urls.delete(simple)
410
+ s.fail do
411
+ err "URL #{url} is not mapped to this application."
412
+ return
413
+ end
414
+ end
415
+
416
+ app.update!
417
+ end
418
+ end
419
+ end
420
+
421
+ desc "url SUBCOMMAND ...ARGS", "Manage application URL bindings"
422
+ subcommand "url", URL
423
+
424
+ class Env < Command
425
+ VALID_NAME = /^[a-zA-Za-z_][[:alnum:]_]*$/
426
+
427
+ desc "set APP [NAME] [VALUE]", "Set an environment variable"
428
+ group :apps, :info, :hidden => true
429
+ def set(appname, name, value)
430
+ unless name =~ VALID_NAME
431
+ fail "Invalid variable name; must match #{VALID_NAME.inspect}"
432
+ end
433
+
434
+ app = client.app(appname)
435
+ fail "Unknown application." unless app.exists?
436
+
437
+ with_progress("Updating #{c(app.name, :blue)}") do
438
+ app.update!("env" =>
439
+ app.env.reject { |v|
440
+ v.start_with?("#{name}=")
441
+ }.push("#{name}=#{value}"))
442
+ end
443
+ end
444
+
445
+ desc "unset APP [NAME]", "Remove an environment variable"
446
+ group :apps, :info, :hidden => true
447
+ def unset(appname, name)
448
+ app = client.app(appname)
449
+ fail "Unknown application." unless app.exists?
450
+
451
+ with_progress("Updating #{c(app.name, :blue)}") do
452
+ app.update!("env" =>
453
+ app.env.reject { |v|
454
+ v.start_with?("#{name}=")
455
+ })
456
+ end
457
+ end
458
+
459
+ desc "list APP", "Show all environment variables set for an app"
460
+ group :apps, :info, :hidden => true
461
+ def list(appname)
462
+ vars =
463
+ with_progress("Getting variables") do |s|
464
+ app = client.app(appname)
465
+
466
+ unless app.exists?
467
+ s.fail do
468
+ err "Unknown application."
469
+ return
470
+ end
471
+ end
472
+
473
+ app.env
474
+ end
475
+
476
+ puts "" unless simple_output?
477
+
478
+ vars.each do |pair|
479
+ name, val = pair.split("=", 2)
480
+ puts "#{c(name, :blue)}: #{val}"
481
+ end
482
+ end
483
+ end
484
+
485
+ desc "env SUBCOMMAND ...ARGS", "Manage application environment variables"
486
+ subcommand "env", Env
487
+
488
+ private
489
+
490
+ def upload_app(app, path)
491
+ with_progress("Uploading #{c(app.name, :blue)}") do
492
+ app.upload(path)
493
+ end
494
+ end
495
+
496
+ # set app debug mode, ensuring it's valid, and shutting it down
497
+ def switch_mode(app, mode)
498
+ mode = nil if mode == "none"
499
+
500
+ return false if app.debug_mode == mode
501
+
502
+ if mode.nil?
503
+ with_progress("Removing debug mode") do
504
+ app.debug_mode = nil
505
+ app.stop! if app.running?
506
+ end
507
+
508
+ return true
509
+ end
510
+
511
+ with_progress("Switching mode to #{c(mode, :blue)}") do |s|
512
+ runtimes = client.system_runtimes
513
+ modes = runtimes[app.runtime]["debug_modes"] || []
514
+ if modes.include?(mode)
515
+ app.debug_mode = mode
516
+ app.stop! if app.running?
517
+ true
518
+ else
519
+ s.fail do
520
+ err "Unknown mode '#{mode}'; available: #{modes.inspect}"
521
+ false
522
+ end
523
+ end
524
+ end
525
+ end
526
+
527
+ APP_CHECK_LIMIT = 60
528
+
529
+ def check_application(app)
530
+ with_progress("Checking #{c(app.name, :blue)}") do |s|
531
+ if app.debug_mode == "suspend"
532
+ s.skip do
533
+ puts "Application is in suspended debugging mode."
534
+ puts "It will wait for you to attach to it before starting."
535
+ end
536
+ end
537
+
538
+ seconds = 0
539
+ until app.healthy?
540
+ sleep 1
541
+ seconds += 1
542
+ if seconds == APP_CHECK_LIMIT
543
+ s.give_up do
544
+ err "Application failed to start."
545
+ # TODO: print logs
546
+ end
547
+ end
548
+ end
549
+ end
550
+ end
551
+
552
+ # choose the right color for app/instance state
553
+ def state_color(s)
554
+ case s
555
+ when "STARTING"
556
+ :blue
557
+ when "STARTED", "RUNNING"
558
+ :green
559
+ when "DOWN"
560
+ :red
561
+ when "FLAPPING"
562
+ :magenta
563
+ when "N/A"
564
+ :cyan
565
+ else
566
+ :yellow
567
+ end
568
+ end
569
+
570
+ def app_status(a)
571
+ health = a.health
572
+
573
+ if a.debug_mode == "suspend" && health == "0%"
574
+ c("suspended", :yellow)
575
+ else
576
+ c(health.downcase, state_color(health))
577
+ end
578
+ end
579
+
580
+ def display_app(a)
581
+ if simple_output?
582
+ puts a.name
583
+ return
584
+ end
585
+
586
+ puts ""
587
+
588
+ status = app_status(a)
589
+
590
+ print "#{c(a.name, :blue)}: #{status}"
591
+
592
+ unless a.total_instances == 1
593
+ print ", #{b(a.total_instances)} instances"
594
+ end
595
+
596
+ puts ""
597
+
598
+ unless a.urls.empty?
599
+ puts " urls: #{a.urls.collect { |u| b(u) }.join(", ")}"
600
+ end
601
+
602
+ unless a.services.empty?
603
+ puts " services: #{a.services.collect { |s| b(s) }.join(", ")}"
604
+ end
605
+ end
606
+
607
+ def display_instance(i)
608
+ print "instance #{c("\##{i.index}", :blue)}: "
609
+ puts "#{b(c(i.state.downcase, state_color(i.state)))} "
610
+
611
+ puts " started: #{c(i.since.strftime("%F %r"), :cyan)}"
612
+
613
+ if d = i.debugger
614
+ puts " debugger: port #{c(d["port"], :blue)} at #{c(d["ip"], :blue)}"
615
+ end
616
+
617
+ if c = i.console
618
+ puts " console: port #{b(c["port"])} at #{b(c["ip"])}"
619
+ end
620
+ end
621
+ end
622
+ end