udn 0.3.23.0.pre

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 +113 -0
  3. data/Rakefile +101 -0
  4. data/bin/udn +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 +1129 -0
  15. data/lib/cli/commands/base.rb +228 -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 +259 -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 +170 -0
  23. data/lib/cli/core_ext.rb +122 -0
  24. data/lib/cli/errors.rb +19 -0
  25. data/lib/cli/frameworks.rb +266 -0
  26. data/lib/cli/manifest_helper.rb +323 -0
  27. data/lib/cli/runner.rb +556 -0
  28. data/lib/cli/services_helper.rb +109 -0
  29. data/lib/cli/tunnel_helper.rb +332 -0
  30. data/lib/cli/usage.rb +124 -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 +562 -0
  35. data/lib/vmc/const.rb +23 -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 +168 -0
  43. metadata +310 -0
@@ -0,0 +1,170 @@
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
+ banner = "Connecting to '#{appname}' console: "
36
+ display banner, false
37
+ t = Thread.new do
38
+ count = 0
39
+ while count < 90 do
40
+ display '.', false
41
+ sleep 1
42
+ count += 1
43
+ end
44
+ end
45
+ prompt = console_login(auth_info, port)
46
+ Thread.kill(t)
47
+ clear(80)
48
+ display "#{banner}#{'OK'.green}"
49
+ display "\n"
50
+ initialize_readline
51
+ run_console prompt
52
+ end
53
+
54
+ def console_login(auth_info, port)
55
+ if !auth_info["username"] || !auth_info["password"]
56
+ err "Unable to verify console credentials."
57
+ end
58
+ @telnet_client = telnet_client(port)
59
+ prompt = nil
60
+ err_msg = "Login attempt timed out."
61
+ 3.times do
62
+ begin
63
+ results = @telnet_client.login("Name"=>auth_info["username"],
64
+ "Password"=>auth_info["password"])
65
+ lines = results.sub("Login: Password: ", "").split("\n")
66
+ last_line = lines.pop
67
+ if last_line =~ /[$%#>] \z/n
68
+ prompt = last_line
69
+ elsif last_line =~ /Login failed/
70
+ err_msg = last_line
71
+ end
72
+ break
73
+ rescue TimeoutError
74
+ sleep 1
75
+ rescue EOFError
76
+ #This may happen if we login right after app starts
77
+ close_console
78
+ sleep 5
79
+ @telnet_client = telnet_client(port)
80
+ end
81
+ end
82
+ unless prompt
83
+ close_console
84
+ err err_msg
85
+ end
86
+ prompt
87
+ end
88
+
89
+ def send_console_command(cmd)
90
+ results = @telnet_client.cmd(cmd)
91
+ results.split("\n")
92
+ end
93
+
94
+ def console_credentials(appname)
95
+ content = client.app_files(appname, '/app/cf-rails-console/.consoleaccess', '0')
96
+ YAML.load(content)
97
+ end
98
+
99
+ def close_console
100
+ @telnet_client.close
101
+ end
102
+
103
+ def console_tab_completion_data(cmd)
104
+ begin
105
+ results = @telnet_client.cmd("String"=> cmd + "\t", "Match"=>/\S*\n$/, "Timeout"=>10)
106
+ results.chomp.split(",")
107
+ rescue TimeoutError
108
+ [] #Just return empty results if timeout occurred on tab completion
109
+ end
110
+ end
111
+
112
+ private
113
+ def telnet_client(port)
114
+ Net::Telnet.new({"Port"=>port, "Prompt"=>/[$%#>] \z|Login failed/n, "Timeout"=>30, "FailEOF"=>true})
115
+ end
116
+
117
+ def readline_with_history(prompt)
118
+ line = Readline::readline(prompt)
119
+ return nil if line == nil || line == 'quit' || line == 'exit'
120
+ Readline::HISTORY.push(line) if not line =~ /^\s*$/ and Readline::HISTORY.to_a[-1] != line
121
+ line
122
+ end
123
+
124
+ def run_console(prompt)
125
+ prev = trap("INT") { |x| exit_console; prev.call(x); exit }
126
+ prev = trap("TERM") { |x| exit_console; prev.call(x); exit }
127
+ loop do
128
+ cmd = readline_with_history(prompt)
129
+ if(cmd == nil)
130
+ exit_console
131
+ break
132
+ end
133
+ prompt = send_console_command_display_results(cmd, prompt)
134
+ end
135
+ end
136
+
137
+ def exit_console
138
+ #TimeoutError expected, as exit doesn't return anything
139
+ @telnet_client.cmd("String"=>"exit","Timeout"=>1) rescue TimeoutError
140
+ close_console
141
+ end
142
+
143
+ def send_console_command_display_results(cmd, prompt)
144
+ begin
145
+ lines = send_console_command cmd
146
+ #Assumes the last line is a prompt
147
+ prompt = lines.pop
148
+ lines.each {|line| display line if line != cmd}
149
+ rescue TimeoutError
150
+ display "Timed out sending command to server.".red
151
+ rescue EOFError
152
+ err "The console connection has been terminated. Perhaps the app was stopped or deleted?"
153
+ end
154
+ prompt
155
+ end
156
+
157
+ def initialize_readline
158
+ if Readline.respond_to?("basic_word_break_characters=")
159
+ Readline.basic_word_break_characters= " \t\n`><=;|&{("
160
+ end
161
+ Readline.completion_append_character = nil
162
+ #Assumes that sending a String ending with tab will return a non-empty
163
+ #String of comma-separated completion options, terminated by a new line
164
+ #For example, "app.\t" might result in "to_s,nil?,etc\n"
165
+ Readline.completion_proc = proc {|s|
166
+ console_tab_completion_data s
167
+ }
168
+ end
169
+ end
170
+ 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
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,266 @@
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
+ 'Play' => ['play', { :mem => '256M', :description => 'Play Framework Application'}]
24
+ }
25
+
26
+ class << self
27
+
28
+ def known_frameworks(available_frameworks)
29
+ frameworks = []
30
+ FRAMEWORKS.each do |key,fw|
31
+ frameworks << key if available_frameworks.include? [fw[0]]
32
+ end
33
+ frameworks
34
+ end
35
+
36
+ def lookup(name)
37
+ return create(*FRAMEWORKS[name])
38
+ end
39
+
40
+ def lookup_by_framework(name)
41
+ FRAMEWORKS.each do |key,fw|
42
+ return create(fw[0],fw[1]) if fw[0] == name
43
+ end
44
+ Framework.new(name)
45
+ end
46
+
47
+ def create(name,opts)
48
+ if name == "standalone"
49
+ return StandaloneFramework.new(name, opts)
50
+ else
51
+ return Framework.new(name,opts)
52
+ end
53
+ end
54
+
55
+ def detect(path, available_frameworks)
56
+ if !File.directory? path
57
+ if path.end_with?('.war')
58
+ return detect_framework_from_war path
59
+ elsif path.end_with?('.zip')
60
+ return detect_framework_from_zip path, available_frameworks
61
+ elsif available_frameworks.include?(["standalone"])
62
+ return Framework.lookup('Standalone')
63
+ else
64
+ return nil
65
+ end
66
+ end
67
+ Dir.chdir(path) do
68
+ # Rails
69
+ if File.exist?('config/environment.rb')
70
+ return Framework.lookup('Rails')
71
+
72
+ # Rack
73
+ elsif File.exist?('config.ru') && available_frameworks.include?(["rack"])
74
+ return Framework.lookup('Rack')
75
+
76
+ # Java Web Apps
77
+ elsif Dir.glob('*.war').first
78
+ return detect_framework_from_war(Dir.glob('*.war').first)
79
+
80
+ elsif File.exist?('WEB-INF/web.xml')
81
+ return detect_framework_from_war
82
+
83
+ # Simple Ruby Apps
84
+ elsif !Dir.glob('*.rb').empty?
85
+ matched_file = nil
86
+ Dir.glob('*.rb').each do |fname|
87
+ next if matched_file
88
+ File.open(fname, 'r') do |f|
89
+ str = f.read # This might want to be limited
90
+ matched_file = fname if (str && str.match(/^\s*require[\s\(]*['"]sinatra(\/base)?['"]/))
91
+ end
92
+ end
93
+ if matched_file
94
+ # Sinatra apps
95
+ f = Framework.lookup('Sinatra')
96
+ f.exec = "ruby #{matched_file}"
97
+ return f
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
+ # .Net
118
+ elsif !Dir.glob('web.config').empty?
119
+ return Framework.lookup('dotNet')
120
+
121
+ # Node.js
122
+ elsif !Dir.glob('*.js').empty?
123
+ if File.exist?('server.js') || File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js')
124
+ return Framework.lookup('Node')
125
+ end
126
+
127
+ # Play or Standalone Apps
128
+ elsif Dir.glob('*.zip').first
129
+ zip_file = Dir.glob('*.zip').first
130
+ return detect_framework_from_zip zip_file, available_frameworks
131
+ end
132
+
133
+ # Default to Standalone if no other match was made
134
+ return Framework.lookup('Standalone') if available_frameworks.include?(["standalone"])
135
+ end
136
+ end
137
+
138
+ def detect_framework_from_war(war_file=nil)
139
+ if war_file
140
+ contents = ZipUtil.entry_lines(war_file)
141
+ else
142
+ #assume we are working with current dir
143
+ contents = Dir['**/*'].join("\n")
144
+ end
145
+
146
+ # Spring/Lift Variations
147
+ if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/
148
+ return Framework.lookup('Grails')
149
+ elsif contents =~ /WEB-INF\/lib\/lift-webkit.*\.jar/
150
+ return Framework.lookup('Lift')
151
+ elsif contents =~ /WEB-INF\/classes\/org\/springframework/
152
+ return Framework.lookup('Spring')
153
+ elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
154
+ return Framework.lookup('Spring')
155
+ elsif contents =~ /WEB-INF\/lib\/org\.springframework\.core.*\.jar/
156
+ return Framework.lookup('Spring')
157
+ else
158
+ return Framework.lookup('JavaWeb')
159
+ end
160
+ end
161
+
162
+ def detect_framework_from_zip(zip_file, available_frameworks)
163
+ contents = ZipUtil.entry_lines(zip_file)
164
+ detect_framework_from_zip_contents(contents, available_frameworks)
165
+ end
166
+
167
+ def detect_framework_from_zip_contents(contents, available_frameworks)
168
+ if available_frameworks.include?(["play"]) && contents =~ /lib\/play\..*\.jar/
169
+ return Framework.lookup('Play')
170
+ elsif available_frameworks.include?(["standalone"])
171
+ return Framework.lookup('Standalone')
172
+ end
173
+ end
174
+ end
175
+
176
+ attr_reader :name, :description, :console
177
+ attr_accessor :exec
178
+
179
+ def initialize(framework=nil, opts={})
180
+ @name = framework || DEFAULT_FRAMEWORK
181
+ @memory = opts[:mem] || DEFAULT_MEM
182
+ @description = opts[:description] || 'Unknown Application Type'
183
+ @exec = opts[:exec]
184
+ @console = opts[:console] || false
185
+ end
186
+
187
+ def to_s
188
+ description
189
+ end
190
+
191
+ def require_url?
192
+ true
193
+ end
194
+
195
+ def require_start_command?
196
+ false
197
+ end
198
+
199
+ def prompt_for_runtime?
200
+ false
201
+ end
202
+
203
+ def default_runtime(path)
204
+ nil
205
+ end
206
+
207
+ def memory(runtime=nil)
208
+ @memory
209
+ end
210
+
211
+ alias :mem :memory
212
+ end
213
+
214
+ class StandaloneFramework < Framework
215
+ def require_url?
216
+ false
217
+ end
218
+
219
+ def require_start_command?
220
+ true
221
+ end
222
+
223
+ def prompt_for_runtime?
224
+ true
225
+ end
226
+
227
+ def default_runtime(path)
228
+ if !File.directory? path
229
+ if path =~ /\.(jar|class)$/
230
+ return "java"
231
+ elsif path =~ /\.(rb)$/
232
+ return "ruby18"
233
+ elsif path =~ /\.(zip)$/
234
+ return detect_runtime_from_zip path
235
+ end
236
+ else
237
+ Dir.chdir(path) do
238
+ return "ruby18" if not Dir.glob('**/*.rb').empty?
239
+ if !Dir.glob('**/*.class').empty? || !Dir.glob('**/*.jar').empty?
240
+ return "java"
241
+ elsif Dir.glob('*.zip').first
242
+ zip_file = Dir.glob('*.zip').first
243
+ return detect_runtime_from_zip zip_file
244
+ end
245
+ end
246
+ end
247
+ return nil
248
+ end
249
+
250
+ def memory(runtime=nil)
251
+ default_mem = @memory
252
+ default_mem = '128M' if runtime =~ /\Aruby/ || runtime == "php"
253
+ default_mem = '512M' if runtime == "java" || runtime == "java7"
254
+ default_mem
255
+ end
256
+
257
+ private
258
+ def detect_runtime_from_zip(zip_file)
259
+ contents = ZipUtil.entry_lines(zip_file)
260
+ if contents =~ /\.(jar)$/
261
+ return "java"
262
+ end
263
+ end
264
+ end
265
+
266
+ end