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
|
@@ -23,6 +23,60 @@ module Capistrano
|
|
|
23
23
|
def initialize_with_invocation(*args) #:nodoc:
|
|
24
24
|
initialize_without_invocation(*args)
|
|
25
25
|
set :default_environment, {}
|
|
26
|
+
set :default_run_options, {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Executes different commands in parallel. This is useful for commands
|
|
30
|
+
# that need to be different on different hosts, but which could be
|
|
31
|
+
# otherwise run in parallel.
|
|
32
|
+
#
|
|
33
|
+
# The +options+ parameter is currently unused.
|
|
34
|
+
#
|
|
35
|
+
# Example:
|
|
36
|
+
#
|
|
37
|
+
# task :restart_everything do
|
|
38
|
+
# parallel do |session|
|
|
39
|
+
# session.when "in?(:app)", "/path/to/restart/mongrel"
|
|
40
|
+
# session.when "in?(:web)", "/path/to/restart/apache"
|
|
41
|
+
# session.when "in?(:db)", "/path/to/restart/mysql"
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# Each command may have its own callback block, for capturing and
|
|
46
|
+
# responding to output, with semantics identical to #run:
|
|
47
|
+
#
|
|
48
|
+
# session.when "in?(:app)", "/path/to/restart/mongrel" do |ch, stream, data|
|
|
49
|
+
# # ch is the SSH channel for this command, used to send data
|
|
50
|
+
# # back to the command (e.g. ch.send_data("password\n"))
|
|
51
|
+
# # stream is either :out or :err, for which stream the data arrived on
|
|
52
|
+
# # data is a string containing data sent from the remote command
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
# Also, you can specify a fallback command, to use when none of the
|
|
56
|
+
# conditions match a server:
|
|
57
|
+
#
|
|
58
|
+
# session.else "/execute/something/else"
|
|
59
|
+
#
|
|
60
|
+
# The string specified as the first argument to +when+ may be any valid
|
|
61
|
+
# Ruby code. It has access to the following variables and methods:
|
|
62
|
+
#
|
|
63
|
+
# * +in?(role)+ returns true if the server participates in the given role
|
|
64
|
+
# * +server+ is the ServerDefinition object for the server. This can be
|
|
65
|
+
# used to get the host-name, etc.
|
|
66
|
+
# * +configuration+ is the current Capistrano::Configuration object, which
|
|
67
|
+
# you can use to get the value of variables, etc.
|
|
68
|
+
#
|
|
69
|
+
# For example:
|
|
70
|
+
#
|
|
71
|
+
# session.when "server.host =~ /app/", "/some/command"
|
|
72
|
+
# session.when "server.host == configuration[:some_var]", "/another/command"
|
|
73
|
+
# session.when "in?(:web) || in?(:app)", "/more/commands"
|
|
74
|
+
#
|
|
75
|
+
# See #run for a description of the valid +options+.
|
|
76
|
+
def parallel(options={})
|
|
77
|
+
raise ArgumentError, "parallel() requires a block" unless block_given?
|
|
78
|
+
tree = Command::Tree.new(self) { |t| yield t }
|
|
79
|
+
run_tree(tree, options)
|
|
26
80
|
end
|
|
27
81
|
|
|
28
82
|
# Invokes the given command. If a +via+ key is given, it will be used
|
|
@@ -41,37 +95,145 @@ module Capistrano
|
|
|
41
95
|
# channel (which may be used to send data back to the remote process),
|
|
42
96
|
# the stream identifier (<tt>:err</tt> for stderr, and <tt>:out</tt> for
|
|
43
97
|
# stdout), and the data that was received.
|
|
98
|
+
#
|
|
99
|
+
# The +options+ hash may include any of the following keys:
|
|
100
|
+
#
|
|
101
|
+
# * :hosts - this is either a string (for a single target host) or an array
|
|
102
|
+
# of strings, indicating which hosts the command should run on. By default,
|
|
103
|
+
# the hosts are determined from the task definition.
|
|
104
|
+
# * :roles - this is either a string or symbol (for a single target role) or
|
|
105
|
+
# an array of strings or symbols, indicating which roles the command should
|
|
106
|
+
# run on. If :hosts is specified, :roles will be ignored.
|
|
107
|
+
# * :only - specifies a condition limiting which hosts will be selected to
|
|
108
|
+
# run the command. This should refer to values set in the role definition.
|
|
109
|
+
# For example, if a role is defined with :primary => true, then you could
|
|
110
|
+
# select only hosts with :primary true by setting :only => { :primary => true }.
|
|
111
|
+
# * :except - specifies a condition limiting which hosts will be selected to
|
|
112
|
+
# run the command. This is the inverse of :only (hosts that do _not_ match
|
|
113
|
+
# the condition will be selected).
|
|
114
|
+
# * :on_no_matching_servers - if :continue, will continue to execute tasks if
|
|
115
|
+
# no matching servers are found for the host criteria. The default is to raise
|
|
116
|
+
# a NoMatchingServersError exception.
|
|
117
|
+
# * :once - if true, only the first matching server will be selected. The default
|
|
118
|
+
# is false (all matching servers will be selected).
|
|
119
|
+
# * :max_hosts - specifies the maximum number of hosts that should be selected
|
|
120
|
+
# at a time. If this value is less than the number of hosts that are selected
|
|
121
|
+
# to run, then the hosts will be run in groups of max_hosts. The default is nil,
|
|
122
|
+
# which indicates that there is no maximum host limit. Please note this does not
|
|
123
|
+
# limit the number of SSH channels that can be open, only the number of hosts upon
|
|
124
|
+
# which this will be called.
|
|
125
|
+
# * :shell - says which shell should be used to invoke commands. This
|
|
126
|
+
# defaults to "sh". Setting this to false causes Capistrano to invoke
|
|
127
|
+
# the commands directly, without wrapping them in a shell invocation.
|
|
128
|
+
# * :data - if not nil (the default), this should be a string that will
|
|
129
|
+
# be passed to the command's stdin stream.
|
|
130
|
+
# * :pty - if true, a pseudo-tty will be allocated for each command. The
|
|
131
|
+
# default is false. Note that there are benefits and drawbacks both ways.
|
|
132
|
+
# Empirically, it appears that if a pty is allocated, the SSH server daemon
|
|
133
|
+
# will _not_ read user shell start-up scripts (e.g. bashrc, etc.). However,
|
|
134
|
+
# if a pty is _not_ allocated, some commands will refuse to run in
|
|
135
|
+
# interactive mode and will not prompt for (e.g.) passwords.
|
|
136
|
+
# * :env - a hash of environment variable mappings that should be made
|
|
137
|
+
# available to the command. The keys should be environment variable names,
|
|
138
|
+
# and the values should be their corresponding values. The default is
|
|
139
|
+
# empty, but may be modified by changing the +default_environment+
|
|
140
|
+
# Capistrano variable.
|
|
141
|
+
# * :eof - if true, the standard input stream will be closed after sending
|
|
142
|
+
# any data specified in the :data option. If false, the input stream is
|
|
143
|
+
# left open. The default is to close the input stream only if no block is
|
|
144
|
+
# passed.
|
|
145
|
+
#
|
|
146
|
+
# Note that if you set these keys in the +default_run_options+ Capistrano
|
|
147
|
+
# variable, they will apply for all invocations of #run, #invoke_command,
|
|
148
|
+
# and #parallel.
|
|
44
149
|
def run(cmd, options={}, &block)
|
|
150
|
+
if options[:eof].nil? && !cmd.include?(sudo)
|
|
151
|
+
options = options.merge(:eof => !block_given?)
|
|
152
|
+
end
|
|
45
153
|
block ||= self.class.default_io_proc
|
|
46
|
-
|
|
154
|
+
tree = Command::Tree.new(self) { |t| t.else(cmd, &block) }
|
|
155
|
+
run_tree(tree, options)
|
|
156
|
+
end
|
|
47
157
|
|
|
158
|
+
# Executes a Capistrano::Command::Tree object. This is not for direct
|
|
159
|
+
# use, but should instead be called indirectly, via #run or #parallel,
|
|
160
|
+
# or #invoke_command.
|
|
161
|
+
def run_tree(tree, options={}) #:nodoc:
|
|
48
162
|
options = add_default_command_options(options)
|
|
49
163
|
|
|
164
|
+
if tree.branches.any? || tree.fallback
|
|
165
|
+
_, servers = filter_servers(options)
|
|
166
|
+
branches = servers.map{|server| tree.branches_for(server)}.compact
|
|
167
|
+
case branches.size
|
|
168
|
+
when 0
|
|
169
|
+
branches = tree.branches.dup + [tree.fallback]
|
|
170
|
+
case branches.size
|
|
171
|
+
when 1
|
|
172
|
+
logger.debug "no servers for #{branches.first}"
|
|
173
|
+
else
|
|
174
|
+
logger.debug "no servers for commands"
|
|
175
|
+
branches.each{ |branch| logger.trace "-> #{branch.to_s(true)}" }
|
|
176
|
+
end
|
|
177
|
+
when 1
|
|
178
|
+
logger.debug "executing #{branches.first}" unless options[:silent]
|
|
179
|
+
else
|
|
180
|
+
logger.debug "executing multiple commands in parallel"
|
|
181
|
+
branches.each{ |branch| logger.trace "-> #{branch.to_s(true)}" }
|
|
182
|
+
end
|
|
183
|
+
else
|
|
184
|
+
raise ArgumentError, "attempt to execute without specifying a command"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
return if dry_run || (debug && continue_execution(tree) == false)
|
|
188
|
+
|
|
189
|
+
tree.each do |branch|
|
|
190
|
+
if branch.command.include?(sudo)
|
|
191
|
+
branch.callback = sudo_behavior_callback(branch.callback)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
50
195
|
execute_on_servers(options) do |servers|
|
|
51
196
|
targets = servers.map { |s| sessions[s] }
|
|
52
|
-
Command.process(
|
|
197
|
+
Command.process(tree, targets, options.merge(:logger => logger))
|
|
53
198
|
end
|
|
54
199
|
end
|
|
55
200
|
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
201
|
+
# Returns the command string used by capistrano to invoke a comamnd via
|
|
202
|
+
# sudo.
|
|
203
|
+
#
|
|
204
|
+
# run "#{sudo :as => 'bob'} mkdir /path/to/dir"
|
|
205
|
+
#
|
|
206
|
+
# It can also be invoked like #run, but executing the command via sudo.
|
|
207
|
+
# This assumes that the sudo password (if required) is the same as the
|
|
208
|
+
# password for logging in to the server.
|
|
59
209
|
#
|
|
60
|
-
#
|
|
210
|
+
# sudo "mkdir /path/to/dir"
|
|
211
|
+
#
|
|
212
|
+
# Also, this method understands a <tt>:sudo</tt> configuration variable,
|
|
61
213
|
# which (if specified) will be used as the full path to the sudo
|
|
62
214
|
# executable on the remote machine:
|
|
63
215
|
#
|
|
64
216
|
# set :sudo, "/opt/local/bin/sudo"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
217
|
+
#
|
|
218
|
+
# If you know what you're doing, you can also set <tt>:sudo_prompt</tt>,
|
|
219
|
+
# which tells capistrano which prompt sudo should use when asking for
|
|
220
|
+
# a password. (This is so that capistrano knows what prompt to look for
|
|
221
|
+
# in the output.) If you set :sudo_prompt to an empty string, Capistrano
|
|
222
|
+
# will not send a preferred prompt.
|
|
223
|
+
def sudo(*parameters, &block)
|
|
224
|
+
options = parameters.last.is_a?(Hash) ? parameters.pop.dup : {}
|
|
225
|
+
command = parameters.first
|
|
226
|
+
user = options[:as] && "-u #{options.delete(:as)}"
|
|
70
227
|
|
|
71
|
-
|
|
72
|
-
|
|
228
|
+
sudo_prompt_option = "-p '#{sudo_prompt}'" unless sudo_prompt.empty?
|
|
229
|
+
sudo_command = [fetch(:sudo, "sudo"), sudo_prompt_option, user].compact.join(" ")
|
|
73
230
|
|
|
74
|
-
|
|
231
|
+
if command
|
|
232
|
+
command = sudo_command + " " + command
|
|
233
|
+
run(command, options, &block)
|
|
234
|
+
else
|
|
235
|
+
return sudo_command
|
|
236
|
+
end
|
|
75
237
|
end
|
|
76
238
|
|
|
77
239
|
# Returns a Proc object that defines the behavior of the sudo
|
|
@@ -83,17 +245,19 @@ module Capistrano
|
|
|
83
245
|
# was wrong, let's track which host prompted first and only allow
|
|
84
246
|
# subsequent prompts from that host.
|
|
85
247
|
prompt_host = nil
|
|
86
|
-
|
|
248
|
+
|
|
87
249
|
Proc.new do |ch, stream, out|
|
|
88
|
-
if out =~ /
|
|
89
|
-
ch.send_data "#{self[:password]}\n"
|
|
90
|
-
elsif out =~ /try again/
|
|
250
|
+
if out =~ /^Sorry, try again/
|
|
91
251
|
if prompt_host.nil? || prompt_host == ch[:server]
|
|
92
252
|
prompt_host = ch[:server]
|
|
93
253
|
logger.important out, "#{stream} :: #{ch[:server]}"
|
|
94
254
|
reset! :password
|
|
95
255
|
end
|
|
96
|
-
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
if out =~ /^#{Regexp.escape(sudo_prompt)}/
|
|
259
|
+
ch.send_data "#{self[:password]}\n"
|
|
260
|
+
elsif fallback
|
|
97
261
|
fallback.call(ch, stream, out)
|
|
98
262
|
end
|
|
99
263
|
end
|
|
@@ -110,18 +274,44 @@ module Capistrano
|
|
|
110
274
|
# Otherwise, if the :default_shell key exists in the configuration,
|
|
111
275
|
# it will be used. Otherwise, no :shell key is added.
|
|
112
276
|
def add_default_command_options(options)
|
|
113
|
-
|
|
277
|
+
defaults = self[:default_run_options]
|
|
278
|
+
options = defaults.merge(options)
|
|
114
279
|
|
|
115
280
|
env = self[:default_environment]
|
|
116
281
|
env = env.merge(options[:env]) if options[:env]
|
|
117
282
|
options[:env] = env unless env.empty?
|
|
118
283
|
|
|
119
284
|
shell = options[:shell] || self[:default_shell]
|
|
120
|
-
options[:shell] = shell
|
|
285
|
+
options[:shell] = shell unless shell.nil?
|
|
121
286
|
|
|
122
287
|
options
|
|
123
288
|
end
|
|
289
|
+
|
|
290
|
+
# Returns the prompt text to use with sudo
|
|
291
|
+
def sudo_prompt
|
|
292
|
+
fetch(:sudo_prompt, "sudo password: ")
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def continue_execution(tree)
|
|
296
|
+
if tree.branches.length == 1
|
|
297
|
+
continue_execution_for_branch(tree.branches.first)
|
|
298
|
+
else
|
|
299
|
+
tree.each { |branch| branch.skip! unless continue_execution_for_branch(branch) }
|
|
300
|
+
tree.any? { |branch| !branch.skip? }
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def continue_execution_for_branch(branch)
|
|
305
|
+
case Capistrano::CLI.debug_prompt(branch)
|
|
306
|
+
when "y"
|
|
307
|
+
true
|
|
308
|
+
when "n"
|
|
309
|
+
false
|
|
310
|
+
when "a"
|
|
311
|
+
exit(-1)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
124
314
|
end
|
|
125
315
|
end
|
|
126
316
|
end
|
|
127
|
-
end
|
|
317
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Capistrano
|
|
2
|
+
class Configuration
|
|
3
|
+
module AliasTask
|
|
4
|
+
# Attempts to find the task at the given fully-qualified path, and
|
|
5
|
+
# alias it. If arguments don't have correct task names, an ArgumentError
|
|
6
|
+
# wil be raised. If no such task exists, a Capistrano::NoSuchTaskError
|
|
7
|
+
# will be raised.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
#
|
|
11
|
+
# alias_task :original_deploy, :deploy
|
|
12
|
+
#
|
|
13
|
+
def alias_task(new_name, old_name)
|
|
14
|
+
if !new_name.respond_to?(:to_sym) or !old_name.respond_to?(:to_sym)
|
|
15
|
+
raise ArgumentError, "expected a valid task name"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
original_task = find_task(old_name) or raise NoSuchTaskError, "the task `#{old_name}' does not exist"
|
|
19
|
+
task = original_task.dup # Dup. task to avoid modify original task
|
|
20
|
+
task.name = new_name
|
|
21
|
+
|
|
22
|
+
define_task(task)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -4,7 +4,7 @@ module Capistrano
|
|
|
4
4
|
class Configuration
|
|
5
5
|
module Callbacks
|
|
6
6
|
def self.included(base) #:nodoc:
|
|
7
|
-
%w(initialize
|
|
7
|
+
%w(initialize invoke_task_directly).each do |method|
|
|
8
8
|
base.send :alias_method, "#{method}_without_callbacks", method
|
|
9
9
|
base.send :alias_method, method, "#{method}_with_callbacks"
|
|
10
10
|
end
|
|
@@ -18,19 +18,14 @@ module Capistrano
|
|
|
18
18
|
@callbacks = {}
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def
|
|
22
|
-
before = find_hook(task, :before)
|
|
23
|
-
execute_task(before) if before
|
|
21
|
+
def invoke_task_directly_with_callbacks(task) #:nodoc:
|
|
24
22
|
|
|
25
23
|
trigger :before, task
|
|
26
24
|
|
|
27
|
-
result =
|
|
25
|
+
result = invoke_task_directly_without_callbacks(task)
|
|
28
26
|
|
|
29
27
|
trigger :after, task
|
|
30
28
|
|
|
31
|
-
after = find_hook(task, :after)
|
|
32
|
-
execute_task(after) if after
|
|
33
|
-
|
|
34
29
|
return result
|
|
35
30
|
end
|
|
36
31
|
|
|
@@ -95,7 +90,7 @@ module Capistrano
|
|
|
95
90
|
# Usage:
|
|
96
91
|
#
|
|
97
92
|
# on :before, "some:hook", "another:hook", :only => "deploy:update"
|
|
98
|
-
# on :after, "some:hook", :except => "deploy:
|
|
93
|
+
# on :after, "some:hook", :except => "deploy:create_symlink"
|
|
99
94
|
# on :before, "global:hook"
|
|
100
95
|
# on :after, :only => :deploy do
|
|
101
96
|
# puts "after deploy here"
|
|
@@ -111,10 +106,28 @@ module Capistrano
|
|
|
111
106
|
elsif block
|
|
112
107
|
callbacks[event] << ProcCallback.new(block, options)
|
|
113
108
|
else
|
|
114
|
-
args
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
args = filter_deprecated_tasks(args)
|
|
110
|
+
options[:only] = filter_deprecated_tasks(options[:only])
|
|
111
|
+
options[:except] = filter_deprecated_tasks(options[:except])
|
|
112
|
+
|
|
113
|
+
callbacks[event].concat(args.map { |name| TaskCallback.new(self, name, options) })
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Filters the given task name or names and attempts to replace deprecated tasks with their equivalents.
|
|
118
|
+
def filter_deprecated_tasks(names)
|
|
119
|
+
deprecation_msg = "[Deprecation Warning] This API has changed, please hook `deploy:create_symlink` instead of" \
|
|
120
|
+
" `deploy:symlink`."
|
|
121
|
+
|
|
122
|
+
if names == "deploy:symlink"
|
|
123
|
+
warn deprecation_msg
|
|
124
|
+
names = "deploy:create_symlink"
|
|
125
|
+
elsif names.is_a?(Array) && names.include?("deploy:symlink")
|
|
126
|
+
warn deprecation_msg
|
|
127
|
+
names = names.map { |name| name == "deploy:symlink" ? "deploy:create_symlink" : name }
|
|
117
128
|
end
|
|
129
|
+
|
|
130
|
+
names
|
|
118
131
|
end
|
|
119
132
|
|
|
120
133
|
# Trigger the named event for the named task. All associated callbacks
|
|
@@ -129,20 +142,6 @@ module Capistrano
|
|
|
129
142
|
end
|
|
130
143
|
end
|
|
131
144
|
|
|
132
|
-
private
|
|
133
|
-
|
|
134
|
-
# Looks for before_foo or after_foo tasks. This method of extending tasks
|
|
135
|
-
# is now discouraged (though not formally deprecated). You should use the
|
|
136
|
-
# before and after methods to declare hooks for such callbacks.
|
|
137
|
-
def find_hook(task, hook)
|
|
138
|
-
if task == task.namespace.default_task
|
|
139
|
-
result = task.namespace.search_task("#{hook}_#{task.namespace.name}")
|
|
140
|
-
return result if result
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
task.namespace.search_task("#{hook}_#{task.name}")
|
|
144
|
-
end
|
|
145
|
-
|
|
146
145
|
end
|
|
147
146
|
end
|
|
148
|
-
end
|
|
147
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'enumerator'
|
|
2
|
+
require 'net/ssh/gateway'
|
|
2
3
|
require 'capistrano/ssh'
|
|
4
|
+
require 'capistrano/errors'
|
|
3
5
|
|
|
4
6
|
module Capistrano
|
|
5
7
|
class Configuration
|
|
@@ -9,8 +11,6 @@ module Capistrano
|
|
|
9
11
|
base.send :alias_method, :initialize, :initialize_with_connections
|
|
10
12
|
end
|
|
11
13
|
|
|
12
|
-
# An adaptor for making the SSH interface look and act like that of the
|
|
13
|
-
# Gateway class.
|
|
14
14
|
class DefaultConnectionFactory #:nodoc:
|
|
15
15
|
def initialize(options)
|
|
16
16
|
@options = options
|
|
@@ -21,27 +21,76 @@ module Capistrano
|
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
class GatewayConnectionFactory #:nodoc:
|
|
25
|
+
def initialize(gateway, options)
|
|
26
|
+
@options = options
|
|
27
|
+
Thread.abort_on_exception = true
|
|
28
|
+
@gateways = {}
|
|
29
|
+
if gateway.is_a?(Hash)
|
|
30
|
+
@options[:logger].debug "Creating multiple gateways using #{gateway.inspect}" if @options[:logger]
|
|
31
|
+
gateway.each do |gw, hosts|
|
|
32
|
+
gateway_connection = add_gateway(gw)
|
|
33
|
+
[*hosts].each do |host|
|
|
34
|
+
@gateways[:default] ||= gateway_connection
|
|
35
|
+
@gateways[host] = gateway_connection
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
@options[:logger].debug "Creating gateway using #{[*gateway].join(', ')}" if @options[:logger]
|
|
40
|
+
@gateways[:default] = add_gateway(gateway)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add_gateway(gateway)
|
|
45
|
+
gateways = [*gateway].collect { |g| ServerDefinition.new(g) }
|
|
46
|
+
tunnel = SSH.connection_strategy(gateways[0], @options) do |host, user, connect_options|
|
|
47
|
+
Net::SSH::Gateway.new(host, user, connect_options)
|
|
48
|
+
end
|
|
49
|
+
(gateways[1..-1]).inject(tunnel) do |tunnel, destination|
|
|
50
|
+
@options[:logger].debug "Creating tunnel to #{destination}" if @options[:logger]
|
|
51
|
+
local_host = ServerDefinition.new("127.0.0.1", :user => destination.user, :port => tunnel.open(destination.host, (destination.port || 22)))
|
|
52
|
+
SSH.connection_strategy(local_host, @options) do |host, user, connect_options|
|
|
53
|
+
Net::SSH::Gateway.new(host, user, connect_options)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def connect_to(server)
|
|
59
|
+
@options[:logger].debug "establishing connection to `#{server}' via gateway" if @options[:logger]
|
|
60
|
+
local_host = ServerDefinition.new("127.0.0.1", :user => server.user, :port => gateway_for(server).open(server.host, server.port || 22))
|
|
61
|
+
session = SSH.connect(local_host, @options)
|
|
62
|
+
session.xserver = server
|
|
63
|
+
session
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def gateway_for(server)
|
|
67
|
+
@gateways[server.host] || @gateways[:default]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
24
71
|
# A hash of the SSH sessions that are currently open and available.
|
|
25
72
|
# Because sessions are constructed lazily, this will only contain
|
|
26
73
|
# connections to those servers that have been the targets of one or more
|
|
27
|
-
# executed tasks.
|
|
28
|
-
|
|
74
|
+
# executed tasks. Stored on a per-thread basis to improve thread-safety.
|
|
75
|
+
def sessions
|
|
76
|
+
Thread.current[:sessions] ||= {}
|
|
77
|
+
end
|
|
29
78
|
|
|
30
79
|
def initialize_with_connections(*args) #:nodoc:
|
|
31
80
|
initialize_without_connections(*args)
|
|
32
|
-
|
|
33
|
-
|
|
81
|
+
Thread.current[:sessions] = {}
|
|
82
|
+
Thread.current[:failed_sessions] = []
|
|
34
83
|
end
|
|
35
84
|
|
|
36
85
|
# Indicate that the given server could not be connected to.
|
|
37
86
|
def failed!(server)
|
|
38
|
-
|
|
87
|
+
Thread.current[:failed_sessions] << server
|
|
39
88
|
end
|
|
40
89
|
|
|
41
90
|
# Query whether previous connection attempts to the given server have
|
|
42
91
|
# failed.
|
|
43
92
|
def has_failed?(server)
|
|
44
|
-
|
|
93
|
+
Thread.current[:failed_sessions].include?(server)
|
|
45
94
|
end
|
|
46
95
|
|
|
47
96
|
# Used to force connections to be made to the current task's servers.
|
|
@@ -57,9 +106,9 @@ module Capistrano
|
|
|
57
106
|
# establish connections to servers defined via ServerDefinition objects.
|
|
58
107
|
def connection_factory
|
|
59
108
|
@connection_factory ||= begin
|
|
60
|
-
if exists?(:gateway)
|
|
61
|
-
logger.debug "establishing connection to gateway `#{fetch(:gateway)}'"
|
|
62
|
-
|
|
109
|
+
if exists?(:gateway) && !fetch(:gateway).nil? && !fetch(:gateway).empty?
|
|
110
|
+
logger.debug "establishing connection to gateway `#{fetch(:gateway).inspect}'"
|
|
111
|
+
GatewayConnectionFactory.new(fetch(:gateway), self)
|
|
63
112
|
else
|
|
64
113
|
DefaultConnectionFactory.new(self)
|
|
65
114
|
end
|
|
@@ -70,22 +119,13 @@ module Capistrano
|
|
|
70
119
|
def establish_connections_to(servers)
|
|
71
120
|
failed_servers = []
|
|
72
121
|
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
if fetch(:synchronous_connect, false)
|
|
78
|
-
logger.trace "synchronous_connect: true"
|
|
79
|
-
Array(servers).each { |server| safely_establish_connection_to(server, failed_servers) }
|
|
80
|
-
else
|
|
81
|
-
# force the connection factory to be instantiated synchronously,
|
|
82
|
-
# otherwise we wind up with multiple gateway instances, because
|
|
83
|
-
# each connection is done in parallel.
|
|
84
|
-
connection_factory
|
|
122
|
+
# force the connection factory to be instantiated synchronously,
|
|
123
|
+
# otherwise we wind up with multiple gateway instances, because
|
|
124
|
+
# each connection is done in parallel.
|
|
125
|
+
connection_factory
|
|
85
126
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
end
|
|
127
|
+
threads = Array(servers).map { |server| establish_connection_to(server, failed_servers) }
|
|
128
|
+
threads.each { |t| t.join }
|
|
89
129
|
|
|
90
130
|
if failed_servers.any?
|
|
91
131
|
errors = failed_servers.map { |h| "#{h[:server]} (#{h[:error].class}: #{h[:error].message})" }
|
|
@@ -95,47 +135,83 @@ module Capistrano
|
|
|
95
135
|
end
|
|
96
136
|
end
|
|
97
137
|
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
138
|
+
# Destroys sessions for each server in the list.
|
|
139
|
+
def teardown_connections_to(servers)
|
|
140
|
+
servers.each do |server|
|
|
141
|
+
begin
|
|
142
|
+
session = sessions.delete(server)
|
|
143
|
+
session.close if session
|
|
144
|
+
rescue IOError, Net::SSH::Disconnect
|
|
145
|
+
# the TCP connection is already dead
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
103
149
|
|
|
150
|
+
# Determines the set of servers within the current task's scope
|
|
151
|
+
def filter_servers(options={})
|
|
104
152
|
if task = current_task
|
|
105
153
|
servers = find_servers_for_task(task, options)
|
|
106
154
|
|
|
107
155
|
if servers.empty?
|
|
108
|
-
|
|
156
|
+
if ENV['HOSTFILTER'] || task.options.merge(options)[:on_no_matching_servers] == :continue
|
|
157
|
+
logger.info "skipping `#{task.fully_qualified_name}' because no servers matched"
|
|
158
|
+
else
|
|
159
|
+
unless dry_run
|
|
160
|
+
raise Capistrano::NoMatchingServersError, "`#{task.fully_qualified_name}' is only run for servers matching #{task.options.inspect}, but no servers matched"
|
|
161
|
+
end
|
|
162
|
+
end
|
|
109
163
|
end
|
|
110
164
|
|
|
111
165
|
if task.continue_on_error?
|
|
112
166
|
servers.delete_if { |s| has_failed?(s) }
|
|
113
|
-
return if servers.empty?
|
|
114
167
|
end
|
|
115
168
|
else
|
|
116
169
|
servers = find_servers(options)
|
|
117
|
-
|
|
170
|
+
if servers.empty? && !dry_run
|
|
171
|
+
raise Capistrano::NoMatchingServersError, "no servers found to match #{options.inspect}" if options[:on_no_matching_servers] != :continue
|
|
172
|
+
end
|
|
118
173
|
end
|
|
119
174
|
|
|
120
175
|
servers = [servers.first] if options[:once]
|
|
176
|
+
[task, servers.compact]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Determines the set of servers within the current task's scope and
|
|
180
|
+
# establishes connections to them, and then yields that list of
|
|
181
|
+
# servers.
|
|
182
|
+
def execute_on_servers(options={})
|
|
183
|
+
raise ArgumentError, "expected a block" unless block_given?
|
|
184
|
+
|
|
185
|
+
task, servers = filter_servers(options)
|
|
186
|
+
return if servers.empty?
|
|
121
187
|
logger.trace "servers: #{servers.map { |s| s.host }.inspect}"
|
|
122
188
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
189
|
+
max_hosts = (options[:max_hosts] || (task && task.max_hosts) || servers.size).to_i
|
|
190
|
+
is_subset = max_hosts < servers.size
|
|
191
|
+
|
|
192
|
+
# establish connections to those servers in groups of max_hosts, as necessary
|
|
193
|
+
servers.each_slice(max_hosts) do |servers_slice|
|
|
194
|
+
begin
|
|
195
|
+
establish_connections_to(servers_slice)
|
|
196
|
+
rescue ConnectionError => error
|
|
197
|
+
raise error unless task && task.continue_on_error?
|
|
198
|
+
error.hosts.each do |h|
|
|
199
|
+
servers_slice.delete(h)
|
|
200
|
+
failed!(h)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
begin
|
|
205
|
+
yield servers_slice
|
|
206
|
+
rescue RemoteError => error
|
|
207
|
+
raise error unless task && task.continue_on_error?
|
|
208
|
+
error.hosts.each { |h| failed!(h) }
|
|
131
209
|
end
|
|
132
|
-
end
|
|
133
210
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
error.hosts.each { |h| failed!(h) }
|
|
211
|
+
# if dealing with a subset (e.g., :max_hosts is less than the
|
|
212
|
+
# number of servers available) teardown the subset of connections
|
|
213
|
+
# that were just made, so that we can make room for the next subset.
|
|
214
|
+
teardown_connections_to(servers_slice) if is_subset
|
|
139
215
|
end
|
|
140
216
|
end
|
|
141
217
|
|
|
@@ -145,11 +221,13 @@ module Capistrano
|
|
|
145
221
|
# prevents problems with the thread's scope seeing the wrong 'server'
|
|
146
222
|
# variable if the thread just happens to take too long to start up.
|
|
147
223
|
def establish_connection_to(server, failures=nil)
|
|
148
|
-
Thread.
|
|
224
|
+
current_thread = Thread.current
|
|
225
|
+
Thread.new { safely_establish_connection_to(server, current_thread, failures) }
|
|
149
226
|
end
|
|
150
227
|
|
|
151
|
-
def safely_establish_connection_to(server, failures=nil)
|
|
152
|
-
sessions
|
|
228
|
+
def safely_establish_connection_to(server, thread, failures=nil)
|
|
229
|
+
thread[:sessions] ||= {}
|
|
230
|
+
thread[:sessions][server] ||= connection_factory.connect_to(server)
|
|
153
231
|
rescue Exception => err
|
|
154
232
|
raise unless failures
|
|
155
233
|
failures << { :server => server, :error => err }
|