capistrano 1.4.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +140 -4
- data/MIT-LICENSE +1 -1
- data/README +22 -14
- data/bin/cap +1 -8
- data/bin/capify +77 -0
- data/examples/sample.rb +10 -109
- data/lib/capistrano.rb +1 -0
- data/lib/capistrano/callback.rb +41 -0
- data/lib/capistrano/cli.rb +17 -317
- data/lib/capistrano/cli/execute.rb +82 -0
- data/lib/capistrano/cli/help.rb +102 -0
- data/lib/capistrano/cli/help.txt +53 -0
- data/lib/capistrano/cli/options.rb +183 -0
- data/lib/capistrano/cli/ui.rb +28 -0
- data/lib/capistrano/command.rb +62 -29
- data/lib/capistrano/configuration.rb +25 -226
- data/lib/capistrano/configuration/actions/file_transfer.rb +35 -0
- data/lib/capistrano/configuration/actions/inspect.rb +46 -0
- data/lib/capistrano/configuration/actions/invocation.rb +127 -0
- data/lib/capistrano/configuration/callbacks.rb +148 -0
- data/lib/capistrano/configuration/connections.rb +159 -0
- data/lib/capistrano/configuration/execution.rb +126 -0
- data/lib/capistrano/configuration/loading.rb +112 -0
- data/lib/capistrano/configuration/namespaces.rb +190 -0
- data/lib/capistrano/configuration/roles.rb +51 -0
- data/lib/capistrano/configuration/servers.rb +75 -0
- data/lib/capistrano/configuration/variables.rb +127 -0
- data/lib/capistrano/errors.rb +15 -0
- data/lib/capistrano/extensions.rb +27 -8
- data/lib/capistrano/gateway.rb +54 -29
- data/lib/capistrano/logger.rb +11 -11
- data/lib/capistrano/recipes/compat.rb +32 -0
- data/lib/capistrano/recipes/deploy.rb +483 -0
- data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
- data/lib/capistrano/recipes/deploy/local_dependency.rb +46 -0
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +65 -0
- data/lib/capistrano/recipes/deploy/scm.rb +19 -0
- data/lib/capistrano/recipes/deploy/scm/base.rb +180 -0
- data/lib/capistrano/recipes/deploy/scm/bzr.rb +86 -0
- data/lib/capistrano/recipes/deploy/scm/cvs.rb +151 -0
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
- data/lib/capistrano/recipes/deploy/scm/mercurial.rb +129 -0
- data/lib/capistrano/recipes/deploy/scm/perforce.rb +126 -0
- data/lib/capistrano/recipes/deploy/scm/subversion.rb +103 -0
- data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
- data/lib/capistrano/recipes/deploy/strategy/base.rb +64 -0
- data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +143 -0
- data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
- data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
- data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +47 -0
- data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
- data/lib/capistrano/recipes/standard.rb +26 -276
- data/lib/capistrano/recipes/templates/maintenance.rhtml +1 -1
- data/lib/capistrano/recipes/upgrade.rb +33 -0
- data/lib/capistrano/server_definition.rb +51 -0
- data/lib/capistrano/shell.rb +125 -81
- data/lib/capistrano/ssh.rb +80 -36
- data/lib/capistrano/task_definition.rb +69 -0
- data/lib/capistrano/upload.rb +146 -0
- data/lib/capistrano/version.rb +13 -17
- data/test/cli/execute_test.rb +132 -0
- data/test/cli/help_test.rb +139 -0
- data/test/cli/options_test.rb +226 -0
- data/test/cli/ui_test.rb +28 -0
- data/test/cli_test.rb +17 -0
- data/test/command_test.rb +284 -25
- data/test/configuration/actions/file_transfer_test.rb +40 -0
- data/test/configuration/actions/inspect_test.rb +62 -0
- data/test/configuration/actions/invocation_test.rb +195 -0
- data/test/configuration/callbacks_test.rb +206 -0
- data/test/configuration/connections_test.rb +288 -0
- data/test/configuration/execution_test.rb +159 -0
- data/test/configuration/loading_test.rb +119 -0
- data/test/configuration/namespace_dsl_test.rb +283 -0
- data/test/configuration/roles_test.rb +47 -0
- data/test/configuration/servers_test.rb +90 -0
- data/test/configuration/variables_test.rb +180 -0
- data/test/configuration_test.rb +60 -212
- data/test/deploy/scm/base_test.rb +55 -0
- data/test/deploy/strategy/copy_test.rb +146 -0
- data/test/extensions_test.rb +69 -0
- data/test/fixtures/cli_integration.rb +5 -0
- data/test/fixtures/custom.rb +2 -2
- data/test/gateway_test.rb +167 -0
- data/test/logger_test.rb +123 -0
- data/test/server_definition_test.rb +108 -0
- data/test/shell_test.rb +64 -0
- data/test/ssh_test.rb +67 -154
- data/test/task_definition_test.rb +101 -0
- data/test/upload_test.rb +131 -0
- data/test/utils.rb +31 -39
- data/test/version_test.rb +24 -0
- metadata +145 -98
- data/THANKS +0 -4
- data/lib/capistrano/actor.rb +0 -567
- data/lib/capistrano/generators/rails/deployment/deployment_generator.rb +0 -25
- data/lib/capistrano/generators/rails/deployment/templates/capistrano.rake +0 -49
- data/lib/capistrano/generators/rails/deployment/templates/deploy.rb +0 -122
- data/lib/capistrano/generators/rails/loader.rb +0 -20
- data/lib/capistrano/scm/base.rb +0 -61
- data/lib/capistrano/scm/baz.rb +0 -118
- data/lib/capistrano/scm/bzr.rb +0 -70
- data/lib/capistrano/scm/cvs.rb +0 -129
- data/lib/capistrano/scm/darcs.rb +0 -27
- data/lib/capistrano/scm/mercurial.rb +0 -83
- data/lib/capistrano/scm/perforce.rb +0 -139
- data/lib/capistrano/scm/subversion.rb +0 -128
- data/lib/capistrano/transfer.rb +0 -97
- data/lib/capistrano/utils.rb +0 -26
- data/test/actor_test.rb +0 -402
- data/test/scm/cvs_test.rb +0 -196
- data/test/scm/subversion_test.rb +0 -145
@@ -0,0 +1,33 @@
|
|
1
|
+
# Tasks to aid the migration of an established Capistrano 1.x installation to
|
2
|
+
# Capistrano 2.x.
|
3
|
+
|
4
|
+
namespace :upgrade do
|
5
|
+
desc <<-DESC
|
6
|
+
Migrate from the revisions log to REVISION. Capistrano 1.x recorded each \
|
7
|
+
deployment to a revisions.log file. Capistrano 2.x is cleaner, and just \
|
8
|
+
puts a REVISION file in the root of the deployed revision. This task \
|
9
|
+
migrates from the revisions.log used in Capistrano 1.x, to the REVISION \
|
10
|
+
tag file used in Capistrano 2.x. It is non-destructive and may be safely \
|
11
|
+
run any number of times.
|
12
|
+
DESC
|
13
|
+
task :revisions do
|
14
|
+
revisions = capture("cat #{deploy_to}/revisions.log")
|
15
|
+
|
16
|
+
mapping = {}
|
17
|
+
revisions.each do |line|
|
18
|
+
revision, directory = line.chomp.split[-2,2]
|
19
|
+
mapping[directory] = revision
|
20
|
+
end
|
21
|
+
|
22
|
+
commands = mapping.keys.map do |directory|
|
23
|
+
"echo '.'; test -d #{directory} && echo '#{mapping[directory]}' > #{directory}/REVISION"
|
24
|
+
end
|
25
|
+
|
26
|
+
command = commands.join(";")
|
27
|
+
|
28
|
+
run "cd #{releases_path}; #{command}; true" do |ch, stream, out|
|
29
|
+
STDOUT.print(".")
|
30
|
+
STDOUT.flush
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Capistrano
|
2
|
+
class ServerDefinition
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
attr_reader :host
|
6
|
+
attr_reader :user
|
7
|
+
attr_reader :port
|
8
|
+
attr_reader :options
|
9
|
+
|
10
|
+
def initialize(string, options={})
|
11
|
+
@user, @host, @port = string.match(/^(?:([^;,:=]+)@|)(.*?)(?::(\d+)|)$/)[1,3]
|
12
|
+
|
13
|
+
@options = options.dup
|
14
|
+
user_opt, port_opt = @options.delete(:user), @options.delete(:port)
|
15
|
+
|
16
|
+
@user ||= user_opt
|
17
|
+
@port ||= port_opt
|
18
|
+
|
19
|
+
@port = @port.to_i if @port
|
20
|
+
end
|
21
|
+
|
22
|
+
def <=>(server)
|
23
|
+
[host, port, user] <=> [server.host, server.port, server.user]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Redefined, so that Array#uniq will work to remove duplicate server
|
27
|
+
# definitions, based solely on their host names.
|
28
|
+
def eql?(server)
|
29
|
+
host == server.host &&
|
30
|
+
user == server.user &&
|
31
|
+
port == server.port
|
32
|
+
end
|
33
|
+
|
34
|
+
alias :== :eql?
|
35
|
+
|
36
|
+
# Redefined, so that Array#uniq will work to remove duplicate server
|
37
|
+
# definitions, based on their connection information.
|
38
|
+
def hash
|
39
|
+
@hash ||= [host, user, port].hash
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
@to_s ||= begin
|
44
|
+
s = host
|
45
|
+
s = "#{user}@#{s}" if user
|
46
|
+
s = "#{s}:#{port}" if port && port != 22
|
47
|
+
s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/capistrano/shell.rb
CHANGED
@@ -1,23 +1,34 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
|
-
# The Capistrano::Shell class is the guts of the "shell" task. It implements
|
4
|
-
# an interactive REPL interface that users can employ to execute tasks and
|
5
|
-
# commands. It makes for a GREAT way to monitor systems, and perform quick
|
6
|
-
# maintenance on one or more machines.
|
7
|
-
|
8
3
|
module Capistrano
|
4
|
+
# The Capistrano::Shell class is the guts of the "shell" task. It implements
|
5
|
+
# an interactive REPL interface that users can employ to execute tasks and
|
6
|
+
# commands. It makes for a GREAT way to monitor systems, and perform quick
|
7
|
+
# maintenance on one or more machines.
|
9
8
|
class Shell
|
10
|
-
#
|
11
|
-
|
9
|
+
# A Readline replacement for platforms where readline is either
|
10
|
+
# unavailable, or has not been installed.
|
11
|
+
class ReadlineFallback #:nodoc:
|
12
|
+
HISTORY = []
|
13
|
+
|
14
|
+
def self.readline(prompt)
|
15
|
+
STDOUT.print(prompt)
|
16
|
+
STDOUT.flush
|
17
|
+
STDIN.gets
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# The configuration instance employed by this shell
|
22
|
+
attr_reader :configuration
|
12
23
|
|
13
24
|
# Instantiate a new shell and begin executing it immediately.
|
14
|
-
def self.run
|
15
|
-
new(
|
25
|
+
def self.run(config)
|
26
|
+
new(config).run!
|
16
27
|
end
|
17
28
|
|
18
29
|
# Instantiate a new shell
|
19
|
-
def initialize(
|
20
|
-
@
|
30
|
+
def initialize(config)
|
31
|
+
@configuration = config
|
21
32
|
end
|
22
33
|
|
23
34
|
# Start the shell running. This method will block until the shell
|
@@ -28,78 +39,88 @@ module Capistrano
|
|
28
39
|
puts <<-INTRO
|
29
40
|
====================================================================
|
30
41
|
Welcome to the interactive Capistrano shell! This is an experimental
|
31
|
-
feature, and is liable to change in future releases.
|
42
|
+
feature, and is liable to change in future releases. Type 'help' for
|
43
|
+
a summary of how to use the shell.
|
32
44
|
--------------------------------------------------------------------
|
33
45
|
INTRO
|
34
46
|
|
35
47
|
loop do
|
36
|
-
|
37
|
-
|
38
|
-
case command ? command.strip : command
|
39
|
-
when "" then next
|
40
|
-
when "help" then help
|
41
|
-
when nil, "quit", "exit" then
|
42
|
-
puts if command.nil?
|
43
|
-
puts "exiting"
|
44
|
-
break
|
45
|
-
when /^set -(\w)\s*(\S+)/
|
46
|
-
set_option($1, $2)
|
47
|
-
when /^(?:(with|on)\s*(\S+))?\s*(\S.*)?/i
|
48
|
-
process_command($1, $2, $3)
|
49
|
-
else
|
50
|
-
raise "eh?"
|
51
|
-
end
|
48
|
+
break if !read_and_execute
|
52
49
|
end
|
53
50
|
|
54
51
|
@bgthread.kill
|
55
52
|
end
|
56
53
|
|
54
|
+
def read_and_execute
|
55
|
+
command = read_line
|
56
|
+
|
57
|
+
case command
|
58
|
+
when "?", "help" then help
|
59
|
+
when "quit", "exit" then
|
60
|
+
puts "exiting"
|
61
|
+
return false
|
62
|
+
when /^set -(\w)\s*(\S+)/
|
63
|
+
set_option($1, $2)
|
64
|
+
when /^(?:(with|on)\s*(\S+))?\s*(\S.*)?/i
|
65
|
+
process_command($1, $2, $3)
|
66
|
+
else
|
67
|
+
raise "eh?"
|
68
|
+
end
|
69
|
+
|
70
|
+
return true
|
71
|
+
end
|
72
|
+
|
57
73
|
private
|
58
74
|
|
59
|
-
#
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
75
|
+
# Present the prompt and read a single line from the console. It also
|
76
|
+
# detects ^D and returns "exit" in that case. Adds the input to the
|
77
|
+
# history, unless the input is empty. Loops repeatedly until a non-empty
|
78
|
+
# line is input.
|
79
|
+
def read_line
|
80
|
+
loop do
|
81
|
+
command = reader.readline("cap> ")
|
82
|
+
|
83
|
+
if command.nil?
|
84
|
+
command = "exit"
|
85
|
+
puts(command)
|
86
|
+
else
|
87
|
+
command.strip!
|
88
|
+
end
|
89
|
+
|
90
|
+
unless command.empty?
|
91
|
+
reader::HISTORY << command
|
92
|
+
return command
|
93
|
+
end
|
66
94
|
end
|
67
95
|
end
|
68
96
|
|
69
97
|
# Display a verbose help message.
|
70
98
|
def help
|
71
99
|
puts <<-HELP
|
72
|
-
|
73
|
-
|
74
|
-
|
100
|
+
--- HELP! ---------------------------------------------------
|
101
|
+
"Get me out of this thing. I just want to quit."
|
102
|
+
-> Easy enough. Just type "exit", or "quit". Or press ctrl-D.
|
75
103
|
|
76
|
-
|
104
|
+
"I want to execute a command on all servers."
|
105
|
+
-> Just type the command, and press enter. It will be passed,
|
106
|
+
verbatim, to all defined servers.
|
77
107
|
|
78
|
-
|
108
|
+
"What if I only want it to execute on a subset of them?"
|
109
|
+
-> No problem, just specify the list of servers, separated by
|
110
|
+
commas, before the command, with the `on' keyword:
|
79
111
|
|
80
|
-
|
81
|
-
Note that if you specify more than one host name, they must be comma-
|
82
|
-
delimited, with NO SPACES between them.
|
112
|
+
cap> on app1.foo.com,app2.foo.com echo ping
|
83
113
|
|
84
|
-
|
114
|
+
"Nice, but can I specify the servers by role?"
|
115
|
+
-> You sure can. Just use the `with' keyword, followed by the
|
116
|
+
comma-delimited list of role names:
|
85
117
|
|
86
|
-
|
118
|
+
cap> with app,db echo ping
|
87
119
|
|
88
|
-
|
120
|
+
"Can I execute a Capistrano task from within this shell?"
|
121
|
+
-> Yup. Just prefix the task with an exclamation mark:
|
89
122
|
|
90
|
-
|
91
|
-
|
92
|
-
cap> !deploy
|
93
|
-
|
94
|
-
You can specify multiple tasks to execute, separated by spaces:
|
95
|
-
|
96
|
-
cap> !update_code symlink
|
97
|
-
|
98
|
-
And, lastly, you can specify 'on' or 'with' with tasks:
|
99
|
-
|
100
|
-
cap> on app6.foo.com !setup
|
101
|
-
|
102
|
-
Enjoy!
|
123
|
+
cap> !deploy
|
103
124
|
HELP
|
104
125
|
end
|
105
126
|
|
@@ -107,11 +128,11 @@ HELP
|
|
107
128
|
# establish connections to them if necessary. Return the list of
|
108
129
|
# servers (names).
|
109
130
|
def connect(task)
|
110
|
-
servers =
|
111
|
-
needing_connections = servers -
|
131
|
+
servers = configuration.find_servers_for_task(task)
|
132
|
+
needing_connections = servers - configuration.sessions.keys
|
112
133
|
unless needing_connections.empty?
|
113
134
|
puts "[establishing connection(s) to #{needing_connections.join(', ')}]"
|
114
|
-
|
135
|
+
configuration.establish_connections_to(needing_connections)
|
115
136
|
end
|
116
137
|
servers
|
117
138
|
end
|
@@ -124,7 +145,7 @@ HELP
|
|
124
145
|
if command[0] == ?!
|
125
146
|
exec_tasks(command[1..-1].split)
|
126
147
|
else
|
127
|
-
servers = connect(
|
148
|
+
servers = connect(configuration.current_task)
|
128
149
|
exec_command(command, servers)
|
129
150
|
end
|
130
151
|
ensure
|
@@ -134,10 +155,13 @@ HELP
|
|
134
155
|
# Given an array of task names, invoke them in sequence.
|
135
156
|
def exec_tasks(list)
|
136
157
|
list.each do |task_name|
|
137
|
-
task = task_name
|
138
|
-
|
139
|
-
|
158
|
+
task = configuration.find_task(task_name)
|
159
|
+
raise Capistrano::NoSuchTaskError, "no such task `#{task_name}'" unless task
|
160
|
+
connect(task)
|
161
|
+
configuration.execute_task(task)
|
140
162
|
end
|
163
|
+
rescue Capistrano::NoMatchingServersError, Capistrano::NoSuchTaskError => error
|
164
|
+
warn "error: #{error.message}"
|
141
165
|
end
|
142
166
|
|
143
167
|
# Execute a command on the given list of servers.
|
@@ -147,36 +171,45 @@ HELP
|
|
147
171
|
out.each do |line|
|
148
172
|
if stream == :out
|
149
173
|
if out =~ /Password:\s*/i
|
150
|
-
ch.send_data "#{
|
174
|
+
ch.send_data "#{configuration[:password]}\n"
|
151
175
|
else
|
152
|
-
puts "[#{ch[:
|
176
|
+
puts "[#{ch[:server]}] #{line.chomp}"
|
153
177
|
end
|
154
178
|
elsif stream == :err
|
155
|
-
puts "[#{ch[:
|
179
|
+
puts "[#{ch[:server]} ERR] #{line.chomp}"
|
156
180
|
end
|
157
181
|
end
|
158
182
|
end
|
159
183
|
|
160
|
-
cmd = Command.new(servers, command, processor, {}, actor)
|
161
184
|
previous = trap("INT") { cmd.stop! }
|
162
|
-
|
185
|
+
sessions = servers.map { |server| configuration.sessions[server] }
|
186
|
+
Command.process(command, sessions, :logger => configuration.logger, &Capistrano::Configuration.default_io_proc)
|
187
|
+
rescue Capistrano::Error => error
|
188
|
+
warn "error: #{error.message}"
|
189
|
+
ensure
|
163
190
|
trap("INT", previous)
|
164
191
|
end
|
165
192
|
|
166
|
-
#
|
167
|
-
#
|
168
|
-
def
|
169
|
-
begin
|
193
|
+
# Return the object that will be used to query input from the console.
|
194
|
+
# The returned object will quack (more or less) like Readline.
|
195
|
+
def reader
|
196
|
+
@reader ||= begin
|
170
197
|
require 'readline'
|
171
|
-
|
198
|
+
Readline
|
172
199
|
rescue LoadError
|
173
|
-
|
200
|
+
ReadlineFallback
|
174
201
|
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Prepare every little thing for the shell. Starts the background
|
205
|
+
# thread and generally gets things ready for the REPL.
|
206
|
+
def setup
|
207
|
+
configuration.logger.level = Capistrano::Logger::INFO
|
175
208
|
|
176
209
|
@mutex = Mutex.new
|
177
210
|
@bgthread = Thread.new do
|
178
211
|
loop do
|
179
|
-
ready =
|
212
|
+
ready = configuration.sessions.values.select { |sess| sess.connection.reader_ready? }
|
180
213
|
if ready.empty?
|
181
214
|
sleep 0.1
|
182
215
|
else
|
@@ -193,9 +226,20 @@ HELP
|
|
193
226
|
case opt
|
194
227
|
when "v" then
|
195
228
|
puts "setting log verbosity to #{value.to_i}"
|
196
|
-
|
229
|
+
configuration.logger.level = value.to_i
|
230
|
+
when "o" then
|
231
|
+
case value
|
232
|
+
when "vi" then
|
233
|
+
puts "using vi edit mode"
|
234
|
+
reader.vi_editing_mode
|
235
|
+
when "emacs" then
|
236
|
+
puts "using emacs edit mode"
|
237
|
+
reader.emacs_editing_mode
|
238
|
+
else
|
239
|
+
puts "unknown -o option #{value.inspect}"
|
240
|
+
end
|
197
241
|
else
|
198
|
-
puts "unknown setting #{
|
242
|
+
puts "unknown setting #{opt.inspect}"
|
199
243
|
end
|
200
244
|
end
|
201
245
|
|
@@ -220,5 +264,5 @@ HELP
|
|
220
264
|
puts "scoping #{scope_type} #{scope_value}"
|
221
265
|
end
|
222
266
|
end
|
223
|
-
|
267
|
+
end
|
224
268
|
end
|
data/lib/capistrano/ssh.rb
CHANGED
@@ -1,9 +1,3 @@
|
|
1
|
-
begin
|
2
|
-
require 'rubygems'
|
3
|
-
gem 'net-ssh', '< 1.99.0'
|
4
|
-
rescue LoadError, NameError
|
5
|
-
end
|
6
|
-
|
7
1
|
require 'net/ssh'
|
8
2
|
|
9
3
|
module Capistrano
|
@@ -11,55 +5,105 @@ module Capistrano
|
|
11
5
|
require 'capistrano/version'
|
12
6
|
require 'net/ssh/version'
|
13
7
|
ssh_version = [Net::SSH::Version::MAJOR, Net::SSH::Version::MINOR, Net::SSH::Version::TINY]
|
14
|
-
if !Version.check(
|
15
|
-
raise "You have Net::SSH #{ssh_version.join(".")}, but you need
|
8
|
+
if !Version.check(Version::SSH_REQUIRED, ssh_version)
|
9
|
+
raise "You have Net::SSH #{ssh_version.join(".")}, but you need at least #{Version::SSH_REQUIRED.join(".")}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Now, Net::SSH is kind of silly, and tries to lazy-load everything. This
|
14
|
+
# wreaks havoc with the parallel connection trick that Capistrano wants to
|
15
|
+
# use, so we're going to do something hideously ugly here and force all the
|
16
|
+
# files that Net::SSH uses to load RIGHT NOW, rather than lazily.
|
17
|
+
|
18
|
+
net_ssh_dependencies = %w(connection/services connection/channel connection/driver
|
19
|
+
service/agentforward/services service/agentforward/driver
|
20
|
+
service/process/driver util/prompter
|
21
|
+
service/forward/services service/forward/driver service/forward/local-network-handler service/forward/remote-network-handler
|
22
|
+
service/shell/services service/shell/driver
|
23
|
+
lenient-host-key-verifier
|
24
|
+
transport/compress/services transport/compress/zlib-compressor transport/compress/none-compressor transport/compress/zlib-decompressor transport/compress/none-decompressor
|
25
|
+
transport/kex/services transport/kex/dh transport/kex/dh-gex
|
26
|
+
transport/ossl/services
|
27
|
+
transport/ossl/hmac/services transport/ossl/hmac/sha1 transport/ossl/hmac/sha1-96 transport/ossl/hmac/md5 transport/ossl/hmac/md5-96 transport/ossl/hmac/none
|
28
|
+
transport/ossl/cipher-factory transport/ossl/hmac-factory transport/ossl/buffer-factory transport/ossl/key-factory transport/ossl/digest-factory
|
29
|
+
transport/identity-cipher transport/packet-stream transport/version-negotiator transport/algorithm-negotiator transport/session
|
30
|
+
userauth/methods/services userauth/methods/password userauth/methods/keyboard-interactive userauth/methods/publickey userauth/methods/hostbased
|
31
|
+
userauth/services userauth/agent userauth/userkeys userauth/driver
|
32
|
+
transport/services service/services
|
33
|
+
)
|
34
|
+
|
35
|
+
net_ssh_dependencies << "userauth/pageant" if File::ALT_SEPARATOR
|
36
|
+
net_ssh_dependencies.each do |path|
|
37
|
+
begin
|
38
|
+
require "net/ssh/#{path}"
|
39
|
+
rescue LoadError
|
40
|
+
# Ignore load errors from this, since some files are in the list which
|
41
|
+
# do not exist in different (supported) versions of Net::SSH. We know
|
42
|
+
# (by this point) that Net::SSH is installed, though, since we do a
|
43
|
+
# require 'net/ssh' at the very top of this file, and we know the
|
44
|
+
# installed version meets the minimum version requirements because of
|
45
|
+
# the version check, also at the top of this file. So, if we get a
|
46
|
+
# LoadError, it's simply because the file in question does not exist in
|
47
|
+
# the version of Net::SSH that is installed.
|
48
|
+
#
|
49
|
+
# Whew!
|
16
50
|
end
|
17
51
|
end
|
18
52
|
|
19
53
|
# A helper class for dealing with SSH connections.
|
20
54
|
class SSH
|
55
|
+
# Patch an accessor onto an SSH connection so that we can record the server
|
56
|
+
# definition object that defines the connection. This is useful because
|
57
|
+
# the gateway returns connections whose "host" is 127.0.0.1, instead of
|
58
|
+
# the host on the other side of the tunnel.
|
59
|
+
module Server #:nodoc:
|
60
|
+
def self.apply_to(connection, server)
|
61
|
+
connection.extend(Server)
|
62
|
+
connection.xserver = server
|
63
|
+
connection
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_accessor :xserver
|
67
|
+
end
|
68
|
+
|
69
|
+
# The default port for SSH.
|
70
|
+
DEFAULT_PORT = 22
|
71
|
+
|
21
72
|
# An abstraction to make it possible to connect to the server via public key
|
22
73
|
# without prompting for the password. If the public key authentication fails
|
23
74
|
# this will fall back to password authentication.
|
24
75
|
#
|
76
|
+
# +server+ must be an instance of ServerDefinition.
|
77
|
+
#
|
25
78
|
# If a block is given, the new session is yielded to it, otherwise the new
|
26
79
|
# session is returned.
|
27
|
-
|
80
|
+
#
|
81
|
+
# If an :ssh_options key exists in +options+, it is passed to the Net::SSH
|
82
|
+
# constructor. Values in +options+ are then merged into it, and any
|
83
|
+
# connection information in +server+ is added last, so that +server+ info
|
84
|
+
# takes precedence over +options+, which takes precendence over ssh_options.
|
85
|
+
def self.connect(server, options={}, &block)
|
28
86
|
methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
|
29
87
|
password_value = nil
|
30
88
|
|
31
|
-
|
89
|
+
ssh_options = (options[:ssh_options] || {}).dup
|
90
|
+
ssh_options[:username] = server.user || options[:user] || ssh_options[:username]
|
91
|
+
ssh_options[:port] = server.port || options[:port] || ssh_options[:port] || DEFAULT_PORT
|
32
92
|
|
33
93
|
begin
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
Net::SSH.start(
|
94
|
+
connection_options = ssh_options.merge(
|
95
|
+
:password => password_value,
|
96
|
+
:auth_methods => ssh_options[:auth_methods] || methods.shift
|
97
|
+
)
|
98
|
+
|
99
|
+
connection = Net::SSH.start(server.host, connection_options, &block)
|
100
|
+
Server.apply_to(connection, server)
|
101
|
+
|
40
102
|
rescue Net::SSH::AuthenticationFailed
|
41
|
-
raise if methods.empty?
|
42
|
-
password_value =
|
103
|
+
raise if methods.empty? || ssh_options[:auth_methods]
|
104
|
+
password_value = options[:password]
|
43
105
|
retry
|
44
106
|
end
|
45
107
|
end
|
46
|
-
|
47
|
-
# This regex is used for its byproducts, the $1-3 match vars.
|
48
|
-
# This regex will always match the ssh hostname and if there
|
49
|
-
# is a username or port they will be matched as well. This
|
50
|
-
# allows us to set the username and ssh port right in the
|
51
|
-
# server string: "username@123.12.123.12:8088"
|
52
|
-
# This remains fully backwards compatible and can still be
|
53
|
-
# intermixed with the old way of doing things. usernames
|
54
|
-
# and ports will be used from the server string if present
|
55
|
-
# but they will fall back to the regular defaults when not
|
56
|
-
# present. Returns and array like:
|
57
|
-
# ['bob', 'demo.server.com', '8088']
|
58
|
-
# will always at least return the server:
|
59
|
-
# [nil, 'demo.server.com', nil]
|
60
|
-
def self.parse_server(server)
|
61
|
-
server =~ /^(?:([^;,:=]+)@|)(.*?)(?::(\d+)|)$/
|
62
|
-
[$1, $2, $3]
|
63
|
-
end
|
64
108
|
end
|
65
109
|
end
|