vmc 0.3.23 → 0.4.0.beta.1

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 (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