capistrano 2.0.0 → 2.15.2
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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +7 -0
- data/CHANGELOG +715 -18
- data/Gemfile +12 -0
- data/README.md +94 -0
- data/Rakefile +11 -0
- data/bin/cap +0 -0
- data/bin/capify +37 -22
- data/capistrano.gemspec +40 -0
- data/lib/capistrano/callback.rb +5 -1
- data/lib/capistrano/cli/execute.rb +10 -7
- data/lib/capistrano/cli/help.rb +39 -16
- data/lib/capistrano/cli/help.txt +44 -16
- data/lib/capistrano/cli/options.rb +71 -11
- data/lib/capistrano/cli/ui.rb +13 -1
- data/lib/capistrano/cli.rb +5 -5
- data/lib/capistrano/command.rb +215 -58
- data/lib/capistrano/configuration/actions/file_transfer.rb +29 -14
- data/lib/capistrano/configuration/actions/inspect.rb +3 -3
- data/lib/capistrano/configuration/actions/invocation.rb +212 -22
- data/lib/capistrano/configuration/alias_task.rb +26 -0
- data/lib/capistrano/configuration/callbacks.rb +26 -27
- data/lib/capistrano/configuration/connections.rb +130 -52
- data/lib/capistrano/configuration/execution.rb +34 -18
- data/lib/capistrano/configuration/loading.rb +91 -6
- data/lib/capistrano/configuration/log_formatters.rb +75 -0
- data/lib/capistrano/configuration/namespaces.rb +45 -12
- data/lib/capistrano/configuration/roles.rb +28 -2
- data/lib/capistrano/configuration/servers.rb +51 -10
- data/lib/capistrano/configuration/variables.rb +3 -3
- data/lib/capistrano/configuration.rb +20 -4
- data/lib/capistrano/errors.rb +12 -8
- data/lib/capistrano/ext/multistage.rb +62 -0
- data/lib/capistrano/ext/string.rb +5 -0
- data/lib/capistrano/extensions.rb +1 -1
- data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
- data/lib/capistrano/logger.rb +112 -5
- data/lib/capistrano/processable.rb +55 -0
- data/lib/capistrano/recipes/compat.rb +2 -2
- data/lib/capistrano/recipes/deploy/assets.rb +185 -0
- data/lib/capistrano/recipes/deploy/dependencies.rb +2 -2
- data/lib/capistrano/recipes/deploy/local_dependency.rb +10 -2
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +54 -2
- data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
- data/lib/capistrano/recipes/deploy/scm/base.rb +31 -11
- data/lib/capistrano/recipes/deploy/scm/bzr.rb +14 -14
- data/lib/capistrano/recipes/deploy/scm/cvs.rb +10 -8
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +12 -1
- data/lib/capistrano/recipes/deploy/scm/git.rb +293 -0
- data/lib/capistrano/recipes/deploy/scm/mercurial.rb +23 -15
- data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
- data/lib/capistrano/recipes/deploy/scm/perforce.rb +54 -28
- data/lib/capistrano/recipes/deploy/scm/subversion.rb +35 -17
- data/lib/capistrano/recipes/deploy/scm.rb +1 -1
- data/lib/capistrano/recipes/deploy/strategy/base.rb +32 -4
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +238 -43
- data/lib/capistrano/recipes/deploy/strategy/remote.rb +1 -1
- data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +11 -1
- data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
- data/lib/capistrano/recipes/deploy/strategy.rb +1 -1
- data/lib/capistrano/recipes/deploy.rb +265 -123
- data/lib/capistrano/recipes/standard.rb +1 -1
- data/lib/capistrano/role.rb +102 -0
- data/lib/capistrano/server_definition.rb +6 -1
- data/lib/capistrano/shell.rb +30 -33
- data/lib/capistrano/ssh.rb +46 -60
- data/lib/capistrano/task_definition.rb +16 -8
- data/lib/capistrano/transfer.rb +218 -0
- data/lib/capistrano/version.rb +6 -17
- data/lib/capistrano.rb +4 -1
- data/test/cli/execute_test.rb +3 -3
- data/test/cli/help_test.rb +33 -7
- data/test/cli/options_test.rb +109 -6
- data/test/cli/ui_test.rb +2 -2
- data/test/cli_test.rb +3 -3
- data/test/command_test.rb +144 -124
- data/test/configuration/actions/file_transfer_test.rb +41 -20
- data/test/configuration/actions/inspect_test.rb +21 -7
- data/test/configuration/actions/invocation_test.rb +91 -30
- data/test/configuration/alias_task_test.rb +118 -0
- data/test/configuration/callbacks_test.rb +41 -46
- data/test/configuration/connections_test.rb +187 -36
- data/test/configuration/execution_test.rb +18 -2
- data/test/configuration/loading_test.rb +17 -4
- data/test/configuration/namespace_dsl_test.rb +54 -5
- data/test/configuration/roles_test.rb +114 -4
- data/test/configuration/servers_test.rb +97 -4
- data/test/configuration/variables_test.rb +12 -2
- data/test/configuration_test.rb +9 -13
- data/test/deploy/local_dependency_test.rb +76 -0
- data/test/deploy/remote_dependency_test.rb +146 -0
- data/test/deploy/scm/accurev_test.rb +23 -0
- data/test/deploy/scm/base_test.rb +1 -1
- data/test/deploy/scm/bzr_test.rb +51 -0
- data/test/deploy/scm/darcs_test.rb +37 -0
- data/test/deploy/scm/git_test.rb +221 -0
- data/test/deploy/scm/mercurial_test.rb +134 -0
- data/test/deploy/scm/none_test.rb +35 -0
- data/test/deploy/scm/perforce_test.rb +23 -0
- data/test/deploy/scm/subversion_test.rb +40 -0
- data/test/deploy/strategy/copy_test.rb +240 -26
- data/test/extensions_test.rb +2 -2
- data/test/logger_formatting_test.rb +149 -0
- data/test/logger_test.rb +13 -2
- data/test/recipes_test.rb +25 -0
- data/test/role_test.rb +11 -0
- data/test/server_definition_test.rb +15 -2
- data/test/shell_test.rb +33 -1
- data/test/ssh_test.rb +40 -24
- data/test/task_definition_test.rb +18 -2
- data/test/transfer_test.rb +168 -0
- data/test/utils.rb +27 -33
- metadata +215 -102
- data/MIT-LICENSE +0 -20
- data/README +0 -43
- data/examples/sample.rb +0 -14
- data/lib/capistrano/gateway.rb +0 -131
- data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
- data/lib/capistrano/recipes/templates/maintenance.rhtml +0 -53
- data/lib/capistrano/recipes/upgrade.rb +0 -33
- data/lib/capistrano/upload.rb +0 -146
- data/test/gateway_test.rb +0 -167
- data/test/upload_test.rb +0 -131
- data/test/version_test.rb +0 -24
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Capistrano
|
|
2
|
+
class Role
|
|
3
|
+
include Enumerable
|
|
4
|
+
|
|
5
|
+
def initialize(*list)
|
|
6
|
+
@static_servers = []
|
|
7
|
+
@dynamic_servers = []
|
|
8
|
+
push(*list)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def each(&block)
|
|
12
|
+
servers.each &block
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def push(*list)
|
|
16
|
+
options = list.last.is_a?(Hash) ? list.pop : {}
|
|
17
|
+
list.each do |item|
|
|
18
|
+
if item.respond_to?(:call)
|
|
19
|
+
@dynamic_servers << DynamicServerList.new(item, options)
|
|
20
|
+
else
|
|
21
|
+
@static_servers << self.class.wrap_server(item, options)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
alias_method :<<, :push
|
|
26
|
+
|
|
27
|
+
def servers
|
|
28
|
+
@static_servers + dynamic_servers
|
|
29
|
+
end
|
|
30
|
+
alias_method :to_ary, :servers
|
|
31
|
+
|
|
32
|
+
def empty?
|
|
33
|
+
servers.empty?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def clear
|
|
37
|
+
@dynamic_servers.clear
|
|
38
|
+
@static_servers.clear
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def include?(server)
|
|
42
|
+
servers.include?(server)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
protected
|
|
46
|
+
|
|
47
|
+
# This is the combination of a block, a hash of options, and a cached value.
|
|
48
|
+
class DynamicServerList
|
|
49
|
+
def initialize (block, options)
|
|
50
|
+
@block = block
|
|
51
|
+
@options = options
|
|
52
|
+
@cached = []
|
|
53
|
+
@is_cached = false
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Convert to a list of ServerDefinitions
|
|
57
|
+
def to_ary
|
|
58
|
+
unless @is_cached
|
|
59
|
+
@cached = Role::wrap_list(@block.call(@options), @options)
|
|
60
|
+
@is_cached = true
|
|
61
|
+
end
|
|
62
|
+
@cached
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Clear the cached value
|
|
66
|
+
def reset!
|
|
67
|
+
@cached.clear
|
|
68
|
+
@is_cached = false
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Attribute reader for the cached results of executing the blocks in turn
|
|
73
|
+
def dynamic_servers
|
|
74
|
+
@dynamic_servers.inject([]) { |list, item| list.concat item }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Wraps a string in a ServerDefinition, if it isn't already.
|
|
78
|
+
# This and wrap_list should probably go in ServerDefinition in some form.
|
|
79
|
+
def self.wrap_server (item, options)
|
|
80
|
+
item.is_a?(ServerDefinition) ? item : ServerDefinition.new(item, options)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Turns a list, or something resembling a list, into a properly-formatted
|
|
84
|
+
# ServerDefinition list. Keep an eye on this one -- it's entirely too
|
|
85
|
+
# magical for its own good. In particular, if ServerDefinition ever inherits
|
|
86
|
+
# from Array, this will break.
|
|
87
|
+
def self.wrap_list (*list)
|
|
88
|
+
options = list.last.is_a?(Hash) ? list.pop : {}
|
|
89
|
+
if list.length == 1
|
|
90
|
+
if list.first.nil?
|
|
91
|
+
return []
|
|
92
|
+
elsif list.first.is_a?(Array)
|
|
93
|
+
list = list.first
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
options.merge! list.pop if list.last.is_a?(Hash)
|
|
97
|
+
list.map do |item|
|
|
98
|
+
self.wrap_server item, options
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -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
|
|
|
@@ -48,4 +53,4 @@ module Capistrano
|
|
|
48
53
|
end
|
|
49
54
|
end
|
|
50
55
|
end
|
|
51
|
-
end
|
|
56
|
+
end
|
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:
|
|
@@ -61,6 +64,9 @@ INTRO
|
|
|
61
64
|
return false
|
|
62
65
|
when /^set -(\w)\s*(\S+)/
|
|
63
66
|
set_option($1, $2)
|
|
67
|
+
when /^set :(.*)\s+(.*)/
|
|
68
|
+
configuration.set($1.to_sym, $2)
|
|
69
|
+
puts "updated :#{$1} to #{$2}"
|
|
64
70
|
when /^(?:(with|on)\s*(\S+))?\s*(\S.*)?/i
|
|
65
71
|
process_command($1, $2, $3)
|
|
66
72
|
else
|
|
@@ -142,11 +148,13 @@ HELP
|
|
|
142
148
|
# be invoked. Otherwise, it is executed as a command on all associated
|
|
143
149
|
# servers.
|
|
144
150
|
def exec(command)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
@mutex.synchronize do
|
|
152
|
+
if command[0] == ?!
|
|
153
|
+
exec_tasks(command[1..-1].split)
|
|
154
|
+
else
|
|
155
|
+
servers = connect(configuration.current_task)
|
|
156
|
+
exec_command(command, servers)
|
|
157
|
+
end
|
|
150
158
|
end
|
|
151
159
|
ensure
|
|
152
160
|
STDOUT.flush
|
|
@@ -166,24 +174,13 @@ HELP
|
|
|
166
174
|
|
|
167
175
|
# Execute a command on the given list of servers.
|
|
168
176
|
def exec_command(command, servers)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
out.each do |line|
|
|
172
|
-
if stream == :out
|
|
173
|
-
if out =~ /Password:\s*/i
|
|
174
|
-
ch.send_data "#{configuration[:password]}\n"
|
|
175
|
-
else
|
|
176
|
-
puts "[#{ch[:server]}] #{line.chomp}"
|
|
177
|
-
end
|
|
178
|
-
elsif stream == :err
|
|
179
|
-
puts "[#{ch[:server]} ERR] #{line.chomp}"
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
previous = trap("INT") { cmd.stop! }
|
|
177
|
+
command = command.gsub(/^(\s*)sudo\b|([|;&])\s*sudo\b/, "\\0 -p '#{configuration.sudo_prompt}'")
|
|
178
|
+
processor = configuration.sudo_behavior_callback(Configuration.default_io_proc)
|
|
185
179
|
sessions = servers.map { |server| configuration.sessions[server] }
|
|
186
|
-
|
|
180
|
+
options = configuration.add_default_command_options({})
|
|
181
|
+
cmd = Command.new(command, sessions, options.merge(:logger => configuration.logger), &processor)
|
|
182
|
+
previous = trap("INT") { cmd.stop! }
|
|
183
|
+
cmd.process!
|
|
187
184
|
rescue Capistrano::Error => error
|
|
188
185
|
warn "error: #{error.message}"
|
|
189
186
|
ensure
|
|
@@ -205,20 +202,15 @@ HELP
|
|
|
205
202
|
# thread and generally gets things ready for the REPL.
|
|
206
203
|
def setup
|
|
207
204
|
configuration.logger.level = Capistrano::Logger::INFO
|
|
205
|
+
wait_for = 0.1
|
|
208
206
|
|
|
209
207
|
@mutex = Mutex.new
|
|
210
208
|
@bgthread = Thread.new do
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
sleep 0.1
|
|
215
|
-
else
|
|
216
|
-
@mutex.synchronize do
|
|
217
|
-
ready.each { |session| session.connection.process(true) }
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
end
|
|
209
|
+
loop do
|
|
210
|
+
ret = @mutex.synchronize { process_iteration(wait_for) }
|
|
211
|
+
sleep wait_for if !ret
|
|
221
212
|
end
|
|
213
|
+
end
|
|
222
214
|
end
|
|
223
215
|
|
|
224
216
|
# Set the given option to +value+.
|
|
@@ -256,7 +248,7 @@ HELP
|
|
|
256
248
|
old_var, ENV[env_var] = ENV[env_var], (scope_value == "all" ? nil : scope_value) if env_var
|
|
257
249
|
if command
|
|
258
250
|
begin
|
|
259
|
-
|
|
251
|
+
exec(command)
|
|
260
252
|
ensure
|
|
261
253
|
ENV[env_var] = old_var if env_var
|
|
262
254
|
end
|
|
@@ -264,5 +256,10 @@ HELP
|
|
|
264
256
|
puts "scoping #{scope_type} #{scope_value}"
|
|
265
257
|
end
|
|
266
258
|
end
|
|
259
|
+
|
|
260
|
+
# All open sessions, needed to satisfy the Command::Processable include
|
|
261
|
+
def sessions
|
|
262
|
+
configuration.sessions.values
|
|
263
|
+
end
|
|
267
264
|
end
|
|
268
265
|
end
|
data/lib/capistrano/ssh.rb
CHANGED
|
@@ -1,55 +1,6 @@
|
|
|
1
1
|
require 'net/ssh'
|
|
2
2
|
|
|
3
3
|
module Capistrano
|
|
4
|
-
unless ENV['SKIP_VERSION_CHECK']
|
|
5
|
-
require 'capistrano/version'
|
|
6
|
-
require 'net/ssh/version'
|
|
7
|
-
ssh_version = [Net::SSH::Version::MAJOR, Net::SSH::Version::MINOR, Net::SSH::Version::TINY]
|
|
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!
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
4
|
# A helper class for dealing with SSH connections.
|
|
54
5
|
class SSH
|
|
55
6
|
# Patch an accessor onto an SSH connection so that we can record the server
|
|
@@ -66,9 +17,6 @@ module Capistrano
|
|
|
66
17
|
attr_accessor :xserver
|
|
67
18
|
end
|
|
68
19
|
|
|
69
|
-
# The default port for SSH.
|
|
70
|
-
DEFAULT_PORT = 22
|
|
71
|
-
|
|
72
20
|
# An abstraction to make it possible to connect to the server via public key
|
|
73
21
|
# without prompting for the password. If the public key authentication fails
|
|
74
22
|
# this will fall back to password authentication.
|
|
@@ -82,13 +30,53 @@ module Capistrano
|
|
|
82
30
|
# constructor. Values in +options+ are then merged into it, and any
|
|
83
31
|
# connection information in +server+ is added last, so that +server+ info
|
|
84
32
|
# takes precedence over +options+, which takes precendence over ssh_options.
|
|
85
|
-
def self.connect(server, options={}
|
|
33
|
+
def self.connect(server, options={})
|
|
34
|
+
connection_strategy(server, options) do |host, user, connection_options|
|
|
35
|
+
connection = Net::SSH.start(host, user, connection_options)
|
|
36
|
+
Server.apply_to(connection, server)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Abstracts the logic for establishing an SSH connection (which includes
|
|
41
|
+
# testing for connection failures and retrying with a password, and so forth,
|
|
42
|
+
# mostly made complicated because of the fact that some of these variables
|
|
43
|
+
# might be lazily evaluated and try to do something like prompt the user,
|
|
44
|
+
# which should only happen when absolutely necessary.
|
|
45
|
+
#
|
|
46
|
+
# This will yield the hostname, username, and a hash of connection options
|
|
47
|
+
# to the given block, which should return a new connection.
|
|
48
|
+
def self.connection_strategy(server, options={}, &block)
|
|
86
49
|
methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
|
|
87
50
|
password_value = nil
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
51
|
+
|
|
52
|
+
# construct the hash of ssh options that should be passed more-or-less
|
|
53
|
+
# directly to Net::SSH. This will be the general ssh options, merged with
|
|
54
|
+
# the server-specific ssh-options.
|
|
55
|
+
ssh_options = (options[:ssh_options] || {}).merge(server.options[:ssh_options] || {})
|
|
56
|
+
|
|
57
|
+
# load any SSH configuration files that were specified in the SSH options. This
|
|
58
|
+
# will load from ~/.ssh/config and /etc/ssh_config by default (see Net::SSH
|
|
59
|
+
# for details). Merge the explicitly given ssh_options over the top of the info
|
|
60
|
+
# from the config file.
|
|
61
|
+
ssh_options = Net::SSH.configuration_for(server.host, ssh_options.fetch(:config, true)).merge(ssh_options)
|
|
62
|
+
|
|
63
|
+
# Once we've loaded the config, we don't need Net::SSH to do it again.
|
|
64
|
+
ssh_options[:config] = false
|
|
65
|
+
|
|
66
|
+
ssh_options[:verbose] = :debug if options[:verbose] && options[:verbose] > 0
|
|
67
|
+
|
|
68
|
+
user = server.user || options[:user] || ssh_options[:username] ||
|
|
69
|
+
ssh_options[:user] || ServerDefinition.default_user
|
|
70
|
+
port = server.port || options[:port] || ssh_options[:port]
|
|
71
|
+
|
|
72
|
+
# the .ssh/config file might have changed the host-name on us
|
|
73
|
+
host = ssh_options.fetch(:host_name, server.host)
|
|
74
|
+
|
|
75
|
+
ssh_options[:port] = port if port
|
|
76
|
+
|
|
77
|
+
# delete these, since we've determined which username to use by this point
|
|
78
|
+
ssh_options.delete(:username)
|
|
79
|
+
ssh_options.delete(:user)
|
|
92
80
|
|
|
93
81
|
begin
|
|
94
82
|
connection_options = ssh_options.merge(
|
|
@@ -96,9 +84,7 @@ module Capistrano
|
|
|
96
84
|
:auth_methods => ssh_options[:auth_methods] || methods.shift
|
|
97
85
|
)
|
|
98
86
|
|
|
99
|
-
|
|
100
|
-
Server.apply_to(connection, server)
|
|
101
|
-
|
|
87
|
+
yield host, user, connection_options
|
|
102
88
|
rescue Net::SSH::AuthenticationFailed
|
|
103
89
|
raise if methods.empty? || ssh_options[:auth_methods]
|
|
104
90
|
password_value = options[:password]
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
require 'capistrano/server_definition'
|
|
2
2
|
|
|
3
3
|
module Capistrano
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
class TaskDefinition
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
attr_reader :name, :namespace, :options, :body, :desc, :on_error, :max_hosts
|
|
7
8
|
|
|
8
9
|
def initialize(name, namespace, options={}, &block)
|
|
9
10
|
@name, @namespace, @options = name, namespace, options
|
|
10
|
-
@desc
|
|
11
|
-
@on_error
|
|
12
|
-
@
|
|
13
|
-
@
|
|
11
|
+
@desc = @options.delete(:desc)
|
|
12
|
+
@on_error = options.delete(:on_error)
|
|
13
|
+
@max_hosts = options[:max_hosts] && options[:max_hosts].to_i
|
|
14
|
+
@body = block or raise ArgumentError, "a task requires a block"
|
|
15
|
+
@servers = nil
|
|
14
16
|
end
|
|
15
|
-
|
|
17
|
+
|
|
16
18
|
# Returns the task's fully-qualified name, including the namespace
|
|
17
19
|
def fully_qualified_name
|
|
18
20
|
@fully_qualified_name ||= begin
|
|
@@ -24,6 +26,11 @@ module Capistrano
|
|
|
24
26
|
end
|
|
25
27
|
end
|
|
26
28
|
|
|
29
|
+
def name=(value)
|
|
30
|
+
raise ArgumentError, "expected a valid task name" if !value.respond_to?(:to_sym)
|
|
31
|
+
@name = value.to_sym
|
|
32
|
+
end
|
|
33
|
+
|
|
27
34
|
# Returns the description for this task, with newlines collapsed and
|
|
28
35
|
# whitespace stripped. Returns the empty string if there is no
|
|
29
36
|
# description for this task.
|
|
@@ -65,5 +72,6 @@ module Capistrano
|
|
|
65
72
|
def continue_on_error?
|
|
66
73
|
@on_error == :continue
|
|
67
74
|
end
|
|
75
|
+
|
|
68
76
|
end
|
|
69
|
-
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,218 @@
|
|
|
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 = block
|
|
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
|
+
raise error if error.message.include?('expected a file to upload')
|
|
207
|
+
|
|
208
|
+
transfer = session_map[error.session]
|
|
209
|
+
transfer[:error] = error
|
|
210
|
+
transfer[:failed] = true
|
|
211
|
+
|
|
212
|
+
case transport
|
|
213
|
+
when :sftp then transfer.abort!
|
|
214
|
+
when :scp then transfer.close
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
data/lib/capistrano/version.rb
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
module Capistrano
|
|
2
|
-
|
|
3
|
-
# A method for comparing versions of required modules. It expects two
|
|
4
|
-
# arrays of integers as parameters, the first being the minimum version
|
|
5
|
-
# required, and the second being the actual version available. It returns
|
|
6
|
-
# true if the actual version is at least equal to the required version.
|
|
7
|
-
def self.check(required, actual) #:nodoc:
|
|
8
|
-
required = required.map { |v| "%06d" % v }.join(".")
|
|
9
|
-
actual = actual.map { |v| "%06d" % v }.join(".")
|
|
10
|
-
return actual >= required
|
|
11
|
-
end
|
|
12
|
-
|
|
2
|
+
class Version
|
|
13
3
|
MAJOR = 2
|
|
14
|
-
MINOR =
|
|
15
|
-
|
|
4
|
+
MINOR = 15
|
|
5
|
+
PATCH = 2
|
|
16
6
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
SFTP_REQUIRED = [1,1,0]
|
|
7
|
+
def self.to_s
|
|
8
|
+
"#{MAJOR}.#{MINOR}.#{PATCH}"
|
|
9
|
+
end
|
|
21
10
|
end
|
|
22
11
|
end
|
data/lib/capistrano.rb
CHANGED