capistrano 2.2.0 → 2.3.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 +29 -0
- data/README +4 -7
- data/bin/cap +0 -0
- data/bin/capify +0 -0
- data/lib/capistrano/command.rb +32 -38
- data/lib/capistrano/configuration/actions/file_transfer.rb +21 -13
- data/lib/capistrano/configuration/actions/invocation.rb +1 -1
- data/lib/capistrano/configuration/connections.rb +30 -20
- data/lib/capistrano/errors.rb +1 -1
- data/lib/capistrano/processable.rb +53 -0
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +6 -0
- data/lib/capistrano/recipes/deploy/scm/git.rb +26 -11
- data/lib/capistrano/recipes/deploy/scm/none.rb +44 -0
- data/lib/capistrano/recipes/deploy/strategy/base.rb +6 -0
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +74 -3
- data/lib/capistrano/recipes/deploy.rb +15 -13
- data/lib/capistrano/role.rb +0 -14
- data/lib/capistrano/server_definition.rb +5 -0
- data/lib/capistrano/shell.rb +21 -17
- data/lib/capistrano/ssh.rb +24 -58
- data/lib/capistrano/transfer.rb +216 -0
- data/lib/capistrano/version.rb +1 -1
- data/test/cli/execute_test.rb +1 -1
- data/test/cli/help_test.rb +1 -1
- data/test/cli/options_test.rb +1 -1
- data/test/cli/ui_test.rb +1 -1
- data/test/cli_test.rb +1 -1
- data/test/command_test.rb +31 -51
- data/test/configuration/actions/file_transfer_test.rb +21 -19
- data/test/configuration/actions/inspect_test.rb +1 -1
- data/test/configuration/actions/invocation_test.rb +6 -6
- data/test/configuration/callbacks_test.rb +1 -1
- data/test/configuration/connections_test.rb +11 -12
- data/test/configuration/execution_test.rb +1 -1
- data/test/configuration/loading_test.rb +1 -1
- data/test/configuration/namespace_dsl_test.rb +1 -1
- data/test/configuration/roles_test.rb +1 -1
- data/test/configuration/servers_test.rb +1 -1
- data/test/configuration/variables_test.rb +1 -1
- data/test/configuration_test.rb +1 -1
- data/test/deploy/scm/accurev_test.rb +1 -1
- data/test/deploy/scm/base_test.rb +1 -1
- data/test/deploy/scm/git_test.rb +10 -6
- data/test/deploy/scm/mercurial_test.rb +1 -1
- data/test/deploy/strategy/copy_test.rb +120 -27
- data/test/extensions_test.rb +1 -1
- data/test/logger_test.rb +1 -1
- data/test/server_definition_test.rb +1 -1
- data/test/shell_test.rb +27 -1
- data/test/ssh_test.rb +27 -21
- data/test/task_definition_test.rb +1 -1
- data/test/transfer_test.rb +160 -0
- data/test/utils.rb +30 -34
- data/test/version_test.rb +1 -1
- metadata +26 -14
- data/lib/capistrano/gateway.rb +0 -131
- data/lib/capistrano/upload.rb +0 -152
- data/test/gateway_test.rb +0 -167
- data/test/upload_test.rb +0 -131
@@ -89,6 +89,15 @@ ensure
|
|
89
89
|
ENV[name] = saved
|
90
90
|
end
|
91
91
|
|
92
|
+
# If :run_method is :sudo (or :use_sudo is true), this executes the given command
|
93
|
+
# via +sudo+. Otherwise is uses +run+. Further, if sudo is being used and :runner
|
94
|
+
# is set, the command will be executed as the user given by :runner.
|
95
|
+
def try_sudo(command)
|
96
|
+
as = fetch(:runner, "app")
|
97
|
+
via = fetch(:run_method, :sudo)
|
98
|
+
invoke_command(command, :via => via, :as => as)
|
99
|
+
end
|
100
|
+
|
92
101
|
# =========================================================================
|
93
102
|
# These are the tasks that are available to help with deploying web apps,
|
94
103
|
# and specifically, Rails applications. You can have cap give you a summary
|
@@ -122,7 +131,7 @@ namespace :deploy do
|
|
122
131
|
task :setup, :except => { :no_release => true } do
|
123
132
|
dirs = [deploy_to, releases_path, shared_path]
|
124
133
|
dirs += %w(system log pids).map { |d| File.join(shared_path, d) }
|
125
|
-
|
134
|
+
try_sudo "umask 02 && mkdir -p #{dirs.join(' ')}"
|
126
135
|
end
|
127
136
|
|
128
137
|
desc <<-DESC
|
@@ -246,9 +255,7 @@ namespace :deploy do
|
|
246
255
|
set :use_sudo, false
|
247
256
|
DESC
|
248
257
|
task :restart, :roles => :app, :except => { :no_release => true } do
|
249
|
-
|
250
|
-
via = fetch(:run_method, :sudo)
|
251
|
-
invoke_command "#{current_path}/script/process/reaper", :via => via, :as => as
|
258
|
+
try_sudo "#{current_path}/script/process/reaper"
|
252
259
|
end
|
253
260
|
|
254
261
|
desc <<-DESC
|
@@ -337,7 +344,7 @@ namespace :deploy do
|
|
337
344
|
directories = (releases - releases.last(count)).map { |release|
|
338
345
|
File.join(releases_path, release) }.join(" ")
|
339
346
|
|
340
|
-
|
347
|
+
try_sudo "rm -rf #{directories}"
|
341
348
|
end
|
342
349
|
end
|
343
350
|
|
@@ -406,9 +413,7 @@ namespace :deploy do
|
|
406
413
|
the :use_sudo variable to false.
|
407
414
|
DESC
|
408
415
|
task :start, :roles => :app do
|
409
|
-
|
410
|
-
via = fetch(:run_method, :sudo)
|
411
|
-
invoke_command "sh -c 'cd #{current_path} && nohup script/spin'", :via => via, :as => as
|
416
|
+
try_sudo "sh -c 'cd #{current_path} && nohup script/spin'"
|
412
417
|
end
|
413
418
|
|
414
419
|
desc <<-DESC
|
@@ -423,11 +428,8 @@ namespace :deploy do
|
|
423
428
|
the :use_sudo variable to false.
|
424
429
|
DESC
|
425
430
|
task :stop, :roles => :app do
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
invoke_command "if [ -f #{current_path}/tmp/pids/dispatch.spawner.pid ]; then #{current_path}/script/process/reaper -a kill -r dispatch.spawner.pid; fi", :via => via, :as => as
|
430
|
-
invoke_command "#{current_path}/script/process/reaper -a kill", :via => via, :as => as
|
431
|
+
try_sudo "if [ -f #{current_path}/tmp/pids/dispatch.spawner.pid ]; then #{current_path}/script/process/reaper -a kill -r dispatch.spawner.pid; fi"
|
432
|
+
try_sudo "#{current_path}/script/process/reaper -a kill"
|
431
433
|
end
|
432
434
|
|
433
435
|
namespace :pending do
|
data/lib/capistrano/role.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
module Capistrano
|
3
2
|
class Role
|
4
3
|
include Enumerable
|
@@ -34,27 +33,14 @@ module Capistrano
|
|
34
33
|
servers.empty?
|
35
34
|
end
|
36
35
|
|
37
|
-
# Resets the cache, so that proc values may be recalculated.
|
38
|
-
# There should be a command in Configuration::Roles to do this,
|
39
|
-
# but I haven't needed it yet, and I'm not sure yet
|
40
|
-
# what to call that command. Suggestions?
|
41
|
-
def reset!
|
42
|
-
@dynamic_servers.each { |item| item.reset! }
|
43
|
-
end
|
44
|
-
|
45
|
-
# Clears everything. I still thing this should be 'clear!', but that's not
|
46
|
-
# the way Array does it.
|
47
36
|
def clear
|
48
37
|
@dynamic_servers.clear
|
49
38
|
@static_servers.clear
|
50
39
|
end
|
51
40
|
|
52
|
-
# Mostly for documentation purposes. Doesn't seem to do anything.
|
53
41
|
protected
|
54
42
|
|
55
43
|
# This is the combination of a block, a hash of options, and a cached value.
|
56
|
-
# It is protected because it is an implementation detail -- the original
|
57
|
-
# implementation was two lists (blocks and cached results of calling them).
|
58
44
|
class DynamicServerList
|
59
45
|
def initialize (block, options)
|
60
46
|
@block = block
|
@@ -7,6 +7,11 @@ module Capistrano
|
|
7
7
|
attr_reader :port
|
8
8
|
attr_reader :options
|
9
9
|
|
10
|
+
# The default user name to use when a user name is not explicitly provided
|
11
|
+
def self.default_user
|
12
|
+
ENV['USER'] || ENV['USERNAME'] || "not-specified"
|
13
|
+
end
|
14
|
+
|
10
15
|
def initialize(string, options={})
|
11
16
|
@user, @host, @port = string.match(/^(?:([^;,:=]+)@|)(.*?)(?::(\d+)|)$/)[1,3]
|
12
17
|
|
data/lib/capistrano/shell.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'capistrano/processable'
|
2
3
|
|
3
4
|
module Capistrano
|
4
5
|
# The Capistrano::Shell class is the guts of the "shell" task. It implements
|
@@ -6,6 +7,8 @@ module Capistrano
|
|
6
7
|
# commands. It makes for a GREAT way to monitor systems, and perform quick
|
7
8
|
# maintenance on one or more machines.
|
8
9
|
class Shell
|
10
|
+
include Processable
|
11
|
+
|
9
12
|
# A Readline replacement for platforms where readline is either
|
10
13
|
# unavailable, or has not been installed.
|
11
14
|
class ReadlineFallback #:nodoc:
|
@@ -142,11 +145,13 @@ HELP
|
|
142
145
|
# be invoked. Otherwise, it is executed as a command on all associated
|
143
146
|
# servers.
|
144
147
|
def exec(command)
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
148
|
+
@mutex.synchronize do
|
149
|
+
if command[0] == ?!
|
150
|
+
exec_tasks(command[1..-1].split)
|
151
|
+
else
|
152
|
+
servers = connect(configuration.current_task)
|
153
|
+
exec_command(command, servers)
|
154
|
+
end
|
150
155
|
end
|
151
156
|
ensure
|
152
157
|
STDOUT.flush
|
@@ -169,7 +174,8 @@ HELP
|
|
169
174
|
command = command.gsub(/\bsudo\b/, "sudo -p '#{configuration.sudo_prompt}'")
|
170
175
|
processor = configuration.sudo_behavior_callback(Configuration.default_io_proc)
|
171
176
|
sessions = servers.map { |server| configuration.sessions[server] }
|
172
|
-
|
177
|
+
options = configuration.add_default_command_options({})
|
178
|
+
cmd = Command.new(command, sessions, options.merge(:logger => configuration.logger), &processor)
|
173
179
|
previous = trap("INT") { cmd.stop! }
|
174
180
|
cmd.process!
|
175
181
|
rescue Capistrano::Error => error
|
@@ -196,17 +202,10 @@ HELP
|
|
196
202
|
|
197
203
|
@mutex = Mutex.new
|
198
204
|
@bgthread = Thread.new do
|
199
|
-
|
200
|
-
|
201
|
-
if ready.empty?
|
202
|
-
sleep 0.1
|
203
|
-
else
|
204
|
-
@mutex.synchronize do
|
205
|
-
ready.each { |session| session.connection.process(true) }
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
205
|
+
loop do
|
206
|
+
@mutex.synchronize { process_iteration(0.1) }
|
209
207
|
end
|
208
|
+
end
|
210
209
|
end
|
211
210
|
|
212
211
|
# Set the given option to +value+.
|
@@ -244,7 +243,7 @@ HELP
|
|
244
243
|
old_var, ENV[env_var] = ENV[env_var], (scope_value == "all" ? nil : scope_value) if env_var
|
245
244
|
if command
|
246
245
|
begin
|
247
|
-
|
246
|
+
exec(command)
|
248
247
|
ensure
|
249
248
|
ENV[env_var] = old_var if env_var
|
250
249
|
end
|
@@ -253,4 +252,9 @@ HELP
|
|
253
252
|
end
|
254
253
|
end
|
255
254
|
end
|
255
|
+
|
256
|
+
# All open sessions, needed to satisfy the Command::Processable include
|
257
|
+
def sessions
|
258
|
+
configuration.sessions.values
|
259
|
+
end
|
256
260
|
end
|
data/lib/capistrano/ssh.rb
CHANGED
@@ -1,61 +1,12 @@
|
|
1
1
|
begin
|
2
2
|
require 'rubygems'
|
3
|
-
gem 'net-ssh', "
|
3
|
+
gem 'net-ssh', ">= 1.99.1"
|
4
4
|
rescue LoadError, NameError
|
5
5
|
end
|
6
6
|
|
7
7
|
require 'net/ssh'
|
8
8
|
|
9
9
|
module Capistrano
|
10
|
-
unless ENV['SKIP_VERSION_CHECK']
|
11
|
-
require 'capistrano/version'
|
12
|
-
require 'net/ssh/version'
|
13
|
-
ssh_version = [Net::SSH::Version::MAJOR, Net::SSH::Version::MINOR, Net::SSH::Version::TINY]
|
14
|
-
if !Version.check(Version::SSH_REQUIRED, ssh_version)
|
15
|
-
raise "You have Net::SSH #{ssh_version.join(".")}, but you need at least #{Version::SSH_REQUIRED.join(".")}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# Now, Net::SSH is kind of silly, and tries to lazy-load everything. This
|
20
|
-
# wreaks havoc with the parallel connection trick that Capistrano wants to
|
21
|
-
# use, so we're going to do something hideously ugly here and force all the
|
22
|
-
# files that Net::SSH uses to load RIGHT NOW, rather than lazily.
|
23
|
-
|
24
|
-
net_ssh_dependencies = %w(connection/services connection/channel connection/driver
|
25
|
-
service/agentforward/services service/agentforward/driver
|
26
|
-
service/process/driver util/prompter
|
27
|
-
service/forward/services service/forward/driver service/forward/local-network-handler service/forward/remote-network-handler
|
28
|
-
service/shell/services service/shell/driver
|
29
|
-
lenient-host-key-verifier
|
30
|
-
transport/compress/services transport/compress/zlib-compressor transport/compress/none-compressor transport/compress/zlib-decompressor transport/compress/none-decompressor
|
31
|
-
transport/kex/services transport/kex/dh transport/kex/dh-gex
|
32
|
-
transport/ossl/services
|
33
|
-
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
|
34
|
-
transport/ossl/cipher-factory transport/ossl/hmac-factory transport/ossl/buffer-factory transport/ossl/key-factory transport/ossl/digest-factory
|
35
|
-
transport/identity-cipher transport/packet-stream transport/version-negotiator transport/algorithm-negotiator transport/session
|
36
|
-
userauth/methods/services userauth/methods/password userauth/methods/keyboard-interactive userauth/methods/publickey userauth/methods/hostbased
|
37
|
-
userauth/services userauth/agent userauth/userkeys userauth/driver
|
38
|
-
transport/services service/services
|
39
|
-
)
|
40
|
-
|
41
|
-
net_ssh_dependencies << "userauth/pageant" if File::ALT_SEPARATOR
|
42
|
-
net_ssh_dependencies.each do |path|
|
43
|
-
begin
|
44
|
-
require "net/ssh/#{path}"
|
45
|
-
rescue LoadError
|
46
|
-
# Ignore load errors from this, since some files are in the list which
|
47
|
-
# do not exist in different (supported) versions of Net::SSH. We know
|
48
|
-
# (by this point) that Net::SSH is installed, though, since we do a
|
49
|
-
# require 'net/ssh' at the very top of this file, and we know the
|
50
|
-
# installed version meets the minimum version requirements because of
|
51
|
-
# the version check, also at the top of this file. So, if we get a
|
52
|
-
# LoadError, it's simply because the file in question does not exist in
|
53
|
-
# the version of Net::SSH that is installed.
|
54
|
-
#
|
55
|
-
# Whew!
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
10
|
# A helper class for dealing with SSH connections.
|
60
11
|
class SSH
|
61
12
|
# Patch an accessor onto an SSH connection so that we can record the server
|
@@ -88,13 +39,30 @@ module Capistrano
|
|
88
39
|
# constructor. Values in +options+ are then merged into it, and any
|
89
40
|
# connection information in +server+ is added last, so that +server+ info
|
90
41
|
# takes precedence over +options+, which takes precendence over ssh_options.
|
91
|
-
def self.connect(server, options={}
|
42
|
+
def self.connect(server, options={})
|
43
|
+
connection_strategy(server, options) do |host, user, connection_options|
|
44
|
+
connection = Net::SSH.start(host, user, connection_options)
|
45
|
+
Server.apply_to(connection, server)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Abstracts the logic for establishing an SSH connection (which includes
|
50
|
+
# testing for connection failures and retrying with a password, and so forth,
|
51
|
+
# mostly made complicated because of the fact that some of these variables
|
52
|
+
# might be lazily evaluated and try to do something like prompt the user,
|
53
|
+
# which should only happen when absolutely necessary.
|
54
|
+
#
|
55
|
+
# This will yield the hostname, username, and a hash of connection options
|
56
|
+
# to the given block, which should return a new connection.
|
57
|
+
def self.connection_strategy(server, options={}, &block)
|
92
58
|
methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
|
93
59
|
password_value = nil
|
94
|
-
|
95
|
-
ssh_options
|
96
|
-
|
97
|
-
ssh_options[:port]
|
60
|
+
|
61
|
+
ssh_options = (server.options[:ssh_options] || {}).merge(options[:ssh_options] || {})
|
62
|
+
user = server.user || options[:user] || ssh_options[:username] || ServerDefinition.default_user
|
63
|
+
ssh_options[:port] = server.port || options[:port] || ssh_options[:port] || DEFAULT_PORT
|
64
|
+
|
65
|
+
ssh_options.delete(:username)
|
98
66
|
|
99
67
|
begin
|
100
68
|
connection_options = ssh_options.merge(
|
@@ -102,9 +70,7 @@ module Capistrano
|
|
102
70
|
:auth_methods => ssh_options[:auth_methods] || methods.shift
|
103
71
|
)
|
104
72
|
|
105
|
-
|
106
|
-
Server.apply_to(connection, server)
|
107
|
-
|
73
|
+
yield server.host, user, connection_options
|
108
74
|
rescue Net::SSH::AuthenticationFailed
|
109
75
|
raise if methods.empty? || ssh_options[:auth_methods]
|
110
76
|
password_value = options[:password]
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'net/scp'
|
2
|
+
require 'net/sftp'
|
3
|
+
|
4
|
+
require 'capistrano/processable'
|
5
|
+
|
6
|
+
module Capistrano
|
7
|
+
class Transfer
|
8
|
+
include Processable
|
9
|
+
|
10
|
+
def self.process(direction, from, to, sessions, options={}, &block)
|
11
|
+
new(direction, from, to, sessions, options, &block).process!
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :sessions
|
15
|
+
attr_reader :options
|
16
|
+
attr_reader :callback
|
17
|
+
|
18
|
+
attr_reader :transport
|
19
|
+
attr_reader :direction
|
20
|
+
attr_reader :from
|
21
|
+
attr_reader :to
|
22
|
+
|
23
|
+
attr_reader :logger
|
24
|
+
attr_reader :transfers
|
25
|
+
|
26
|
+
def initialize(direction, from, to, sessions, options={}, &block)
|
27
|
+
@direction = direction
|
28
|
+
@from = from
|
29
|
+
@to = to
|
30
|
+
@sessions = sessions
|
31
|
+
@options = options
|
32
|
+
@callback = callback
|
33
|
+
|
34
|
+
@transport = options.fetch(:via, :sftp)
|
35
|
+
@logger = options.delete(:logger)
|
36
|
+
|
37
|
+
@session_map = {}
|
38
|
+
|
39
|
+
prepare_transfers
|
40
|
+
end
|
41
|
+
|
42
|
+
def process!
|
43
|
+
loop do
|
44
|
+
begin
|
45
|
+
break unless process_iteration { active? }
|
46
|
+
rescue Exception => error
|
47
|
+
if error.respond_to?(:session)
|
48
|
+
handle_error(error)
|
49
|
+
else
|
50
|
+
raise
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
failed = transfers.select { |txfr| txfr[:failed] }
|
56
|
+
if failed.any?
|
57
|
+
hosts = failed.map { |txfr| txfr[:server] }
|
58
|
+
errors = failed.map { |txfr| "#{txfr[:error]} (#{txfr[:error].message})" }.uniq.join(", ")
|
59
|
+
error = TransferError.new("#{operation} via #{transport} failed on #{hosts.join(',')}: #{errors}")
|
60
|
+
error.hosts = hosts
|
61
|
+
|
62
|
+
logger.important(error.message) if logger
|
63
|
+
raise error
|
64
|
+
end
|
65
|
+
|
66
|
+
logger.debug "#{transport} #{operation} complete" if logger
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def active?
|
71
|
+
transfers.any? { |transfer| transfer.active? }
|
72
|
+
end
|
73
|
+
|
74
|
+
def operation
|
75
|
+
"#{direction}load"
|
76
|
+
end
|
77
|
+
|
78
|
+
def sanitized_from
|
79
|
+
if from.responds_to?(:read)
|
80
|
+
"#<#{from.class}>"
|
81
|
+
else
|
82
|
+
from
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def sanitized_to
|
87
|
+
if to.responds_to?(:read)
|
88
|
+
"#<#{to.class}>"
|
89
|
+
else
|
90
|
+
to
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def session_map
|
97
|
+
@session_map
|
98
|
+
end
|
99
|
+
|
100
|
+
def prepare_transfers
|
101
|
+
logger.info "#{transport} #{operation} #{from} -> #{to}" if logger
|
102
|
+
|
103
|
+
@transfers = sessions.map do |session|
|
104
|
+
session_from = normalize(from, session)
|
105
|
+
session_to = normalize(to, session)
|
106
|
+
|
107
|
+
session_map[session] = case transport
|
108
|
+
when :sftp
|
109
|
+
prepare_sftp_transfer(session_from, session_to, session)
|
110
|
+
when :scp
|
111
|
+
prepare_scp_transfer(session_from, session_to, session)
|
112
|
+
else
|
113
|
+
raise ArgumentError, "unsupported transport type: #{transport.inspect}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def prepare_scp_transfer(from, to, session)
|
119
|
+
real_callback = callback || Proc.new do |channel, name, sent, total|
|
120
|
+
logger.trace "[#{channel[:host]}] #{name}" if logger && sent == 0
|
121
|
+
end
|
122
|
+
|
123
|
+
channel = case direction
|
124
|
+
when :up
|
125
|
+
session.scp.upload(from, to, options, &real_callback)
|
126
|
+
when :down
|
127
|
+
session.scp.download(from, to, options, &real_callback)
|
128
|
+
else
|
129
|
+
raise ArgumentError, "unsupported transfer direction: #{direction.inspect}"
|
130
|
+
end
|
131
|
+
|
132
|
+
channel[:server] = session.xserver
|
133
|
+
channel[:host] = session.xserver.host
|
134
|
+
|
135
|
+
return channel
|
136
|
+
end
|
137
|
+
|
138
|
+
class SFTPTransferWrapper
|
139
|
+
attr_reader :operation
|
140
|
+
|
141
|
+
def initialize(session, &callback)
|
142
|
+
session.sftp(false).connect do |sftp|
|
143
|
+
@operation = callback.call(sftp)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def active?
|
148
|
+
@operation.nil? || @operation.active?
|
149
|
+
end
|
150
|
+
|
151
|
+
def [](key)
|
152
|
+
@operation[key]
|
153
|
+
end
|
154
|
+
|
155
|
+
def []=(key, value)
|
156
|
+
@operation[key] = value
|
157
|
+
end
|
158
|
+
|
159
|
+
def abort!
|
160
|
+
@operation.abort!
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def prepare_sftp_transfer(from, to, session)
|
165
|
+
SFTPTransferWrapper.new(session) do |sftp|
|
166
|
+
real_callback = Proc.new do |event, op, *args|
|
167
|
+
if callback
|
168
|
+
callback.call(event, op, *args)
|
169
|
+
elsif event == :open
|
170
|
+
logger.trace "[#{op[:host]}] #{args[0].remote}"
|
171
|
+
elsif event == :finish
|
172
|
+
logger.trace "[#{op[:host]}] done"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
opts = options.dup
|
177
|
+
opts[:properties] = (opts[:properties] || {}).merge(
|
178
|
+
:server => session.xserver,
|
179
|
+
:host => session.xserver.host)
|
180
|
+
|
181
|
+
case direction
|
182
|
+
when :up
|
183
|
+
sftp.upload(from, to, opts, &real_callback)
|
184
|
+
when :down
|
185
|
+
sftp.download(from, to, opts, &real_callback)
|
186
|
+
else
|
187
|
+
raise ArgumentError, "unsupported transfer direction: #{direction.inspect}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def normalize(argument, session)
|
193
|
+
if argument.is_a?(String)
|
194
|
+
argument.gsub(/\$CAPISTRANO:HOST\$/, session.xserver.host)
|
195
|
+
elsif argument.respond_to?(:read)
|
196
|
+
pos = argument.pos
|
197
|
+
clone = StringIO.new(argument.read)
|
198
|
+
clone.pos = argument.pos = pos
|
199
|
+
clone
|
200
|
+
else
|
201
|
+
argument
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def handle_error(error)
|
206
|
+
transfer = session_map[error.session]
|
207
|
+
transfer[:error] = error
|
208
|
+
transfer[:failed] = true
|
209
|
+
|
210
|
+
case transport
|
211
|
+
when :sftp then transfer.abort!
|
212
|
+
when :scp then transfer.close
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
data/lib/capistrano/version.rb
CHANGED
data/test/cli/execute_test.rb
CHANGED
data/test/cli/help_test.rb
CHANGED
data/test/cli/options_test.rb
CHANGED
data/test/cli/ui_test.rb
CHANGED
data/test/cli_test.rb
CHANGED