jdc 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/cli/config.rb ADDED
@@ -0,0 +1,173 @@
1
+ require "yaml"
2
+ require 'fileutils'
3
+
4
+ require 'rubygems'
5
+ require 'json/pure'
6
+
7
+ module JDC::Cli
8
+ class Config
9
+
10
+ DEFAULT_TARGET = 'api.vcap.me'
11
+
12
+ TARGET_FILE = '~/.jdc_target'
13
+ TOKEN_FILE = '~/.jdc_token'
14
+ INSTANCES_FILE = '~/.jdc_instances'
15
+ ALIASES_FILE = '~/.jdc_aliases'
16
+ CLIENTS_FILE = '~/.jdc_clients'
17
+ MICRO_FILE = '~/.jdc_micro'
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
+ @suggest_url ||= base_of(target_url)
46
+ end
47
+
48
+ def store_target(target_host)
49
+ target_file = File.expand_path(TARGET_FILE)
50
+ lock_and_write(target_file, target_host)
51
+ end
52
+
53
+ def all_tokens(token_file_path=nil)
54
+ token_file = File.expand_path(token_file_path || TOKEN_FILE)
55
+ return nil unless File.exists? token_file
56
+ contents = lock_and_read(token_file).strip
57
+ JSON.parse(contents)
58
+ end
59
+
60
+ alias :targets :all_tokens
61
+
62
+ def auth_token(token_file_path=nil)
63
+ return @token if @token
64
+ tokens = all_tokens(token_file_path)
65
+ @token = tokens[target_url] if tokens
66
+ end
67
+
68
+ def remove_token_file
69
+ FileUtils.rm_f(File.expand_path(TOKEN_FILE))
70
+ end
71
+
72
+ def store_token(token, token_file_path=nil)
73
+ tokens = all_tokens(token_file_path) || {}
74
+ tokens[target_url] = token
75
+ token_file = File.expand_path(token_file_path || TOKEN_FILE)
76
+ lock_and_write(token_file, tokens.to_json)
77
+ end
78
+
79
+ def instances
80
+ instances_file = File.expand_path(INSTANCES_FILE)
81
+ return nil unless File.exists? instances_file
82
+ contents = lock_and_read(instances_file).strip
83
+ JSON.parse(contents)
84
+ end
85
+
86
+ def store_instances(instances)
87
+ instances_file = File.expand_path(INSTANCES_FILE)
88
+ lock_and_write(instances_file, instances.to_json)
89
+ end
90
+
91
+ def aliases
92
+ aliases_file = File.expand_path(ALIASES_FILE)
93
+ # bacward compatible
94
+ unless File.exists? aliases_file
95
+ old_aliases_file = File.expand_path('~/.jdc-aliases')
96
+ FileUtils.mv(old_aliases_file, aliases_file) if File.exists? old_aliases_file
97
+ end
98
+ aliases = YAML.load_file(aliases_file) rescue {}
99
+ end
100
+
101
+ def store_aliases(aliases)
102
+ aliases_file = File.expand_path(ALIASES_FILE)
103
+ File.open(aliases_file, 'wb') {|f| f.write(aliases.to_yaml)}
104
+ end
105
+
106
+ def micro
107
+ micro_file = File.expand_path(MICRO_FILE)
108
+ return {} unless File.exists? micro_file
109
+ contents = lock_and_read(micro_file).strip
110
+ JSON.parse(contents)
111
+ end
112
+
113
+ def store_micro(micro)
114
+ micro_file = File.expand_path(MICRO_FILE)
115
+ lock_and_write(micro_file, micro.to_json)
116
+ end
117
+
118
+ def deep_merge(a, b)
119
+ merge = proc do |_, old, new|
120
+ if new.is_a?(Hash) and old.is_a?(Hash)
121
+ old.merge(new, &merge)
122
+ else
123
+ new
124
+ end
125
+ end
126
+
127
+ a.merge(b, &merge)
128
+ end
129
+
130
+ def clients
131
+ return @clients if @clients
132
+
133
+ stock = YAML.load_file(STOCK_CLIENTS)
134
+ clients = File.expand_path CLIENTS_FILE
135
+ if File.exists? clients
136
+ user = YAML.load_file(clients)
137
+ @clients = deep_merge(stock, user)
138
+ else
139
+ @clients = stock
140
+ end
141
+ end
142
+
143
+ def lock_and_read(file)
144
+ File.open(file, File::RDONLY) {|f|
145
+ if defined? JRUBY_VERSION
146
+ f.flock(File::LOCK_SH)
147
+ else
148
+ f.flock(File::LOCK_EX)
149
+ end
150
+ contents = f.read
151
+ f.flock(File::LOCK_UN)
152
+ contents
153
+ }
154
+ end
155
+
156
+ def lock_and_write(file, contents)
157
+ File.open(file, File::RDWR | File::CREAT, 0600) {|f|
158
+ f.flock(File::LOCK_EX)
159
+ f.rewind
160
+ f.puts contents
161
+ f.flush
162
+ f.truncate(f.pos)
163
+ f.flock(File::LOCK_UN)
164
+ }
165
+ end
166
+ end
167
+
168
+ def initialize(work_dir = Dir.pwd)
169
+ @work_dir = work_dir
170
+ end
171
+
172
+ end
173
+ end
@@ -0,0 +1,170 @@
1
+ require 'net/telnet'
2
+ require 'readline'
3
+
4
+ module JDC::Cli
5
+ module ConsoleHelper
6
+
7
+ def console_connection_info(appname)
8
+ app = client.app_info(appname)
9
+ fw = JDC::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 JDC::Client::TargetError, JDC::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 JDCExtensions
2
+
3
+ def say(message)
4
+ JDC::Cli::Config.output.puts(message) if JDC::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 JDC::Cli::Config.output
23
+ JDC::Cli::Config.output.print(message)
24
+ JDC::Cli::Config.output.flush
25
+ end
26
+ end
27
+ end
28
+
29
+ def clear(size=80)
30
+ return unless JDC::Cli::Config.output
31
+ JDC::Cli::Config.output.print("\r")
32
+ JDC::Cli::Config.output.print(" " * size)
33
+ JDC::Cli::Config.output.print("\r")
34
+ #JDC::Cli::Config.output.flush
35
+ end
36
+
37
+ def err(message, prefix='Error: ')
38
+ raise JDC::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 JDC::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 JDCStringExtensions
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 JDC::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 JDCExtensions
118
+ end
119
+
120
+ class String
121
+ include JDCStringExtensions
122
+ end
data/lib/cli/errors.rb ADDED
@@ -0,0 +1,19 @@
1
+ module JDC::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