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.
Files changed (43) hide show
  1. data/LICENSE +24 -0
  2. data/README.md +102 -0
  3. data/Rakefile +99 -0
  4. data/bin/vmc +6 -0
  5. data/caldecott_helper/Gemfile +10 -0
  6. data/caldecott_helper/Gemfile.lock +48 -0
  7. data/caldecott_helper/server.rb +43 -0
  8. data/config/clients.yml +17 -0
  9. data/config/micro/offline.conf +2 -0
  10. data/config/micro/paths.yml +22 -0
  11. data/config/micro/refresh_ip.rb +20 -0
  12. data/lib/cli.rb +46 -0
  13. data/lib/cli/commands/admin.rb +80 -0
  14. data/lib/cli/commands/apps.rb +1103 -0
  15. data/lib/cli/commands/base.rb +227 -0
  16. data/lib/cli/commands/manifest.rb +56 -0
  17. data/lib/cli/commands/micro.rb +115 -0
  18. data/lib/cli/commands/misc.rb +129 -0
  19. data/lib/cli/commands/services.rb +180 -0
  20. data/lib/cli/commands/user.rb +65 -0
  21. data/lib/cli/config.rb +173 -0
  22. data/lib/cli/console_helper.rb +157 -0
  23. data/lib/cli/core_ext.rb +122 -0
  24. data/lib/cli/errors.rb +19 -0
  25. data/lib/cli/frameworks.rb +142 -0
  26. data/lib/cli/manifest_helper.rb +262 -0
  27. data/lib/cli/runner.rb +532 -0
  28. data/lib/cli/services_helper.rb +84 -0
  29. data/lib/cli/tunnel_helper.rb +332 -0
  30. data/lib/cli/usage.rb +115 -0
  31. data/lib/cli/version.rb +7 -0
  32. data/lib/cli/zip_util.rb +77 -0
  33. data/lib/vmc.rb +3 -0
  34. data/lib/vmc/client.rb +471 -0
  35. data/lib/vmc/const.rb +22 -0
  36. data/lib/vmc/micro.rb +56 -0
  37. data/lib/vmc/micro/switcher/base.rb +97 -0
  38. data/lib/vmc/micro/switcher/darwin.rb +19 -0
  39. data/lib/vmc/micro/switcher/dummy.rb +15 -0
  40. data/lib/vmc/micro/switcher/linux.rb +16 -0
  41. data/lib/vmc/micro/switcher/windows.rb +31 -0
  42. data/lib/vmc/micro/vmrun.rb +158 -0
  43. metadata +207 -0
