vmc 0.0.4 → 0.0.5
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 +18 -2
- data/README +0 -3
- data/Rakefile +35 -3
- data/bin/vmc +5 -0
- data/bin/vmc.bat +1 -0
- data/lib/parse.rb +719 -0
- data/lib/vmc.rb +1588 -0
- data/lib/vmc_base.rb +205 -0
- data/vendor/gems/httpclient/VERSION +1 -0
- data/vendor/gems/httpclient/lib/http-access2/cookie.rb +1 -0
- data/vendor/gems/httpclient/lib/http-access2/http.rb +1 -0
- data/vendor/gems/httpclient/lib/http-access2.rb +53 -0
- data/vendor/gems/httpclient/lib/httpclient/auth.rb +522 -0
- data/vendor/gems/httpclient/lib/httpclient/cacert.p7s +1579 -0
- data/vendor/gems/httpclient/lib/httpclient/cacert_sha1.p7s +1579 -0
- data/vendor/gems/httpclient/lib/httpclient/connection.rb +84 -0
- data/vendor/gems/httpclient/lib/httpclient/cookie.rb +562 -0
- data/vendor/gems/httpclient/lib/httpclient/http.rb +867 -0
- data/vendor/gems/httpclient/lib/httpclient/session.rb +864 -0
- data/vendor/gems/httpclient/lib/httpclient/ssl_config.rb +417 -0
- data/vendor/gems/httpclient/lib/httpclient/timeout.rb +136 -0
- data/vendor/gems/httpclient/lib/httpclient/util.rb +86 -0
- data/vendor/gems/httpclient/lib/httpclient.rb +1020 -0
- data/vendor/gems/httpclient/lib/tags +908 -0
- metadata +142 -10
data/lib/vmc.rb
CHANGED
@@ -0,0 +1,1588 @@
|
|
1
|
+
# Copyright 2010, VMware, Inc. Licensed under the
|
2
|
+
# MIT license, please see the LICENSE file. All rights reserved
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'pp'
|
6
|
+
require 'vmc_base'
|
7
|
+
require 'highline/import'
|
8
|
+
|
9
|
+
module VMC
|
10
|
+
|
11
|
+
class Client < VMC::BaseClient
|
12
|
+
|
13
|
+
VERSION = 0.999
|
14
|
+
|
15
|
+
attr_reader :host, :base_uri, :droplets_uri, :services_uri, :resources_uri, :token, :args
|
16
|
+
|
17
|
+
attr_accessor :puser
|
18
|
+
|
19
|
+
def setup_target_uris
|
20
|
+
get_host_target
|
21
|
+
@base_uri = "http://#{@host}"
|
22
|
+
@droplets_uri = "#{base_uri}/apps"
|
23
|
+
@services_uri = "#{base_uri}/services"
|
24
|
+
@resources_uri = "#{base_uri}/resources"
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_target
|
28
|
+
get_token # Just load it if its around
|
29
|
+
@check = HTTPClient.get "#{base_uri}/info", nil, auth_hdr
|
30
|
+
error "\nERROR: Unable to contact target server: [#{@base_uri}]\n\n" if @check.status != 200
|
31
|
+
#display "\n[#{@base_uri}]\n\n"
|
32
|
+
check_puser if @puser
|
33
|
+
rescue
|
34
|
+
error "\nERROR: Unable to contact target server: [#{@base_uri}]\n\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_puser(puser)
|
38
|
+
@puser = puser
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_puser
|
42
|
+
info_json = JSON.parse(@check.content)
|
43
|
+
username = info_json['user']
|
44
|
+
error "ERROR: Proxying to #{@puser} failed." unless (username && (username.downcase == @puser.downcase))
|
45
|
+
end
|
46
|
+
|
47
|
+
# ------- commands --------
|
48
|
+
|
49
|
+
# show version string
|
50
|
+
def version
|
51
|
+
display VERSION
|
52
|
+
exit 0
|
53
|
+
end
|
54
|
+
|
55
|
+
# Define a new cloud controller target
|
56
|
+
def target(host=nil)
|
57
|
+
unless host
|
58
|
+
display "\n[#{@base_uri}]\n\n"
|
59
|
+
return
|
60
|
+
end
|
61
|
+
|
62
|
+
target_host = host.gsub(/^http(s*):\/\//i, '')
|
63
|
+
try_host = "http://#{target_host}/info"
|
64
|
+
target_host_display = "http://#{target_host}"
|
65
|
+
display ''
|
66
|
+
begin
|
67
|
+
response = HTTPClient.get try_host
|
68
|
+
if response.status != 200
|
69
|
+
display "New Target host is not valid: '#{target_host_display}'"
|
70
|
+
show_response = ask "Would you like to use one of these (y/n)? "
|
71
|
+
display response.content if show_response.upcase == 'Y'
|
72
|
+
exit(1)
|
73
|
+
end
|
74
|
+
response_json = JSON.parse(response.content)
|
75
|
+
error "New Target host is not valid: '#{target_host_display}'" unless
|
76
|
+
response_json['name'] && response_json['version'] && response_json['support'] && response_json['description']
|
77
|
+
rescue => e
|
78
|
+
display e
|
79
|
+
error "New Target host is not valid: '#{target_host_display}'"
|
80
|
+
end
|
81
|
+
write_host_target(target_host)
|
82
|
+
display "Succesfully targeted to [#{target_host_display}]\n\n"
|
83
|
+
rescue => e
|
84
|
+
error "Problem executing command, #{e}"
|
85
|
+
end
|
86
|
+
|
87
|
+
# get information about this cloud
|
88
|
+
def info
|
89
|
+
info_json = JSON.parse(@check.content)
|
90
|
+
|
91
|
+
display "\n#{info_json['description']}"
|
92
|
+
display "For support visit #{info_json['support']}"
|
93
|
+
display ""
|
94
|
+
display "Target: #{@base_uri} (v#{info_json['version']})"
|
95
|
+
display "Client: v#{VERSION}"
|
96
|
+
if info_json['user']
|
97
|
+
display ''
|
98
|
+
display "User: #{info_json['user']}"
|
99
|
+
end
|
100
|
+
if usage = info_json['usage'] and limits = info_json['limits']
|
101
|
+
tmem = pretty_size(limits['memory']*1024*1024)
|
102
|
+
mem = pretty_size(usage['memory']*1024*1024)
|
103
|
+
tser = limits['services']
|
104
|
+
ser = usage['services']
|
105
|
+
tapps = limits['apps'] || 0
|
106
|
+
apps = usage['apps'] || 0
|
107
|
+
|
108
|
+
display "Usage: Memory (#{mem} of #{tmem} total)"
|
109
|
+
display " Services (#{ser} of #{tser} total)"
|
110
|
+
display " Apps (#{apps} of #{tapps} total)" if limits['apps']
|
111
|
+
end
|
112
|
+
display ''
|
113
|
+
rescue => e
|
114
|
+
error "Problem executing command, #{e}"
|
115
|
+
end
|
116
|
+
|
117
|
+
# register me as a user of this cloud
|
118
|
+
def register(email=nil,password=nil)
|
119
|
+
if not email and not password
|
120
|
+
puts "Register your account with an email account and password."
|
121
|
+
email = ask("Email: ")
|
122
|
+
password = ask("Password: ") {|q| q.echo = '*'}
|
123
|
+
password2 = ask("Verify Password: ") {|q| q.echo = '*'}
|
124
|
+
error "Passwords did not match, try again" if password != password2
|
125
|
+
end
|
126
|
+
get_token # Just load it if its around
|
127
|
+
register_internal(@base_uri, email, password, auth_hdr)
|
128
|
+
display "Registration completed"
|
129
|
+
# do an autologin also to setup token, avoiding login on next VMC command.
|
130
|
+
login_save_token(email, password) unless @token
|
131
|
+
rescue => e
|
132
|
+
error "Problem registering, #{e}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def login(email=nil,password=nil)
|
136
|
+
if email and not password
|
137
|
+
tries = 0
|
138
|
+
begin
|
139
|
+
password = ask("Password: ") {|q| q.echo = '*'}
|
140
|
+
login_save_token(email, password)
|
141
|
+
display ""
|
142
|
+
rescue => e
|
143
|
+
display "Problem with login, #{e}, try again or register for an account."
|
144
|
+
retry if (tries += 1) < 3
|
145
|
+
exit 1
|
146
|
+
end
|
147
|
+
elsif !(email && password)
|
148
|
+
tries = 0
|
149
|
+
begin
|
150
|
+
email = ask("Email: ")
|
151
|
+
password = ask("Password: ") {|q| q.echo = '*'}
|
152
|
+
login_save_token(email, password)
|
153
|
+
display ""
|
154
|
+
rescue => e
|
155
|
+
display "Problem with login, #{e}, try again or register for an account."
|
156
|
+
retry if (tries += 1) < 3
|
157
|
+
exit 1
|
158
|
+
end
|
159
|
+
else
|
160
|
+
begin
|
161
|
+
login_save_token(email, password)
|
162
|
+
display ""
|
163
|
+
rescue => e
|
164
|
+
error "Problem with login, #{e}, try again or register for an account."
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def logout
|
170
|
+
FileUtils.rm_f(token_file)
|
171
|
+
display "Succesfully logged out."
|
172
|
+
end
|
173
|
+
|
174
|
+
# Change the current users passwd
|
175
|
+
def passwd(password=nil)
|
176
|
+
check_for_token
|
177
|
+
user_info = JSON.parse(@check.content)
|
178
|
+
email = user_info['user']
|
179
|
+
puts "Changing password for #{email}\n\n"
|
180
|
+
if !password
|
181
|
+
password = ask("Password: ") {|q| q.echo = '*'}
|
182
|
+
password2 = ask("Verify Password: ") {|q| q.echo = '*'}
|
183
|
+
error "Passwords did not match, try again" if password != password2
|
184
|
+
end
|
185
|
+
|
186
|
+
response = get_user_internal(@base_uri, email, auth_hdr)
|
187
|
+
user_info = JSON.parse(response.content)
|
188
|
+
user_info['password'] = password
|
189
|
+
change_passwd_internal(@base_uri, user_info, auth_hdr)
|
190
|
+
|
191
|
+
display "Password succesfully changed."
|
192
|
+
# do an autologin also to setup token, avoiding login on next VMC command.
|
193
|
+
# only if not proxying
|
194
|
+
login_save_token(email, password) unless @puser
|
195
|
+
rescue => e
|
196
|
+
error "Problem changing password, #{e}"
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
def login_save_token(email, password)
|
201
|
+
# TODO: We should be url encoding email
|
202
|
+
@token = login_internal(@base_uri, email, password)
|
203
|
+
write_token
|
204
|
+
end
|
205
|
+
|
206
|
+
def target_file
|
207
|
+
"#{ENV['HOME']}/.vmc_target"
|
208
|
+
end
|
209
|
+
|
210
|
+
def get_host_target
|
211
|
+
return if @host
|
212
|
+
if File.exists? target_file
|
213
|
+
@host = File.read(target_file).strip!
|
214
|
+
ha = @host.split('.')
|
215
|
+
ha.shift
|
216
|
+
@suggest_url = ha.join('.')
|
217
|
+
@suggest_url = 'b29.me' if @suggest_url.empty?
|
218
|
+
else
|
219
|
+
@host = 'api.b29.me'
|
220
|
+
@suggest_url = 'b29.me'
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def write_host_target(target_host)
|
225
|
+
File.open(target_file, 'w+') { |f| f.puts target_host }
|
226
|
+
FileUtils.chmod 0600, target_file
|
227
|
+
end
|
228
|
+
|
229
|
+
def token_file
|
230
|
+
"#{ENV['HOME']}/.vmc_token"
|
231
|
+
end
|
232
|
+
|
233
|
+
def instance_file
|
234
|
+
"#{ENV['HOME']}/.vmc_instances"
|
235
|
+
end
|
236
|
+
|
237
|
+
def get_token
|
238
|
+
return @token if @token
|
239
|
+
if File.exists?(token_file)
|
240
|
+
contents = File.read(token_file).strip
|
241
|
+
begin
|
242
|
+
tokens = JSON.parse(contents)
|
243
|
+
@token = tokens[@base_uri]
|
244
|
+
rescue
|
245
|
+
@token = contents
|
246
|
+
write_token
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def check_for_token
|
252
|
+
return if get_token
|
253
|
+
display "Please Login:\n\n"
|
254
|
+
login
|
255
|
+
check_for_token
|
256
|
+
end
|
257
|
+
|
258
|
+
def write_token
|
259
|
+
contents = File.exists?(token_file) ? File.read(token_file).strip : nil
|
260
|
+
tokens = {}
|
261
|
+
|
262
|
+
if contents
|
263
|
+
begin
|
264
|
+
tokens = JSON.parse(contents)
|
265
|
+
rescue
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
tokens[@base_uri] = @token
|
270
|
+
|
271
|
+
File.open(token_file, 'w+') { |f| f.write(tokens.to_json) }
|
272
|
+
FileUtils.chmod 0600, token_file
|
273
|
+
end
|
274
|
+
|
275
|
+
def auth_hdr
|
276
|
+
auth = { 'AUTHORIZATION' => "#{@token}" }
|
277
|
+
auth['PROXY-USER'] = @puser if @puser
|
278
|
+
return auth
|
279
|
+
end
|
280
|
+
|
281
|
+
def display_services_banner
|
282
|
+
#display "#{'Name'.ljust(15)} #{'Service'.ljust(10)} #{'Version'.ljust(10)} #{'Plans'}"
|
283
|
+
#display "#{'----'.ljust(15)} #{'-------'.ljust(10)} #{'-------'.ljust(10)} #{'-----'}"
|
284
|
+
display "#{'Name'.ljust(15)} #{'Service'.ljust(10)}"
|
285
|
+
display "#{'----'.ljust(15)} #{'-------'.ljust(10)}"
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
def list_services(service_names, banner)
|
290
|
+
services = []
|
291
|
+
service_names.each { |service_name|
|
292
|
+
sn = URI.escape("#{services_uri}/#{service_name}")
|
293
|
+
response = HTTPClient.get(sn, nil, auth_hdr)
|
294
|
+
error "Problem getting services list" if response.status != 200
|
295
|
+
services << JSON.parse(response.content)
|
296
|
+
}
|
297
|
+
|
298
|
+
display(banner)
|
299
|
+
|
300
|
+
if services.empty?
|
301
|
+
display "None provisioned yet."
|
302
|
+
else
|
303
|
+
display_services_banner
|
304
|
+
services.each { |h|
|
305
|
+
display "#{h['name'].to_s.ljust(15)} ", false
|
306
|
+
display "#{h['vendor'].ljust(10)} "
|
307
|
+
#display "#{h['vendor'].ljust(10)} ", false
|
308
|
+
#display "#{h['version'].ljust(10)} ", false
|
309
|
+
#display "#{h['tier']}"
|
310
|
+
}
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def app_list_services(appname=nil)
|
315
|
+
error "Application name required\nvmc app services <appname>" unless appname
|
316
|
+
|
317
|
+
response = HTTPClient.get "#{droplets_uri}/#{appname}", nil, auth_hdr
|
318
|
+
error "Application does not exist." if response.status == 404
|
319
|
+
return '' if response.status != 200
|
320
|
+
service_names = JSON.parse(response.content)['services']
|
321
|
+
service_names.empty? ? "None" : service_names.join(", ")
|
322
|
+
end
|
323
|
+
|
324
|
+
def user_list_services_helper
|
325
|
+
service_names = get_user_service_names
|
326
|
+
#list_services(service_names, "Provisioned Services:\n\n")
|
327
|
+
list_services(service_names, "\n======= Provisioned Services ========\n\n")
|
328
|
+
end
|
329
|
+
|
330
|
+
def get_user_service_names
|
331
|
+
service_names = []
|
332
|
+
response = HTTPClient.get "#{services_uri}", nil, auth_hdr
|
333
|
+
response_json = JSON.parse(response.content)
|
334
|
+
response_json.each { |service_desc| service_names << service_desc['name'] }
|
335
|
+
service_names
|
336
|
+
end
|
337
|
+
|
338
|
+
def services
|
339
|
+
check_for_token
|
340
|
+
response = HTTPClient.get "#{base_uri}/info/services", nil, auth_hdr
|
341
|
+
handle_response(response)
|
342
|
+
display "\n========== System Services ==========\n\n"
|
343
|
+
services = JSON.parse(response.content)
|
344
|
+
display_services_directory_banner
|
345
|
+
services.each { |service_type, value|
|
346
|
+
value.each { |vendor, version|
|
347
|
+
version.each { |version_str, service|
|
348
|
+
display "#{vendor.ljust(10)} ", false
|
349
|
+
display "#{version_str.ljust(5)} ", false
|
350
|
+
display "#{service['description']}"
|
351
|
+
}
|
352
|
+
}
|
353
|
+
}
|
354
|
+
display ""
|
355
|
+
user_list_services_helper
|
356
|
+
display ""
|
357
|
+
rescue => e
|
358
|
+
error "Problem executing command, #{e}"
|
359
|
+
end
|
360
|
+
|
361
|
+
def bind_service(service_name, appname)
|
362
|
+
display "Creating new service binding to '#{service_name}' for '#{appname}'."
|
363
|
+
|
364
|
+
# Now get the app and update it with the provisioned service
|
365
|
+
response = get_app_internal(droplets_uri, appname, auth_hdr)
|
366
|
+
appinfo = JSON.parse(response.content)
|
367
|
+
provisioned_service = appinfo['services']
|
368
|
+
provisioned_service = [] unless provisioned_service
|
369
|
+
provisioned_service << service_name
|
370
|
+
appinfo['services'] = provisioned_service
|
371
|
+
response = update_app_state_internal droplets_uri, appname, appinfo, auth_hdr
|
372
|
+
error "Problem updating application with new services" if response.status >= 400
|
373
|
+
display "Application '#{appname}' updated."
|
374
|
+
restart appname if appinfo['state'] == 'STARTED'
|
375
|
+
rescue => e
|
376
|
+
error "Problem executing command, #{e}"
|
377
|
+
end
|
378
|
+
|
379
|
+
def unbind_service(service_name, appname)
|
380
|
+
app_remove_service(appname, service_name)
|
381
|
+
end
|
382
|
+
|
383
|
+
def create_service(service, opts={})
|
384
|
+
check_for_token
|
385
|
+
response = HTTPClient.get "#{base_uri}/info/services", nil, auth_hdr
|
386
|
+
handle_response(response)
|
387
|
+
services = JSON.parse(response.content)
|
388
|
+
types = []
|
389
|
+
services.each {|k,v| types << v }
|
390
|
+
selection = nil
|
391
|
+
types.each { |t| selection = t if t[service] }
|
392
|
+
if ! selection
|
393
|
+
options = []
|
394
|
+
types.each {|t| options << t.keys }
|
395
|
+
options.flatten!
|
396
|
+
error("Service type must be one of: #{options.join(',')}") unless options.empty?
|
397
|
+
error("No selectable services at this time") if options.empty?
|
398
|
+
else
|
399
|
+
info = selection[service][selection[service].keys.first]
|
400
|
+
service_name = opts[:name] || "#{service}-#{fast_uuid[0..6]}"
|
401
|
+
service_type = info['type']
|
402
|
+
service_vendor = info['vendor']
|
403
|
+
service_tier = 'free'
|
404
|
+
service_version = info['version']
|
405
|
+
|
406
|
+
services = {
|
407
|
+
:name => service_name,
|
408
|
+
:type => service_type,
|
409
|
+
:vendor => service_vendor,
|
410
|
+
:tier => service_tier,
|
411
|
+
:version => service_version,
|
412
|
+
#:options => option_values
|
413
|
+
}
|
414
|
+
|
415
|
+
check_for_token
|
416
|
+
response = add_service_internal @services_uri, services, auth_hdr
|
417
|
+
handle_response(response)
|
418
|
+
display "Service '#{service_name}' provisioned."
|
419
|
+
|
420
|
+
if appname = opts[:bind]
|
421
|
+
display "Creating new service binding to '#{service_name}' for '#{appname}'."
|
422
|
+
|
423
|
+
# Now get the app and update it with the provisioned service
|
424
|
+
response = get_app_internal(droplets_uri, appname, auth_hdr)
|
425
|
+
handle_response(response)
|
426
|
+
appinfo = JSON.parse(response.content)
|
427
|
+
provisioned_service = appinfo['services']
|
428
|
+
provisioned_service = [] unless provisioned_service
|
429
|
+
provisioned_service << service_name
|
430
|
+
appinfo['services'] = provisioned_service
|
431
|
+
response = update_app_state_internal droplets_uri, appname, appinfo, auth_hdr
|
432
|
+
handle_response(response)
|
433
|
+
display "Application '#{appname}' updated."
|
434
|
+
restart appname if appinfo['state'] == 'STARTED'
|
435
|
+
end
|
436
|
+
end
|
437
|
+
rescue => e
|
438
|
+
error "Problem executing command, #{e}"
|
439
|
+
end
|
440
|
+
|
441
|
+
def delete_service(service)
|
442
|
+
check_for_token
|
443
|
+
user_remove_service_helper(service)
|
444
|
+
end
|
445
|
+
|
446
|
+
def user_remove_service_helper(service_name=nil)
|
447
|
+
available_services = get_user_service_names
|
448
|
+
error "No services to delete" if available_services.empty?
|
449
|
+
|
450
|
+
unless service_name
|
451
|
+
choose do |menu|
|
452
|
+
#menu.header = "The following services are provisioned"
|
453
|
+
menu.prompt = 'Please select the service you wish to delete: '
|
454
|
+
menu.select_by = :index
|
455
|
+
available_services.each { |key| menu.choice(key) { service_name = key } }
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
error "Error: Invalid service" unless available_services.include? service_name
|
460
|
+
|
461
|
+
remove_service_internal(services_uri, service_name, auth_hdr)
|
462
|
+
display("Successfully removed service: #{service_name}")
|
463
|
+
end
|
464
|
+
|
465
|
+
def user_add_service_helper(service_name_prefix, opts={})
|
466
|
+
service_type = opts[:type]
|
467
|
+
service_vendor = opts[:vendor]
|
468
|
+
service_version = opts[:version]
|
469
|
+
service_tier = opts[:tier]
|
470
|
+
service_name = opts[:name]
|
471
|
+
service_size = opts[:size]
|
472
|
+
response = HTTPClient.get "#{base_uri}/info/services", nil, auth_hdr
|
473
|
+
handle_response(response)
|
474
|
+
|
475
|
+
last_print = :none
|
476
|
+
|
477
|
+
services = JSON.parse(response.content)
|
478
|
+
|
479
|
+
available_services = {}
|
480
|
+
services.each do |type, vendors|
|
481
|
+
vendors.each do |vendor, versions|
|
482
|
+
versions.each do |version, entry|
|
483
|
+
tiers = entry["tiers"]
|
484
|
+
if tiers.size > 1
|
485
|
+
tier = tiers.has_key?("free") ? "free" : tiers.keys.first
|
486
|
+
entry["tiers"] = {tier => tiers[tier]}
|
487
|
+
end
|
488
|
+
available_services[vendor] = {:type => type, :version => version} if (service_type.nil? or service_type == type)
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
return nil if available_services.empty?
|
494
|
+
|
495
|
+
service_type_hash = nil
|
496
|
+
service_vendor = nil if (service_vendor and service_vendor.empty?)
|
497
|
+
unless service_vendor
|
498
|
+
|
499
|
+
if available_services.length == 1
|
500
|
+
service_vendor = available_services.keys.first
|
501
|
+
service_type_hash = services[available_services[service_vendor][:type]]
|
502
|
+
|
503
|
+
service_type = available_services[service_vendor][:type]
|
504
|
+
say("Single #{service_type} service available: #{service_vendor}")
|
505
|
+
last_print = :auto
|
506
|
+
else
|
507
|
+
puts "" if last_print == :auto
|
508
|
+
choose do |menu|
|
509
|
+
menu.header = "The following #{service_type} services are available"
|
510
|
+
menu.prompt = 'Please select one you wish to provision: '
|
511
|
+
menu.select_by = :index
|
512
|
+
|
513
|
+
available_services.each_key do |key|
|
514
|
+
menu.choice(key) do
|
515
|
+
service_type = available_services[key][:type]
|
516
|
+
service_type_hash = services[service_type]
|
517
|
+
service_vendor = key
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
last_print = :menu
|
522
|
+
end
|
523
|
+
end
|
524
|
+
error "Could not find vendor '#{service_vendor}' for #{service_type} services." unless service_type_hash[service_vendor]
|
525
|
+
|
526
|
+
service_vendor_hash = service_type_hash[service_vendor]
|
527
|
+
|
528
|
+
unless service_version
|
529
|
+
puts "" if last_print == :menu
|
530
|
+
if service_vendor_hash.length == 1
|
531
|
+
service_version = service_vendor_hash.first[0]
|
532
|
+
say("Single version available: #{service_version}")
|
533
|
+
last_print = :auto
|
534
|
+
else
|
535
|
+
puts "" if last_print == :auto
|
536
|
+
choose do |menu|
|
537
|
+
menu.header = "The following #{service_vendor} #{service_type} versions are available"
|
538
|
+
menu.prompt = 'Please select one you wish to provision: '
|
539
|
+
menu.select_by = :index
|
540
|
+
|
541
|
+
service_vendor_hash.each_key do |key|
|
542
|
+
menu.choice(key) { service_version = key }
|
543
|
+
end
|
544
|
+
end
|
545
|
+
last_print = :menu
|
546
|
+
end
|
547
|
+
end
|
548
|
+
error "Could not find version '#{service_version}' for #{service_vendor} #{service_type}." unless service_vendor_hash[service_version]
|
549
|
+
|
550
|
+
service_version_hash = service_vendor_hash[service_version]
|
551
|
+
service_tier = service_version_hash['tiers'].keys.first
|
552
|
+
|
553
|
+
service_tier_hash = service_version_hash['tiers'][service_tier]
|
554
|
+
options = service_tier_hash['options']
|
555
|
+
|
556
|
+
option_values = {}
|
557
|
+
if options
|
558
|
+
options.each do |k, v|
|
559
|
+
|
560
|
+
if service_size
|
561
|
+
option_values['size'] = service_size
|
562
|
+
else
|
563
|
+
puts "" if last_print == :menu
|
564
|
+
if v['type'] == 'value'
|
565
|
+
puts "" if last_print == :auto
|
566
|
+
choose do |menu|
|
567
|
+
menu.prompt = "#{k} (#{v['description']}): "
|
568
|
+
menu.select_by = :index
|
569
|
+
|
570
|
+
|
571
|
+
v['values'].each do |option|
|
572
|
+
option_value = option
|
573
|
+
menu.choice("#{option}") { option_values[k] = option_value }
|
574
|
+
end
|
575
|
+
end
|
576
|
+
last_print = :menu
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
default_service_name = "#{service_vendor}-#{fast_uuid[0..6]}"
|
583
|
+
if service_name.nil?
|
584
|
+
puts ""
|
585
|
+
service_name = ask("Specify the name of the service [#{default_service_name}]: ")
|
586
|
+
service_name = default_service_name if service_name.empty?
|
587
|
+
end
|
588
|
+
|
589
|
+
services = {
|
590
|
+
:name => service_name,
|
591
|
+
:type => service_type,
|
592
|
+
:vendor => service_vendor,
|
593
|
+
:tier => service_tier,
|
594
|
+
:version => service_version,
|
595
|
+
:options => option_values
|
596
|
+
}
|
597
|
+
|
598
|
+
check_for_token
|
599
|
+
response = add_service_internal @services_uri, services, auth_hdr
|
600
|
+
handle_response(response)
|
601
|
+
display "Service '#{service_vendor}' provisioned."
|
602
|
+
|
603
|
+
service_name
|
604
|
+
end
|
605
|
+
|
606
|
+
|
607
|
+
def app_add_service(opts={})
|
608
|
+
service_type = opts[:service]
|
609
|
+
service_vendor = opts[:vendor]
|
610
|
+
service_version = opts[:version]
|
611
|
+
service_tier = opts[:tier]
|
612
|
+
appname = opts[:appname]
|
613
|
+
|
614
|
+
error "Application name required\nvmc apps add-service <appname> [service] [vendor] [version] [tier]" unless appname
|
615
|
+
|
616
|
+
app_add_service_helper(appname, service_type, service_vendor, service_version, service_tier)
|
617
|
+
|
618
|
+
@appname = appname
|
619
|
+
restart
|
620
|
+
end
|
621
|
+
|
622
|
+
def app_add_service_helper(appname, service_type=nil, service_vendor=nil, service_version=nil, service_tier=nil)
|
623
|
+
check_for_token
|
624
|
+
|
625
|
+
# If we have a single arg, let's assume its a service name..
|
626
|
+
if service_type && !service_vendor && !service_version && !service_tier
|
627
|
+
service_name = service_type
|
628
|
+
service_type = nil
|
629
|
+
end
|
630
|
+
|
631
|
+
service_names = get_user_service_names
|
632
|
+
|
633
|
+
if service_name && (service_names.empty? || !service_names.include?(service_name))
|
634
|
+
service_name = nil
|
635
|
+
end
|
636
|
+
|
637
|
+
if !service_names.empty? && !service_name
|
638
|
+
list_services(get_user_service_names, "The following services have been provisioned for you:")
|
639
|
+
display ''
|
640
|
+
use_existing = ask "Would you like to use one of these (y/n)? "
|
641
|
+
if use_existing.upcase == 'Y'
|
642
|
+
begin
|
643
|
+
service_name = ask "Which service (name)? "
|
644
|
+
if !service_names.include?(service_name)
|
645
|
+
display "Incorrect service name, please type again"
|
646
|
+
service_name = nil
|
647
|
+
end
|
648
|
+
end while !service_name
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
if !service_name
|
653
|
+
display "Let's provision a new service"
|
654
|
+
opts = {
|
655
|
+
:type => service_type,
|
656
|
+
:vendor => service_vendor,
|
657
|
+
:version => service_version,
|
658
|
+
:tier => service_tier
|
659
|
+
}
|
660
|
+
service_name = user_add_service_helper(appname, opts)
|
661
|
+
end
|
662
|
+
|
663
|
+
unless service_name
|
664
|
+
display "No services available to provision"
|
665
|
+
# Should we ask if they want to continue?
|
666
|
+
return
|
667
|
+
end
|
668
|
+
|
669
|
+
display "Creating new service binding to '#{service_name}' for '#{appname}'."
|
670
|
+
|
671
|
+
# Now get the app and update it with the provisioned service
|
672
|
+
response = get_app_internal(droplets_uri, appname, auth_hdr)
|
673
|
+
appinfo = JSON.parse(response.content)
|
674
|
+
provisioned_service = appinfo['services']
|
675
|
+
provisioned_service = [] unless provisioned_service
|
676
|
+
provisioned_service << service_name
|
677
|
+
appinfo['services'] = provisioned_service
|
678
|
+
response = update_app_state_internal droplets_uri, appname, appinfo, auth_hdr
|
679
|
+
handle_response(response)
|
680
|
+
display "Application '#{appname}' updated."
|
681
|
+
end
|
682
|
+
|
683
|
+
def format_price(amount, type, *rest)
|
684
|
+
case type
|
685
|
+
when 'flat'
|
686
|
+
return "$#{amount}/#{rest[0]}"
|
687
|
+
when 'metered'
|
688
|
+
raise "Not Implemented"
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
def app_remove_service(appname=nil, service_name=nil)
|
693
|
+
check_for_token
|
694
|
+
|
695
|
+
app_response = get_app_internal(droplets_uri, appname, auth_hdr)
|
696
|
+
appinfo = JSON.parse(app_response.content)
|
697
|
+
provisioned_service = appinfo['services']
|
698
|
+
provisioned_service = [] unless provisioned_service
|
699
|
+
provisioned_service.delete(service_name)
|
700
|
+
appinfo['services'] = provisioned_service
|
701
|
+
response = update_app_state_internal droplets_uri, appname, appinfo, auth_hdr
|
702
|
+
handle_response(response)
|
703
|
+
display "Application '#{appname}' updated"
|
704
|
+
display "Service #{service_name} removed."
|
705
|
+
@appname = appname
|
706
|
+
restart
|
707
|
+
rescue => e
|
708
|
+
error "Problem executing command, #{e}"
|
709
|
+
end
|
710
|
+
|
711
|
+
def list(first_arg=nil)
|
712
|
+
apps(first_arg)
|
713
|
+
end
|
714
|
+
|
715
|
+
def apps(first_arg=nil, opts={})
|
716
|
+
check_for_token
|
717
|
+
|
718
|
+
case first_arg
|
719
|
+
when "list" then list_apps
|
720
|
+
when nil then list_apps
|
721
|
+
when 'add-service' then app_add_service(opts)
|
722
|
+
when 'remove-service' then app_remove_service(opts[:appname], opts[:service_name])
|
723
|
+
when 'list-services' then app_list_services(opts[:appname])
|
724
|
+
else "Incorrect option #{first_arg}. Must be 'list' (default), 'services', 'add-service', or 'remove-service"
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
def health(d)
|
729
|
+
return 'N/A' unless (d and d['state'])
|
730
|
+
|
731
|
+
healthy_instances = d['runningInstances']
|
732
|
+
expected_instance = d['instances']
|
733
|
+
health = nil
|
734
|
+
|
735
|
+
if d['state'] == "STARTED" && expected_instance > 0 && healthy_instances
|
736
|
+
health = format("%.3f", healthy_instances.to_f / expected_instance).to_f
|
737
|
+
end
|
738
|
+
|
739
|
+
if health
|
740
|
+
if health == 1.0
|
741
|
+
health = "RUNNING"
|
742
|
+
else
|
743
|
+
health = "#{(health * 100).round}%"
|
744
|
+
end
|
745
|
+
else
|
746
|
+
if d['state'] == 'STOPPED'
|
747
|
+
health = 'STOPPED'
|
748
|
+
else
|
749
|
+
health = 'N/A'
|
750
|
+
end
|
751
|
+
end
|
752
|
+
|
753
|
+
health
|
754
|
+
end
|
755
|
+
|
756
|
+
def list_apps
|
757
|
+
droplets_full = get_apps_internal @droplets_uri, auth_hdr
|
758
|
+
if droplets_full.empty?
|
759
|
+
display "No applications available."
|
760
|
+
return
|
761
|
+
end
|
762
|
+
|
763
|
+
display "#{'APPNAME'.ljust 15} #{'HEALTH'.ljust 8} #{'INSTANCES'.ljust 10} #{'SERVICES'.ljust 25} #{'URL'.ljust 15}\n"
|
764
|
+
display "#{'-------'.ljust 15} #{'------'.ljust 8} #{'---------'.ljust 10} #{'--------'.ljust 25} #{'---'.ljust 15}\n"
|
765
|
+
droplets_full.each { |d|
|
766
|
+
display "#{d['name'].ljust 15} ", false
|
767
|
+
display "#{health(d).ljust 8} ", false
|
768
|
+
display "#{d['instances'].to_s.ljust 10} ", false
|
769
|
+
display "#{d['services'].join(", ").ljust(25)} ", false
|
770
|
+
#display "#{app_list_services(d['name']).ljust(25)} ", false
|
771
|
+
display "#{d['uris'].join(', ').ljust(15)}"
|
772
|
+
}
|
773
|
+
rescue => e
|
774
|
+
error "Problem executing command, #{e}"
|
775
|
+
end
|
776
|
+
|
777
|
+
# opts = {:instances => [optional], :exec => [optional],
|
778
|
+
# :ignore_framework => [true/false/optional],
|
779
|
+
# :noframework => [true/false/optional],
|
780
|
+
# :appname => [optional],
|
781
|
+
# :url => [optional]}
|
782
|
+
|
783
|
+
def push(opts={})
|
784
|
+
instances = opts.delete(:instances) || 1
|
785
|
+
exec = opts.delete(:exec) || 'thin start'
|
786
|
+
ignore_framework = opts.delete(:noframework)
|
787
|
+
no_start = opts[:no_start]
|
788
|
+
|
789
|
+
path = opts[:path] || '.'
|
790
|
+
appname = opts[:appname]
|
791
|
+
url = opts[:url]
|
792
|
+
memswitch = opts[:mem]
|
793
|
+
|
794
|
+
check_for_token
|
795
|
+
|
796
|
+
# check if we have hit our app limit
|
797
|
+
check_app_limit
|
798
|
+
|
799
|
+
# check memsize here for capacity
|
800
|
+
check_has_capacity_for(mem_choice_to_quota(memswitch)) if memswitch
|
801
|
+
|
802
|
+
unless (appname && url)
|
803
|
+
unless opts[:path]
|
804
|
+
proceed = ask("Would you like to deploy from the current directory? [Yn]: ")
|
805
|
+
error "Push aborted" if proceed == 'n' || proceed == 'N'
|
806
|
+
end
|
807
|
+
appname = ask("Application Name: ") unless appname
|
808
|
+
error "Push aborted: Application Name required." if appname.empty?
|
809
|
+
url = ask("Application Deployed URL: '#{appname}.#{@suggest_url}'? ") unless url
|
810
|
+
url = "#{appname}.#{@suggest_url}" if url.empty?
|
811
|
+
end
|
812
|
+
|
813
|
+
response = get_app_internal(@droplets_uri, appname, auth_hdr)
|
814
|
+
error "Application #{appname} already exists, use update or delete." if response.status == 200
|
815
|
+
|
816
|
+
Dir.chdir(path) do
|
817
|
+
# Detect the appropriate framework.
|
818
|
+
framework = "http://b20nine.com/unknown"
|
819
|
+
mem = '256M'
|
820
|
+
unless ignore_framework
|
821
|
+
if File.exist?('config/environment.rb')
|
822
|
+
display "Rails application detected."
|
823
|
+
framework = "rails/1.0"
|
824
|
+
elsif Dir.glob('*.war').first
|
825
|
+
opt_war_file = Dir.glob('*.war').first
|
826
|
+
display "Java war file found, detecting framework..."
|
827
|
+
|
828
|
+
entries = []
|
829
|
+
Zip::ZipFile.foreach(opt_war_file) { |zentry| entries << zentry }
|
830
|
+
contents = entries.join("\n")
|
831
|
+
|
832
|
+
if contents =~ /WEB-INF\/grails-app/
|
833
|
+
display "Grails application detected."
|
834
|
+
framework = "grails/1.0"
|
835
|
+
mem = '512M'
|
836
|
+
elsif contents =~ /WEB-INF\/classes\/org\/springframework/
|
837
|
+
display "SpringSource application detected."
|
838
|
+
framework = "spring_web/1.0"
|
839
|
+
mem = '512M'
|
840
|
+
elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
|
841
|
+
display "SpringSource application detected."
|
842
|
+
framework = "spring_web/1.0"
|
843
|
+
mem = '512M'
|
844
|
+
else
|
845
|
+
display "Unknown J2EE Web Application"
|
846
|
+
framework = "spring_web/1.0"
|
847
|
+
end
|
848
|
+
elsif File.exist?('web.config')
|
849
|
+
display "ASP.NET application detected."
|
850
|
+
framework = "asp_web/1.0"
|
851
|
+
elsif !Dir.glob('*.rb').empty?
|
852
|
+
matched_file = nil
|
853
|
+
Dir.glob('*.rb').each do |fname|
|
854
|
+
next if matched_file
|
855
|
+
File.open(fname, 'r') do |f|
|
856
|
+
str = f.read # This might want to be limited
|
857
|
+
matched_file = fname if (str && str.match(/^\s*require\s*'sinatra'/i))
|
858
|
+
end
|
859
|
+
end
|
860
|
+
if matched_file && !File.exist?('config.ru')
|
861
|
+
display "Simple Sinatra application detected in #{matched_file}."
|
862
|
+
exec = "ruby #{matched_file}"
|
863
|
+
end
|
864
|
+
mem = '128M'
|
865
|
+
elsif !Dir.glob('*.js').empty?
|
866
|
+
# Fixme, make other files work too..
|
867
|
+
if File.exist?('app.js')
|
868
|
+
display "Node.js application detected."
|
869
|
+
framework = "nodejs/1.0"
|
870
|
+
mem = '64M'
|
871
|
+
end
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
mem = memswitch if memswitch
|
876
|
+
unless memswitch
|
877
|
+
choose do |menu|
|
878
|
+
menu.layout = :one_line
|
879
|
+
menu.prompt = "Memory Reservation [Default:#{mem}] "
|
880
|
+
menu.default = mem
|
881
|
+
mem_choices.each { |choice| menu.choice(choice) { mem = choice } }
|
882
|
+
end
|
883
|
+
end
|
884
|
+
|
885
|
+
# Set to MB number
|
886
|
+
mem_quota = mem_choice_to_quota(mem)
|
887
|
+
|
888
|
+
# check memsize here for capacity
|
889
|
+
check_has_capacity_for(mem_quota)
|
890
|
+
|
891
|
+
manifest = {
|
892
|
+
:name => "#{appname}",
|
893
|
+
:staging => {
|
894
|
+
:model => framework,
|
895
|
+
:stack => exec
|
896
|
+
},
|
897
|
+
:uris => [url],
|
898
|
+
:instances => instances,
|
899
|
+
:resources => {
|
900
|
+
:memory => mem_quota
|
901
|
+
},
|
902
|
+
}
|
903
|
+
|
904
|
+
display "Uploading Application Information."
|
905
|
+
|
906
|
+
# Send the manifest to the cloud controller
|
907
|
+
response = create_app_internal(@droplets_uri, manifest, auth_hdr)
|
908
|
+
handle_response(response)
|
909
|
+
|
910
|
+
# Provision database here if needed.
|
911
|
+
if framework_needs_db?(framework)
|
912
|
+
proceed = ask("This framework usually needs a database, would you like to provision it? [Yn]: ")
|
913
|
+
if proceed != 'n' && proceed != 'N'
|
914
|
+
app_add_service_helper(appname, 'database', '')
|
915
|
+
provisioned_db = true
|
916
|
+
end
|
917
|
+
end
|
918
|
+
|
919
|
+
# Stage and upload the app bits.
|
920
|
+
display "Uploading Application."
|
921
|
+
upload_size = upload_app_bits(@resources_uri, @droplets_uri, appname, auth_hdr, opt_war_file, provisioned_db)
|
922
|
+
if upload_size > 1024*1024
|
923
|
+
upload_size = (upload_size/(1024.0*1024.0)).round.to_s + 'M'
|
924
|
+
elsif upload_size > 0
|
925
|
+
upload_size = (upload_size/1024.0).round.to_s + 'K'
|
926
|
+
end
|
927
|
+
display "\nUploaded Application '#{appname}' (#{upload_size})."
|
928
|
+
|
929
|
+
display "Push completed."
|
930
|
+
@push = true
|
931
|
+
@appname = appname
|
932
|
+
start unless no_start
|
933
|
+
end
|
934
|
+
rescue => e
|
935
|
+
error "Problem executing command, #{e}"
|
936
|
+
end
|
937
|
+
|
938
|
+
def delete(appname=nil, force=false)
|
939
|
+
error "Application name required, vmc delete <appname || --all> [--force]." unless appname
|
940
|
+
|
941
|
+
check_for_token
|
942
|
+
|
943
|
+
# wildcard behavior
|
944
|
+
apps = []
|
945
|
+
if appname == '--all'
|
946
|
+
droplets_full = get_apps_internal @droplets_uri, auth_hdr
|
947
|
+
apps = droplets_full.collect { |d| "#{d['name']}" }
|
948
|
+
apps.each { |app| delete_app(app, force) }
|
949
|
+
else
|
950
|
+
delete_app(appname, force)
|
951
|
+
end
|
952
|
+
rescue => e
|
953
|
+
error "Problem executing command, #{e}"
|
954
|
+
end
|
955
|
+
|
956
|
+
def delete_app(appname, force)
|
957
|
+
response = get_app_internal @droplets_uri, appname, auth_hdr
|
958
|
+
if response.status != 200
|
959
|
+
display "Application '#{appname}' does not exist."
|
960
|
+
return
|
961
|
+
end
|
962
|
+
appinfo = JSON.parse(response.content)
|
963
|
+
services_to_delete = []
|
964
|
+
app_services = appinfo['services']
|
965
|
+
app_services.each { |service|
|
966
|
+
del_service = 'Y' if force
|
967
|
+
del_service = ask("Application '#{appname}' uses '#{service}' service, would you like to delete it? [Yn]: ") unless force
|
968
|
+
services_to_delete << service unless (del_service == 'n' || del_service == 'N')
|
969
|
+
}
|
970
|
+
delete_app_internal @droplets_uri, appname, services_to_delete, auth_hdr
|
971
|
+
services_to_delete.each { |s| display "Service '#{s}' deleted."}
|
972
|
+
display "Application '#{appname}' deleted."
|
973
|
+
end
|
974
|
+
|
975
|
+
def stop(appname=nil)
|
976
|
+
appname ||= @appname
|
977
|
+
error "Application name required, vmc stop <appname>." unless appname
|
978
|
+
|
979
|
+
check_for_token
|
980
|
+
response = get_app_internal @droplets_uri, appname, auth_hdr
|
981
|
+
if response.status != 200
|
982
|
+
display "Application '#{appname}' does not exist, use push first."
|
983
|
+
return
|
984
|
+
end
|
985
|
+
appinfo = JSON.parse(response.content)
|
986
|
+
appinfo['state'] = 'STOPPED'
|
987
|
+
#display JSON.pretty_generate(appinfo)
|
988
|
+
|
989
|
+
hdrs = auth_hdr.merge({'content-type' => 'application/json'})
|
990
|
+
update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
|
991
|
+
display "Application '#{appname}' stopped."
|
992
|
+
rescue => e
|
993
|
+
error "Problem executing command, #{e}"
|
994
|
+
end
|
995
|
+
|
996
|
+
def start(appname=nil)
|
997
|
+
appname ||= @appname
|
998
|
+
error "Application name required, vmc start <appname>." unless appname
|
999
|
+
|
1000
|
+
check_for_token
|
1001
|
+
response = get_app_internal @droplets_uri, appname, auth_hdr
|
1002
|
+
if response.status != 200
|
1003
|
+
display"Application #{appname} does not exist, use push first."
|
1004
|
+
return
|
1005
|
+
end
|
1006
|
+
appinfo = JSON.parse(response.content)
|
1007
|
+
if (appinfo['state'] == 'STARTED')
|
1008
|
+
display "Application '#{appname}' is already running."
|
1009
|
+
return
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
appinfo['state'] = 'STARTED'
|
1013
|
+
|
1014
|
+
response = update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
|
1015
|
+
handle_response(response)
|
1016
|
+
|
1017
|
+
count = 0
|
1018
|
+
log_lines_displayed = 0
|
1019
|
+
failed = false
|
1020
|
+
display "Trying to start Application: '#{appname}'."
|
1021
|
+
loop do
|
1022
|
+
display ".", false unless count > 9
|
1023
|
+
sleep 1
|
1024
|
+
begin
|
1025
|
+
if app_started_properly(appname)
|
1026
|
+
display ''
|
1027
|
+
break;
|
1028
|
+
elsif !crashes(appname, false).empty?
|
1029
|
+
# Check for the existance of crashes
|
1030
|
+
display "\n\nERROR - Application '#{appname}' failed to start, logs information below.\n\n"
|
1031
|
+
grab_crash_logs(appname, '0', true)
|
1032
|
+
if @push
|
1033
|
+
display ''
|
1034
|
+
should_delete = ask "Should I delete the application? (y/n)? "
|
1035
|
+
delete_app(appname, false) if should_delete == 'Y' || should_delete == 'y'
|
1036
|
+
end
|
1037
|
+
failed = true
|
1038
|
+
break
|
1039
|
+
elsif count > 9
|
1040
|
+
log_lines_displayed = grab_startup_tail(appname, log_lines_displayed)
|
1041
|
+
end
|
1042
|
+
rescue => e
|
1043
|
+
display e.message, false
|
1044
|
+
end
|
1045
|
+
count += 1
|
1046
|
+
if count > 300 # 5 minutes
|
1047
|
+
error "\n\nApplication is taking too long to start, check your logs"
|
1048
|
+
break
|
1049
|
+
end
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
display("Application '#{appname}' started.") unless failed
|
1053
|
+
rescue => e
|
1054
|
+
error "Problem executing command, #{e}"
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
def restart(appname=nil)
|
1058
|
+
appname ||= @appname
|
1059
|
+
error "Application name required, vmc start <appname>." unless appname
|
1060
|
+
|
1061
|
+
stop(appname)
|
1062
|
+
start(appname)
|
1063
|
+
rescue => e
|
1064
|
+
error "Problem executing command, #{e}"
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
def mem(appname, memsize=nil)
|
1068
|
+
check_for_token
|
1069
|
+
|
1070
|
+
response = get_app_internal @droplets_uri, appname, auth_hdr
|
1071
|
+
handle_response(response)
|
1072
|
+
|
1073
|
+
appinfo = JSON.parse(response.content)
|
1074
|
+
|
1075
|
+
mem = current_mem = mem_quota_to_choice(appinfo['resources']['memory'])
|
1076
|
+
|
1077
|
+
if ! memsize
|
1078
|
+
choose do |menu|
|
1079
|
+
menu.layout = :one_line
|
1080
|
+
menu.prompt = "Update Memory Reservation? [Current:#{current_mem}] "
|
1081
|
+
menu.default = current_mem
|
1082
|
+
mem_choices.each { |choice| menu.choice(choice) { memsize = choice } }
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
memsize = mem_choice_to_quota(memsize)
|
1087
|
+
mem = mem_choice_to_quota(mem)
|
1088
|
+
|
1089
|
+
# check memsize here for capacity
|
1090
|
+
check_has_capacity_for(memsize - mem)
|
1091
|
+
|
1092
|
+
mem = memsize
|
1093
|
+
|
1094
|
+
if (mem != current_mem)
|
1095
|
+
appinfo['resources']['memory'] = mem
|
1096
|
+
response = update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
|
1097
|
+
handle_response(response)
|
1098
|
+
display "Updated memory reservation to '#{mem_quota_to_choice(mem)}'."
|
1099
|
+
restart appname if appinfo['state'] == 'STARTED'
|
1100
|
+
end
|
1101
|
+
rescue => e
|
1102
|
+
error "Problem executing command, #{e}"
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
def update(appname=nil, canary=false)
|
1106
|
+
error "Application name required, vmc update <appname>." unless appname
|
1107
|
+
|
1108
|
+
check_for_token
|
1109
|
+
|
1110
|
+
response = get_app_internal @droplets_uri, appname, auth_hdr
|
1111
|
+
if response.status != 200
|
1112
|
+
display "Application '#{appname}' does not exist, use push first."
|
1113
|
+
return
|
1114
|
+
end
|
1115
|
+
appinfo = JSON.parse(response.content)
|
1116
|
+
|
1117
|
+
display "Uploading Application."
|
1118
|
+
upload_app_bits(@resources_uri, @droplets_uri, appname, auth_hdr, Dir.glob('*.war').first)
|
1119
|
+
|
1120
|
+
unless canary
|
1121
|
+
@appname = appname
|
1122
|
+
display "Application updated."
|
1123
|
+
restart appname if appinfo['state'] == 'STARTED'
|
1124
|
+
return
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
response = update_app_internal @droplets_uri, appname, auth_hdr
|
1128
|
+
handle_response(response)
|
1129
|
+
|
1130
|
+
last_state = 'NONE'
|
1131
|
+
begin
|
1132
|
+
response = get_update_app_status @droplets_uri, appname, auth_hdr
|
1133
|
+
|
1134
|
+
update_info = JSON.parse(response.content)
|
1135
|
+
update_state = update_info['state']
|
1136
|
+
if update_state != last_state
|
1137
|
+
display('') unless last_state == 'NONE'
|
1138
|
+
display("#{update_state.ljust(15)}", false)
|
1139
|
+
else
|
1140
|
+
display('.', false)
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
if update_state == 'SUCCEEDED' || update_state == 'CANARY_FAILED'
|
1144
|
+
display('')
|
1145
|
+
if update_state == 'CANARY_FAILED' && update_info['canary']
|
1146
|
+
begin
|
1147
|
+
map = File.open(instance_file, 'r') { |f| JSON.parse(f.read) }
|
1148
|
+
rescue
|
1149
|
+
map = {}
|
1150
|
+
end
|
1151
|
+
|
1152
|
+
map["#{appname}-canary"] = update_info['canary']
|
1153
|
+
|
1154
|
+
File.open(instance_file, 'w') {|f| f.write(map.to_json)}
|
1155
|
+
display("Debug the canary using 'vmc files #{appname} --instance #{appname}-canary'")
|
1156
|
+
end
|
1157
|
+
break
|
1158
|
+
else
|
1159
|
+
last_state = update_state
|
1160
|
+
end
|
1161
|
+
sleep(0.5)
|
1162
|
+
end while true
|
1163
|
+
rescue => e
|
1164
|
+
error "Problem executing command, #{e}"
|
1165
|
+
end
|
1166
|
+
|
1167
|
+
def change_instances(appinfo, appname, instances)
|
1168
|
+
match = instances.match(/([+-])?\d+/)
|
1169
|
+
error "Invalid number of instances '#{instances}', vmc instances <appname> <num>." unless match
|
1170
|
+
|
1171
|
+
instances = instances.to_i
|
1172
|
+
current_instances = appinfo['instances']
|
1173
|
+
new_instances = match.captures[0] ? current_instances + instances : instances
|
1174
|
+
error "There must be at least 1 instance." if new_instances < 1
|
1175
|
+
|
1176
|
+
if current_instances == new_instances
|
1177
|
+
display "Application '#{appname}' is already running #{new_instances} instance#{'s' if new_instances > 1}."
|
1178
|
+
return
|
1179
|
+
end
|
1180
|
+
|
1181
|
+
appinfo['instances'] = new_instances
|
1182
|
+
response = update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
|
1183
|
+
|
1184
|
+
handle_response(response)
|
1185
|
+
|
1186
|
+
display "Scaled '#{appname}' #{new_instances > current_instances ? 'up' : 'down'} to " +
|
1187
|
+
"#{new_instances} instance#{'s' if new_instances > 1}."
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
def get_instances(appname)
|
1191
|
+
instances_info_envelope = get_app_instances_internal(@droplets_uri, appname, auth_hdr)
|
1192
|
+
|
1193
|
+
# Empty array is returned if there are no instances running.
|
1194
|
+
error "No running instances for '#{appname}'" if instances_info_envelope.is_a?(Array)
|
1195
|
+
|
1196
|
+
instances_info = instances_info_envelope['instances']
|
1197
|
+
display "#{'Index'.ljust 5} #{'State'.ljust 15} #{'Since'.ljust 20}\n"
|
1198
|
+
display "#{'--'.ljust 5} #{'--------'.ljust 15} #{'-------------'.ljust 20}\n"
|
1199
|
+
|
1200
|
+
instances_info.each {|entry| entry[0] = entry[0].to_i}
|
1201
|
+
instances_info = instances_info.sort {|a,b| a['index'] - b['index']}
|
1202
|
+
instances_info.each do |entry|
|
1203
|
+
display "#{entry['index'].to_s.ljust 5} #{entry['state'].ljust 15} #{Time.at(entry['since']).strftime("%m/%d/%Y %I:%M%p").ljust 20}\n"
|
1204
|
+
end
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
def instances(appname=nil, instances=nil)
|
1208
|
+
appname ||= @appname
|
1209
|
+
error "Application name required, vmc instances <appname> [num | delta]." unless appname
|
1210
|
+
|
1211
|
+
check_for_token
|
1212
|
+
response = get_app_internal @droplets_uri, appname, auth_hdr
|
1213
|
+
handle_response(response)
|
1214
|
+
appinfo = JSON.parse(response.content)
|
1215
|
+
|
1216
|
+
if instances
|
1217
|
+
change_instances(appinfo, appname, instances) if instances
|
1218
|
+
elsif
|
1219
|
+
get_instances(appname)
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
rescue => e
|
1223
|
+
error "Problem executing command, #{e}"
|
1224
|
+
end
|
1225
|
+
|
1226
|
+
def crashes(appname=nil, print_results=true)
|
1227
|
+
error "Application name required, vmc crashes <appname>." unless appname
|
1228
|
+
|
1229
|
+
check_for_token
|
1230
|
+
response = get_app_crashes_internal @droplets_uri, appname, auth_hdr
|
1231
|
+
if response.status == 404
|
1232
|
+
display"Application #{appname} does not exist, use push first."
|
1233
|
+
return []
|
1234
|
+
elsif response.status != 200
|
1235
|
+
display"Could not fetch application crashes."
|
1236
|
+
return []
|
1237
|
+
end
|
1238
|
+
crashes = JSON.parse(response.content)['crashes']
|
1239
|
+
|
1240
|
+
instance_map = {}
|
1241
|
+
|
1242
|
+
if print_results
|
1243
|
+
display "#{'Name'.ljust 10} #{'Id'.ljust 40} #{'Since'.ljust 20}\n"
|
1244
|
+
display "#{'--'.ljust 10} #{'--------'.ljust 40} #{'-------------'.ljust 20}\n"
|
1245
|
+
end
|
1246
|
+
|
1247
|
+
counter = 1
|
1248
|
+
|
1249
|
+
crashes = crashes.to_a.sort {|a,b| a['since'] - b['since']}
|
1250
|
+
crashes.each do |crash|
|
1251
|
+
name = "#{appname}-#{counter}"
|
1252
|
+
display "#{name.ljust 10} #{crash['instance'].ljust 40} #{Time.at(crash['since']).strftime("%m/%d/%Y %I:%M%p").ljust 20}\n" if print_results
|
1253
|
+
instance_map[name] = crash['instance']
|
1254
|
+
counter +=1
|
1255
|
+
end
|
1256
|
+
|
1257
|
+
File.open(instance_file, 'w') {|f| f.write(instance_map.to_json)}
|
1258
|
+
crashes
|
1259
|
+
rescue => e
|
1260
|
+
error "Problem executing command, #{e}"
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
def map(appname=nil, url=nil)
|
1264
|
+
error "Application name and url required, vmc map <appname> <url>." unless appname && url
|
1265
|
+
|
1266
|
+
check_for_token
|
1267
|
+
response = get_app_internal @droplets_uri, appname, auth_hdr
|
1268
|
+
error "Application #{appname} does not exist, use push first." if response.status != 200
|
1269
|
+
|
1270
|
+
appinfo = JSON.parse(response.content)
|
1271
|
+
|
1272
|
+
appinfo['uris'] << url
|
1273
|
+
|
1274
|
+
#display JSON.pretty_generate(appinfo)
|
1275
|
+
response = update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
|
1276
|
+
handle_response(response)
|
1277
|
+
display "Map completed."
|
1278
|
+
rescue => e
|
1279
|
+
error "Problem executing command, #{e}"
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
def unmap(appname=nil, url=nil)
|
1283
|
+
error "Application name and url required, vmc unmap <appname> <url>." unless appname && url
|
1284
|
+
|
1285
|
+
check_for_token
|
1286
|
+
response = get_app_internal @droplets_uri, appname, auth_hdr
|
1287
|
+
handle_response(response)
|
1288
|
+
|
1289
|
+
url = url.gsub(/^http(s*):\/\//i, '')
|
1290
|
+
appinfo = JSON.parse(response.content)
|
1291
|
+
|
1292
|
+
if appinfo['uris'].delete(url) == nil
|
1293
|
+
error "Error: You can only unmap a previously registered URL."
|
1294
|
+
end
|
1295
|
+
|
1296
|
+
#display JSON.pretty_generate(appinfo)
|
1297
|
+
update_app_state_internal @droplets_uri, appname, appinfo, auth_hdr
|
1298
|
+
|
1299
|
+
display "Unmap completed."
|
1300
|
+
rescue => e
|
1301
|
+
error "Problem executing command, #{e}"
|
1302
|
+
end
|
1303
|
+
|
1304
|
+
def files(appname=nil, path=nil, instance=nil)
|
1305
|
+
path ||= '/'
|
1306
|
+
error "Application name required, vmc files <appname> <pathinfo> [--instance <instance>]." unless appname
|
1307
|
+
check_for_token
|
1308
|
+
response = get_app_internal @droplets_uri, appname, auth_hdr
|
1309
|
+
if response.status != 200
|
1310
|
+
display"Application #{appname} does not exist, use push first."
|
1311
|
+
return
|
1312
|
+
end
|
1313
|
+
instance ||= '0'
|
1314
|
+
|
1315
|
+
begin
|
1316
|
+
map = File.open(instance_file) {|f| JSON.parse(f.read)}
|
1317
|
+
instance = map[instance] if map[instance]
|
1318
|
+
rescue
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
response = get_app_files_internal @droplets_uri, appname, instance, path, auth_hdr
|
1322
|
+
if response.status != 200 && response.status == 400
|
1323
|
+
error "Information not available, either pathinfo is incorrect or instance index is out of bounds."
|
1324
|
+
end
|
1325
|
+
display response.content
|
1326
|
+
rescue => e
|
1327
|
+
error "Problem executing command, #{e}"
|
1328
|
+
end
|
1329
|
+
|
1330
|
+
def log_file_paths
|
1331
|
+
%w[logs/stderr.log logs/stdout.log logs/startup.log]
|
1332
|
+
end
|
1333
|
+
|
1334
|
+
def grab_logs(appname, instance)
|
1335
|
+
display "No information available" and return unless appname
|
1336
|
+
instance ||= '0'
|
1337
|
+
log_file_paths.each do |path|
|
1338
|
+
response = get_app_files_internal @droplets_uri, appname, instance, path, auth_hdr
|
1339
|
+
unless response.status != 200 || response.content.empty?
|
1340
|
+
display "==== #{path} ====\n\n"
|
1341
|
+
display response.content
|
1342
|
+
display ''
|
1343
|
+
end
|
1344
|
+
end
|
1345
|
+
end
|
1346
|
+
|
1347
|
+
def grab_crash_logs(appname, instance, was_staged=false)
|
1348
|
+
display "No information available" and return unless appname
|
1349
|
+
|
1350
|
+
# stage crash info
|
1351
|
+
crashes(appname, false) unless was_staged
|
1352
|
+
|
1353
|
+
instance ||= '0'
|
1354
|
+
begin
|
1355
|
+
map = File.open(instance_file) {|f| JSON.parse(f.read)}
|
1356
|
+
instance = map[instance] if map[instance]
|
1357
|
+
rescue
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
['/logs/err.log', 'logs/stderr.log', 'logs/stdout.log', 'logs/startup.log'].each do |path|
|
1361
|
+
response = get_app_files_internal @droplets_uri, appname, instance, path, auth_hdr
|
1362
|
+
unless response.status != 200 || response.content.empty?
|
1363
|
+
display "==== #{path} ====\n\n"
|
1364
|
+
display response.content
|
1365
|
+
display ''
|
1366
|
+
end
|
1367
|
+
end
|
1368
|
+
end
|
1369
|
+
|
1370
|
+
def grab_startup_tail(appname, since = 0)
|
1371
|
+
new_lines = 0
|
1372
|
+
path = "logs/startup.log"
|
1373
|
+
response = get_app_files_internal @droplets_uri, appname, '0', path, auth_hdr
|
1374
|
+
unless response.status != 200 || response.content.empty?
|
1375
|
+
display "\n==== displaying startup log ====\n\n" if since == 0
|
1376
|
+
response_lines = response.content.split("\n")
|
1377
|
+
lines = response_lines.size
|
1378
|
+
tail = response_lines[since, lines] || []
|
1379
|
+
new_lines = tail.size
|
1380
|
+
display tail.join("\n") if new_lines > 0
|
1381
|
+
end
|
1382
|
+
since + new_lines
|
1383
|
+
end
|
1384
|
+
|
1385
|
+
def app_started_properly(appname)
|
1386
|
+
check_for_token
|
1387
|
+
response = get_app_internal @droplets_uri, appname, auth_hdr
|
1388
|
+
if response.status != 200
|
1389
|
+
error "Application '#{appname}'s state is undetermined, not enough information available at this time."
|
1390
|
+
return
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
droplet = JSON.parse(response.content)
|
1394
|
+
|
1395
|
+
case health(droplet)
|
1396
|
+
when 'N/A'
|
1397
|
+
# Health manager not running.
|
1398
|
+
error "Application '#{appname}'s state is undetermined, not enough information available at this time."
|
1399
|
+
when 'RUNNING'
|
1400
|
+
return true
|
1401
|
+
else
|
1402
|
+
return false
|
1403
|
+
end
|
1404
|
+
end
|
1405
|
+
|
1406
|
+
# Get the current logged in user
|
1407
|
+
def user
|
1408
|
+
info_json = JSON.parse(@check.content)
|
1409
|
+
username = info_json['user'] || 'N/A'
|
1410
|
+
display "[#{username}]"
|
1411
|
+
end
|
1412
|
+
|
1413
|
+
def display_services_directory_banner
|
1414
|
+
display "#{'Service'.ljust(10)} #{'Ver'.ljust(5)} #{'Description'}"
|
1415
|
+
display "#{'-------'.ljust(10)} #{'---'.ljust(5)} #{'-----------'}"
|
1416
|
+
end
|
1417
|
+
|
1418
|
+
# Get stats for application
|
1419
|
+
def stats(appname=nil)
|
1420
|
+
error "Application name required, vmc stats <appname>." unless appname
|
1421
|
+
check_for_token
|
1422
|
+
response = get_app_internal @droplets_uri, appname, auth_hdr
|
1423
|
+
if response.status != 200
|
1424
|
+
display"Application #{appname} does not exist, use push first."
|
1425
|
+
return
|
1426
|
+
end
|
1427
|
+
response = get_app_stats_internal @droplets_uri, appname, auth_hdr
|
1428
|
+
if response.status != 200 && response.status == 400
|
1429
|
+
error "Information not available, is instance index out of bounds?"
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
display " #{'Instance '.ljust(10)} #{'CPU (Cores)'.ljust(15)} #{'Memory (limit)'.ljust(15)} #{'Disk (limit)'.ljust(15)} #{'Uptime '.ljust(5)}"
|
1433
|
+
display " #{'---------'.ljust(10)} #{'-----------'.ljust(15)} #{'--------------'.ljust(15)} #{'------------'.ljust(15)} #{'------ '.ljust(5)}"
|
1434
|
+
|
1435
|
+
stats = JSON.parse(response.content).to_a
|
1436
|
+
|
1437
|
+
stats.each {|entry| entry[0] = entry[0].to_i}
|
1438
|
+
stats = stats.sort {|a,b| a[0] - b[0]}
|
1439
|
+
stats.each do |entry|
|
1440
|
+
index, index_entry = entry
|
1441
|
+
stat = index_entry['stats']
|
1442
|
+
next unless stat
|
1443
|
+
hp = "#{stat['host']}:#{stat['port']}"
|
1444
|
+
uptime = uptime_string(stat['uptime'])
|
1445
|
+
usage = stat['usage']
|
1446
|
+
if usage
|
1447
|
+
cpu = usage['cpu']
|
1448
|
+
mem = (usage['mem'] * 1024) # mem comes in K's
|
1449
|
+
disk = usage['disk']
|
1450
|
+
end
|
1451
|
+
|
1452
|
+
mem_quota = stat['mem_quota']
|
1453
|
+
disk_quota = stat['disk_quota']
|
1454
|
+
|
1455
|
+
mem = "#{pretty_size(mem)} (#{pretty_size(mem_quota, 0)})"
|
1456
|
+
disk = "#{pretty_size(disk)} (#{pretty_size(disk_quota, 0)})"
|
1457
|
+
cpu = cpu ? cpu.to_s : 'NA'
|
1458
|
+
cpu = "#{cpu}% (#{stat['cores']})"
|
1459
|
+
|
1460
|
+
display " #{index.to_s.ljust(10)} #{cpu.ljust(15)} #{mem.ljust(15)} #{disk.ljust(15)} #{uptime.ljust(5)}"
|
1461
|
+
|
1462
|
+
end
|
1463
|
+
rescue => e
|
1464
|
+
error "Problem executing command, #{e}"
|
1465
|
+
end
|
1466
|
+
|
1467
|
+
|
1468
|
+
##################################################################
|
1469
|
+
# Non vmc Commands
|
1470
|
+
##################################################################
|
1471
|
+
|
1472
|
+
def handle_response(response)
|
1473
|
+
return unless response
|
1474
|
+
return if (response and response.status < 400)
|
1475
|
+
error "Error: Unknown error in response from server." unless response.content
|
1476
|
+
begin
|
1477
|
+
error_json = JSON.parse(response.content)
|
1478
|
+
error "Error: #{error_json['description']}"
|
1479
|
+
rescue
|
1480
|
+
response.content ||= "System error has occurred."
|
1481
|
+
error "Error: #{response.content}"
|
1482
|
+
end
|
1483
|
+
end
|
1484
|
+
|
1485
|
+
def framework_needs_db?(framework)
|
1486
|
+
return true if (framework == 'rails/1.0')
|
1487
|
+
return true if (framework == 'grails/1.0')
|
1488
|
+
return true if (framework == 'spring_web/1.0')
|
1489
|
+
return true if (framework == "asp_web/1.0")
|
1490
|
+
return false
|
1491
|
+
end
|
1492
|
+
|
1493
|
+
def uptime_string(delta)
|
1494
|
+
num_seconds = delta.to_i
|
1495
|
+
days = num_seconds / (60 * 60 * 24);
|
1496
|
+
num_seconds -= days * (60 * 60 * 24);
|
1497
|
+
hours = num_seconds / (60 * 60);
|
1498
|
+
num_seconds -= hours * (60 * 60);
|
1499
|
+
minutes = num_seconds / 60;
|
1500
|
+
num_seconds -= minutes * 60;
|
1501
|
+
"#{days}d:#{hours}h:#{minutes}m:#{num_seconds}s"
|
1502
|
+
end
|
1503
|
+
|
1504
|
+
def pretty_size(size, prec=1)
|
1505
|
+
return 'NA' unless size
|
1506
|
+
return "#{size}B" if size < 1024
|
1507
|
+
return sprintf("%.#{prec}fK", size/1024.0) if size < (1024*1024)
|
1508
|
+
return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)
|
1509
|
+
return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
|
1510
|
+
end
|
1511
|
+
|
1512
|
+
def display(msg, nl=true)
|
1513
|
+
if nl
|
1514
|
+
puts(msg)
|
1515
|
+
else
|
1516
|
+
print(msg)
|
1517
|
+
STDOUT.flush
|
1518
|
+
end
|
1519
|
+
end
|
1520
|
+
|
1521
|
+
def error(msg)
|
1522
|
+
STDERR.puts(msg)
|
1523
|
+
STDERR.puts('')
|
1524
|
+
exit 1
|
1525
|
+
end
|
1526
|
+
|
1527
|
+
def check_app_limit
|
1528
|
+
info_json = JSON.parse(@check.content)
|
1529
|
+
usage = info_json['usage']
|
1530
|
+
limits = info_json['limits']
|
1531
|
+
return unless limits['apps']
|
1532
|
+
if limits['apps'] == usage['apps']
|
1533
|
+
display "Not enough capacity for operation."
|
1534
|
+
tapps = limits['apps'] || 0
|
1535
|
+
apps = usage['apps'] || 0
|
1536
|
+
error "Current Usage: (#{apps} of #{tapps} total apps already in use)"
|
1537
|
+
end
|
1538
|
+
end
|
1539
|
+
|
1540
|
+
def check_has_capacity_for(mem_wanted)
|
1541
|
+
info_json = JSON.parse(@check.content)
|
1542
|
+
usage = info_json['usage']
|
1543
|
+
limits = info_json['limits']
|
1544
|
+
available_for_use = limits['memory'].to_i - usage['memory'].to_i
|
1545
|
+
if mem_wanted > available_for_use
|
1546
|
+
info_json = JSON.parse(@check.content)
|
1547
|
+
tmem = pretty_size(limits['memory']*1024*1024)
|
1548
|
+
mem = pretty_size(usage['memory']*1024*1024)
|
1549
|
+
display "Not enough capacity for operation."
|
1550
|
+
available = pretty_size(available_for_use * 1024 * 1024)
|
1551
|
+
error "Current Usage: (#{mem} of #{tmem} total, #{available} available for use)"
|
1552
|
+
end
|
1553
|
+
end
|
1554
|
+
|
1555
|
+
def mem_choices
|
1556
|
+
default = ['64M', '128M', '256M', '512M', '1G', '2G']
|
1557
|
+
info_json = JSON.parse(@check.content)
|
1558
|
+
|
1559
|
+
default unless info_json
|
1560
|
+
default unless (usage = info_json['usage'] and limits = info_json['limits'])
|
1561
|
+
|
1562
|
+
available_for_use = limits['memory'].to_i - usage['memory'].to_i
|
1563
|
+
check_has_capacity_for(64) if available_for_use < 64
|
1564
|
+
return ['64M'] if available_for_use < 128
|
1565
|
+
return ['64M', '128M'] if available_for_use < 256
|
1566
|
+
return ['64M', '128M', '256M'] if available_for_use < 512
|
1567
|
+
return ['64M', '128M', '256M', '512M'] if available_for_use < 1024
|
1568
|
+
return ['64M', '128M', '256M', '512M', '1G'] if available_for_use < 2048
|
1569
|
+
return ['64M', '128M', '256M', '512M', '1G', '2G']
|
1570
|
+
end
|
1571
|
+
|
1572
|
+
def mem_choice_to_quota(mem_choice)
|
1573
|
+
(mem_choice =~ /(\d+)M/i) ? mem_quota = $1.to_i : mem_quota = mem_choice.to_i * 1024
|
1574
|
+
mem_quota
|
1575
|
+
end
|
1576
|
+
|
1577
|
+
def mem_quota_to_choice(mem)
|
1578
|
+
if mem < 1024
|
1579
|
+
mem_choice = "#{mem}M"
|
1580
|
+
else
|
1581
|
+
mem_choice = "#{(mem/1024).to_i}G"
|
1582
|
+
end
|
1583
|
+
mem_choice
|
1584
|
+
end
|
1585
|
+
|
1586
|
+
end
|
1587
|
+
|
1588
|
+
end
|