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