@@ -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,142 @@
1
+ module VMC::Cli
2
+
3
+ class Framework
4
+
5
+ DEFAULT_FRAMEWORK = "http://b20nine.com/unknown"
6
+ DEFAULT_MEM = '256M'
7
+
8
+ FRAMEWORKS = {
9
+ 'Topaz' => ['topaz', { :mem => '128M', :description => 'Topaz for GemStone/S Application'}],
10
+ 'Rails' => ['rails3', { :mem => '256M', :description => 'Rails Application', :console=>true}],
11
+ 'Spring' => ['spring', { :mem => '512M', :description => 'Java SpringSource Spring Application'}],
12
+ 'Grails' => ['grails', { :mem => '512M', :description => 'Java SpringSource Grails Application'}],
13
+ 'Lift' => ['lift', { :mem => '512M', :description => 'Scala Lift Application'}],
14
+ 'JavaWeb' => ['java_web',{ :mem => '512M', :description => 'Java Web Application'}],
15
+ 'Sinatra' => ['sinatra', { :mem => '128M', :description => 'Sinatra Application'}],
16
+ 'Node' => ['node', { :mem => '64M', :description => 'Node.js Application'}],
17
+ 'PHP' => ['php', { :mem => '128M', :description => 'PHP Application'}],
18
+ 'Erlang/OTP Rebar' => ['otp_rebar', { :mem => '64M', :description => 'Erlang/OTP Rebar Application'}],
19
+ 'WSGI' => ['wsgi', { :mem => '64M', :description => 'Python WSGI Application'}],
20
+ 'Django' => ['django', { :mem => '128M', :description => 'Python Django Application'}],
21
+ 'Rack' => ['rack', { :mem => '128M', :description => 'Rack Application'}]
22
+ }
23
+
24
+ class << self
25
+
26
+ def known_frameworks
27
+ FRAMEWORKS.keys
28
+ end
29
+
30
+ def lookup(name)
31
+ return Framework.new(*FRAMEWORKS[name])
32
+ end
33
+
34
+ def lookup_by_framework(name)
35
+ FRAMEWORKS.each do |key,fw|
36
+ return Framework.new(fw[0], fw[1]) if fw[0] == name
37
+ end
38
+ end
39
+
40
+ def detect(path, available_frameworks)
41
+ Dir.chdir(path) do
42
+ # Rails
43
+ if File.exist?('config/environment.rb')
44
+ return Framework.lookup('Rails')
45
+
46
+ # Topaz
47
+ elsif File.exist?('main.tpz') && available_frameworks.include?(["topaz"])
48
+ return Framework.lookup('Topaz')
49
+
50
+ # Rack
51
+ elsif File.exist?('config.ru') && available_frameworks.include?(["rack"])
52
+ return Framework.lookup('Rack')
53
+
54
+ # Java
55
+ elsif Dir.glob('*.war').first || File.exist?('WEB-INF/web.xml')
56
+ war_file = Dir.glob('*.war').first
57
+
58
+ if war_file
59
+ contents = ZipUtil.entry_lines(war_file)
60
+ else
61
+ contents = Dir['**/*'].join("\n")
62
+ end
63
+
64
+ # Spring/Lift Variations
65
+ if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/
66
+ return Framework.lookup('Grails')
67
+ elsif contents =~ /WEB-INF\/lib\/lift-webkit.*\.jar/
68
+ return Framework.lookup('Lift')
69
+ elsif contents =~ /WEB-INF\/classes\/org\/springframework/
70
+ return Framework.lookup('Spring')
71
+ elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
72
+ return Framework.lookup('Spring')
73
+ elsif contents =~ /WEB-INF\/lib\/org\.springframework\.core.*\.jar/
74
+ return Framework.lookup('Spring')
75
+ else
76
+ return Framework.lookup('JavaWeb')
77
+ end
78
+ # Simple Ruby Apps
79
+ elsif !Dir.glob('*.rb').empty?
80
+ matched_file = nil
81
+ Dir.glob('*.rb').each do |fname|
82
+ next if matched_file
83
+ File.open(fname, 'r') do |f|
84
+ str = f.read # This might want to be limited
85
+ matched_file = fname if (str && str.match(/^\s*require[\s\(]*['"]sinatra['"]/))
86
+ end
87
+ end
88
+ if matched_file
89
+ f = Framework.lookup('Sinatra')
90
+ f.exec = "ruby #{matched_file}"
91
+ return f
92
+ end
93
+
94
+ # Node.js
95
+ elsif !Dir.glob('*.js').empty?
96
+ if File.exist?('server.js') || File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js')
97
+ return Framework.lookup('Node')
98
+ end
99
+
100
+ # PHP
101
+ elsif !Dir.glob('*.php').empty?
102
+ return Framework.lookup('PHP')
103
+
104
+ # Erlang/OTP using Rebar
105
+ elsif !Dir.glob('releases/*/*.rel').empty? && !Dir.glob('releases/*/*.boot').empty?
106
+ return Framework.lookup('Erlang/OTP Rebar')
107
+
108
+ # Python Django
109
+ # XXX: not all django projects keep settings.py in top-level directory
110
+ elsif File.exist?('manage.py') && File.exist?('settings.py')
111
+ return Framework.lookup('Django')
112
+
113
+ # Python
114
+ elsif !Dir.glob('wsgi.py').empty?
115
+ return Framework.lookup('WSGI')
116
+
117
+ end
118
+ end
119
+ nil
120
+ end
121
+
122
+ end
123
+
124
+ attr_reader :name, :description, :memory, :console
125
+ attr_accessor :exec
126
+
127
+ alias :mem :memory
128
+
129
+ def initialize(framework=nil, opts={})
130
+ @name = framework || DEFAULT_FRAMEWORK
131
+ @memory = opts[:mem] || DEFAULT_MEM
132
+ @description = opts[:description] || 'Unknown Application Type'
133
+ @exec = opts[:exec]
134
+ @console = opts[:console] || false
135
+ end
136
+
137
+ def to_s
138
+ description
139
+ end
140
+ end
141
+
142
+ end
@@ -0,0 +1,262 @@
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
+ user_services = client.services
129
+ user_services.sort! {|a, b| a[:name] <=> b[:name] }
130
+
131
+ unless user_services.empty?
132
+ if ask "Bind existing services to '#{name}'?", :default => false
133
+ bind_services(user_services)
134
+ end
135
+ end
136
+
137
+ services = client.services_info
138
+ unless services.empty?
139
+ if ask "Create services to bind to '#{name}'?", :default => false
140
+ create_services(services.values.collect(&:keys).flatten)
141
+ end
142
+ end
143
+ end
144
+
145
+ if many and ask("Configure for another application?", :default => false)
146
+ @application = ask "Application path?"
147
+ configure_app
148
+ end
149
+ end
150
+
151
+ def set(what, *where)
152
+ where.unshift "applications", @application
153
+
154
+ which = @manifest
155
+ where.each_with_index do |k, i|
156
+ if i + 1 == where.size
157
+ which[k] = what
158
+ else
159
+ which = (which[k] ||= {})
160
+ end
161
+ end
162
+
163
+ what
164
+ end
165
+
166
+ # Detect the appropriate framework.
167
+ def detect_framework(prompt_ok = true)
168
+ framework = VMC::Cli::Framework.detect(@application, frameworks_info)
169
+ framework_correct = ask("Detected a #{framework}, is this correct?", :default => true) if prompt_ok && framework
170
+ if prompt_ok && (framework.nil? || !framework_correct)
171
+ display "#{"[WARNING]".yellow} Can't determine the Application Type." unless framework
172
+ framework = nil if !framework_correct
173
+ framework = VMC::Cli::Framework.lookup(
174
+ ask(
175
+ "Select Application Type",
176
+ :indexed => true,
177
+ :default => framework,
178
+ :choices => VMC::Cli::Framework.known_frameworks
179
+ )
180
+ )
181
+ display "Selected #{framework}"
182
+ end
183
+
184
+ framework
185
+ end
186
+
187
+ def bind_services(user_services, chosen = 0)
188
+ svcname = ask(
189
+ "Which one?",
190
+ :indexed => true,
191
+ :choices => user_services.collect { |p| p[:name] })
192
+
193
+ svc = user_services.find { |p| p[:name] == svcname }
194
+
195
+ set svc[:vendor], "services", svcname, "type"
196
+
197
+ if chosen + 1 < user_services.size && ask("Bind another?", :default => false)
198
+ bind_services(user_services, chosen + 1)
199
+ end
200
+ end
201
+
202
+ def create_services(services)
203
+ svcs = services.collect(&:to_s).sort!
204
+
205
+ configure_service(
206
+ ask(
207
+ "What kind of service?",
208
+ :indexed => true,
209
+ :choices => svcs
210
+ )
211
+ )
212
+
213
+ if ask "Create another?", :default => false
214
+ create_services(services)
215
+ end
216
+ end
217
+
218
+ def configure_service(vendor)
219
+ default_name = random_service_name(vendor)
220
+ name = ask "Specify the name of the service", :default => default_name
221
+
222
+ set vendor, "services", name, "type"
223
+ end
224
+
225
+ private
226
+ def ordered_by_deps(apps, abspaths = nil, processed = Set[])
227
+ unless abspaths
228
+ abspaths = {}
229
+ apps.each do |p, i|
230
+ ep = File.expand_path("../" + p, manifest_file)
231
+ abspaths[ep] = i
232
+ end
233
+ end
234
+
235
+ ordered = []
236
+ apps.each do |path, info|
237
+ epath = File.expand_path("../" + path, manifest_file)
238
+
239
+ if deps = info["depends-on"]
240
+ dep_apps = {}
241
+ deps.each do |dep|
242
+ edep = File.expand_path("../" + dep, manifest_file)
243
+
244
+ err "Circular dependency detected." if processed.include? edep
245
+
246
+ dep_apps[dep] = abspaths[edep]
247
+ end
248
+
249
+ processed.add(epath)
250
+
251
+ ordered += ordered_by_deps(dep_apps, abspaths, processed)
252
+ ordered << [path, info]
253
+ elsif not processed.include? epath
254
+ ordered << [path, info]
255
+ processed.add(epath)
256
+ end
257
+ end
258
+
259
+ ordered
260
+ end
261
+
262
+ end