af 0.3.22 → 0.5.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 (157) hide show
  1. checksums.yaml +14 -6
  2. data/LICENSE +1277 -24
  3. data/Rakefile +24 -87
  4. data/bin/af +7 -2
  5. data/lib/af/version.rb +3 -0
  6. data/lib/vmc.rb +7 -2
  7. data/lib/vmc/cli.rb +475 -0
  8. data/lib/vmc/cli/app/app.rb +45 -0
  9. data/lib/vmc/cli/app/apps.rb +105 -0
  10. data/lib/vmc/cli/app/base.rb +82 -0
  11. data/lib/vmc/cli/app/crashes.rb +46 -0
  12. data/lib/vmc/cli/app/delete.rb +95 -0
  13. data/lib/vmc/cli/app/deprecated.rb +11 -0
  14. data/lib/vmc/cli/app/env.rb +78 -0
  15. data/lib/vmc/cli/app/files.rb +137 -0
  16. data/lib/vmc/cli/app/health.rb +26 -0
  17. data/lib/vmc/cli/app/instances.rb +53 -0
  18. data/lib/vmc/cli/app/logs.rb +76 -0
  19. data/lib/vmc/cli/app/push.rb +107 -0
  20. data/lib/vmc/cli/app/push/create.rb +150 -0
  21. data/lib/vmc/cli/app/push/interactions.rb +100 -0
  22. data/lib/vmc/cli/app/push/sync.rb +64 -0
  23. data/lib/vmc/cli/app/rename.rb +39 -0
  24. data/lib/vmc/cli/app/restart.rb +20 -0
  25. data/lib/vmc/cli/app/scale.rb +71 -0
  26. data/lib/vmc/cli/app/start.rb +93 -0
  27. data/lib/vmc/cli/app/stats.rb +67 -0
  28. data/lib/vmc/cli/app/stop.rb +27 -0
  29. data/lib/vmc/cli/domain/base.rb +12 -0
  30. data/lib/vmc/cli/domain/domains.rb +40 -0
  31. data/lib/vmc/cli/domain/map.rb +55 -0
  32. data/lib/vmc/cli/domain/unmap.rb +56 -0
  33. data/lib/vmc/cli/help.rb +16 -0
  34. data/lib/vmc/cli/interactive.rb +105 -0
  35. data/lib/vmc/cli/organization/base.rb +14 -0
  36. data/lib/vmc/cli/organization/create.rb +32 -0
  37. data/lib/vmc/cli/organization/delete.rb +73 -0
  38. data/lib/vmc/cli/organization/org.rb +45 -0
  39. data/lib/vmc/cli/organization/orgs.rb +35 -0
  40. data/lib/vmc/cli/organization/rename.rb +36 -0
  41. data/lib/vmc/cli/route/base.rb +12 -0
  42. data/lib/vmc/cli/route/map.rb +80 -0
  43. data/lib/vmc/cli/route/routes.rb +26 -0
  44. data/lib/vmc/cli/route/unmap.rb +94 -0
  45. data/lib/vmc/cli/service/base.rb +8 -0
  46. data/lib/vmc/cli/service/bind.rb +44 -0
  47. data/lib/vmc/cli/service/create.rb +126 -0
  48. data/lib/vmc/cli/service/delete.rb +86 -0
  49. data/lib/vmc/cli/service/rename.rb +35 -0
  50. data/lib/vmc/cli/service/service.rb +42 -0
  51. data/lib/vmc/cli/service/services.rb +115 -0
  52. data/lib/vmc/cli/service/unbind.rb +38 -0
  53. data/lib/vmc/cli/space/base.rb +21 -0
  54. data/lib/vmc/cli/space/create.rb +56 -0
  55. data/lib/vmc/cli/space/delete.rb +95 -0
  56. data/lib/vmc/cli/space/rename.rb +39 -0
  57. data/lib/vmc/cli/space/space.rb +64 -0
  58. data/lib/vmc/cli/space/spaces.rb +55 -0
  59. data/lib/vmc/cli/space/take.rb +16 -0
  60. data/lib/vmc/cli/start/base.rb +80 -0
  61. data/lib/vmc/cli/start/colors.rb +13 -0
  62. data/lib/vmc/cli/start/info.rb +122 -0
  63. data/lib/vmc/cli/start/login.rb +92 -0
  64. data/lib/vmc/cli/start/logout.rb +13 -0
  65. data/lib/vmc/cli/start/target.rb +64 -0
  66. data/lib/vmc/cli/start/target_interactions.rb +37 -0
  67. data/lib/vmc/cli/start/targets.rb +16 -0
  68. data/lib/vmc/cli/user/base.rb +29 -0
  69. data/lib/vmc/cli/user/create.rb +39 -0
  70. data/lib/vmc/cli/user/delete.rb +25 -0
  71. data/lib/vmc/cli/user/passwd.rb +50 -0
  72. data/lib/vmc/cli/user/register.rb +42 -0
  73. data/lib/vmc/cli/user/users.rb +32 -0
  74. data/lib/vmc/constants.rb +13 -0
  75. data/lib/vmc/detect.rb +134 -0
  76. data/lib/vmc/errors.rb +17 -0
  77. data/lib/vmc/plugin.rb +56 -0
  78. data/lib/vmc/spacing.rb +89 -0
  79. data/lib/vmc/spec_helper.rb +1 -0
  80. data/lib/vmc/test_support.rb +4 -0
  81. data/lib/vmc/test_support/command_helper.rb +32 -0
  82. data/lib/vmc/test_support/common_input_examples.rb +14 -0
  83. data/lib/vmc/test_support/fake_home_dir.rb +16 -0
  84. data/lib/vmc/test_support/interact_helper.rb +29 -0
  85. data/lib/vmc/version.rb +3 -0
  86. data/spec/assets/hello-sinatra/Gemfile +3 -0
  87. data/spec/assets/hello-sinatra/main.rb +6 -0
  88. data/spec/features/new_user_flow_spec.rb +71 -0
  89. data/spec/spec_helper.rb +63 -0
  90. data/spec/vmc/cli/app/base_spec.rb +17 -0
  91. data/spec/vmc/cli/app/delete_spec.rb +188 -0
  92. data/spec/vmc/cli/app/instances_spec.rb +65 -0
  93. data/spec/vmc/cli/app/push/create_spec.rb +571 -0
  94. data/spec/vmc/cli/app/push_spec.rb +369 -0
  95. data/spec/vmc/cli/app/rename_spec.rb +104 -0
  96. data/spec/vmc/cli/app/scale_spec.rb +81 -0
  97. data/spec/vmc/cli/app/stats_spec.rb +62 -0
  98. data/spec/vmc/cli/domain/map_spec.rb +140 -0
  99. data/spec/vmc/cli/domain/unmap_spec.rb +73 -0
  100. data/spec/vmc/cli/organization/orgs_spec.rb +108 -0
  101. data/spec/vmc/cli/organization/rename_spec.rb +113 -0
  102. data/spec/vmc/cli/route/map_spec.rb +138 -0
  103. data/spec/vmc/cli/route/unmap_spec.rb +215 -0
  104. data/spec/vmc/cli/service/bind_spec.rb +25 -0
  105. data/spec/vmc/cli/service/delete_spec.rb +22 -0
  106. data/spec/vmc/cli/service/rename_spec.rb +105 -0
  107. data/spec/vmc/cli/service/service_spec.rb +23 -0
  108. data/spec/vmc/cli/service/unbind_spec.rb +25 -0
  109. data/spec/vmc/cli/space/rename_spec.rb +102 -0
  110. data/spec/vmc/cli/space/spaces_spec.rb +104 -0
  111. data/spec/vmc/cli/start/info_spec.rb +153 -0
  112. data/spec/vmc/cli/start/login_spec.rb +71 -0
  113. data/spec/vmc/cli/user/create_spec.rb +54 -0
  114. data/spec/vmc/cli/user/passwd_spec.rb +102 -0
  115. data/spec/vmc/cli/user/register_spec.rb +148 -0
  116. data/spec/vmc/cli_spec.rb +448 -0
  117. data/spec/vmc/detect_spec.rb +54 -0
  118. metadata +231 -124
  119. data/README.md +0 -155
  120. data/caldecott_helper/Gemfile +0 -10
  121. data/caldecott_helper/Gemfile.lock +0 -48
  122. data/caldecott_helper/server.rb +0 -43
  123. data/config/clients.yml +0 -17
  124. data/config/micro/offline.conf +0 -2
  125. data/config/micro/paths.yml +0 -22
  126. data/config/micro/refresh_ip.rb +0 -20
  127. data/lib/cli.rb +0 -48
  128. data/lib/cli/commands/admin.rb +0 -81
  129. data/lib/cli/commands/apps.rb +0 -1358
  130. data/lib/cli/commands/base.rb +0 -233
  131. data/lib/cli/commands/manifest.rb +0 -56
  132. data/lib/cli/commands/micro.rb +0 -115
  133. data/lib/cli/commands/misc.rb +0 -147
  134. data/lib/cli/commands/services.rb +0 -217
  135. data/lib/cli/commands/user.rb +0 -70
  136. data/lib/cli/config.rb +0 -176
  137. data/lib/cli/console_helper.rb +0 -163
  138. data/lib/cli/core_ext.rb +0 -122
  139. data/lib/cli/errors.rb +0 -19
  140. data/lib/cli/file_helper.rb +0 -123
  141. data/lib/cli/frameworks.rb +0 -265
  142. data/lib/cli/manifest_helper.rb +0 -316
  143. data/lib/cli/runner.rb +0 -633
  144. data/lib/cli/services_helper.rb +0 -104
  145. data/lib/cli/tunnel_helper.rb +0 -336
  146. data/lib/cli/usage.rb +0 -129
  147. data/lib/cli/version.rb +0 -7
  148. data/lib/cli/zip_util.rb +0 -102
  149. data/lib/vmc/client.rb +0 -574
  150. data/lib/vmc/const.rb +0 -27
  151. data/lib/vmc/micro.rb +0 -56
  152. data/lib/vmc/micro/switcher/base.rb +0 -97
  153. data/lib/vmc/micro/switcher/darwin.rb +0 -19
  154. data/lib/vmc/micro/switcher/dummy.rb +0 -15
  155. data/lib/vmc/micro/switcher/linux.rb +0 -16
  156. data/lib/vmc/micro/switcher/windows.rb +0 -31
  157. data/lib/vmc/micro/vmrun.rb +0 -158
