capistrano 1.4.2 → 2.0.0
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.
- 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
|