vmcu 0.3.17

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 +160 -0
  3. data/Rakefile +101 -0
  4. data/bin/vmcu +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 +47 -0
  13. data/lib/cli/commands/admin.rb +80 -0
  14. data/lib/cli/commands/apps.rb +1128 -0
  15. data/lib/cli/commands/base.rb +238 -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 +277 -0
  19. data/lib/cli/commands/services.rb +180 -0
  20. data/lib/cli/commands/user.rb +96 -0
  21. data/lib/cli/config.rb +192 -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 +244 -0
  26. data/lib/cli/manifest_helper.rb +302 -0
  27. data/lib/cli/runner.rb +543 -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 +118 -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 +591 -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 +263 -0
@@ -0,0 +1,157 @@
1
+ require 'net/telnet'
2
+ require 'readline'
3
+
4
+ module VMC::Cli
5
+ module ConsoleHelper
6
+
7
+ def console_connection_info(appname)
8
+ app = client.app_info(appname)
9
+ fw = VMC::Cli::Framework.lookup_by_framework(app[:staging][:model])
10
+ if !fw.console
11
+ err "'#{appname}' is a #{fw.name} application. " +
12
+ "Console access is not supported for #{fw.name} applications."
13
+ end
14
+ instances_info_envelope = client.app_instances(appname)
15
+ instances_info_envelope = {} if instances_info_envelope.is_a?(Array)
16
+
17
+ instances_info = instances_info_envelope[:instances] || []
18
+ err "No running instances for [#{appname}]" if instances_info.empty?
19
+
20
+ entry = instances_info[0]
21
+ if !entry[:console_port]
22
+ begin
23
+ client.app_files(appname, '/app/cf-rails-console')
24
+ err "Console port not provided for [#{appname}]. Try restarting the app."
25
+ rescue VMC::Client::TargetError, VMC::Client::NotFound
26
+ err "Console access not supported for [#{appname}]. " +
27
+ "Please redeploy your app to enable support."
28
+ end
29
+ end
30
+ conn_info = {'hostname' => entry[:console_ip], 'port' => entry[:console_port]}
31
+ end
32
+
33
+ def start_local_console(port, appname)
34
+ auth_info = console_credentials(appname)
35
+ display "Connecting to '#{appname}' console: ", false
36
+ prompt = console_login(auth_info, port)
37
+ display "OK".green
38
+ display "\n"
39
+ initialize_readline
40
+ run_console prompt
41
+ end
42
+
43
+ def console_login(auth_info, port)
44
+ if !auth_info["username"] || !auth_info["password"]
45
+ err "Unable to verify console credentials."
46
+ end
47
+ @telnet_client = telnet_client(port)
48
+ prompt = nil
49
+ err_msg = "Login attempt timed out."
50
+ 5.times do
51
+ begin
52
+ results = @telnet_client.login("Name"=>auth_info["username"],
53
+ "Password"=>auth_info["password"]) {|line|
54
+ if line =~ /[$%#>] \z/n
55
+ prompt = line
56
+ elsif line =~ /Login failed/
57
+ err_msg = line
58
+ end
59
+ }
60
+ break
61
+ rescue TimeoutError
62
+ sleep 1
63
+ rescue EOFError
64
+ #This may happen if we login right after app starts
65
+ close_console
66
+ sleep 5
67
+ @telnet_client = telnet_client(port)
68
+ end
69
+ display ".", false
70
+ end
71
+ unless prompt
72
+ close_console
73
+ err err_msg
74
+ end
75
+ prompt
76
+ end
77
+
78
+ def send_console_command(cmd)
79
+ results = @telnet_client.cmd(cmd)
80
+ results.split("\n")
81
+ end
82
+
83
+ def console_credentials(appname)
84
+ content = client.app_files(appname, '/app/cf-rails-console/.consoleaccess', '0')
85
+ YAML.load(content)
86
+ end
87
+
88
+ def close_console
89
+ @telnet_client.close
90
+ end
91
+
92
+ def console_tab_completion_data(cmd)
93
+ begin
94
+ results = @telnet_client.cmd("String"=> cmd + "\t", "Match"=>/\S*\n$/, "Timeout"=>10)
95
+ results.chomp.split(",")
96
+ rescue TimeoutError
97
+ [] #Just return empty results if timeout occurred on tab completion
98
+ end
99
+ end
100
+
101
+ private
102
+ def telnet_client(port)
103
+ Net::Telnet.new({"Port"=>port, "Prompt"=>/[$%#>] \z|Login failed/n, "Timeout"=>30, "FailEOF"=>true})
104
+ end
105
+
106
+ def readline_with_history(prompt)
107
+ line = Readline.readline(prompt, true)
108
+ return '' if line.nil?
109
+ #Don't keep blank or repeat commands in history
110
+ if line =~ /^\s*$/ or Readline::HISTORY.to_a[-2] == line
111
+ Readline::HISTORY.pop
112
+ end
113
+ line
114
+ end
115
+
116
+ def run_console(prompt)
117
+ while(cmd = readline_with_history(prompt))
118
+ if(cmd == "exit" || cmd == "quit")
119
+ #TimeoutError expected, as exit doesn't return anything
120
+ @telnet_client.cmd("String"=>cmd,"Timeout"=>1) rescue TimeoutError
121
+ close_console
122
+ break
123
+ end
124
+ if !cmd.empty?
125
+ prompt = send_console_command_display_results(cmd, prompt)
126
+ end
127
+ end
128
+ end
129
+
130
+ def send_console_command_display_results(cmd, prompt)
131
+ begin
132
+ lines = send_console_command cmd
133
+ #Assumes the last line is a prompt
134
+ prompt = lines.pop
135
+ lines.each {|line| display line if line != cmd}
136
+ rescue TimeoutError
137
+ display "Timed out sending command to server.".red
138
+ rescue EOFError
139
+ err "The console connection has been terminated. Perhaps the app was stopped or deleted?"
140
+ end
141
+ prompt
142
+ end
143
+
144
+ def initialize_readline
145
+ if Readline.respond_to?("basic_word_break_characters=")
146
+ Readline.basic_word_break_characters= " \t\n`><=;|&{("
147
+ end
148
+ Readline.completion_append_character = nil
149
+ #Assumes that sending a String ending with tab will return a non-empty
150
+ #String of comma-separated completion options, terminated by a new line
151
+ #For example, "app.\t" might result in "to_s,nil?,etc\n"
152
+ Readline.completion_proc = proc {|s|
153
+ console_tab_completion_data s
154
+ }
155
+ end
156
+ end
157
+ end
@@ -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
@@ -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,244 @@
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', :console=>true}],
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
+ 'Standalone' => ['standalone', { :mem => '64M', :description => 'Standalone 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
+ 'dotNet' => ['dotNet', { :mem => '128M', :description => '.Net Web Application'}],
22
+ 'Rack' => ['rack', { :mem => '128M', :description => 'Rack Application'}]
23
+ }
24
+
25
+ class << self
26
+
27
+ def known_frameworks(available_frameworks)
28
+ frameworks = []
29
+ FRAMEWORKS.each do |key,fw|
30
+ frameworks << key if available_frameworks.include? [fw[0]]
31
+ end
32
+ frameworks
33
+ end
34
+
35
+ def lookup(name)
36
+ return create(*FRAMEWORKS[name])
37
+ end
38
+
39
+ def lookup_by_framework(name)
40
+ FRAMEWORKS.each do |key,fw|
41
+ return create(fw[0],fw[1]) if fw[0] == name
42
+ end
43
+ end
44
+
45
+ def create(name,opts)
46
+ if name == "standalone"
47
+ return StandaloneFramework.new(name, opts)
48
+ else
49
+ return Framework.new(name,opts)
50
+ end
51
+ end
52
+
53
+ def detect(path, available_frameworks)
54
+ if !File.directory? path
55
+ if path.end_with?('.war')
56
+ return detect_framework_from_war path
57
+ elsif available_frameworks.include?(["standalone"])
58
+ return Framework.lookup('Standalone')
59
+ else
60
+ return nil
61
+ end
62
+ end
63
+ Dir.chdir(path) do
64
+ # Rails
65
+ if File.exist?('config/environment.rb')
66
+ return Framework.lookup('Rails')
67
+
68
+ # Rack
69
+ elsif File.exist?('config.ru') && available_frameworks.include?(["rack"])
70
+ return Framework.lookup('Rack')
71
+
72
+ # Java Web Apps
73
+ elsif Dir.glob('*.war').first
74
+ return detect_framework_from_war(Dir.glob('*.war').first)
75
+
76
+ elsif File.exist?('WEB-INF/web.xml')
77
+ return detect_framework_from_war
78
+
79
+ # Simple Ruby Apps
80
+ elsif !Dir.glob('*.rb').empty?
81
+ matched_file = nil
82
+ Dir.glob('*.rb').each do |fname|
83
+ next if matched_file
84
+ File.open(fname, 'r') do |f|
85
+ str = f.read # This might want to be limited
86
+ matched_file = fname if (str && str.match(/^\s*require[\s\(]*['"]sinatra['"]/))
87
+ end
88
+ end
89
+ if matched_file
90
+ # Sinatra apps
91
+ f = Framework.lookup('Sinatra')
92
+ f.exec = "ruby #{matched_file}"
93
+ return f
94
+ end
95
+
96
+ # PHP
97
+ elsif !Dir.glob('*.php').empty?
98
+ return Framework.lookup('PHP')
99
+
100
+ # Erlang/OTP using Rebar
101
+ elsif !Dir.glob('releases/*/*.rel').empty? && !Dir.glob('releases/*/*.boot').empty?
102
+ return Framework.lookup('Erlang/OTP Rebar')
103
+
104
+ # Python Django
105
+ # XXX: not all django projects keep settings.py in top-level directory
106
+ elsif File.exist?('manage.py') && File.exist?('settings.py')
107
+ return Framework.lookup('Django')
108
+
109
+ # Python
110
+ elsif !Dir.glob('wsgi.py').empty?
111
+ return Framework.lookup('WSGI')
112
+ # .Net
113
+ elsif !Dir.glob('web.config').empty?
114
+ return Framework.lookup('dotNet')
115
+
116
+ # Node.js
117
+ elsif !Dir.glob('*.js').empty?
118
+ if File.exist?('server.js') || File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js')
119
+ return Framework.lookup('Node')
120
+ end
121
+ end
122
+
123
+ # Default to Standalone if no other match was made
124
+ return Framework.lookup('Standalone') if available_frameworks.include?(["standalone"])
125
+ end
126
+ end
127
+
128
+ private
129
+ def detect_framework_from_war(war_file=nil)
130
+ if war_file
131
+ contents = ZipUtil.entry_lines(war_file)
132
+ else
133
+ #assume we are working with current dir
134
+ contents = Dir['**/*'].join("\n")
135
+ end
136
+
137
+ # Spring/Lift Variations
138
+ if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/
139
+ return Framework.lookup('Grails')
140
+ elsif contents =~ /WEB-INF\/lib\/lift-webkit.*\.jar/
141
+ return Framework.lookup('Lift')
142
+ elsif contents =~ /WEB-INF\/classes\/org\/springframework/
143
+ return Framework.lookup('Spring')
144
+ elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
145
+ return Framework.lookup('Spring')
146
+ elsif contents =~ /WEB-INF\/lib\/org\.springframework\.core.*\.jar/
147
+ return Framework.lookup('Spring')
148
+ else
149
+ return Framework.lookup('JavaWeb')
150
+ end
151
+ end
152
+ end
153
+
154
+ attr_reader :name, :description, :console
155
+ attr_accessor :exec
156
+
157
+ def initialize(framework=nil, opts={})
158
+ @name = framework || DEFAULT_FRAMEWORK
159
+ @memory = opts[:mem] || DEFAULT_MEM
160
+ @description = opts[:description] || 'Unknown Application Type'
161
+ @exec = opts[:exec]
162
+ @console = opts[:console] || false
163
+ end
164
+
165
+ def to_s
166
+ description
167
+ end
168
+
169
+ def require_url?
170
+ true
171
+ end
172
+
173
+ def require_start_command?
174
+ false
175
+ end
176
+
177
+ def prompt_for_runtime?
178
+ false
179
+ end
180
+
181
+ def default_runtime(path)
182
+ nil
183
+ end
184
+
185
+ def memory(runtime=nil)
186
+ @memory
187
+ end
188
+
189
+ alias :mem :memory
190
+ end
191
+
192
+ class StandaloneFramework < Framework
193
+ def require_url?
194
+ false
195
+ end
196
+
197
+ def require_start_command?
198
+ true
199
+ end
200
+
201
+ def prompt_for_runtime?
202
+ true
203
+ end
204
+
205
+ def default_runtime(path)
206
+ if !File.directory? path
207
+ if path =~ /\.(jar|class)$/
208
+ return "java"
209
+ elsif path =~ /\.(rb)$/
210
+ return "ruby18"
211
+ elsif path =~ /\.(zip)$/
212
+ return detect_runtime_from_zip path
213
+ end
214
+ else
215
+ Dir.chdir(path) do
216
+ return "ruby18" if not Dir.glob('**/*.rb').empty?
217
+ if !Dir.glob('**/*.class').empty? || !Dir.glob('**/*.jar').empty?
218
+ return "java"
219
+ elsif Dir.glob('*.zip').first
220
+ zip_file = Dir.glob('*.zip').first
221
+ return detect_runtime_from_zip zip_file
222
+ end
223
+ end
224
+ end
225
+ return nil
226
+ end
227
+
228
+ def memory(runtime=nil)
229
+ default_mem = @memory
230
+ default_mem = '128M' if runtime =~ /\Aruby/ || runtime == "php"
231
+ default_mem = '512M' if runtime == "java"
232
+ default_mem
233
+ end
234
+
235
+ private
236
+ def detect_runtime_from_zip(zip_file)
237
+ contents = ZipUtil.entry_lines(zip_file)
238
+ if contents =~ /\.(jar)$/
239
+ return "java"
240
+ end
241
+ end
242
+ end
243
+
244
+ end