jdc 0.1.2 → 0.2.0

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