as 0.3.18.11
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of as might be problematic. Click here for more details.
- data/LICENSE +24 -0
- data/README.md +105 -0
- data/Rakefile +101 -0
- data/bin/as +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/config/micro/offline.conf +2 -0
- data/config/micro/paths.yml +22 -0
- data/config/micro/refresh_ip.rb +20 -0
- data/lib/cli/commands/admin.rb +80 -0
- data/lib/cli/commands/apps.rb +1208 -0
- data/lib/cli/commands/base.rb +233 -0
- data/lib/cli/commands/manifest.rb +56 -0
- data/lib/cli/commands/micro.rb +115 -0
- data/lib/cli/commands/misc.rb +140 -0
- data/lib/cli/commands/services.rb +217 -0
- data/lib/cli/commands/user.rb +65 -0
- data/lib/cli/config.rb +170 -0
- data/lib/cli/console_helper.rb +163 -0
- data/lib/cli/core_ext.rb +122 -0
- data/lib/cli/errors.rb +19 -0
- data/lib/cli/file_helper.rb +123 -0
- data/lib/cli/frameworks.rb +265 -0
- data/lib/cli/manifest_helper.rb +316 -0
- data/lib/cli/runner.rb +568 -0
- data/lib/cli/services_helper.rb +104 -0
- data/lib/cli/tunnel_helper.rb +336 -0
- data/lib/cli/usage.rb +125 -0
- data/lib/cli/version.rb +7 -0
- data/lib/cli/zip_util.rb +77 -0
- data/lib/cli.rb +48 -0
- data/lib/vmc/client.rb +558 -0
- data/lib/vmc/const.rb +27 -0
- data/lib/vmc/micro/switcher/base.rb +97 -0
- data/lib/vmc/micro/switcher/darwin.rb +19 -0
- data/lib/vmc/micro/switcher/dummy.rb +15 -0
- data/lib/vmc/micro/switcher/linux.rb +16 -0
- data/lib/vmc/micro/switcher/windows.rb +31 -0
- data/lib/vmc/micro/vmrun.rb +158 -0
- data/lib/vmc/micro.rb +56 -0
- data/lib/vmc.rb +3 -0
- metadata +270 -0
@@ -0,0 +1,217 @@
|
|
1
|
+
require "uuidtools"
|
2
|
+
|
3
|
+
module VMC::Cli::Command
|
4
|
+
|
5
|
+
class Services < Base
|
6
|
+
include VMC::Cli::ServicesHelper
|
7
|
+
include VMC::Cli::TunnelHelper
|
8
|
+
|
9
|
+
def services
|
10
|
+
ss = client.services_info
|
11
|
+
ps = client.services
|
12
|
+
ps.sort! {|a, b| a[:name] <=> b[:name] }
|
13
|
+
|
14
|
+
if @options[:json]
|
15
|
+
services = { :system => ss, :provisioned => ps }
|
16
|
+
return display JSON.pretty_generate(services)
|
17
|
+
end
|
18
|
+
display_system_services(ss)
|
19
|
+
display_provisioned_services(ps)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_service(service=nil, name=nil, appname=nil)
|
23
|
+
|
24
|
+
unless no_prompt || service
|
25
|
+
services = client.services_info
|
26
|
+
err 'No services available to provision' if services.empty?
|
27
|
+
service = ask(
|
28
|
+
"Which service would you like to provision?",
|
29
|
+
{ :indexed => true,
|
30
|
+
:choices =>
|
31
|
+
services.values.collect { |type|
|
32
|
+
type.keys.collect(&:to_s)
|
33
|
+
}.flatten
|
34
|
+
}
|
35
|
+
)
|
36
|
+
end
|
37
|
+
name = @options[:name] unless name
|
38
|
+
unless name
|
39
|
+
name = random_service_name(service)
|
40
|
+
picked_name = true
|
41
|
+
end
|
42
|
+
|
43
|
+
if client.infra_supported?
|
44
|
+
unless no_prompt || @options[:infra]
|
45
|
+
@options[:infra] = client.infra_name_for_description(
|
46
|
+
ask("Select Infrastructure",
|
47
|
+
:indexed => true, :choices => client.infra_descriptions))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
create_service_banner(service, name, picked_name, @options[:infra])
|
52
|
+
appname = @options[:bind] unless appname
|
53
|
+
bind_service_banner(name, appname) if appname
|
54
|
+
end
|
55
|
+
|
56
|
+
def delete_service(service=nil)
|
57
|
+
unless no_prompt || service
|
58
|
+
user_services = client.services
|
59
|
+
err 'No services available to delete' if user_services.empty?
|
60
|
+
service = ask(
|
61
|
+
"Which service would you like to delete?",
|
62
|
+
{ :indexed => true,
|
63
|
+
:choices => user_services.collect { |s| s[:name] }
|
64
|
+
}
|
65
|
+
)
|
66
|
+
end
|
67
|
+
err "Service name required." unless service
|
68
|
+
display "Deleting service [#{service}]: ", false
|
69
|
+
client.delete_service(service)
|
70
|
+
display 'OK'.green
|
71
|
+
end
|
72
|
+
|
73
|
+
def bind_service(service, appname)
|
74
|
+
bind_service_banner(service, appname)
|
75
|
+
end
|
76
|
+
|
77
|
+
def unbind_service(service, appname)
|
78
|
+
unbind_service_banner(service, appname)
|
79
|
+
end
|
80
|
+
|
81
|
+
def clone_services(src_app, dest_app)
|
82
|
+
begin
|
83
|
+
src = client.app_info(src_app)
|
84
|
+
dest = client.app_info(dest_app)
|
85
|
+
rescue
|
86
|
+
end
|
87
|
+
|
88
|
+
err "Application '#{src_app}' does not exist" unless src
|
89
|
+
err "Application '#{dest_app}' does not exist" unless dest
|
90
|
+
|
91
|
+
services = src[:services]
|
92
|
+
err 'No services to clone' unless services && !services.empty?
|
93
|
+
services.each { |service| bind_service_banner(service, dest_app, false) }
|
94
|
+
check_app_for_restart(dest_app)
|
95
|
+
end
|
96
|
+
|
97
|
+
def export_service(service)
|
98
|
+
display "Exporting data from '#{service}': ", false
|
99
|
+
export_info = client.export_service(service)
|
100
|
+
if export_info
|
101
|
+
display 'OK'.green
|
102
|
+
puts export_info[:uri]
|
103
|
+
else
|
104
|
+
err "Export data from '#{service}': failed"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def import_service(service,url)
|
109
|
+
display "Importing data into '#{service}': ", false
|
110
|
+
import_info = client.import_service(service,url)
|
111
|
+
if import_info
|
112
|
+
display 'OK'.green
|
113
|
+
else
|
114
|
+
err "Import data into '#{service}' failed"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def tunnel(service=nil, client_name=nil)
|
119
|
+
unless defined? Caldecott
|
120
|
+
display "To use `as tunnel', you must first install Caldecott:"
|
121
|
+
display ""
|
122
|
+
display "\tgem install caldecott"
|
123
|
+
display ""
|
124
|
+
display "Note that you'll need a C compiler. If you're on OS X, Xcode"
|
125
|
+
display "will provide one. If you're on Windows, try DevKit."
|
126
|
+
display ""
|
127
|
+
display "This manual step will be removed in the future."
|
128
|
+
display ""
|
129
|
+
err "Caldecott is not installed."
|
130
|
+
end
|
131
|
+
|
132
|
+
ps = client.services
|
133
|
+
err "No services available to tunnel to" if ps.empty?
|
134
|
+
|
135
|
+
unless service
|
136
|
+
choices = ps.collect { |s| s[:name] }.sort
|
137
|
+
service = ask(
|
138
|
+
"Which service to tunnel to?",
|
139
|
+
:choices => choices,
|
140
|
+
:indexed => true
|
141
|
+
)
|
142
|
+
end
|
143
|
+
|
144
|
+
info = ps.select { |s| s[:name] == service }.first
|
145
|
+
|
146
|
+
err "Unknown service '#{service}'" unless info
|
147
|
+
|
148
|
+
port = pick_tunnel_port(@options[:port] || 10000)
|
149
|
+
|
150
|
+
raise VMC::Client::AuthError unless client.logged_in?
|
151
|
+
|
152
|
+
infra_name = nil
|
153
|
+
if client.infra_supported?
|
154
|
+
infra_name = info[:infra] ? info[:infra][:name] : default_infra
|
155
|
+
err "Infra '#{infra_name}' is not valid" unless client.infra_valid?(infra_name)
|
156
|
+
end
|
157
|
+
|
158
|
+
if not tunnel_pushed?(infra_name)
|
159
|
+
display "Deploying tunnel application '#{tunnel_appname(infra_name)}'."
|
160
|
+
auth = UUIDTools::UUID.random_create.to_s
|
161
|
+
push_caldecott(auth,infra_name)
|
162
|
+
bind_service_banner(service, tunnel_appname(infra_name), false)
|
163
|
+
start_caldecott(infra_name)
|
164
|
+
else
|
165
|
+
auth = tunnel_auth(infra_name)
|
166
|
+
end
|
167
|
+
|
168
|
+
if not tunnel_healthy?(auth,infra_name)
|
169
|
+
display "Redeploying tunnel application '#{tunnel_appname(infra_name)}'."
|
170
|
+
|
171
|
+
# We don't expect caldecott not to be running, so take the
|
172
|
+
# most aggressive restart method.. delete/re-push
|
173
|
+
client.delete_app(tunnel_appname(infra_name))
|
174
|
+
invalidate_tunnel_app_info(infra_name)
|
175
|
+
|
176
|
+
push_caldecott(auth,infra_name)
|
177
|
+
bind_service_banner(service, tunnel_appname(infra_name), false)
|
178
|
+
start_caldecott(infra_name)
|
179
|
+
end
|
180
|
+
|
181
|
+
if not tunnel_bound?(service,infra_name)
|
182
|
+
bind_service_banner(service, tunnel_appname(infra_name))
|
183
|
+
end
|
184
|
+
|
185
|
+
conn_info = tunnel_connection_info info[:vendor], service, auth, infra_name
|
186
|
+
display_tunnel_connection_info(conn_info)
|
187
|
+
display "Starting tunnel to #{service.bold} on port #{port.to_s.bold}."
|
188
|
+
start_tunnel(port, conn_info, auth, infra_name)
|
189
|
+
|
190
|
+
clients = get_clients_for(info[:vendor])
|
191
|
+
|
192
|
+
if clients.empty?
|
193
|
+
client_name ||= "none"
|
194
|
+
else
|
195
|
+
client_name ||= ask(
|
196
|
+
"Which client would you like to start?",
|
197
|
+
:choices => ["none"] + clients.keys,
|
198
|
+
:indexed => true
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
202
|
+
if client_name == "none"
|
203
|
+
wait_for_tunnel_end
|
204
|
+
else
|
205
|
+
wait_for_tunnel_start(port)
|
206
|
+
unless start_local_prog(clients, client_name, conn_info, port)
|
207
|
+
err "'#{client_name}' execution failed; is it in your $PATH?"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def get_clients_for(type)
|
213
|
+
conf = VMC::Cli::Config.clients
|
214
|
+
conf[type] || {}
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module VMC::Cli::Command
|
2
|
+
|
3
|
+
class User < Base
|
4
|
+
|
5
|
+
def info
|
6
|
+
info = client_info
|
7
|
+
username = info[:user] || 'N/A'
|
8
|
+
return display JSON.pretty_generate([username]) if @options[:json]
|
9
|
+
display "\n[#{username}]"
|
10
|
+
end
|
11
|
+
|
12
|
+
def login(email=nil)
|
13
|
+
email = @options[:email] unless email
|
14
|
+
password = @options[:password]
|
15
|
+
tries ||= 0
|
16
|
+
|
17
|
+
unless no_prompt
|
18
|
+
display "Attempting login to [#{target_url}]" if target_url
|
19
|
+
email ||= ask("Email")
|
20
|
+
password ||= ask("Password", :echo => "*")
|
21
|
+
end
|
22
|
+
|
23
|
+
err "Need a valid email" unless email
|
24
|
+
err "Need a password" unless password
|
25
|
+
login_and_save_token(email, password)
|
26
|
+
say "Successfully logged into [#{target_url}]".green
|
27
|
+
rescue VMC::Client::TargetError
|
28
|
+
display "Problem with login, invalid account or password when attempting to login to '#{target_url}'".red
|
29
|
+
retry if (tries += 1) < 3 && prompt_ok && !@options[:password]
|
30
|
+
exit 1
|
31
|
+
rescue => e
|
32
|
+
display "Problem with login to '#{target_url}', #{e}, try again or register for an account.".red
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def logout
|
37
|
+
VMC::Cli::Config.remove_token_file
|
38
|
+
say "Successfully logged out of [#{target_url}]".green
|
39
|
+
end
|
40
|
+
|
41
|
+
def change_password(password=nil)
|
42
|
+
info = client_info
|
43
|
+
email = info[:user]
|
44
|
+
err "Need to be logged in to change password." unless email
|
45
|
+
say "Changing password for '#{email}'\n"
|
46
|
+
unless no_prompt
|
47
|
+
password = ask "New Password", :echo => "*"
|
48
|
+
password2 = ask "Verify Password", :echo => "*"
|
49
|
+
err "Passwords did not match, try again" if password != password2
|
50
|
+
end
|
51
|
+
err "Password required" unless password
|
52
|
+
client.change_password(password)
|
53
|
+
say "\nSuccessfully changed password".green
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def login_and_save_token(email, password)
|
59
|
+
token = client.login(email, password)
|
60
|
+
VMC::Cli::Config.store_token(token, @options[:token_file])
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/lib/cli/config.rb
ADDED
@@ -0,0 +1,170 @@
|
|
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.cloud.anotherservice.com'
|
11
|
+
|
12
|
+
TARGET_FILE = '~/.as_target'
|
13
|
+
TOKEN_FILE = '~/.as_token'
|
14
|
+
INSTANCES_FILE = '~/.as_instances'
|
15
|
+
ALIASES_FILE = '~/.as_aliases'
|
16
|
+
CLIENTS_FILE = '~/.as_clients'
|
17
|
+
MICRO_FILE = '~/.as_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
|
+
attr_accessor :infra
|
27
|
+
|
28
|
+
def target_url
|
29
|
+
return @target_url if @target_url
|
30
|
+
target_file = File.expand_path(TARGET_FILE)
|
31
|
+
if File.exists? target_file
|
32
|
+
@target_url = lock_and_read(target_file).strip
|
33
|
+
else
|
34
|
+
@target_url = DEFAULT_TARGET
|
35
|
+
end
|
36
|
+
@target_url = "http://#{@target_url}" unless /^http?/ =~ @target_url
|
37
|
+
@target_url = @target_url.gsub(/\/+$/, '')
|
38
|
+
@target_url
|
39
|
+
end
|
40
|
+
|
41
|
+
def base_of(url)
|
42
|
+
url.sub(/^[^\.]+\./, "")
|
43
|
+
end
|
44
|
+
|
45
|
+
def store_target(target_host)
|
46
|
+
target_file = File.expand_path(TARGET_FILE)
|
47
|
+
lock_and_write(target_file, target_host)
|
48
|
+
end
|
49
|
+
|
50
|
+
def all_tokens(token_file_path=nil)
|
51
|
+
token_file = File.expand_path(token_file_path || TOKEN_FILE)
|
52
|
+
return nil unless File.exists? token_file
|
53
|
+
contents = lock_and_read(token_file).strip
|
54
|
+
JSON.parse(contents)
|
55
|
+
end
|
56
|
+
|
57
|
+
alias :targets :all_tokens
|
58
|
+
|
59
|
+
def auth_token(token_file_path=nil)
|
60
|
+
return @token if @token
|
61
|
+
tokens = all_tokens(token_file_path)
|
62
|
+
@token = tokens[target_url] if tokens
|
63
|
+
end
|
64
|
+
|
65
|
+
def remove_token_file
|
66
|
+
FileUtils.rm_f(File.expand_path(TOKEN_FILE))
|
67
|
+
end
|
68
|
+
|
69
|
+
def store_token(token, token_file_path=nil)
|
70
|
+
tokens = all_tokens(token_file_path) || {}
|
71
|
+
tokens[target_url] = token
|
72
|
+
token_file = File.expand_path(token_file_path || TOKEN_FILE)
|
73
|
+
lock_and_write(token_file, tokens.to_json)
|
74
|
+
end
|
75
|
+
|
76
|
+
def instances
|
77
|
+
instances_file = File.expand_path(INSTANCES_FILE)
|
78
|
+
return nil unless File.exists? instances_file
|
79
|
+
contents = lock_and_read(instances_file).strip
|
80
|
+
JSON.parse(contents)
|
81
|
+
end
|
82
|
+
|
83
|
+
def store_instances(instances)
|
84
|
+
instances_file = File.expand_path(INSTANCES_FILE)
|
85
|
+
lock_and_write(instances_file, instances.to_json)
|
86
|
+
end
|
87
|
+
|
88
|
+
def aliases
|
89
|
+
aliases_file = File.expand_path(ALIASES_FILE)
|
90
|
+
# bacward compatible
|
91
|
+
unless File.exists? aliases_file
|
92
|
+
old_aliases_file = File.expand_path('~/.vmc-aliases')
|
93
|
+
FileUtils.mv(old_aliases_file, aliases_file) if File.exists? old_aliases_file
|
94
|
+
end
|
95
|
+
aliases = YAML.load_file(aliases_file) rescue {}
|
96
|
+
end
|
97
|
+
|
98
|
+
def store_aliases(aliases)
|
99
|
+
aliases_file = File.expand_path(ALIASES_FILE)
|
100
|
+
File.open(aliases_file, 'wb') {|f| f.write(aliases.to_yaml)}
|
101
|
+
end
|
102
|
+
|
103
|
+
def micro
|
104
|
+
micro_file = File.expand_path(MICRO_FILE)
|
105
|
+
return {} unless File.exists? micro_file
|
106
|
+
contents = lock_and_read(micro_file).strip
|
107
|
+
JSON.parse(contents)
|
108
|
+
end
|
109
|
+
|
110
|
+
def store_micro(micro)
|
111
|
+
micro_file = File.expand_path(MICRO_FILE)
|
112
|
+
lock_and_write(micro_file, micro.to_json)
|
113
|
+
end
|
114
|
+
|
115
|
+
def deep_merge(a, b)
|
116
|
+
merge = proc do |_, old, new|
|
117
|
+
if new.is_a?(Hash) and old.is_a?(Hash)
|
118
|
+
old.merge(new, &merge)
|
119
|
+
else
|
120
|
+
new
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
a.merge(b, &merge)
|
125
|
+
end
|
126
|
+
|
127
|
+
def clients
|
128
|
+
return @clients if @clients
|
129
|
+
|
130
|
+
stock = YAML.load_file(STOCK_CLIENTS)
|
131
|
+
clients = File.expand_path CLIENTS_FILE
|
132
|
+
if File.exists? clients
|
133
|
+
user = YAML.load_file(clients)
|
134
|
+
@clients = deep_merge(stock, user)
|
135
|
+
else
|
136
|
+
@clients = stock
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def lock_and_read(file)
|
141
|
+
File.open(file, File::RDONLY) {|f|
|
142
|
+
if defined? JRUBY_VERSION
|
143
|
+
f.flock(File::LOCK_SH)
|
144
|
+
else
|
145
|
+
f.flock(File::LOCK_EX)
|
146
|
+
end
|
147
|
+
contents = f.read
|
148
|
+
f.flock(File::LOCK_UN)
|
149
|
+
contents
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
def lock_and_write(file, contents)
|
154
|
+
File.open(file, File::RDWR | File::CREAT, 0600) {|f|
|
155
|
+
f.flock(File::LOCK_EX)
|
156
|
+
f.rewind
|
157
|
+
f.puts contents
|
158
|
+
f.flush
|
159
|
+
f.truncate(f.pos)
|
160
|
+
f.flock(File::LOCK_UN)
|
161
|
+
}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def initialize(work_dir = Dir.pwd)
|
166
|
+
@work_dir = work_dir
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,163 @@
|
|
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 = {
|
31
|
+
'hostname' => entry[:console_ip],
|
32
|
+
'port' => entry[:console_port]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def start_local_console(port, appname)
|
37
|
+
auth_info = console_credentials(appname)
|
38
|
+
display "Connecting to '#{appname}' console: ", false
|
39
|
+
prompt = console_login(auth_info, port)
|
40
|
+
display "OK".green
|
41
|
+
display "\n"
|
42
|
+
initialize_readline
|
43
|
+
run_console prompt
|
44
|
+
end
|
45
|
+
|
46
|
+
def console_login(auth_info, port)
|
47
|
+
if !auth_info["username"] || !auth_info["password"]
|
48
|
+
err "Unable to verify console credentials."
|
49
|
+
end
|
50
|
+
@telnet_client = telnet_client(port)
|
51
|
+
prompt = nil
|
52
|
+
err_msg = "Login attempt timed out."
|
53
|
+
5.times do
|
54
|
+
begin
|
55
|
+
results = @telnet_client.login("Name"=>auth_info["username"],
|
56
|
+
"Password"=>auth_info["password"])
|
57
|
+
lines = results.sub("Login: Password: ", "").split("\n")
|
58
|
+
last_line = lines.pop
|
59
|
+
if last_line =~ /[$%#>] \z/n
|
60
|
+
prompt = last_line
|
61
|
+
elsif last_line =~ /Login failed/
|
62
|
+
err_msg = last_line
|
63
|
+
end
|
64
|
+
break
|
65
|
+
rescue TimeoutError
|
66
|
+
sleep 1
|
67
|
+
rescue EOFError
|
68
|
+
#This may happen if we login right after app starts
|
69
|
+
close_console
|
70
|
+
sleep 5
|
71
|
+
@telnet_client = telnet_client(port)
|
72
|
+
end
|
73
|
+
display ".", false
|
74
|
+
end
|
75
|
+
unless prompt
|
76
|
+
close_console
|
77
|
+
err err_msg
|
78
|
+
end
|
79
|
+
prompt
|
80
|
+
end
|
81
|
+
|
82
|
+
def send_console_command(cmd)
|
83
|
+
results = @telnet_client.cmd(cmd)
|
84
|
+
results.split("\n")
|
85
|
+
end
|
86
|
+
|
87
|
+
def console_credentials(appname)
|
88
|
+
content = client.app_files(appname, '/app/cf-rails-console/.consoleaccess', '0')
|
89
|
+
YAML.load(content)
|
90
|
+
end
|
91
|
+
|
92
|
+
def close_console
|
93
|
+
@telnet_client.close
|
94
|
+
end
|
95
|
+
|
96
|
+
def console_tab_completion_data(cmd)
|
97
|
+
begin
|
98
|
+
results = @telnet_client.cmd("String"=> cmd + "\t", "Match"=>/\S*\n$/, "Timeout"=>10)
|
99
|
+
results.chomp.split(",")
|
100
|
+
rescue TimeoutError
|
101
|
+
[] #Just return empty results if timeout occurred on tab completion
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
def telnet_client(port)
|
107
|
+
Net::Telnet.new({"Port"=>port, "Prompt"=>/[$%#>] \z|Login failed/n, "Timeout"=>30, "FailEOF"=>true})
|
108
|
+
end
|
109
|
+
|
110
|
+
def readline_with_history(prompt)
|
111
|
+
line = Readline::readline(prompt)
|
112
|
+
return nil if line == nil || line == 'quit' || line == 'exit'
|
113
|
+
Readline::HISTORY.push(line) if not line =~ /^\s*$/ and Readline::HISTORY.to_a[-1] != line
|
114
|
+
line
|
115
|
+
end
|
116
|
+
|
117
|
+
def run_console(prompt)
|
118
|
+
prev = trap("INT") { |x| exit_console; prev.call(x); exit }
|
119
|
+
prev = trap("TERM") { |x| exit_console; prev.call(x); exit }
|
120
|
+
loop do
|
121
|
+
cmd = readline_with_history(prompt)
|
122
|
+
if(cmd == nil)
|
123
|
+
exit_console
|
124
|
+
break
|
125
|
+
end
|
126
|
+
prompt = send_console_command_display_results(cmd, prompt)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def exit_console
|
131
|
+
#TimeoutError expected, as exit doesn't return anything
|
132
|
+
@telnet_client.cmd("String"=>"exit","Timeout"=>1) rescue TimeoutError
|
133
|
+
close_console
|
134
|
+
end
|
135
|
+
|
136
|
+
def send_console_command_display_results(cmd, prompt)
|
137
|
+
begin
|
138
|
+
lines = send_console_command cmd
|
139
|
+
#Assumes the last line is a prompt
|
140
|
+
prompt = lines.pop
|
141
|
+
lines.each {|line| display line if line != cmd}
|
142
|
+
rescue TimeoutError
|
143
|
+
display "Timed out sending command to server.".red
|
144
|
+
rescue EOFError
|
145
|
+
err "The console connection has been terminated. Perhaps the app was stopped or deleted?"
|
146
|
+
end
|
147
|
+
prompt
|
148
|
+
end
|
149
|
+
|
150
|
+
def initialize_readline
|
151
|
+
if Readline.respond_to?("basic_word_break_characters=")
|
152
|
+
Readline.basic_word_break_characters= " \t\n`><=;|&{("
|
153
|
+
end
|
154
|
+
Readline.completion_append_character = nil
|
155
|
+
#Assumes that sending a String ending with tab will return a non-empty
|
156
|
+
#String of comma-separated completion options, terminated by a new line
|
157
|
+
#For example, "app.\t" might result in "to_s,nil?,etc\n"
|
158
|
+
Readline.completion_proc = proc {|s|
|
159
|
+
console_tab_completion_data s
|
160
|
+
}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|