jdc 0.1.2 → 0.2.0

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