paasio 0.3.16.beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +24 -0
- data/README.md +96 -0
- data/Rakefile +99 -0
- data/bin/paasio +6 -0
- data/caldecott_helper/Gemfile +10 -0
- data/caldecott_helper/Gemfile.lock +48 -0
- data/caldecott_helper/server.rb +43 -0
- data/config/clients.yml +17 -0
- data/lib/cli/commands/admin.rb +80 -0
- data/lib/cli/commands/apps.rb +1284 -0
- data/lib/cli/commands/base.rb +230 -0
- data/lib/cli/commands/manifest.rb +56 -0
- data/lib/cli/commands/misc.rb +129 -0
- data/lib/cli/commands/services.rb +179 -0
- data/lib/cli/commands/user.rb +65 -0
- data/lib/cli/config.rb +165 -0
- data/lib/cli/core_ext.rb +122 -0
- data/lib/cli/errors.rb +19 -0
- data/lib/cli/frameworks.rb +131 -0
- data/lib/cli/manifest_helper.rb +238 -0
- data/lib/cli/runner.rb +535 -0
- data/lib/cli/services_helper.rb +84 -0
- data/lib/cli/tunnel_helper.rb +324 -0
- data/lib/cli/usage.rb +104 -0
- data/lib/cli/version.rb +7 -0
- data/lib/cli/zip_util.rb +77 -0
- data/lib/cli.rb +32 -0
- data/lib/vmc/client.rb +481 -0
- data/lib/vmc/const.rb +22 -0
- data/lib/vmc.rb +3 -0
- metadata +246 -0
data/lib/cli/config.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'json/pure'
|
6
|
+
|
7
|
+
module VMC::Cli
|
8
|
+
class Config
|
9
|
+
|
10
|
+
DEFAULT_TARGET = 'api.paas.io'
|
11
|
+
DEFAULT_SUGGEST = 'paas.io'
|
12
|
+
|
13
|
+
TARGET_FILE = '~/.paasio_target'
|
14
|
+
TOKEN_FILE = '~/.paasio_token'
|
15
|
+
INSTANCES_FILE = '~/.paasio_instances'
|
16
|
+
ALIASES_FILE = '~/.paasio_aliases'
|
17
|
+
CLIENTS_FILE = '~/.paasio_clients'
|
18
|
+
|
19
|
+
STOCK_CLIENTS = File.expand_path("../../../config/clients.yml", __FILE__)
|
20
|
+
|
21
|
+
class << self
|
22
|
+
attr_accessor :colorize
|
23
|
+
attr_accessor :output
|
24
|
+
attr_accessor :trace
|
25
|
+
attr_accessor :nozip
|
26
|
+
|
27
|
+
def target_url
|
28
|
+
return @target_url if @target_url
|
29
|
+
target_file = File.expand_path(TARGET_FILE)
|
30
|
+
if File.exists? target_file
|
31
|
+
@target_url = lock_and_read(target_file).strip
|
32
|
+
else
|
33
|
+
@target_url = DEFAULT_TARGET
|
34
|
+
end
|
35
|
+
@target_url = "http://#{@target_url}" unless /^https?/ =~ @target_url
|
36
|
+
@target_url = @target_url.gsub(/\/+$/, '')
|
37
|
+
@target_url
|
38
|
+
end
|
39
|
+
|
40
|
+
def base_of(url)
|
41
|
+
url.sub(/^[^\.]+\./, "")
|
42
|
+
end
|
43
|
+
|
44
|
+
def suggest_url
|
45
|
+
return @suggest_url if @suggest_url
|
46
|
+
ha = target_url.split('.')
|
47
|
+
ha.shift
|
48
|
+
@suggest_url = ha.join('.')
|
49
|
+
@suggest_url = DEFAULT_SUGGEST if @suggest_url.empty?
|
50
|
+
@suggest_url
|
51
|
+
end
|
52
|
+
|
53
|
+
def store_target(target_host)
|
54
|
+
target_file = File.expand_path(TARGET_FILE)
|
55
|
+
lock_and_write(target_file, target_host)
|
56
|
+
end
|
57
|
+
|
58
|
+
def all_tokens(token_file_path=nil)
|
59
|
+
token_file = File.expand_path(token_file_path || TOKEN_FILE)
|
60
|
+
return nil unless File.exists? token_file
|
61
|
+
contents = lock_and_read(token_file).strip
|
62
|
+
JSON.parse(contents)
|
63
|
+
end
|
64
|
+
|
65
|
+
alias :targets :all_tokens
|
66
|
+
|
67
|
+
def auth_token(token_file_path=nil)
|
68
|
+
return @token if @token
|
69
|
+
tokens = all_tokens(token_file_path)
|
70
|
+
@token = tokens[target_url] if tokens
|
71
|
+
end
|
72
|
+
|
73
|
+
def remove_token_file
|
74
|
+
FileUtils.rm_f(File.expand_path(TOKEN_FILE))
|
75
|
+
end
|
76
|
+
|
77
|
+
def store_token(token, token_file_path=nil)
|
78
|
+
tokens = all_tokens(token_file_path) || {}
|
79
|
+
tokens[target_url] = token
|
80
|
+
token_file = File.expand_path(token_file_path || TOKEN_FILE)
|
81
|
+
lock_and_write(token_file, tokens.to_json)
|
82
|
+
end
|
83
|
+
|
84
|
+
def instances
|
85
|
+
instances_file = File.expand_path(INSTANCES_FILE)
|
86
|
+
return nil unless File.exists? instances_file
|
87
|
+
contents = lock_and_read(instances_file).strip
|
88
|
+
JSON.parse(contents)
|
89
|
+
end
|
90
|
+
|
91
|
+
def store_instances(instances)
|
92
|
+
instances_file = File.expand_path(INSTANCES_FILE)
|
93
|
+
lock_and_write(instances_file, instances.to_json)
|
94
|
+
end
|
95
|
+
|
96
|
+
def aliases
|
97
|
+
aliases_file = File.expand_path(ALIASES_FILE)
|
98
|
+
# bacward compatible
|
99
|
+
unless File.exists? aliases_file
|
100
|
+
old_aliases_file = File.expand_path('~/.vmc-aliases')
|
101
|
+
FileUtils.mv(old_aliases_file, aliases_file) if File.exists? old_aliases_file
|
102
|
+
end
|
103
|
+
aliases = YAML.load_file(aliases_file) rescue {}
|
104
|
+
end
|
105
|
+
|
106
|
+
def store_aliases(aliases)
|
107
|
+
aliases_file = File.expand_path(ALIASES_FILE)
|
108
|
+
File.open(aliases_file, 'wb') {|f| f.write(aliases.to_yaml)}
|
109
|
+
end
|
110
|
+
|
111
|
+
def deep_merge(a, b)
|
112
|
+
merge = proc do |_, old, new|
|
113
|
+
if new.is_a?(Hash) and old.is_a?(Hash)
|
114
|
+
old.merge(new, &merge)
|
115
|
+
else
|
116
|
+
new
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
a.merge(b, &merge)
|
121
|
+
end
|
122
|
+
|
123
|
+
def clients
|
124
|
+
return @clients if @clients
|
125
|
+
|
126
|
+
stock = YAML.load_file(STOCK_CLIENTS)
|
127
|
+
if File.exists? CLIENTS_FILE
|
128
|
+
user = YAML.load_file(CLIENTS_FILE)
|
129
|
+
@clients = deep_merge(stock, user)
|
130
|
+
else
|
131
|
+
@clients = stock
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def lock_and_read(file)
|
136
|
+
File.open(file, File::RDONLY) {|f|
|
137
|
+
if defined? JRUBY_VERSION
|
138
|
+
f.flock(File::LOCK_SH)
|
139
|
+
else
|
140
|
+
f.flock(File::LOCK_EX)
|
141
|
+
end
|
142
|
+
contents = f.read
|
143
|
+
f.flock(File::LOCK_UN)
|
144
|
+
contents
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
def lock_and_write(file, contents)
|
149
|
+
File.open(file, File::RDWR | File::CREAT, 0600) {|f|
|
150
|
+
f.flock(File::LOCK_EX)
|
151
|
+
f.rewind
|
152
|
+
f.puts contents
|
153
|
+
f.flush
|
154
|
+
f.truncate(f.pos)
|
155
|
+
f.flock(File::LOCK_UN)
|
156
|
+
}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def initialize(work_dir = Dir.pwd)
|
161
|
+
@work_dir = work_dir
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
data/lib/cli/core_ext.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
module VMCExtensions
|
2
|
+
|
3
|
+
def say(message)
|
4
|
+
VMC::Cli::Config.output.puts(message) if VMC::Cli::Config.output
|
5
|
+
end
|
6
|
+
|
7
|
+
def header(message, filler = '-')
|
8
|
+
say "\n"
|
9
|
+
say message
|
10
|
+
say filler.to_s * message.size
|
11
|
+
end
|
12
|
+
|
13
|
+
def banner(message)
|
14
|
+
say "\n"
|
15
|
+
say message
|
16
|
+
end
|
17
|
+
|
18
|
+
def display(message, nl=true)
|
19
|
+
if nl
|
20
|
+
say message
|
21
|
+
else
|
22
|
+
if VMC::Cli::Config.output
|
23
|
+
VMC::Cli::Config.output.print(message)
|
24
|
+
VMC::Cli::Config.output.flush
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear(size=80)
|
30
|
+
return unless VMC::Cli::Config.output
|
31
|
+
VMC::Cli::Config.output.print("\r")
|
32
|
+
VMC::Cli::Config.output.print(" " * size)
|
33
|
+
VMC::Cli::Config.output.print("\r")
|
34
|
+
#VMC::Cli::Config.output.flush
|
35
|
+
end
|
36
|
+
|
37
|
+
def err(message, prefix='Error: ')
|
38
|
+
raise VMC::Cli::CliExit, "#{prefix}#{message}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def warn(msg)
|
42
|
+
say "#{"[WARNING]".yellow} #{msg}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def quit(message = nil)
|
46
|
+
raise VMC::Cli::GracefulExit, message
|
47
|
+
end
|
48
|
+
|
49
|
+
def blank?
|
50
|
+
self.to_s.blank?
|
51
|
+
end
|
52
|
+
|
53
|
+
def uptime_string(delta)
|
54
|
+
num_seconds = delta.to_i
|
55
|
+
days = num_seconds / (60 * 60 * 24);
|
56
|
+
num_seconds -= days * (60 * 60 * 24);
|
57
|
+
hours = num_seconds / (60 * 60);
|
58
|
+
num_seconds -= hours * (60 * 60);
|
59
|
+
minutes = num_seconds / 60;
|
60
|
+
num_seconds -= minutes * 60;
|
61
|
+
"#{days}d:#{hours}h:#{minutes}m:#{num_seconds}s"
|
62
|
+
end
|
63
|
+
|
64
|
+
def pretty_size(size, prec=1)
|
65
|
+
return 'NA' unless size
|
66
|
+
return "#{size}B" if size < 1024
|
67
|
+
return sprintf("%.#{prec}fK", size/1024.0) if size < (1024*1024)
|
68
|
+
return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)
|
69
|
+
return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module VMCStringExtensions
|
74
|
+
|
75
|
+
def red
|
76
|
+
colorize("\e[0m\e[31m")
|
77
|
+
end
|
78
|
+
|
79
|
+
def green
|
80
|
+
colorize("\e[0m\e[32m")
|
81
|
+
end
|
82
|
+
|
83
|
+
def yellow
|
84
|
+
colorize("\e[0m\e[33m")
|
85
|
+
end
|
86
|
+
|
87
|
+
def bold
|
88
|
+
colorize("\e[0m\e[1m")
|
89
|
+
end
|
90
|
+
|
91
|
+
def colorize(color_code)
|
92
|
+
if VMC::Cli::Config.colorize
|
93
|
+
"#{color_code}#{self}\e[0m"
|
94
|
+
else
|
95
|
+
self
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def blank?
|
100
|
+
self =~ /^\s*$/
|
101
|
+
end
|
102
|
+
|
103
|
+
def truncate(limit = 30)
|
104
|
+
return "" if self.blank?
|
105
|
+
etc = "..."
|
106
|
+
stripped = self.strip[0..limit]
|
107
|
+
if stripped.length > limit
|
108
|
+
stripped.gsub(/\s+?(\S+)?$/, "") + etc
|
109
|
+
else
|
110
|
+
stripped
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
class Object
|
117
|
+
include VMCExtensions
|
118
|
+
end
|
119
|
+
|
120
|
+
class String
|
121
|
+
include VMCStringExtensions
|
122
|
+
end
|
data/lib/cli/errors.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module VMC::Cli
|
2
|
+
|
3
|
+
class CliError < StandardError
|
4
|
+
def self.error_code(code = nil)
|
5
|
+
define_method(:error_code) { code }
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class UnknownCommand < CliError; error_code(100); end
|
10
|
+
class TargetMissing < CliError; error_code(102); end
|
11
|
+
class TargetInaccessible < CliError; error_code(103); end
|
12
|
+
|
13
|
+
class TargetError < CliError; error_code(201); end
|
14
|
+
class AuthError < TargetError; error_code(202); end
|
15
|
+
|
16
|
+
class CliExit < CliError; error_code(400); end
|
17
|
+
class GracefulExit < CliExit; error_code(401); end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module VMC::Cli
|
2
|
+
|
3
|
+
class Framework
|
4
|
+
|
5
|
+
DEFAULT_FRAMEWORK = "http://b20nine.com/unknown"
|
6
|
+
DEFAULT_MEM = '256M'
|
7
|
+
|
8
|
+
FRAMEWORKS = {
|
9
|
+
'Rails' => ['rails3', { :mem => '256M', :description => 'Rails Application'}],
|
10
|
+
'Spring' => ['spring', { :mem => '512M', :description => 'Java SpringSource Spring Application'}],
|
11
|
+
'Grails' => ['grails', { :mem => '512M', :description => 'Java SpringSource Grails Application'}],
|
12
|
+
'Lift' => ['lift', { :mem => '512M', :description => 'Scala Lift Application'}],
|
13
|
+
'JavaWeb' => ['java_web',{ :mem => '512M', :description => 'Java Web Application'}],
|
14
|
+
'Sinatra' => ['sinatra', { :mem => '128M', :description => 'Sinatra Application'}],
|
15
|
+
'Rack' => ['rack', { :mem => '128M', :description => 'Rack Application'}],
|
16
|
+
'Node' => ['node', { :mem => '128M', :description => 'Node.js Application'}],
|
17
|
+
'PHP' => ['php', { :mem => '128M', :description => 'PHP Application'}],
|
18
|
+
'Erlang/OTP Rebar' => ['otp_rebar', { :mem => '128M', :description => 'Erlang/OTP Rebar Application'}],
|
19
|
+
'WSGI' => ['wsgi', { :mem => '128M', :description => 'Python WSGI Application'}],
|
20
|
+
'Django' => ['django', { :mem => '128M', :description => 'Python Django Application'}],
|
21
|
+
}
|
22
|
+
|
23
|
+
class << self
|
24
|
+
|
25
|
+
def known_frameworks
|
26
|
+
FRAMEWORKS.keys
|
27
|
+
end
|
28
|
+
|
29
|
+
def lookup(name)
|
30
|
+
return Framework.new(*FRAMEWORKS[name])
|
31
|
+
end
|
32
|
+
|
33
|
+
def detect(path)
|
34
|
+
Dir.chdir(path) do
|
35
|
+
|
36
|
+
# Rails
|
37
|
+
if File.exist?('config/environment.rb')
|
38
|
+
return Framework.lookup('Rails')
|
39
|
+
|
40
|
+
# Java
|
41
|
+
elsif Dir.glob('*.war').first || File.exist?('WEB-INF/web.xml')
|
42
|
+
war_file = Dir.glob('*.war').first
|
43
|
+
|
44
|
+
if war_file
|
45
|
+
contents = ZipUtil.entry_lines(war_file)
|
46
|
+
else
|
47
|
+
contents = Dir['**/*'].join("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Spring/Lift Variations
|
51
|
+
if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/
|
52
|
+
return Framework.lookup('Grails')
|
53
|
+
elsif contents =~ /WEB-INF\/lib\/lift-webkit.*\.jar/
|
54
|
+
return Framework.lookup('Lift')
|
55
|
+
elsif contents =~ /WEB-INF\/classes\/org\/springframework/
|
56
|
+
return Framework.lookup('Spring')
|
57
|
+
elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
|
58
|
+
return Framework.lookup('Spring')
|
59
|
+
elsif contents =~ /WEB-INF\/lib\/org\.springframework\.core.*\.jar/
|
60
|
+
return Framework.lookup('Spring')
|
61
|
+
else
|
62
|
+
return Framework.lookup('JavaWeb')
|
63
|
+
end
|
64
|
+
|
65
|
+
elsif !Dir.glob('config.ru').empty?
|
66
|
+
return Framework.lookup('Rack')
|
67
|
+
|
68
|
+
# Sinatra-based Apps
|
69
|
+
elsif !Dir.glob('*.rb').empty?
|
70
|
+
matched_file = nil
|
71
|
+
Dir.glob('*.rb').each do |fname|
|
72
|
+
next if matched_file
|
73
|
+
File.open(fname, 'r') do |f|
|
74
|
+
str = f.read # This might want to be limited
|
75
|
+
matched_file = fname if (str && str.match(/^\s*require[\s\(]*['"]sinatra['"]/))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
if matched_file
|
79
|
+
f = Framework.lookup('Sinatra')
|
80
|
+
f.exec = "ruby #{matched_file}"
|
81
|
+
return f
|
82
|
+
end
|
83
|
+
|
84
|
+
# Node.js
|
85
|
+
elsif !Dir.glob('*.js').empty?
|
86
|
+
if File.exist?('server.js') || File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js')
|
87
|
+
return Framework.lookup('Node')
|
88
|
+
end
|
89
|
+
|
90
|
+
# PHP
|
91
|
+
elsif !Dir.glob('*.php').empty?
|
92
|
+
return Framework.lookup('PHP')
|
93
|
+
|
94
|
+
# Erlang/OTP using Rebar
|
95
|
+
elsif !Dir.glob('releases/*/*.rel').empty? && !Dir.glob('releases/*/*.boot').empty?
|
96
|
+
return Framework.lookup('Erlang/OTP Rebar')
|
97
|
+
|
98
|
+
# Python Django
|
99
|
+
# XXX: not all django projects keep settings.py in top-level directory
|
100
|
+
elsif File.exist?('manage.py') && File.exist?('settings.py')
|
101
|
+
return Framework.lookup('Django')
|
102
|
+
|
103
|
+
# Python
|
104
|
+
elsif !Dir.glob('wsgi.py').empty?
|
105
|
+
return Framework.lookup('WSGI')
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
attr_reader :name, :description, :memory
|
115
|
+
attr_accessor :exec
|
116
|
+
|
117
|
+
alias :mem :memory
|
118
|
+
|
119
|
+
def initialize(framework=nil, opts={})
|
120
|
+
@name = framework || DEFAULT_FRAMEWORK
|
121
|
+
@memory = opts[:mem] || DEFAULT_MEM
|
122
|
+
@description = opts[:description] || 'Unknown Application Type'
|
123
|
+
@exec = opts[:exec]
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_s
|
127
|
+
description
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module VMC::Cli::ManifestHelper
|
4
|
+
include VMC::Cli::ServicesHelper
|
5
|
+
|
6
|
+
DEFAULTS = {
|
7
|
+
"url" => "${name}.${target-base}",
|
8
|
+
"mem" => "128M",
|
9
|
+
"instances" => 1
|
10
|
+
}
|
11
|
+
|
12
|
+
MANIFEST = "manifest.yml"
|
13
|
+
|
14
|
+
YES_SET = Set.new(["y", "Y", "yes", "YES"])
|
15
|
+
|
16
|
+
# take a block and call it once for each app to push/update.
|
17
|
+
# with @application and @app_info set appropriately
|
18
|
+
def each_app(panic=true)
|
19
|
+
if @manifest and all_apps = @manifest["applications"]
|
20
|
+
where = File.expand_path(@path)
|
21
|
+
single = false
|
22
|
+
|
23
|
+
all_apps.each do |path, info|
|
24
|
+
app = File.expand_path("../" + path, manifest_file)
|
25
|
+
if where.start_with?(app)
|
26
|
+
@application = app
|
27
|
+
@app_info = info
|
28
|
+
yield info["name"]
|
29
|
+
single = true
|
30
|
+
break
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
unless single
|
35
|
+
if where == File.expand_path("../", manifest_file)
|
36
|
+
ordered_by_deps(all_apps).each do |path, info|
|
37
|
+
app = File.expand_path("../" + path, manifest_file)
|
38
|
+
@application = app
|
39
|
+
@app_info = info
|
40
|
+
yield info["name"]
|
41
|
+
end
|
42
|
+
else
|
43
|
+
err "Path '#{@path}' is not known to manifest '#{manifest_file}'."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else
|
47
|
+
@application = @path
|
48
|
+
@app_info = @manifest
|
49
|
+
if @app_info
|
50
|
+
yield @app_info["name"]
|
51
|
+
elsif panic
|
52
|
+
err "No applications."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
nil
|
57
|
+
ensure
|
58
|
+
@application = nil
|
59
|
+
@app_info = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def interact(many=false)
|
63
|
+
@manifest ||= {}
|
64
|
+
configure_app(many)
|
65
|
+
end
|
66
|
+
|
67
|
+
def target_manifest
|
68
|
+
@options[:manifest] || MANIFEST
|
69
|
+
end
|
70
|
+
|
71
|
+
def save_manifest(save_to = nil)
|
72
|
+
save_to ||= target_manifest
|
73
|
+
|
74
|
+
File.open(save_to, "w") do |f|
|
75
|
+
f.write @manifest.to_yaml
|
76
|
+
end
|
77
|
+
|
78
|
+
say "Manifest written to #{save_to}."
|
79
|
+
end
|
80
|
+
|
81
|
+
def configure_app(many=false)
|
82
|
+
name = manifest("name") ||
|
83
|
+
set(ask("Application Name", :default => manifest("name")), "name")
|
84
|
+
|
85
|
+
url_template = manifest("url") || DEFAULTS["url"]
|
86
|
+
url_resolved = url_template.dup
|
87
|
+
resolve_lexically(url_resolved)
|
88
|
+
|
89
|
+
url = ask("Application Deployed URL", :default => url_resolved)
|
90
|
+
|
91
|
+
url = url_template if url == url_resolved
|
92
|
+
|
93
|
+
# common error case is for prompted users to answer y or Y or yes or
|
94
|
+
# YES to this ask() resulting in an unintended URL of y. Special
|
95
|
+
# case this common error
|
96
|
+
url = DEFAULTS["url"] if YES_SET.member? url
|
97
|
+
|
98
|
+
set url, "url"
|
99
|
+
|
100
|
+
unless manifest "framework"
|
101
|
+
framework = detect_framework
|
102
|
+
set framework.name, "framework", "name"
|
103
|
+
set(
|
104
|
+
{ "mem" => framework.mem,
|
105
|
+
"description" => framework.description,
|
106
|
+
"exec" => framework.exec
|
107
|
+
},
|
108
|
+
"framework",
|
109
|
+
"info"
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
113
|
+
set ask(
|
114
|
+
"Memory reservation",
|
115
|
+
:default =>
|
116
|
+
manifest("mem") ||
|
117
|
+
manifest("framework", "info", "mem") ||
|
118
|
+
DEFAULTS["mem"],
|
119
|
+
:choices => ["128M", "256M", "512M", "1G", "2G"]
|
120
|
+
), "mem"
|
121
|
+
|
122
|
+
set ask(
|
123
|
+
"How many instances?",
|
124
|
+
:default => manifest("instances") || DEFAULTS["instances"]
|
125
|
+
), "instances"
|
126
|
+
|
127
|
+
unless manifest "services"
|
128
|
+
services = client.services_info
|
129
|
+
unless services.empty?
|
130
|
+
bind = ask "Would you like to bind any services to '#{name}'?", :default => false
|
131
|
+
bind_services(services.values.collect(&:keys).flatten) if bind
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
if many and ask("Configure for another application?", :default => false)
|
136
|
+
@application = ask "Application path?"
|
137
|
+
configure_app
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def set(what, *where)
|
142
|
+
where.unshift "applications", @application
|
143
|
+
|
144
|
+
which = @manifest
|
145
|
+
where.each_with_index do |k, i|
|
146
|
+
if i + 1 == where.size
|
147
|
+
which[k] = what
|
148
|
+
else
|
149
|
+
which = (which[k] ||= {})
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
what
|
154
|
+
end
|
155
|
+
|
156
|
+
# Detect the appropriate framework.
|
157
|
+
def detect_framework(prompt_ok = true)
|
158
|
+
framework = VMC::Cli::Framework.detect(@application)
|
159
|
+
framework_correct = ask("Detected a #{framework}, is this correct?", :default => true) if prompt_ok && framework
|
160
|
+
if prompt_ok && (framework.nil? || !framework_correct)
|
161
|
+
display "#{"[WARNING]".yellow} Can't determine the Application Type." unless framework
|
162
|
+
framework = nil if !framework_correct
|
163
|
+
framework = VMC::Cli::Framework.lookup(
|
164
|
+
ask(
|
165
|
+
"Select Application Type",
|
166
|
+
:indexed => true,
|
167
|
+
:default => framework,
|
168
|
+
:choices => VMC::Cli::Framework.known_frameworks
|
169
|
+
)
|
170
|
+
)
|
171
|
+
display "Selected #{framework}"
|
172
|
+
end
|
173
|
+
|
174
|
+
framework
|
175
|
+
end
|
176
|
+
|
177
|
+
def bind_services(services)
|
178
|
+
svcs = services.collect(&:to_s).sort!
|
179
|
+
|
180
|
+
display "The following system services are available"
|
181
|
+
configure_service(
|
182
|
+
ask(
|
183
|
+
"Please select the one you wish to provision",
|
184
|
+
:indexed => true,
|
185
|
+
:choices => svcs
|
186
|
+
).to_sym
|
187
|
+
)
|
188
|
+
|
189
|
+
if ask "Would you like to bind another service?", :default => false
|
190
|
+
bind_services(services)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def configure_service(vendor)
|
195
|
+
default_name = random_service_name(vendor)
|
196
|
+
name = ask "Specify the name of the service", :default => default_name
|
197
|
+
|
198
|
+
set vendor, "services", name, "type"
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
def ordered_by_deps(apps, abspaths = nil, processed = Set[])
|
203
|
+
unless abspaths
|
204
|
+
abspaths = {}
|
205
|
+
apps.each do |p, i|
|
206
|
+
ep = File.expand_path("../" + p, manifest_file)
|
207
|
+
abspaths[ep] = i
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
ordered = []
|
212
|
+
apps.each do |path, info|
|
213
|
+
epath = File.expand_path("../" + path, manifest_file)
|
214
|
+
|
215
|
+
if deps = info["depends-on"]
|
216
|
+
dep_apps = {}
|
217
|
+
deps.each do |dep|
|
218
|
+
edep = File.expand_path("../" + dep, manifest_file)
|
219
|
+
|
220
|
+
err "Circular dependency detected." if processed.include? edep
|
221
|
+
|
222
|
+
dep_apps[dep] = abspaths[edep]
|
223
|
+
end
|
224
|
+
|
225
|
+
processed.add(epath)
|
226
|
+
|
227
|
+
ordered += ordered_by_deps(dep_apps, abspaths, processed)
|
228
|
+
ordered << [path, info]
|
229
|
+
elsif not processed.include? epath
|
230
|
+
ordered << [path, info]
|
231
|
+
processed.add(epath)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
ordered
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|