@@ -1,1358 +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
- require 'digest/md5'
10
-
11
- module VMC::Cli::Command
12
-
13
- class Apps < Base
14
- include VMC::Cli::ServicesHelper
15
- include VMC::Cli::ManifestHelper
16
- include VMC::Cli::TunnelHelper
17
- include VMC::Cli::ConsoleHelper
18
- include VMC::Cli::FileHelper
19
-
20
- def list
21
- apps = client.apps
22
- apps.sort! {|a, b| a[:name] <=> b[:name] }
23
- return display JSON.pretty_generate(apps || []) if @options[:json]
24
-
25
- display "\n"
26
- return display "No Applications" if apps.nil? || apps.empty?
27
-
28
- infra_supported = !apps.detect { |a| a[:infra] }.nil?
29
-
30
- apps_table = table do |t|
31
- t.headings = 'Application', '# ', 'Health', 'URLS', 'Services'
32
- t.headings << 'In' if infra_supported
33
- apps.each do |app|
34
- a = [app[:name], app[:instances], health(app), app[:uris].join(', '), app[:services].join(', ')]
35
- if infra_supported
36
- a << ( app[:infra] ? app[:infra][:provider] : " " )
37
- end
38
- t << a
39
- end
40
- end
41
- display apps_table
42
- end
43
-
44
- alias :apps :list
45
-
46
- SLEEP_TIME = 1
47
- LINE_LENGTH = 80
48
-
49
- # Numerators are in secs
50
- TICKER_TICKS = 25/SLEEP_TIME
51
- HEALTH_TICKS = 5/SLEEP_TIME
52
- TAIL_TICKS = 45/SLEEP_TIME
53
- GIVEUP_TICKS = 120/SLEEP_TIME
54
-
55
- def info(what, default=nil)
56
- @options[what] || (@app_info && @app_info[what.to_s]) || default
57
- end
58
-
59
- def console(appname, interactive=true)
60
-
61
- app = client.app_info(appname)
62
- infra_name = app[:infra] ? app[:infra][:name] : 'aws' # FIXME
63
-
64
- unless defined? Caldecott
65
- display "To use `vmc rails-console', you must first install Caldecott:"
66
- display ""
67
- display "\tgem install caldecott"
68
- display ""
69
- display "Note that you'll need a C compiler. If you're on OS X, Xcode"
70
- display "will provide one. If you're on Windows, try DevKit."
71
- display ""
72
- display "This manual step will be removed in the future."
73
- display ""
74
- err "Caldecott is not installed."
75
- end
76
-
77
- #Make sure there is a console we can connect to first
78
- conn_info = console_connection_info appname
79
-
80
- port = pick_tunnel_port(@options[:port] || 20000)
81
-
82
- raise VMC::Client::AuthError unless client.logged_in?
83
-
84
- if not tunnel_pushed?(infra_name)
85
- display "Deploying tunnel application '#{tunnel_appname(infra_name)}'."
86
- auth = UUIDTools::UUID.random_create.to_s
87
- push_caldecott(auth,infra_name)
88
- start_caldecott(infra_name)
89
- else
90
- auth = tunnel_auth(infra_name)
91
- end
92
-
93
-
94
- if not tunnel_healthy?(auth,infra_name)
95
- display "Redeploying tunnel application '#{tunnel_appname(infra_name)}'."
96
- # We don't expect caldecott not to be running, so take the
97
- # most aggressive restart method.. delete/re-push
98
- client.delete_app(tunnel_appname(infra_name))
99
- invalidate_tunnel_app_info(infra_name)
100
- push_caldecott(auth,infra_name)
101
- start_caldecott(infra_name)
102
- end
103
-
104
- start_tunnel(port, conn_info, auth, infra_name)
105
- wait_for_tunnel_start(port)
106
- start_local_console(port, appname) if interactive
107
- port
108
- end
109
-
110
- def start(appname=nil, push=false)
111
- if appname
112
- do_start(appname, push)
113
- else
114
- each_app do |name|
115
- do_start(name, push)
116
- end
117
- end
118
- end
119
-
120
- def stop(appname=nil)
121
- if appname
122
- do_stop(appname)
123
- else
124
- reversed = []
125
- each_app do |name|
126
- reversed.unshift name
127
- end
128
-
129
- reversed.each do |name|
130
- do_stop(name)
131
- end
132
- end
133
- end
134
-
135
- def restart(appname=nil)
136
- stop(appname)
137
- start(appname)
138
- end
139
-
140
- def mem(appname, memsize=nil)
141
- app = client.app_info(appname)
142
- mem = current_mem = mem_quota_to_choice(app[:resources][:memory])
143
- memsize = normalize_mem(memsize) if memsize
144
-
145
- memsize ||= ask(
146
- "Update Memory Reservation?",
147
- :default => current_mem,
148
- :choices => mem_choices
149
- )
150
-
151
- mem = mem_choice_to_quota(mem)
152
- memsize = mem_choice_to_quota(memsize)
153
- current_mem = mem_choice_to_quota(current_mem)
154
-
155
- display "Updating Memory Reservation to #{mem_quota_to_choice(memsize)}: ", false
156
-
157
- # check memsize here for capacity
158
- check_has_capacity_for((memsize - mem) * app[:instances])
159
-
160
- mem = memsize
161
-
162
- if (mem != current_mem)
163
- app[:resources][:memory] = mem
164
- client.update_app(appname, app)
165
- display 'OK'.green
166
- restart appname if app[:state] == 'STARTED'
167
- else
168
- display 'OK'.green
169
- end
170
- end
171
-
172
- def map(appname, url)
173
- app = client.app_info(appname)
174
- uris = app[:uris] || []
175
- uris << url
176
- app[:uris] = uris
177
- client.update_app(appname, app)
178
- display "Successfully mapped url".green
179
- end
180
-
181
- def unmap(appname, url)
182
- app = client.app_info(appname)
183
- uris = app[:uris] || []
184
- url = url.gsub(/^http(s*):\/\//i, '')
185
- deleted = uris.delete(url)
186
- err "Invalid url" unless deleted
187
- app[:uris] = uris
188
- client.update_app(appname, app)
189
- display "Successfully unmapped url".green
190
- end
191
-
192
- def delete(appname=nil)
193
- force = @options[:force]
194
- if @options[:all]
195
- if no_prompt || force || ask("Delete ALL applications?", :default => false)
196
- apps = client.apps
197
- apps.each { |app| delete_app(app[:name], force) }
198
- end
199
- else
200
- err 'No valid appname given' unless appname
201
- delete_app(appname, force)
202
- end
203
- end
204
-
205
- def files(appname, path='/')
206
- return all_files(appname, path) if @options[:all] && !@options[:instance]
207
- instance = @options[:instance] || '0'
208
- content = client.app_files(appname, path, instance)
209
- display content
210
- rescue VMC::Client::NotFound, VMC::Client::TargetError
211
- err 'No such file or directory'
212
- end
213
-
214
- def download(appname, path=nil)
215
- path = File.expand_path(path || "#{appname}.zip" )
216
- banner = "Downloading last pushed source code to #{path}: "
217
- display banner, false
218
- client.app_download(appname, path)
219
- display 'OK'.green
220
- end
221
-
222
- def pull(appname, path=nil)
223
- path = File.expand_path(path || appname)
224
- banner = "Pulling last pushed source code: "
225
- display banner, false
226
- client.app_pull(appname, path)
227
- display 'OK'.green
228
- end
229
-
230
- def clone(src_appname, dest_appname, dest_infra=nil)
231
-
232
- if (@options[:label])
233
- label = @options[:label]
234
- else
235
- label = ''
236
- end
237
-
238
- # FIXME need to ask for dest_appname if nil
239
-
240
- err "Application '#{dest_appname}' already exists" if app_exists?(dest_appname)
241
-
242
- app = client.app_info(src_appname)
243
-
244
- if client.infra_supported?
245
- dest_infra = @options[:infra] || client.infra_name_for_description(
246
- ask("Select Infrastructure",:indexed => true, :choices => client.infra_descriptions))
247
- client.infra = dest_infra
248
- end
249
-
250
- url_template = "#{dest_appname}.${target-base}"
251
- url_resolved = url_template.dup
252
- resolve_lexically(url_resolved)
253
-
254
- url = @options[:url] || ask("Application Deployed URL", :default => url_resolved)
255
-
256
- Dir.mktmpdir do |dir|
257
- zip_path = File.join(dir,src_appname)
258
- pull(src_appname,zip_path)
259
-
260
- display "Cloning '#{src_appname}' to '#{dest_appname}': "
261
-
262
- manifest = {
263
- :name => "#{dest_appname}",
264
- :staging => app[:staging],
265
- :uris => [ url ],
266
- :instances => app[:instances],
267
- :resources => app[:resources]
268
- }
269
- manifest[:staging][:command] = app[:staging][:command] if app[:staging][:command]
270
- manifest[:infra] = { :provider => dest_infra } if dest_infra
271
-
272
- client.create_app(dest_appname, manifest)
273
-
274
- # Stage and upload the app bits.
275
- upload_app_bits(dest_appname, zip_path, dest_infra, label)
276
-
277
- # Clone services
278
- client.services.select { |s| app[:services].include?(s[:name])}.each do |service|
279
- display "Exporting data from #{service[:name]}: ", false
280
- export_info = client.export_service(service[:name])
281
- if export_info
282
- display 'OK'.green
283
- else
284
- err "Export data from '#{service}': failed"
285
- end
286
- cloned_service_name = generate_cloned_service_name(src_appname,dest_appname,service[:name],dest_infra)
287
- display "Creating service #{cloned_service_name}: ", false
288
- client.create_service(dest_infra, service[:vendor], cloned_service_name)
289
- display 'OK'.green
290
- display "Binding service #{cloned_service_name}: ", false
291
- client.bind_service(cloned_service_name, dest_appname)
292
- display 'OK'.green
293
- display "Importing data to #{cloned_service_name}: ", false
294
- import_info = client.import_service(cloned_service_name,export_info[:uri])
295
- if import_info
296
- display 'OK'.green
297
- else
298
- err "Import data into '#{service}' failed"
299
- end
300
- end
301
-
302
- no_start = @options[:nostart]
303
- start(dest_appname, true) unless no_start
304
-
305
- end
306
- end
307
-
308
- def logs(appname)
309
- # Check if we have an app before progressing further
310
- client.app_info(appname)
311
- return grab_all_logs(appname) if @options[:all] && !@options[:instance]
312
- instance = @options[:instance] || '0'
313
- grab_logs(appname, instance)
314
- end
315
-
316
- def crashes(appname, print_results=true, since=0)
317
- crashed = client.app_crashes(appname)[:crashes]
318
- crashed.delete_if { |c| c[:since] < since }
319
- instance_map = {}
320
-
321
- # return display JSON.pretty_generate(apps) if @options[:json]
322
-
323
-
324
- counter = 0
325
- crashed = crashed.to_a.sort { |a,b| a[:since] - b[:since] }
326
- crashed_table = table do |t|
327
- t.headings = 'Name', 'Instance ID', 'Crashed Time'
328
- crashed.each do |crash|
329
- name = "#{appname}-#{counter += 1}"
330
- instance_map[name] = crash[:instance]
331
- t << [name, crash[:instance], Time.at(crash[:since]).strftime("%m/%d/%Y %I:%M%p")]
332
- end
333
- end
334
-
335
- VMC::Cli::Config.store_instances(instance_map)
336
-
337
- if @options[:json]
338
- return display JSON.pretty_generate(crashed)
339
- elsif print_results
340
- display "\n"
341
- if crashed.empty?
342
- display "No crashed instances for [#{appname}]" if print_results
343
- else
344
- display crashed_table if print_results
345
- end
346
- end
347
-
348
- crashed
349
- end
350
-
351
- def crashlogs(appname)
352
- instance = @options[:instance] || '0'
353
- grab_crash_logs(appname, instance)
354
- end
355
-
356
- def instances(appname, num=nil)
357
- if num
358
- change_instances(appname, num)
359
- else
360
- get_instances(appname)
361
- end
362
- end
363
-
364
- def stats(appname=nil)
365
- if appname
366
- display "\n", false
367
- do_stats(appname)
368
- else
369
- each_app do |n|
370
- display "\n#{n}:"
371
- do_stats(n)
372
- end
373
- end
374
- end
375
-
376
- def update(appname=nil)
377
- if (@options[:label])
378
- label = @options[:label]
379
- else
380
- label = ''
381
- end
382
-
383
- if appname
384
- app = client.app_info(appname)
385
- if @options[:canary]
386
- display "[--canary] is deprecated and will be removed in a future version".yellow
387
- end
388
- infra = app[:infra] ? app[:infra][:provider] : nil
389
- upload_app_bits(appname, @path, infra, label)
390
- restart appname if app[:state] == 'STARTED'
391
- else
392
- each_app do |name|
393
- display "Updating application '#{name}'..."
394
-
395
- app = client.app_info(name)
396
- infra = app[:infra] ? app[:infra][:provider] : nil
397
- upload_app_bits(name, @application, infra, label)
398
- restart name if app[:state] == 'STARTED'
399
- end
400
- end
401
- end
402
-
403
- def push(appname=nil)
404
- if (@options[:label])
405
- label = @options[:label]
406
- else
407
- label = ''
408
- end
409
-
410
- unless no_prompt || @options[:path]
411
- proceed = ask(
412
- 'Would you like to deploy from the current directory?',
413
- :default => true
414
- )
415
-
416
- unless proceed
417
- @path = ask('Deployment path')
418
- end
419
- end
420
-
421
- pushed = false
422
- each_app(false) do |name|
423
- display "Pushing application '#{name}'..." if name
424
- do_push(label, name)
425
- pushed = true
426
- end
427
-
428
- unless pushed
429
- @application = @path
430
- do_push(label, appname)
431
- end
432
- end
433
-
434
- def history(appname)
435
- if app_exists?(appname)
436
- history = client.app_history(appname)
437
-
438
- # return display JSON.pretty_generate(history) if @options[:json]
439
- return display "No history available" if history.empty?
440
- history_table = table do |t|
441
- t.headings = 'Label', 'Release ', 'By User', 'Release Date', 'Hash', 'Changed'
442
- history.each do |app|
443
- a = [app[:label], "v" << app[:release].to_s, app[:updated_by], Time.parse(app[:updated_at]).to_time, app[:update_hash][0..9], app[:is_changed]==true ? "Yes" : "No"]
444
- t << a
445
- end
446
- end
447
- display "\n"
448
- display history_table
449
- else
450
- display "No application named '" + appname + "' found"
451
- end
452
- end
453
-
454
- def hash(path=nil)
455
- if (@options[:full])
456
- full = true
457
- else
458
- full = false
459
- end
460
-
461
- current_dir = false
462
- if not path
463
- current_dir = true
464
- path = @path
465
- end
466
-
467
- hash = hash_app_bits(File.expand_path(path))
468
-
469
- if full
470
- display hash.to_s
471
- else
472
- if current_dir
473
- display "The hash for the current directory is: " + hash.to_s[0..9]
474
- else
475
- display "The hash for " + path + " is: " + hash.to_s[0..9]
476
- end
477
- end
478
- end
479
-
480
- def diff(appname)
481
- if app_exists?(appname)
482
- diff = client.app_diff(appname)[0]
483
- return display "No diff available" if diff.nil? or diff.empty?
484
- hash = hash_app_bits(@path)
485
-
486
- comp = (hash == diff[:update_hash])
487
- comparison = comp ? "Deployed app '" + appname + "' matches app in current directory" : "Deployed app '" + appname + "' does not match app in current directory"
488
- display comparison
489
- else
490
- display "No application named '" + appname + "' found"
491
- end
492
- end
493
-
494
- def environment(appname)
495
- app = client.app_info(appname)
496
- env = app[:env] || []
497
- return display JSON.pretty_generate(env) if @options[:json]
498
- return display "No Environment Variables" if env.empty?
499
- etable = table do |t|
500
- t.headings = 'Variable', 'Value'
501
- env.each do |e|
502
- k,v = e.split('=', 2)
503
- t << [k, v]
504
- end
505
- end
506
- display "\n"
507
- display etable
508
- end
509
-
510
- def environment_add(appname, k, v=nil)
511
- app = client.app_info(appname)
512
- env = app[:env] || []
513
- k,v = k.split('=', 2) unless v
514
- env << "#{k}=#{v}"
515
- display "Adding Environment Variable [#{k}=#{v}]: ", false
516
- app[:env] = env
517
- client.update_app(appname, app)
518
- display 'OK'.green
519
- restart appname if app[:state] == 'STARTED'
520
- end
521
-
522
- def environment_del(appname, variable)
523
- app = client.app_info(appname)
524
- env = app[:env] || []
525
- deleted_env = nil
526
- env.each do |e|
527
- k,v = e.split('=')
528
- if (k == variable)
529
- deleted_env = e
530
- break;
531
- end
532
- end
533
- display "Deleting Environment Variable [#{variable}]: ", false
534
- if deleted_env
535
- env.delete(deleted_env)
536
- app[:env] = env
537
- client.update_app(appname, app)
538
- display 'OK'.green
539
- restart appname if app[:state] == 'STARTED'
540
- else
541
- display 'OK'.green
542
- end
543
- end
544
-
545
- def rename(oldname, newname)
546
- # Check if new app name is taken
547
- if newname
548
- err "Application '#{newname}' already exists" if app_exists?(newname)
549
- else
550
- raise VMC::Client::AuthError unless client.logged_in?
551
- end
552
-
553
- app = client.app_info(oldname)
554
- app[:name] = newname
555
- client.update_app(oldname, app)
556
- display "Successfully updated app name to #{newname}".green
557
- end
558
-
559
- private
560
-
561
- def app_exists?(appname)
562
- app_info = client.app_info(appname)
563
- app_info != nil
564
- rescue VMC::Client::NotFound
565
- false
566
- end
567
-
568
- def check_deploy_directory(path)
569
- err 'Deployment path does not exist' unless File.exists? path
570
- return if File.expand_path(Dir.tmpdir) != File.expand_path(path)
571
- err "Can't deploy applications from staging directory: [#{Dir.tmpdir}]"
572
- end
573
-
574
- def upload_app_bits(appname, path, infra, label=nil)
575
- display 'Uploading Application:'
576
-
577
- upload_file, file = "#{Dir.tmpdir}/#{appname}.zip", nil
578
- FileUtils.rm_f(upload_file)
579
-
580
- explode_dir = "#{Dir.tmpdir}/.vmc_#{appname}_files"
581
- FileUtils.rm_rf(explode_dir) # Make sure we didn't have anything left over..
582
-
583
- if path =~ /\.(war|zip)$/
584
- #single file that needs unpacking
585
- VMC::Cli::ZipUtil.unpack(path, explode_dir)
586
- elsif !File.directory? path
587
- #single file that doesn't need unpacking
588
- FileUtils.mkdir(explode_dir)
589
- FileUtils.cp(path,explode_dir)
590
- else
591
- Dir.chdir(path) do
592
- # Stage the app appropriately and do the appropriate fingerprinting, etc.
593
- if war_file = Dir.glob('*.war').first
594
- VMC::Cli::ZipUtil.unpack(war_file, explode_dir)
595
- elsif zip_file = Dir.glob('*.zip').first
596
- VMC::Cli::ZipUtil.unpack(zip_file, explode_dir)
597
- else
598
- FileUtils.mkdir(explode_dir)
599
-
600
- afi = VMC::Cli::FileHelper::AppFogIgnore.from_file("#{path}")
601
-
602
- files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
603
- check_unreachable_links(path,afi.included_files(files))
604
-
605
- copy_files( path, ignore_sockets( afi.included_files(files)), explode_dir )
606
-
607
- end
608
- end
609
- end
610
-
611
- # compute hash for versioning info
612
- tarfile = VMC::Cli::ZipUtil.tar(explode_dir)
613
- hash = Digest::MD5.file(tarfile)
614
-
615
- # Send the resource list to the cloudcontroller, the response will tell us what it already has..
616
- unless @options[:noresources]
617
- display ' Checking for available resources: ', false
618
- fingerprints = []
619
- total_size = 0
620
- resource_files = Dir.glob("#{explode_dir}/**/*", File::FNM_DOTMATCH)
621
- resource_files.each do |filename|
622
- next if (File.directory?(filename) || !File.exists?(filename))
623
- fingerprints << {
624
- :size => File.size(filename),
625
- :sha1 => Digest::SHA1.file(filename).hexdigest,
626
- :fn => filename
627
- }
628
- total_size += File.size(filename)
629
- end
630
-
631
- # Check to see if the resource check is worth the round trip
632
- if (total_size > (64*1024)) # 64k for now
633
- # Send resource fingerprints to the cloud controller
634
- # FIXME where do I get infra?
635
- appcloud_resources = client.check_resources(fingerprints,infra)
636
- end
637
- display 'OK'.green
638
-
639
- if appcloud_resources
640
- display ' Processing resources: ', false
641
- # We can then delete what we do not need to send.
642
- appcloud_resources.each do |resource|
643
- FileUtils.rm_f resource[:fn]
644
- # adjust filenames sans the explode_dir prefix
645
- resource[:fn].sub!("#{explode_dir}/", '')
646
- end
647
- display 'OK'.green
648
- end
649
-
650
- end
651
-
652
- # If no resource needs to be sent, add an empty file to ensure we have
653
- # a multi-part request that is expected by nginx fronting the CC.
654
- if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
655
- Dir.chdir(explode_dir) do
656
- File.new(".__empty__", "w")
657
- end
658
- end
659
- # Perform Packing of the upload bits here.
660
- display ' Packing application: ', false
661
- VMC::Cli::ZipUtil.pack(explode_dir, upload_file)
662
- display 'OK'.green
663
-
664
- upload_size = File.size(upload_file);
665
- if upload_size > 1024*1024
666
- upload_size = (upload_size/(1024.0*1024.0)).round.to_s + 'M'
667
- elsif upload_size > 0
668
- upload_size = (upload_size/1024.0).round.to_s + 'K'
669
- else
670
- upload_size = '0K'
671
- end
672
-
673
- upload_str = " Uploading (#{upload_size}): "
674
- display upload_str, false
675
-
676
- FileWithPercentOutput.display_str = upload_str
677
- FileWithPercentOutput.upload_size = File.size(upload_file);
678
- file = FileWithPercentOutput.open(upload_file, 'rb')
679
-
680
- client.upload_app(appname, file, hash, label.nil? ? "": label, appcloud_resources)
681
- display 'OK'.green if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
682
-
683
- display 'Push Status: ', false
684
- display 'OK'.green
685
-
686
- ensure
687
- # Cleanup if we created an exploded directory.
688
- FileUtils.rm_f(upload_file) if upload_file
689
- FileUtils.rm_rf(explode_dir) if explode_dir
690
- FileUtils.rm_rf(tarfile) if tarfile
691
- end
692
-
693
- # To support the hash command
694
- def hash_app_bits(path)
695
- explode_dir = "#{Dir.tmpdir}/.vmc_temp_files"
696
- FileUtils.rm_rf(explode_dir) # Make sure we didn't have anything left over..
697
-
698
- if path =~ /\.(war|zip)$/
699
- #single file that needs unpacking
700
- VMC::Cli::ZipUtil.unpack(path, explode_dir)
701
- elsif !File.directory? path
702
- #single file that doesn't need unpacking
703
- FileUtils.mkdir(explode_dir)
704
- FileUtils.cp(path,explode_dir)
705
- else
706
- Dir.chdir(path) do
707
- # Stage the app appropriately and do the appropriate fingerprinting, etc.
708
- if war_file = Dir.glob('*.war').first
709
- VMC::Cli::ZipUtil.unpack(war_file, explode_dir)
710
- elsif zip_file = Dir.glob('*.zip').first
711
- VMC::Cli::ZipUtil.unpack(zip_file, explode_dir)
712
- else
713
- FileUtils.mkdir(explode_dir)
714
-
715
- afi = VMC::Cli::FileHelper::AppFogIgnore.from_file("#{path}")
716
-
717
- files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
718
- check_unreachable_links(path,afi.included_files(files))
719
-
720
- copy_files( path, ignore_sockets( afi.included_files(files)), explode_dir )
721
-
722
- end
723
- end
724
- end
725
-
726
- # compute hash for versioning info
727
- tarfile = VMC::Cli::ZipUtil.tar(explode_dir)
728
- hash = Digest::MD5.file(tarfile)
729
-
730
- ensure
731
- # Cleanup if we created an exploded directory.
732
- FileUtils.rm_rf(explode_dir) if explode_dir
733
- FileUtils.rm_rf(tarfile) if tarfile
734
-
735
- hash
736
- end
737
-
738
- def check_app_limit
739
- usage = client_info[:usage]
740
- limits = client_info[:limits]
741
- return unless usage and limits and limits[:apps]
742
- if limits[:apps] == usage[:apps]
743
- display "Not enough capacity for operation.".red
744
- tapps = limits[:apps] || 0
745
- apps = usage[:apps] || 0
746
- err "Current Usage: (#{apps} of #{tapps} total apps already in use)"
747
- end
748
- end
749
-
750
- def check_has_capacity_for(mem_wanted)
751
- usage = client_info[:usage]
752
- limits = client_info[:limits]
753
- return unless usage and limits
754
- available_for_use = limits[:memory].to_i - usage[:memory].to_i
755
- if mem_wanted > available_for_use
756
- tmem = pretty_size(limits[:memory]*1024*1024)
757
- mem = pretty_size(usage[:memory]*1024*1024)
758
- display "Not enough capacity for operation.".yellow
759
- available = pretty_size(available_for_use * 1024 * 1024)
760
- err "Current Usage: (#{mem} of #{tmem} total, #{available} available for use)"
761
- end
762
- end
763
-
764
- def mem_choices
765
- default = ['64M', '128M', '256M', '512M', '1G', '2G']
766
-
767
- return default unless client_info
768
- return default unless (usage = client_info[:usage] and limits = client_info[:limits])
769
-
770
- available_for_use = limits[:memory].to_i - usage[:memory].to_i
771
- check_has_capacity_for(64) if available_for_use < 64
772
- return ['64M'] if available_for_use < 128
773
- return ['64M', '128M'] if available_for_use < 256
774
- return ['64M', '128M', '256M'] if available_for_use < 512
775
- return ['64M', '128M', '256M', '512M'] if available_for_use < 1024
776
- return ['64M', '128M', '256M', '512M', '1G'] if available_for_use < 2048
777
- return ['64M', '128M', '256M', '512M', '1G', '2G']
778
- end
779
-
780
- def normalize_mem(mem)
781
- return mem if /K|G|M/i =~ mem
782
- "#{mem}M"
783
- end
784
-
785
- def mem_choice_to_quota(mem_choice)
786
- (mem_choice =~ /(\d+)M/i) ? mem_quota = $1.to_i : mem_quota = mem_choice.to_i * 1024
787
- mem_quota
788
- end
789
-
790
- def mem_quota_to_choice(mem)
791
- if mem < 1024
792
- mem_choice = "#{mem}M"
793
- else
794
- mem_choice = "#{(mem/1024).to_i}G"
795
- end
796
- mem_choice
797
- end
798
-
799
- def get_instances(appname)
800
- instances_info_envelope = client.app_instances(appname)
801
- # Empty array is returned if there are no instances running.
802
- instances_info_envelope = {} if instances_info_envelope.is_a?(Array)
803
-
804
- instances_info = instances_info_envelope[:instances] || []
805
- instances_info = instances_info.sort {|a,b| a[:index] - b[:index]}
806
-
807
- return display JSON.pretty_generate(instances_info) if @options[:json]
808
-
809
- return display "No running instances for [#{appname}]".yellow if instances_info.empty?
810
-
811
- instances_table = table do |t|
812
- show_debug = instances_info.any? { |e| e[:debug_port] }
813
-
814
- headings = ['Index', 'State', 'Start Time']
815
- headings << 'Debug IP' if show_debug
816
- headings << 'Debug Port' if show_debug
817
-
818
- t.headings = headings
819
-
820
- instances_info.each do |entry|
821
- row = [entry[:index], entry[:state], Time.at(entry[:since]).strftime("%m/%d/%Y %I:%M%p")]
822
- row << entry[:debug_ip] if show_debug
823
- row << entry[:debug_port] if show_debug
824
- t << row
825
- end
826
- end
827
- display "\n"
828
- display instances_table
829
- end
830
-
831
- def change_instances(appname, instances)
832
- app = client.app_info(appname)
833
-
834
- match = instances.match(/([+-])?\d+/)
835
- err "Invalid number of instances '#{instances}'" unless match
836
-
837
- instances = instances.to_i
838
- current_instances = app[:instances]
839
- new_instances = match.captures[0] ? current_instances + instances : instances
840
- err "There must be at least 1 instance." if new_instances < 1
841
-
842
- if current_instances == new_instances
843
- display "Application [#{appname}] is already running #{new_instances} instance#{'s' if new_instances > 1}.".yellow
844
- return
845
- end
846
-
847
- up_or_down = new_instances > current_instances ? 'up' : 'down'
848
- display "Scaling Application instances #{up_or_down} to #{new_instances}: ", false
849
- app[:instances] = new_instances
850
- client.update_app(appname, app)
851
- display 'OK'.green
852
- end
853
-
854
- def health(d)
855
- return 'N/A' unless (d and d[:state])
856
- return 'STOPPED' if d[:state] == 'STOPPED'
857
-
858
- healthy_instances = d[:runningInstances]
859
- expected_instance = d[:instances]
860
- health = nil
861
-
862
- if d[:state] == "STARTED" && expected_instance > 0 && healthy_instances
863
- health = format("%.3f", healthy_instances.to_f / expected_instance).to_f
864
- end
865
-
866
- if health
867
- if health == 1.0
868
- return "RUNNING"
869
- else
870
- return "#{(health * 100).round}%"
871
- end
872
- elsif d[:state] == "STARTED"
873
- return 'N/A' # unstarted instances
874
- else
875
- return d[:state]
876
- end
877
- end
878
-
879
- def app_started_properly(appname, error_on_health)
880
- app = client.app_info(appname)
881
- case health(app)
882
- when 'N/A'
883
- # Health manager not running.
884
- err "\nApplication '#{appname}'s state is undetermined, not enough information available." if error_on_health
885
- return false
886
- when 'RUNNING'
887
- return true
888
- else
889
- if app[:meta][:debug] == "suspend"
890
- display "\nApplication [#{appname}] has started in a mode that is waiting for you to trigger startup."
891
- return true
892
- else
893
- return false
894
- end
895
- end
896
- end
897
-
898
- def display_logfile(path, content, instance='0', banner=nil)
899
- banner ||= "====> #{path} <====\n\n"
900
-
901
- unless content.empty?
902
- display banner
903
- prefix = "[#{instance}: #{path}] -".bold if @options[:prefixlogs]
904
- unless prefix
905
- display content
906
- else
907
- lines = content.split("\n")
908
- lines.each { |line| display "#{prefix} #{line}"}
909
- end
910
- display ''
911
- end
912
- end
913
-
914
- def grab_all_logs(appname)
915
- instances_info_envelope = client.app_instances(appname)
916
- return if instances_info_envelope.is_a?(Array)
917
- instances_info = instances_info_envelope[:instances] || []
918
- instances_info.each do |entry|
919
- grab_logs(appname, entry[:index])
920
- end
921
- end
922
-
923
- def grab_logs(appname, instance)
924
- files_under(appname, instance, "/logs").each do |path|
925
- begin
926
- content = client.app_files(appname, path, instance)
927
- display_logfile(path, content, instance)
928
- rescue VMC::Client::NotFound, VMC::Client::TargetError
929
- end
930
- end
931
- end
932
-
933
- def files_under(appname, instance, path)
934
- client.app_files(appname, path, instance).split("\n").collect do |l|
935
- "#{path}/#{l.split[0]}"
936
- end
937
- rescue VMC::Client::NotFound, VMC::Client::TargetError
938
- []
939
- end
940
-
941
- def grab_crash_logs(appname, instance, was_staged=false)
942
- # stage crash info
943
- crashes(appname, false) unless was_staged
944
-
945
- instance ||= '0'
946
- map = VMC::Cli::Config.instances
947
- instance = map[instance] if map[instance]
948
-
949
- (files_under(appname, instance, "/logs") +
950
- files_under(appname, instance, "/app/logs") +
951
- files_under(appname, instance, "/app/log")).each do |path|
952
- content = client.app_files(appname, path, instance)
953
- display_logfile(path, content, instance)
954
- end
955
- end
956
-
957
- def grab_startup_tail(appname, since = 0)
958
- new_lines = 0
959
- path = "logs/startup.log"
960
- content = client.app_files(appname, path)
961
- if content && !content.empty?
962
- display "\n==== displaying startup log ====\n\n" if since == 0
963
- response_lines = content.split("\n")
964
- lines = response_lines.size
965
- tail = response_lines[since, lines] || []
966
- new_lines = tail.size
967
- display tail.join("\n") if new_lines > 0
968
- end
969
- since + new_lines
970
- rescue VMC::Client::NotFound, VMC::Client::TargetError
971
- 0
972
- end
973
-
974
- def provisioned_services_apps_hash
975
- apps = client.apps
976
- services_apps_hash = {}
977
- apps.each {|app|
978
- app[:services].each { |svc|
979
- svc_apps = services_apps_hash[svc]
980
- unless svc_apps
981
- svc_apps = Set.new
982
- services_apps_hash[svc] = svc_apps
983
- end
984
- svc_apps.add(app[:name])
985
- } unless app[:services] == nil
986
- }
987
- services_apps_hash
988
- end
989
-
990
- def delete_app(appname, force)
991
- app = client.app_info(appname)
992
- services_to_delete = []
993
- app_services = app[:services]
994
- services_apps_hash = provisioned_services_apps_hash
995
- app_services.each { |service|
996
- del_service = force && no_prompt
997
- unless no_prompt || force
998
- del_service = ask(
999
- "Provisioned service [#{service}] detected, would you like to delete it?",
1000
- :default => false
1001
- )
1002
-
1003
- if del_service
1004
- apps_using_service = services_apps_hash[service].reject!{ |app| app == appname}
1005
- if apps_using_service.size > 0
1006
- del_service = ask(
1007
- "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?",
1008
- :default => false
1009
- )
1010
- end
1011
- end
1012
- end
1013
- services_to_delete << service if del_service
1014
- }
1015
-
1016
- display "Deleting application [#{appname}]: ", false
1017
- client.delete_app(appname)
1018
- display 'OK'.green
1019
-
1020
- services_to_delete.each do |s|
1021
- delete_service_banner(s)
1022
- end
1023
- end
1024
-
1025
- def do_start(appname, push=false)
1026
- app = client.app_info(appname)
1027
- return display "Application '#{appname}' could not be found".red if app.nil?
1028
- return display "Application '#{appname}' already started".yellow if app[:state] == 'STARTED'
1029
-
1030
-
1031
-
1032
- if @options[:debug]
1033
- runtimes = client.runtimes_info
1034
- return display "Cannot get runtime information." unless runtimes
1035
-
1036
- runtime = runtimes[app[:staging][:stack].to_sym]
1037
- return display "Unknown runtime." unless runtime
1038
-
1039
- unless runtime[:debug_modes] and runtime[:debug_modes].include? @options[:debug]
1040
- modes = runtime[:debug_modes] || []
1041
-
1042
- display "\nApplication '#{appname}' cannot start in '#{@options[:debug]}' mode"
1043
-
1044
- if push
1045
- display "Try 'vmc start' with one of the following modes: #{modes.inspect}"
1046
- else
1047
- display "Available modes: #{modes.inspect}"
1048
- end
1049
-
1050
- return
1051
- end
1052
- end
1053
-
1054
- banner = "Staging Application '#{appname}': "
1055
- display banner, false
1056
-
1057
- t = Thread.new do
1058
- count = 0
1059
- while count < TAIL_TICKS do
1060
- display '.', false
1061
- sleep SLEEP_TIME
1062
- count += 1
1063
- end
1064
- end
1065
-
1066
- app[:state] = 'STARTED'
1067
- app[:debug] = @options[:debug]
1068
- app[:console] = VMC::Cli::Framework.lookup_by_framework(app[:staging][:model]).console
1069
- client.update_app(appname, app)
1070
-
1071
- Thread.kill(t)
1072
- clear(LINE_LENGTH)
1073
- display "#{banner}#{'OK'.green}"
1074
-
1075
- banner = "Starting Application '#{appname}': "
1076
- display banner, false
1077
-
1078
- count = log_lines_displayed = 0
1079
- failed = false
1080
- start_time = Time.now.to_i
1081
-
1082
- loop do
1083
- display '.', false unless count > TICKER_TICKS
1084
- sleep SLEEP_TIME
1085
-
1086
- break if app_started_properly(appname, count > HEALTH_TICKS)
1087
-
1088
- if !crashes(appname, false, start_time).empty?
1089
- # Check for the existance of crashes
1090
- display "\nError: Application [#{appname}] failed to start, logs information below.\n".red
1091
- grab_crash_logs(appname, '0', true)
1092
- if push and !no_prompt
1093
- display "\n"
1094
- delete_app(appname, false) if ask "Delete the application?", :default => true
1095
- end
1096
- failed = true
1097
- break
1098
- elsif count > TAIL_TICKS
1099
- log_lines_displayed = grab_startup_tail(appname, log_lines_displayed)
1100
- end
1101
-
1102
- count += 1
1103
- if count > GIVEUP_TICKS # 2 minutes
1104
- display "\nApplication is taking too long to start, check your logs".yellow
1105
- break
1106
- end
1107
- end
1108
- exit(false) if failed
1109
- clear(LINE_LENGTH)
1110
- display "#{banner}#{'OK'.green}"
1111
- end
1112
-
1113
- def do_stop(appname)
1114
- app = client.app_info(appname)
1115
- return display "Application '#{appname}' already stopped".yellow if app[:state] == 'STOPPED'
1116
- display "Stopping Application '#{appname}': ", false
1117
- app[:state] = 'STOPPED'
1118
- client.update_app(appname, app)
1119
- display 'OK'.green
1120
- end
1121
-
1122
- def do_push(label, appname=nil)
1123
-
1124
- unless @app_info || no_prompt
1125
- @manifest = { "applications" => { @path => { "name" => appname } } }
1126
-
1127
- interact
1128
-
1129
- if ask("Would you like to save this configuration?", :default => false)
1130
- save_manifest
1131
- end
1132
-
1133
- resolve_manifest(@manifest)
1134
-
1135
- @app_info = @manifest["applications"][@path]
1136
- end
1137
-
1138
- instances = info(:instances, 1)
1139
- exec = info(:exec, 'thin start')
1140
-
1141
- ignore_framework = @options[:noframework]
1142
- no_start = @options[:nostart]
1143
-
1144
- appname ||= info(:name)
1145
- url = info(:url) || info(:urls)
1146
- mem, memswitch = nil, info(:mem)
1147
- memswitch = normalize_mem(memswitch) if memswitch
1148
- command = info(:command)
1149
- runtime = info(:runtime)
1150
- infra = info(:infra)
1151
-
1152
- if client.infra_supported? && infra
1153
- err "Infra '#{infra}' is not valid" unless client.infra_valid?(infra)
1154
- end
1155
-
1156
- # Check app existing upfront if we have appname
1157
- app_checked = false
1158
- if appname
1159
- err "Application '#{appname}' already exists, use update" if app_exists?(appname)
1160
- app_checked = true
1161
- else
1162
- raise VMC::Client::AuthError unless client.logged_in?
1163
- end
1164
-
1165
- # check if we have hit our app limit
1166
- check_app_limit
1167
- # check memsize here for capacity
1168
- if memswitch && !no_start
1169
- check_has_capacity_for(mem_choice_to_quota(memswitch) * instances)
1170
- end
1171
-
1172
- appname ||= ask("Application Name") unless no_prompt
1173
- err "Application Name required." if appname.nil? || appname.empty?
1174
-
1175
- check_deploy_directory(@application)
1176
-
1177
- if !app_checked and app_exists?(appname)
1178
- err "Application '#{appname}' already exists, use update or delete."
1179
- end
1180
-
1181
- if ignore_framework
1182
- framework = VMC::Cli::Framework.new
1183
- elsif f = info(:framework)
1184
- info = Hash[f["info"].collect { |k, v| [k.to_sym, v] }]
1185
-
1186
- framework = VMC::Cli::Framework.create(f["name"], info)
1187
- exec = framework.exec if framework && framework.exec
1188
- else
1189
- framework = detect_framework(prompt_ok)
1190
- end
1191
-
1192
- err "Application Type undetermined for path '#{@application}'" unless framework
1193
-
1194
- if not runtime
1195
- default_runtime = framework.default_runtime @application
1196
- runtime = detect_runtime(default_runtime, !no_prompt) if framework.prompt_for_runtime?
1197
- end
1198
- command = ask("Start Command") if !command && framework.require_start_command?
1199
-
1200
- default_url = "None"
1201
- default_url = "#{appname}.#{client.suggest_url(infra)}" if framework.require_url?
1202
-
1203
- unless no_prompt || url || !framework.require_url?
1204
- url = ask(
1205
- "Application Deployed URL",
1206
- :default => default_url
1207
- )
1208
-
1209
- # common error case is for prompted users to answer y or Y or yes or
1210
- # YES to this ask() resulting in an unintended URL of y. Special case
1211
- # this common error
1212
- url = nil if YES_SET.member? url
1213
- end
1214
- url = nil if url == "None"
1215
- default_url = nil if default_url == "None"
1216
- url ||= default_url
1217
-
1218
- if memswitch
1219
- mem = memswitch
1220
- elsif prompt_ok
1221
- mem = ask("Memory Reservation",
1222
- :default => framework.memory(runtime),
1223
- :choices => mem_choices)
1224
- else
1225
- mem = framework.memory runtime
1226
- end
1227
-
1228
- # Set to MB number
1229
- mem_quota = mem_choice_to_quota(mem)
1230
-
1231
- # check memsize here for capacity
1232
- check_has_capacity_for(mem_quota * instances) unless no_start
1233
-
1234
- display 'Creating Application: ', false
1235
-
1236
- manifest = {
1237
- :name => "#{appname}",
1238
- :staging => {
1239
- :framework => framework.name,
1240
- :runtime => runtime
1241
- },
1242
- :uris => Array(url),
1243
- :instances => instances,
1244
- :resources => {
1245
- :memory => mem_quota
1246
- }
1247
- }
1248
- manifest[:staging][:command] = command if command
1249
- manifest[:infra] = { :provider => infra } if infra
1250
-
1251
- # Send the manifest to the cloud controller
1252
- client.create_app(appname, manifest)
1253
- display 'OK'.green
1254
-
1255
-
1256
- existing = Set.new(client.services.collect { |s| s[:name] })
1257
-
1258
- if @app_info && services = @app_info["services"]
1259
- services.each do |name, info|
1260
- unless existing.include? name
1261
- create_service_banner(info["type"], name, true, infra)
1262
- end
1263
-
1264
- bind_service_banner(name, appname)
1265
- end
1266
- end
1267
-
1268
- # Stage and upload the app bits.
1269
- upload_app_bits(appname, @application, infra, label)
1270
-
1271
- start(appname, true) unless no_start
1272
- end
1273
-
1274
- def do_stats(appname)
1275
- stats = client.app_stats(appname)
1276
- return display JSON.pretty_generate(stats) if @options[:json]
1277
-
1278
- stats_table = table do |t|
1279
- t.headings = 'Instance', 'CPU (Cores)', 'Memory (limit)', 'Disk (limit)', 'Uptime'
1280
- stats.each do |entry|
1281
- index = entry[:instance]
1282
- stat = entry[:stats]
1283
- hp = "#{stat[:host]}:#{stat[:port]}"
1284
- uptime = uptime_string(stat[:uptime])
1285
- usage = stat[:usage]
1286
- if usage
1287
- cpu = usage[:cpu]
1288
- mem = (usage[:mem] * 1024) # mem comes in K's
1289
- disk = usage[:disk]
1290
- end
1291
- mem_quota = stat[:mem_quota]
1292
- disk_quota = stat[:disk_quota]
1293
- mem = "#{pretty_size(mem)} (#{pretty_size(mem_quota, 0)})"
1294
- disk = "#{pretty_size(disk)} (#{pretty_size(disk_quota, 0)})"
1295
- cpu = cpu ? cpu.to_s : 'NA'
1296
- cpu = "#{cpu}% (#{stat[:cores]})"
1297
- t << [index, cpu, mem, disk, uptime]
1298
- end
1299
- end
1300
-
1301
- if stats.empty?
1302
- display "No running instances for [#{appname}]".yellow
1303
- else
1304
- display stats_table
1305
- end
1306
- end
1307
-
1308
- def all_files(appname, path)
1309
- instances_info_envelope = client.app_instances(appname)
1310
- return if instances_info_envelope.is_a?(Array)
1311
- instances_info = instances_info_envelope[:instances] || []
1312
- instances_info.each do |entry|
1313
- begin
1314
- content = client.app_files(appname, path, entry[:index])
1315
- display_logfile(
1316
- path,
1317
- content,
1318
- entry[:index],
1319
- "====> [#{entry[:index]}: #{path}] <====\n".bold
1320
- )
1321
- rescue VMC::Client::NotFound, VMC::Client::TargetError
1322
- end
1323
- end
1324
- end
1325
- end
1326
-
1327
- class FileWithPercentOutput < ::File
1328
- class << self
1329
- attr_accessor :display_str, :upload_size
1330
- end
1331
-
1332
- def update_display(rsize)
1333
- @read ||= 0
1334
- @read += rsize
1335
- p = (@read * 100 / FileWithPercentOutput.upload_size).to_i
1336
- unless VMC::Cli::Config.output.nil? || !STDOUT.tty?
1337
- clear(FileWithPercentOutput.display_str.size + 5)
1338
- VMC::Cli::Config.output.print("#{FileWithPercentOutput.display_str} #{p}%")
1339
- VMC::Cli::Config.output.flush
1340
- end
1341
- end
1342
-
1343
- def read(*args)
1344
- result = super(*args)
1345
- if result && result.size > 0
1346
- update_display(result.size)
1347
- else
1348
- unless VMC::Cli::Config.output.nil? || !STDOUT.tty?
1349
- clear(FileWithPercentOutput.display_str.size + 5)
1350
- VMC::Cli::Config.output.print(FileWithPercentOutput.display_str)
1351
- display('OK'.green)
1352
- end
1353
- end
1354
- result
1355
- end
1356
- end
1357
-
1358
- end