paasio 0.3.16.beta.2
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.
- data/LICENSE +24 -0
- data/README.md +96 -0
- data/Rakefile +99 -0
- data/bin/paasio +6 -0
- data/caldecott_helper/Gemfile +10 -0
- data/caldecott_helper/Gemfile.lock +48 -0
- data/caldecott_helper/server.rb +43 -0
- data/config/clients.yml +17 -0
- data/lib/cli/commands/admin.rb +80 -0
- data/lib/cli/commands/apps.rb +1284 -0
- data/lib/cli/commands/base.rb +230 -0
- data/lib/cli/commands/manifest.rb +56 -0
- data/lib/cli/commands/misc.rb +129 -0
- data/lib/cli/commands/services.rb +179 -0
- data/lib/cli/commands/user.rb +65 -0
- data/lib/cli/config.rb +165 -0
- data/lib/cli/core_ext.rb +122 -0
- data/lib/cli/errors.rb +19 -0
- data/lib/cli/frameworks.rb +131 -0
- data/lib/cli/manifest_helper.rb +238 -0
- data/lib/cli/runner.rb +535 -0
- data/lib/cli/services_helper.rb +84 -0
- data/lib/cli/tunnel_helper.rb +324 -0
- data/lib/cli/usage.rb +104 -0
- data/lib/cli/version.rb +7 -0
- data/lib/cli/zip_util.rb +77 -0
- data/lib/cli.rb +32 -0
- data/lib/vmc/client.rb +481 -0
- data/lib/vmc/const.rb +22 -0
- data/lib/vmc.rb +3 -0
- metadata +246 -0
@@ -0,0 +1,1284 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'pathname'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'tmpdir'
|
6
|
+
require 'set'
|
7
|
+
|
8
|
+
module VMC::Cli::Command
|
9
|
+
|
10
|
+
class Apps < Base
|
11
|
+
include VMC::Cli::ServicesHelper
|
12
|
+
include VMC::Cli::ManifestHelper
|
13
|
+
|
14
|
+
def list
|
15
|
+
apps = client.apps
|
16
|
+
apps.sort! {|a, b| a[:name] <=> b[:name] }
|
17
|
+
return display JSON.pretty_generate(apps || []) if @options[:json]
|
18
|
+
|
19
|
+
display "\n"
|
20
|
+
return display "No Applications" if apps.nil? || apps.empty?
|
21
|
+
|
22
|
+
apps_table = table do |t|
|
23
|
+
t.headings = 'Application', '# ', 'Health', 'URLS', 'Services'
|
24
|
+
apps.each do |app|
|
25
|
+
t << [app[:name], app[:instances], health(app), app[:uris].join(', '), app[:services].join(', ')]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
display apps_table
|
29
|
+
end
|
30
|
+
|
31
|
+
alias :apps :list
|
32
|
+
|
33
|
+
SLEEP_TIME = 1
|
34
|
+
LINE_LENGTH = 80
|
35
|
+
|
36
|
+
# Numerators are in secs
|
37
|
+
TICKER_TICKS = 25/SLEEP_TIME
|
38
|
+
HEALTH_TICKS = 5/SLEEP_TIME
|
39
|
+
TAIL_TICKS = 45/SLEEP_TIME
|
40
|
+
GIVEUP_TICKS = 120/SLEEP_TIME
|
41
|
+
|
42
|
+
def info(what, default=nil)
|
43
|
+
@options[what] || (@app_info && @app_info[what.to_s]) || default
|
44
|
+
end
|
45
|
+
|
46
|
+
def start(appname=nil, push=false)
|
47
|
+
if appname
|
48
|
+
do_start(appname, push)
|
49
|
+
else
|
50
|
+
each_app do |name|
|
51
|
+
do_start(name, push)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def stop(appname=nil)
|
57
|
+
if appname
|
58
|
+
do_stop(appname)
|
59
|
+
else
|
60
|
+
reversed = []
|
61
|
+
each_app do |name|
|
62
|
+
reversed.unshift name
|
63
|
+
end
|
64
|
+
|
65
|
+
reversed.each do |name|
|
66
|
+
do_stop(name)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def restart(appname=nil)
|
72
|
+
stop(appname)
|
73
|
+
start(appname)
|
74
|
+
end
|
75
|
+
|
76
|
+
def rename(appname, newname)
|
77
|
+
app = client.app_info(appname)
|
78
|
+
app[:name] = newname
|
79
|
+
display 'Renaming Appliction: '
|
80
|
+
client.update_app(newname, app)
|
81
|
+
display 'OK'.green
|
82
|
+
end
|
83
|
+
|
84
|
+
def mem(appname, memsize=nil)
|
85
|
+
app = client.app_info(appname)
|
86
|
+
mem = current_mem = mem_quota_to_choice(app[:resources][:memory])
|
87
|
+
memsize = normalize_mem(memsize) if memsize
|
88
|
+
|
89
|
+
memsize ||= ask(
|
90
|
+
"Update Memory Reservation?",
|
91
|
+
:default => current_mem,
|
92
|
+
:choices => mem_choices
|
93
|
+
)
|
94
|
+
|
95
|
+
mem = mem_choice_to_quota(mem)
|
96
|
+
memsize = mem_choice_to_quota(memsize)
|
97
|
+
current_mem = mem_choice_to_quota(current_mem)
|
98
|
+
|
99
|
+
display "Updating Memory Reservation to #{mem_quota_to_choice(memsize)}: ", false
|
100
|
+
|
101
|
+
# check memsize here for capacity
|
102
|
+
check_has_capacity_for((memsize - mem) * app[:instances])
|
103
|
+
|
104
|
+
mem = memsize
|
105
|
+
|
106
|
+
if (mem != current_mem)
|
107
|
+
app[:resources][:memory] = mem
|
108
|
+
client.update_app(appname, app)
|
109
|
+
display 'OK'.green
|
110
|
+
restart appname if app[:state] == 'STARTED'
|
111
|
+
else
|
112
|
+
display 'OK'.green
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def map(appname, url)
|
117
|
+
app = client.app_info(appname)
|
118
|
+
uris = app[:uris] || []
|
119
|
+
uris << url
|
120
|
+
app[:uris] = uris
|
121
|
+
client.update_app(appname, app)
|
122
|
+
display "Successfully mapped url".green
|
123
|
+
end
|
124
|
+
|
125
|
+
def unmap(appname, url)
|
126
|
+
app = client.app_info(appname)
|
127
|
+
uris = app[:uris] || []
|
128
|
+
url = url.gsub(/^http(s*):\/\//i, '')
|
129
|
+
deleted = uris.delete(url)
|
130
|
+
err "Invalid url" unless deleted
|
131
|
+
app[:uris] = uris
|
132
|
+
client.update_app(appname, app)
|
133
|
+
display "Successfully unmapped url".green
|
134
|
+
end
|
135
|
+
|
136
|
+
def delete(appname=nil)
|
137
|
+
force = @options[:force]
|
138
|
+
if @options[:all]
|
139
|
+
if no_prompt || force || ask("Delete ALL applications and services?", :default => false)
|
140
|
+
apps = client.apps
|
141
|
+
apps.each { |app| delete_app(app[:name], force) }
|
142
|
+
end
|
143
|
+
else
|
144
|
+
err 'No valid appname given' unless appname
|
145
|
+
delete_app(appname, force)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def files(appname, path='/')
|
150
|
+
return all_files(appname, path) if @options[:all] && !@options[:instance]
|
151
|
+
instance = @options[:instance] || '0'
|
152
|
+
content = client.app_files(appname, path, instance)
|
153
|
+
display content
|
154
|
+
rescue VMC::Client::NotFound => e
|
155
|
+
err 'No such file or directory'
|
156
|
+
end
|
157
|
+
|
158
|
+
def logs(appname)
|
159
|
+
# Check if we have an app before progressing further
|
160
|
+
client.app_info(appname)
|
161
|
+
return grab_all_logs(appname) if @options[:all] && !@options[:instance]
|
162
|
+
instance = @options[:instance] || '0'
|
163
|
+
grab_logs(appname, instance)
|
164
|
+
end
|
165
|
+
|
166
|
+
def crashes(appname, print_results=true, since=0)
|
167
|
+
crashed = client.app_crashes(appname)[:crashes]
|
168
|
+
crashed.delete_if { |c| c[:since] < since }
|
169
|
+
instance_map = {}
|
170
|
+
|
171
|
+
# return display JSON.pretty_generate(apps) if @options[:json]
|
172
|
+
|
173
|
+
|
174
|
+
counter = 0
|
175
|
+
crashed = crashed.to_a.sort { |a,b| a[:since] - b[:since] }
|
176
|
+
crashed_table = table do |t|
|
177
|
+
t.headings = 'Name', 'Instance ID', 'Crashed Time'
|
178
|
+
crashed.each do |crash|
|
179
|
+
name = "#{appname}-#{counter += 1}"
|
180
|
+
instance_map[name] = crash[:instance]
|
181
|
+
t << [name, crash[:instance], Time.at(crash[:since]).strftime("%m/%d/%Y %I:%M%p")]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
VMC::Cli::Config.store_instances(instance_map)
|
186
|
+
|
187
|
+
if @options[:json]
|
188
|
+
return display JSON.pretty_generate(crashed)
|
189
|
+
elsif print_results
|
190
|
+
display "\n"
|
191
|
+
if crashed.empty?
|
192
|
+
display "No crashed instances for [#{appname}]" if print_results
|
193
|
+
else
|
194
|
+
display crashed_table if print_results
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
crashed
|
199
|
+
end
|
200
|
+
|
201
|
+
def crashlogs(appname)
|
202
|
+
instance = @options[:instance] || '0'
|
203
|
+
grab_crash_logs(appname, instance)
|
204
|
+
end
|
205
|
+
|
206
|
+
def instances(appname, num=nil)
|
207
|
+
if num
|
208
|
+
change_instances(appname, num)
|
209
|
+
else
|
210
|
+
get_instances(appname)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def stats(appname=nil)
|
215
|
+
if appname
|
216
|
+
display "\n", false
|
217
|
+
do_stats(appname)
|
218
|
+
else
|
219
|
+
each_app do |n|
|
220
|
+
display "\n#{n}:"
|
221
|
+
do_stats(n)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def upload(appname=nil)
|
227
|
+
if appname
|
228
|
+
app = client.app_info(appname)
|
229
|
+
|
230
|
+
# check deploy status
|
231
|
+
deploy = client.app_most_recent_deploy(appname)
|
232
|
+
if deploy && deploy[:sha]
|
233
|
+
unless has_git_commit?(@path, deploy[:sha])
|
234
|
+
unless ask("The last commit in the currently deployed code is:\n #{deploy[:sha]}.\n\nThe current path doesn't include that commit.\nManually uploading may revert changes.\nContinue anyway?", :default => false)
|
235
|
+
exit 0
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
upload_app_bits(appname, @path)
|
241
|
+
restart appname if app[:state] == 'STARTED'
|
242
|
+
else
|
243
|
+
each_app do |name|
|
244
|
+
display "Updating application '#{name}'..."
|
245
|
+
app = client.app_info(name)
|
246
|
+
|
247
|
+
# check deploy status
|
248
|
+
deploy = client.app_most_recent_deploy(name)
|
249
|
+
if deploy && deploy[:sha]
|
250
|
+
unless has_git_commit?(@application, deploy[:sha])
|
251
|
+
unless ask("The last commit in the currently deployed code is:\n #{deploy[:sha]}.\n\nThe current path doesn't include that commit.\nManually uploading may revert changes.\nContinue anyway?", :default => false)
|
252
|
+
exit 0
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
upload_app_bits(name, @application)
|
258
|
+
restart name if app[:state] == 'STARTED'
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def update(appname=nil)
|
264
|
+
if appname
|
265
|
+
app = client.app_info(appname)
|
266
|
+
if @options[:canary]
|
267
|
+
display "[--canary] is deprecated and will be removed in a future version".yellow
|
268
|
+
end
|
269
|
+
upload_app_bits(appname, @path)
|
270
|
+
restart appname if app[:state] == 'STARTED'
|
271
|
+
else
|
272
|
+
each_app do |name|
|
273
|
+
display "Updating application '#{name}'..."
|
274
|
+
|
275
|
+
app = client.app_info(name)
|
276
|
+
upload_app_bits(name, @application)
|
277
|
+
restart name if app[:state] == 'STARTED'
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def create(appname=nil)
|
283
|
+
created = false
|
284
|
+
each_app(false) do |name|
|
285
|
+
display "Creating application '#{name}'..." if name
|
286
|
+
do_create(name)
|
287
|
+
created = true
|
288
|
+
end
|
289
|
+
|
290
|
+
unless created
|
291
|
+
do_create(appname)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def push(appname=nil)
|
296
|
+
unless no_prompt || @options[:path]
|
297
|
+
proceed = ask(
|
298
|
+
'Would you like to deploy from the current directory?',
|
299
|
+
:default => true
|
300
|
+
)
|
301
|
+
|
302
|
+
unless proceed
|
303
|
+
@path = ask('Deployment path')
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
pushed = false
|
308
|
+
each_app(false) do |name|
|
309
|
+
display "Pushing application '#{name}'..." if name
|
310
|
+
do_push(name)
|
311
|
+
pushed = true
|
312
|
+
end
|
313
|
+
|
314
|
+
unless pushed
|
315
|
+
@application = @path
|
316
|
+
do_push(appname)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def environment(appname)
|
321
|
+
app = client.app_info(appname)
|
322
|
+
env = app[:env] || []
|
323
|
+
return display JSON.pretty_generate(env) if @options[:json]
|
324
|
+
return display "No Environment Variables" if env.empty?
|
325
|
+
etable = table do |t|
|
326
|
+
t.headings = 'Variable', 'Value'
|
327
|
+
env.each do |e|
|
328
|
+
k,v = e.split('=', 2)
|
329
|
+
t << [k, v]
|
330
|
+
end
|
331
|
+
end
|
332
|
+
display "\n"
|
333
|
+
display etable
|
334
|
+
end
|
335
|
+
|
336
|
+
def environment_add(appname, k, v=nil)
|
337
|
+
app = client.app_info(appname)
|
338
|
+
env = app[:env] || []
|
339
|
+
k,v = k.split('=', 2) unless v
|
340
|
+
env << "#{k}=#{v}"
|
341
|
+
display "Adding Environment Variable [#{k}=#{v}]: ", false
|
342
|
+
app[:env] = env
|
343
|
+
client.update_app(appname, app)
|
344
|
+
display 'OK'.green
|
345
|
+
restart appname if app[:state] == 'STARTED'
|
346
|
+
end
|
347
|
+
|
348
|
+
def environment_del(appname, variable)
|
349
|
+
app = client.app_info(appname)
|
350
|
+
env = app[:env] || []
|
351
|
+
deleted_env = nil
|
352
|
+
env.each do |e|
|
353
|
+
k,v = e.split('=')
|
354
|
+
if (k == variable)
|
355
|
+
deleted_env = e
|
356
|
+
break;
|
357
|
+
end
|
358
|
+
end
|
359
|
+
display "Deleting Environment Variable [#{variable}]: ", false
|
360
|
+
if deleted_env
|
361
|
+
env.delete(deleted_env)
|
362
|
+
app[:env] = env
|
363
|
+
client.update_app(appname, app)
|
364
|
+
display 'OK'.green
|
365
|
+
restart appname if app[:state] == 'STARTED'
|
366
|
+
else
|
367
|
+
display 'OK'.green
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
private
|
372
|
+
|
373
|
+
def app_exists?(appname)
|
374
|
+
app_info = client.app_info(appname)
|
375
|
+
app_info != nil
|
376
|
+
rescue VMC::Client::NotFound
|
377
|
+
false
|
378
|
+
end
|
379
|
+
|
380
|
+
def check_deploy_directory(path)
|
381
|
+
err 'Deployment path does not exist' unless File.exists? path
|
382
|
+
err 'Deployment path is not a directory' unless File.directory? path
|
383
|
+
return if File.expand_path(Dir.tmpdir) != File.expand_path(path)
|
384
|
+
err "Can't deploy applications from staging directory: [#{Dir.tmpdir}]"
|
385
|
+
end
|
386
|
+
|
387
|
+
def check_unreachable_links(path)
|
388
|
+
path = File.expand_path(path)
|
389
|
+
files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
|
390
|
+
|
391
|
+
pwd = Pathname.pwd
|
392
|
+
|
393
|
+
abspath = File.expand_path(path)
|
394
|
+
unreachable = []
|
395
|
+
files.each do |f|
|
396
|
+
file = Pathname.new(f)
|
397
|
+
if file.symlink? && !file.realpath.to_s.start_with?(abspath)
|
398
|
+
unreachable << file.relative_path_from(pwd)
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
unless unreachable.empty?
|
403
|
+
root = Pathname.new(path).relative_path_from(pwd)
|
404
|
+
err "Can't deploy application containing links '#{unreachable}' that reach outside its root '#{root}'"
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def find_sockets(path)
|
409
|
+
files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
|
410
|
+
files && files.select { |f| File.socket? f }
|
411
|
+
end
|
412
|
+
|
413
|
+
def upload_app_bits(appname, path)
|
414
|
+
display 'Uploading Application:'
|
415
|
+
|
416
|
+
upload_file, file = "#{Dir.tmpdir}/#{appname}.zip", nil
|
417
|
+
FileUtils.rm_f(upload_file)
|
418
|
+
|
419
|
+
explode_dir = "#{Dir.tmpdir}/.vmc_#{appname}_files"
|
420
|
+
FileUtils.rm_rf(explode_dir) # Make sure we didn't have anything left over..
|
421
|
+
|
422
|
+
Dir.chdir(path) do
|
423
|
+
# Stage the app appropriately and do the appropriate fingerprinting, etc.
|
424
|
+
if war_file = Dir.glob('*.war').first
|
425
|
+
VMC::Cli::ZipUtil.unpack(war_file, explode_dir)
|
426
|
+
else
|
427
|
+
check_unreachable_links(path)
|
428
|
+
FileUtils.mkdir(explode_dir)
|
429
|
+
|
430
|
+
files = Dir.glob('{*,.[^\.]*}')
|
431
|
+
|
432
|
+
# Do not process .git files
|
433
|
+
files.delete('.git') if files
|
434
|
+
|
435
|
+
FileUtils.cp_r(files, explode_dir)
|
436
|
+
|
437
|
+
find_sockets(explode_dir).each do |s|
|
438
|
+
File.delete s
|
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 VMC::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
|
+
VMC::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 VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
|
508
|
+
|
509
|
+
display 'Push Status: ', false
|
510
|
+
display 'OK'.green
|
511
|
+
end
|
512
|
+
|
513
|
+
ensure
|
514
|
+
# Cleanup if we created an exploded directory.
|
515
|
+
FileUtils.rm_f(upload_file) if upload_file
|
516
|
+
FileUtils.rm_rf(explode_dir) if explode_dir
|
517
|
+
end
|
518
|
+
|
519
|
+
def check_app_limit
|
520
|
+
usage = client_info[:usage]
|
521
|
+
limits = client_info[:limits]
|
522
|
+
return unless usage and limits and limits[:apps]
|
523
|
+
if limits[:apps] == usage[:apps]
|
524
|
+
display "Not enough capacity for operation.".red
|
525
|
+
tapps = limits[:apps] || 0
|
526
|
+
apps = usage[:apps] || 0
|
527
|
+
err "Current Usage: (#{apps} of #{tapps} total apps already in use)"
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
def check_has_capacity_for(mem_wanted)
|
532
|
+
usage = client_info[:usage]
|
533
|
+
limits = client_info[:limits]
|
534
|
+
return unless usage and limits
|
535
|
+
available_for_use = limits[:memory].to_i - usage[:memory].to_i
|
536
|
+
if mem_wanted > available_for_use
|
537
|
+
tmem = pretty_size(limits[:memory]*1024*1024)
|
538
|
+
mem = pretty_size(usage[:memory]*1024*1024)
|
539
|
+
display "Not enough capacity for operation.".yellow
|
540
|
+
available = pretty_size(available_for_use * 1024 * 1024)
|
541
|
+
err "Current Usage: (#{mem} of #{tmem} total, #{available} available for use)"
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def mem_choices
|
546
|
+
default = ['128M', '256M', '512M', '1G', '2G']
|
547
|
+
|
548
|
+
return default unless client_info
|
549
|
+
return default unless (usage = client_info[:usage] and limits = client_info[:limits])
|
550
|
+
|
551
|
+
available_for_use = limits[:memory].to_i - usage[:memory].to_i
|
552
|
+
check_has_capacity_for(128) if available_for_use < 128
|
553
|
+
return ['128M'] if available_for_use < 256
|
554
|
+
return ['128M', '256M'] if available_for_use < 512
|
555
|
+
return ['128M', '256M', '512M'] if available_for_use < 1024
|
556
|
+
return ['128M', '256M', '512M', '1G'] if available_for_use < 2048
|
557
|
+
return ['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
|
+
|
638
|
+
healthy_instances = d[:runningInstances]
|
639
|
+
expected_instance = d[:instances]
|
640
|
+
health = nil
|
641
|
+
|
642
|
+
if d[:state] == "STARTED" && expected_instance > 0 && healthy_instances
|
643
|
+
health = format("%.3f", healthy_instances.to_f / expected_instance).to_f
|
644
|
+
end
|
645
|
+
|
646
|
+
return 'RUNNING' if health && health == 1.0
|
647
|
+
return "#{(health * 100).round}%" if health
|
648
|
+
return 'N/A'
|
649
|
+
end
|
650
|
+
|
651
|
+
def app_started_properly(appname, error_on_health)
|
652
|
+
app = client.app_info(appname)
|
653
|
+
case health(app)
|
654
|
+
when 'N/A'
|
655
|
+
# Health manager not running.
|
656
|
+
err "\nApplication '#{appname}'s state is undetermined, not enough information available." if error_on_health
|
657
|
+
return false
|
658
|
+
when 'RUNNING'
|
659
|
+
return true
|
660
|
+
else
|
661
|
+
if app[:meta][:debug] == "suspend"
|
662
|
+
display "\nApplication [#{appname}] has started in a mode that is waiting for you to trigger startup."
|
663
|
+
return true
|
664
|
+
else
|
665
|
+
return false
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
670
|
+
def display_logfile(path, content, instance='0', banner=nil)
|
671
|
+
banner ||= "====> #{path} <====\n\n"
|
672
|
+
|
673
|
+
unless content.empty?
|
674
|
+
display banner
|
675
|
+
prefix = "[#{instance}: #{path}] -".bold if @options[:prefixlogs]
|
676
|
+
unless prefix
|
677
|
+
display content
|
678
|
+
else
|
679
|
+
lines = content.split("\n")
|
680
|
+
lines.each { |line| display "#{prefix} #{line}"}
|
681
|
+
end
|
682
|
+
display ''
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
def log_file_paths
|
687
|
+
%w[logs/stderr.log logs/stdout.log logs/startup.log]
|
688
|
+
end
|
689
|
+
|
690
|
+
def grab_all_logs(appname)
|
691
|
+
instances_info_envelope = client.app_instances(appname)
|
692
|
+
return if instances_info_envelope.is_a?(Array)
|
693
|
+
instances_info = instances_info_envelope[:instances] || []
|
694
|
+
instances_info.each do |entry|
|
695
|
+
grab_logs(appname, entry[:index])
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
699
|
+
def grab_logs(appname, instance)
|
700
|
+
log_file_paths.each do |path|
|
701
|
+
begin
|
702
|
+
content = client.app_files(appname, path, instance)
|
703
|
+
display_logfile(path, content, instance)
|
704
|
+
rescue VMC::Client::NotFound
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
def grab_crash_logs(appname, instance, was_staged=false)
|
710
|
+
# stage crash info
|
711
|
+
crashes(appname, false) unless was_staged
|
712
|
+
|
713
|
+
instance ||= '0'
|
714
|
+
map = VMC::Cli::Config.instances
|
715
|
+
instance = map[instance] if map[instance]
|
716
|
+
|
717
|
+
%w{
|
718
|
+
/logs/err.log /logs/staging.log /app/logs/stderr.log
|
719
|
+
/app/logs/stdout.log /app/logs/startup.log /app/logs/migration.log
|
720
|
+
}.each do |path|
|
721
|
+
begin
|
722
|
+
content = client.app_files(appname, path, instance)
|
723
|
+
display_logfile(path, content, instance)
|
724
|
+
rescue VMC::Client::NotFound
|
725
|
+
end
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
def grab_startup_tail(appname, since = 0)
|
730
|
+
new_lines = 0
|
731
|
+
path = "logs/startup.log"
|
732
|
+
content = client.app_files(appname, path)
|
733
|
+
if content && !content.empty?
|
734
|
+
display "\n==== displaying startup log ====\n\n" if since == 0
|
735
|
+
response_lines = content.split("\n")
|
736
|
+
lines = response_lines.size
|
737
|
+
tail = response_lines[since, lines] || []
|
738
|
+
new_lines = tail.size
|
739
|
+
display tail.join("\n") if new_lines > 0
|
740
|
+
end
|
741
|
+
since + new_lines
|
742
|
+
end
|
743
|
+
|
744
|
+
def provisioned_services_apps_hash
|
745
|
+
apps = client.apps
|
746
|
+
services_apps_hash = {}
|
747
|
+
apps.each {|app|
|
748
|
+
app[:services].each { |svc|
|
749
|
+
svc_apps = services_apps_hash[svc]
|
750
|
+
unless svc_apps
|
751
|
+
svc_apps = Set.new
|
752
|
+
services_apps_hash[svc] = svc_apps
|
753
|
+
end
|
754
|
+
svc_apps.add(app[:name])
|
755
|
+
} unless app[:services] == nil
|
756
|
+
}
|
757
|
+
services_apps_hash
|
758
|
+
end
|
759
|
+
|
760
|
+
def delete_app(appname, force)
|
761
|
+
app = client.app_info(appname)
|
762
|
+
services_to_delete = []
|
763
|
+
app_services = app[:services]
|
764
|
+
services_apps_hash = provisioned_services_apps_hash
|
765
|
+
app_services.each { |service|
|
766
|
+
del_service = force && no_prompt
|
767
|
+
unless no_prompt || force
|
768
|
+
del_service = ask(
|
769
|
+
"Provisioned service [#{service}] detected, would you like to delete it?",
|
770
|
+
:default => false
|
771
|
+
)
|
772
|
+
|
773
|
+
if del_service
|
774
|
+
apps_using_service = services_apps_hash[service].reject!{ |app| app == appname}
|
775
|
+
if apps_using_service.size > 0
|
776
|
+
del_service = ask(
|
777
|
+
"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?",
|
778
|
+
:default => false
|
779
|
+
)
|
780
|
+
end
|
781
|
+
end
|
782
|
+
end
|
783
|
+
services_to_delete << service if del_service
|
784
|
+
}
|
785
|
+
|
786
|
+
display "Deleting application [#{appname}]: ", false
|
787
|
+
client.delete_app(appname)
|
788
|
+
display 'OK'.green
|
789
|
+
|
790
|
+
services_to_delete.each do |s|
|
791
|
+
delete_service_banner(s)
|
792
|
+
end
|
793
|
+
end
|
794
|
+
|
795
|
+
def do_start(appname, push=false)
|
796
|
+
app = client.app_info(appname)
|
797
|
+
|
798
|
+
return display "Application '#{appname}' could not be found".red if app.nil?
|
799
|
+
return display "Application '#{appname}' already started".yellow if app[:state] == 'STARTED'
|
800
|
+
|
801
|
+
if @options[:debug]
|
802
|
+
runtimes = client.runtimes_info
|
803
|
+
return display "Cannot get runtime information." unless runtimes
|
804
|
+
|
805
|
+
runtime = runtimes[app[:staging][:stack].to_sym]
|
806
|
+
return display "Unknown runtime." unless runtime
|
807
|
+
|
808
|
+
unless runtime[:debug_modes] and runtime[:debug_modes].include? @options[:debug]
|
809
|
+
modes = runtime[:debug_modes] || []
|
810
|
+
|
811
|
+
display "\nApplication '#{appname}' cannot start in '#{@options[:debug]}' mode"
|
812
|
+
|
813
|
+
if push
|
814
|
+
display "Try 'vmc start' with one of the following modes: #{modes.inspect}"
|
815
|
+
else
|
816
|
+
display "Available modes: #{modes.inspect}"
|
817
|
+
end
|
818
|
+
|
819
|
+
return
|
820
|
+
end
|
821
|
+
end
|
822
|
+
|
823
|
+
banner = "Staging Application '#{appname}': "
|
824
|
+
display banner, false
|
825
|
+
|
826
|
+
t = Thread.new do
|
827
|
+
count = 0
|
828
|
+
while count < TAIL_TICKS do
|
829
|
+
display '.', false
|
830
|
+
sleep SLEEP_TIME
|
831
|
+
count += 1
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
app[:state] = 'STARTED'
|
836
|
+
app[:debug] = @options[:debug]
|
837
|
+
client.update_app(appname, app)
|
838
|
+
|
839
|
+
Thread.kill(t)
|
840
|
+
clear(LINE_LENGTH)
|
841
|
+
display "#{banner}#{'OK'.green}"
|
842
|
+
|
843
|
+
banner = "Starting Application '#{appname}': "
|
844
|
+
display banner, false
|
845
|
+
|
846
|
+
count = log_lines_displayed = 0
|
847
|
+
failed = false
|
848
|
+
start_time = Time.now.to_i
|
849
|
+
|
850
|
+
loop do
|
851
|
+
display '.', false unless count > TICKER_TICKS
|
852
|
+
sleep SLEEP_TIME
|
853
|
+
begin
|
854
|
+
break if app_started_properly(appname, count > HEALTH_TICKS)
|
855
|
+
if !crashes(appname, false, start_time).empty?
|
856
|
+
# Check for the existance of crashes
|
857
|
+
display "\nError: Application [#{appname}] failed to start, logs information below.\n".red
|
858
|
+
grab_crash_logs(appname, '0', true)
|
859
|
+
if push and !no_prompt
|
860
|
+
display "\n"
|
861
|
+
delete_app(appname, false) if ask "Delete the application?", :default => true
|
862
|
+
end
|
863
|
+
failed = true
|
864
|
+
break
|
865
|
+
elsif count > TAIL_TICKS
|
866
|
+
log_lines_displayed = grab_startup_tail(appname, log_lines_displayed)
|
867
|
+
end
|
868
|
+
rescue => e
|
869
|
+
err(e.message, '')
|
870
|
+
end
|
871
|
+
count += 1
|
872
|
+
if count > GIVEUP_TICKS # 2 minutes
|
873
|
+
display "\nApplication is taking too long to start, check your logs".yellow
|
874
|
+
break
|
875
|
+
end
|
876
|
+
end
|
877
|
+
exit(false) if failed
|
878
|
+
clear(LINE_LENGTH)
|
879
|
+
display "#{banner}#{'OK'.green}"
|
880
|
+
end
|
881
|
+
|
882
|
+
def do_stop(appname)
|
883
|
+
app = client.app_info(appname)
|
884
|
+
return display "Application '#{appname}' already stopped".yellow if app[:state] == 'STOPPED'
|
885
|
+
display "Stopping Application '#{appname}': ", false
|
886
|
+
app[:state] = 'STOPPED'
|
887
|
+
client.update_app(appname, app)
|
888
|
+
display 'OK'.green
|
889
|
+
end
|
890
|
+
|
891
|
+
def do_create(appname=nil)
|
892
|
+
instances = info(:instances, 1)
|
893
|
+
exec = info(:exec, 'thin start')
|
894
|
+
|
895
|
+
ignore_framework = @options[:noframework]
|
896
|
+
|
897
|
+
appname ||= info(:name)
|
898
|
+
url = info(:url) || info(:urls)
|
899
|
+
mem, memswitch = nil, info(:mem)
|
900
|
+
memswitch = normalize_mem(memswitch) if memswitch
|
901
|
+
|
902
|
+
# Check app existing upfront if we have appname
|
903
|
+
app_checked = false
|
904
|
+
if appname
|
905
|
+
err "Application '#{appname}' already exists, please choose another name" if app_exists?(appname)
|
906
|
+
app_checked = true
|
907
|
+
else
|
908
|
+
raise VMC::Client::AuthError unless client.logged_in?
|
909
|
+
end
|
910
|
+
|
911
|
+
# check if we have hit our app limit
|
912
|
+
check_app_limit
|
913
|
+
# check memsize here for capacity
|
914
|
+
if memswitch && !no_start
|
915
|
+
check_has_capacity_for(mem_choice_to_quota(memswitch) * instances)
|
916
|
+
end
|
917
|
+
|
918
|
+
if !no_prompt && (appname.nil? || appname.empty?)
|
919
|
+
appname ||= ask("Application Name")
|
920
|
+
err "Application Name required." if appname.nil? || appname.empty?
|
921
|
+
else
|
922
|
+
puts "Using application name \"#{appname}\"..."
|
923
|
+
end
|
924
|
+
|
925
|
+
if !app_checked and app_exists?(appname)
|
926
|
+
err "Application '#{appname}' already exists, please choose another name."
|
927
|
+
end
|
928
|
+
|
929
|
+
default_url = "#{appname}.#{VMC::Cli::Config.suggest_url}"
|
930
|
+
|
931
|
+
unless no_prompt || url
|
932
|
+
url = ask(
|
933
|
+
"Application Deployed URL",
|
934
|
+
:default => default_url
|
935
|
+
)
|
936
|
+
|
937
|
+
# common error case is for prompted users to answer y or Y or yes or
|
938
|
+
# YES to this ask() resulting in an unintended URL of y. Special case
|
939
|
+
# this common error
|
940
|
+
url = nil if YES_SET.member? url
|
941
|
+
end
|
942
|
+
|
943
|
+
url ||= default_url
|
944
|
+
|
945
|
+
if ignore_framework
|
946
|
+
framework = VMC::Cli::Framework.new
|
947
|
+
elsif f = info(:framework)
|
948
|
+
info = Hash[f["info"].collect { |k, v| [k.to_sym, v] }]
|
949
|
+
|
950
|
+
framework = VMC::Cli::Framework.new(f["name"], info)
|
951
|
+
exec = framework.exec if framework && framework.exec
|
952
|
+
else
|
953
|
+
@path = @application = '.'
|
954
|
+
framework = detect_framework(prompt_ok)
|
955
|
+
end
|
956
|
+
|
957
|
+
err "Application Type undetermined for '#{appname}'" unless framework
|
958
|
+
|
959
|
+
if memswitch
|
960
|
+
mem = memswitch
|
961
|
+
elsif prompt_ok
|
962
|
+
mem = ask("Memory Reservation",
|
963
|
+
:default => framework.memory, :choices => mem_choices)
|
964
|
+
else
|
965
|
+
mem = framework.memory
|
966
|
+
end
|
967
|
+
|
968
|
+
# Set to MB number
|
969
|
+
mem_quota = mem_choice_to_quota(mem)
|
970
|
+
|
971
|
+
# check memsize here for capacity
|
972
|
+
check_has_capacity_for(mem_quota * instances)
|
973
|
+
|
974
|
+
display 'Creating Application: ', false
|
975
|
+
|
976
|
+
manifest = {
|
977
|
+
:name => "#{appname}",
|
978
|
+
:staging => {
|
979
|
+
:framework => framework.name,
|
980
|
+
:runtime => info(:runtime)
|
981
|
+
},
|
982
|
+
:uris => Array(url),
|
983
|
+
:instances => instances,
|
984
|
+
:resources => {
|
985
|
+
:memory => mem_quota
|
986
|
+
},
|
987
|
+
}
|
988
|
+
manifest[:scm_type] = @options[:scm_type] if @options[:scm_type]
|
989
|
+
|
990
|
+
# Send the manifest to the cloud controller
|
991
|
+
client.create_app(appname, manifest)
|
992
|
+
display 'OK'.green
|
993
|
+
|
994
|
+
# Get the app info back
|
995
|
+
app = client.app_info(appname)
|
996
|
+
case app[:scm_type]
|
997
|
+
when 'git'
|
998
|
+
create_git_remote(appname, @options[:remote] || 'paasio', app[:repository_url])
|
999
|
+
when 'hg'
|
1000
|
+
create_hg_remote(appname, @options[:remote] || 'paasio', app[:repository_url])
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
existing = Set.new(client.services.collect { |s| s[:name] })
|
1004
|
+
|
1005
|
+
if @app_info && services = @app_info["services"]
|
1006
|
+
services.each do |name, info|
|
1007
|
+
unless existing.include? name
|
1008
|
+
create_service_banner(info["type"], name, true)
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
bind_service_banner(name, appname)
|
1012
|
+
end
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
def do_push(appname=nil)
|
1017
|
+
unless @app_info || no_prompt
|
1018
|
+
@manifest = { "applications" => { @path => { "name" => appname } } }
|
1019
|
+
|
1020
|
+
interact
|
1021
|
+
|
1022
|
+
if ask("Would you like to save this configuration?", :default => false)
|
1023
|
+
save_manifest
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
resolve_manifest(@manifest)
|
1027
|
+
|
1028
|
+
@app_info = @manifest["applications"][@path]
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
instances = info(:instances, 1)
|
1032
|
+
exec = info(:exec, 'thin start')
|
1033
|
+
|
1034
|
+
ignore_framework = @options[:noframework]
|
1035
|
+
no_start = @options[:nostart]
|
1036
|
+
|
1037
|
+
appname ||= info(:name)
|
1038
|
+
url = info(:url) || info(:urls)
|
1039
|
+
mem, memswitch = nil, info(:mem)
|
1040
|
+
memswitch = normalize_mem(memswitch) if memswitch
|
1041
|
+
|
1042
|
+
# Check app existing upfront if we have appname
|
1043
|
+
app_checked = false
|
1044
|
+
if appname
|
1045
|
+
err "Application '#{appname}' already exists, use update" if app_exists?(appname)
|
1046
|
+
app_checked = true
|
1047
|
+
else
|
1048
|
+
raise VMC::Client::AuthError unless client.logged_in?
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
# check if we have hit our app limit
|
1052
|
+
check_app_limit
|
1053
|
+
# check memsize here for capacity
|
1054
|
+
if memswitch && !no_start
|
1055
|
+
check_has_capacity_for(mem_choice_to_quota(memswitch) * instances)
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
appname ||= ask("Application Name") unless no_prompt
|
1059
|
+
err "Application Name required." if appname.nil? || appname.empty?
|
1060
|
+
|
1061
|
+
check_deploy_directory(@application)
|
1062
|
+
|
1063
|
+
if !app_checked and app_exists?(appname)
|
1064
|
+
err "Application '#{appname}' already exists, use update or delete."
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
default_url = "#{appname}.#{VMC::Cli::Config.suggest_url}"
|
1068
|
+
|
1069
|
+
unless no_prompt || url
|
1070
|
+
url = ask(
|
1071
|
+
"Application Deployed URL",
|
1072
|
+
:default => default_url
|
1073
|
+
)
|
1074
|
+
|
1075
|
+
# common error case is for prompted users to answer y or Y or yes or
|
1076
|
+
# YES to this ask() resulting in an unintended URL of y. Special case
|
1077
|
+
# this common error
|
1078
|
+
url = nil if YES_SET.member? url
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
url ||= default_url
|
1082
|
+
|
1083
|
+
if ignore_framework
|
1084
|
+
framework = VMC::Cli::Framework.new
|
1085
|
+
elsif f = info(:framework)
|
1086
|
+
info = Hash[f["info"].collect { |k, v| [k.to_sym, v] }]
|
1087
|
+
|
1088
|
+
framework = VMC::Cli::Framework.new(f["name"], info)
|
1089
|
+
exec = framework.exec if framework && framework.exec
|
1090
|
+
else
|
1091
|
+
framework = detect_framework(prompt_ok)
|
1092
|
+
end
|
1093
|
+
|
1094
|
+
err "Application Type undetermined for path '#{@application}'" unless framework
|
1095
|
+
|
1096
|
+
if memswitch
|
1097
|
+
mem = memswitch
|
1098
|
+
elsif prompt_ok
|
1099
|
+
mem = ask("Memory Reservation",
|
1100
|
+
:default => framework.memory, :choices => mem_choices)
|
1101
|
+
else
|
1102
|
+
mem = framework.memory
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
# Set to MB number
|
1106
|
+
mem_quota = mem_choice_to_quota(mem)
|
1107
|
+
|
1108
|
+
# check memsize here for capacity
|
1109
|
+
check_has_capacity_for(mem_quota * instances) unless no_start
|
1110
|
+
|
1111
|
+
display 'Creating Application: ', false
|
1112
|
+
|
1113
|
+
manifest = {
|
1114
|
+
:name => "#{appname}",
|
1115
|
+
:staging => {
|
1116
|
+
:framework => framework.name,
|
1117
|
+
:runtime => info(:runtime)
|
1118
|
+
},
|
1119
|
+
:uris => Array(url),
|
1120
|
+
:instances => instances,
|
1121
|
+
:resources => {
|
1122
|
+
:memory => mem_quota
|
1123
|
+
},
|
1124
|
+
}
|
1125
|
+
|
1126
|
+
# Send the manifest to the cloud controller
|
1127
|
+
client.create_app(appname, manifest)
|
1128
|
+
display 'OK'.green
|
1129
|
+
|
1130
|
+
|
1131
|
+
existing = Set.new(client.services.collect { |s| s[:name] })
|
1132
|
+
|
1133
|
+
if @app_info && services = @app_info["services"]
|
1134
|
+
services.each do |name, info|
|
1135
|
+
unless existing.include? name
|
1136
|
+
create_service_banner(info["type"], name, true)
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
bind_service_banner(name, appname)
|
1140
|
+
end
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
# Stage and upload the app bits.
|
1144
|
+
upload_app_bits(appname, @application)
|
1145
|
+
|
1146
|
+
start(appname, true) unless no_start
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
def do_stats(appname)
|
1150
|
+
stats = client.app_stats(appname)
|
1151
|
+
return display JSON.pretty_generate(stats) if @options[:json]
|
1152
|
+
|
1153
|
+
stats_table = table do |t|
|
1154
|
+
t.headings = 'Instance', 'CPU (Cores)', 'Memory (limit)', 'Disk (limit)', 'Uptime'
|
1155
|
+
stats.each do |entry|
|
1156
|
+
index = entry[:instance]
|
1157
|
+
stat = entry[:stats]
|
1158
|
+
hp = "#{stat[:host]}:#{stat[:port]}"
|
1159
|
+
uptime = uptime_string(stat[:uptime])
|
1160
|
+
usage = stat[:usage]
|
1161
|
+
if usage
|
1162
|
+
cpu = usage[:cpu]
|
1163
|
+
mem = (usage[:mem] * 1024) # mem comes in K's
|
1164
|
+
disk = usage[:disk]
|
1165
|
+
end
|
1166
|
+
mem_quota = stat[:mem_quota]
|
1167
|
+
disk_quota = stat[:disk_quota]
|
1168
|
+
mem = "#{pretty_size(mem)} (#{pretty_size(mem_quota, 0)})"
|
1169
|
+
disk = "#{pretty_size(disk)} (#{pretty_size(disk_quota, 0)})"
|
1170
|
+
cpu = cpu ? cpu.to_s : 'NA'
|
1171
|
+
cpu = "#{cpu}% (#{stat[:cores]})"
|
1172
|
+
t << [index, cpu, mem, disk, uptime]
|
1173
|
+
end
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
if stats.empty?
|
1177
|
+
display "No running instances for [#{appname}]".yellow
|
1178
|
+
else
|
1179
|
+
display stats_table
|
1180
|
+
end
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
def all_files(appname, path)
|
1184
|
+
instances_info_envelope = client.app_instances(appname)
|
1185
|
+
return if instances_info_envelope.is_a?(Array)
|
1186
|
+
instances_info = instances_info_envelope[:instances] || []
|
1187
|
+
instances_info.each do |entry|
|
1188
|
+
begin
|
1189
|
+
content = client.app_files(appname, path, entry[:index])
|
1190
|
+
display_logfile(
|
1191
|
+
path,
|
1192
|
+
content,
|
1193
|
+
entry[:index],
|
1194
|
+
"====> [#{entry[:index]}: #{path}] <====\n".bold
|
1195
|
+
)
|
1196
|
+
rescue VMC::Client::NotFound
|
1197
|
+
end
|
1198
|
+
end
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
def create_hg_remote(appname, remote, repourl)
|
1202
|
+
return unless has_hg?
|
1203
|
+
return unless File.exists?(".hg")
|
1204
|
+
|
1205
|
+
# read hgrc
|
1206
|
+
hgrc = File.exists?('.hg/hgrc') ? File.read(".hg/hgrc") : ""
|
1207
|
+
if hgrc =~ /\[paths\]/
|
1208
|
+
hgrc.sub(/\[paths\]/, "[paths]\n#{remote} = #{repourl}")
|
1209
|
+
else
|
1210
|
+
hgrc << "\n[paths]\n#{remote} = #{repourl}\n"
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
# rewrite the hgrc
|
1214
|
+
f = File.open('.hg/hgrc', 'w')
|
1215
|
+
f.puts hgrc
|
1216
|
+
f.close
|
1217
|
+
|
1218
|
+
display "Mercurial path #{remote} added"
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
def create_git_remote(appname, remote, repourl)
|
1222
|
+
return unless has_git?
|
1223
|
+
return unless File.exists?(".git")
|
1224
|
+
return if git('remote').split("\n").include?(remote)
|
1225
|
+
git "remote add #{remote} #{repourl}"
|
1226
|
+
display "Git remote #{remote} added"
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
def has_hg?
|
1230
|
+
%x{ hg --version }
|
1231
|
+
$?.success?
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
def has_git?
|
1235
|
+
%x{ git --version }
|
1236
|
+
$?.success?
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
def has_git_commit?(path, sha)
|
1240
|
+
result = git("--git-dir=#{File.expand_path(File.join(path, '.git'))} branch --contains #{sha}")
|
1241
|
+
return false unless $?.success?
|
1242
|
+
!!(result =~ /^\*\s/)
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
def git(args)
|
1246
|
+
return "" unless has_git?
|
1247
|
+
flattened_args = [args].flatten.compact.join(" ")
|
1248
|
+
%x{ git #{flattened_args} 2>&1 }.strip
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
end
|
1252
|
+
|
1253
|
+
class FileWithPercentOutput < ::File
|
1254
|
+
class << self
|
1255
|
+
attr_accessor :display_str, :upload_size
|
1256
|
+
end
|
1257
|
+
|
1258
|
+
def update_display(rsize)
|
1259
|
+
@read ||= 0
|
1260
|
+
@read += rsize
|
1261
|
+
p = (@read * 100 / FileWithPercentOutput.upload_size).to_i
|
1262
|
+
unless VMC::Cli::Config.output.nil? || !STDOUT.tty?
|
1263
|
+
clear(FileWithPercentOutput.display_str.size + 5)
|
1264
|
+
VMC::Cli::Config.output.print("#{FileWithPercentOutput.display_str} #{p}%")
|
1265
|
+
VMC::Cli::Config.output.flush
|
1266
|
+
end
|
1267
|
+
end
|
1268
|
+
|
1269
|
+
def read(*args)
|
1270
|
+
result = super(*args)
|
1271
|
+
if result && result.size > 0
|
1272
|
+
update_display(result.size)
|
1273
|
+
else
|
1274
|
+
unless VMC::Cli::Config.output.nil? || !STDOUT.tty?
|
1275
|
+
clear(FileWithPercentOutput.display_str.size + 5)
|
1276
|
+
VMC::Cli::Config.output.print(FileWithPercentOutput.display_str)
|
1277
|
+
display('OK'.green)
|
1278
|
+
end
|
1279
|
+
end
|
1280
|
+
result
|
1281
|
+
end
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
end
|