minmb-capistrano 2.15.4

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.
Files changed (119) hide show
  1. data/.gitignore +10 -0
  2. data/.travis.yml +7 -0
  3. data/CHANGELOG +1170 -0
  4. data/Gemfile +13 -0
  5. data/README.md +94 -0
  6. data/Rakefile +11 -0
  7. data/bin/cap +4 -0
  8. data/bin/capify +92 -0
  9. data/capistrano.gemspec +40 -0
  10. data/lib/capistrano.rb +5 -0
  11. data/lib/capistrano/callback.rb +45 -0
  12. data/lib/capistrano/cli.rb +47 -0
  13. data/lib/capistrano/cli/execute.rb +85 -0
  14. data/lib/capistrano/cli/help.rb +125 -0
  15. data/lib/capistrano/cli/help.txt +81 -0
  16. data/lib/capistrano/cli/options.rb +243 -0
  17. data/lib/capistrano/cli/ui.rb +40 -0
  18. data/lib/capistrano/command.rb +303 -0
  19. data/lib/capistrano/configuration.rb +57 -0
  20. data/lib/capistrano/configuration/actions/file_transfer.rb +50 -0
  21. data/lib/capistrano/configuration/actions/inspect.rb +46 -0
  22. data/lib/capistrano/configuration/actions/invocation.rb +329 -0
  23. data/lib/capistrano/configuration/alias_task.rb +26 -0
  24. data/lib/capistrano/configuration/callbacks.rb +147 -0
  25. data/lib/capistrano/configuration/connections.rb +237 -0
  26. data/lib/capistrano/configuration/execution.rb +142 -0
  27. data/lib/capistrano/configuration/loading.rb +205 -0
  28. data/lib/capistrano/configuration/log_formatters.rb +75 -0
  29. data/lib/capistrano/configuration/namespaces.rb +223 -0
  30. data/lib/capistrano/configuration/roles.rb +77 -0
  31. data/lib/capistrano/configuration/servers.rb +116 -0
  32. data/lib/capistrano/configuration/variables.rb +127 -0
  33. data/lib/capistrano/errors.rb +19 -0
  34. data/lib/capistrano/ext/multistage.rb +64 -0
  35. data/lib/capistrano/ext/string.rb +5 -0
  36. data/lib/capistrano/extensions.rb +57 -0
  37. data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
  38. data/lib/capistrano/logger.rb +166 -0
  39. data/lib/capistrano/processable.rb +57 -0
  40. data/lib/capistrano/recipes/compat.rb +32 -0
  41. data/lib/capistrano/recipes/deploy.rb +625 -0
  42. data/lib/capistrano/recipes/deploy/assets.rb +201 -0
  43. data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
  44. data/lib/capistrano/recipes/deploy/local_dependency.rb +54 -0
  45. data/lib/capistrano/recipes/deploy/remote_dependency.rb +117 -0
  46. data/lib/capistrano/recipes/deploy/scm.rb +19 -0
  47. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  48. data/lib/capistrano/recipes/deploy/scm/base.rb +200 -0
  49. data/lib/capistrano/recipes/deploy/scm/bzr.rb +86 -0
  50. data/lib/capistrano/recipes/deploy/scm/cvs.rb +153 -0
  51. data/lib/capistrano/recipes/deploy/scm/darcs.rb +96 -0
  52. data/lib/capistrano/recipes/deploy/scm/git.rb +293 -0
  53. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +137 -0
  54. data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
  55. data/lib/capistrano/recipes/deploy/scm/perforce.rb +152 -0
  56. data/lib/capistrano/recipes/deploy/scm/subversion.rb +121 -0
  57. data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
  58. data/lib/capistrano/recipes/deploy/strategy/base.rb +92 -0
  59. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
  60. data/lib/capistrano/recipes/deploy/strategy/copy.rb +338 -0
  61. data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
  62. data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
  63. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +57 -0
  64. data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
  65. data/lib/capistrano/recipes/standard.rb +37 -0
  66. data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
  67. data/lib/capistrano/role.rb +102 -0
  68. data/lib/capistrano/server_definition.rb +56 -0
  69. data/lib/capistrano/shell.rb +265 -0
  70. data/lib/capistrano/ssh.rb +95 -0
  71. data/lib/capistrano/task_definition.rb +77 -0
  72. data/lib/capistrano/transfer.rb +218 -0
  73. data/lib/capistrano/version.rb +11 -0
  74. data/test/cli/execute_test.rb +132 -0
  75. data/test/cli/help_test.rb +165 -0
  76. data/test/cli/options_test.rb +329 -0
  77. data/test/cli/ui_test.rb +28 -0
  78. data/test/cli_test.rb +17 -0
  79. data/test/command_test.rb +322 -0
  80. data/test/configuration/actions/file_transfer_test.rb +61 -0
  81. data/test/configuration/actions/inspect_test.rb +76 -0
  82. data/test/configuration/actions/invocation_test.rb +288 -0
  83. data/test/configuration/alias_task_test.rb +118 -0
  84. data/test/configuration/callbacks_test.rb +201 -0
  85. data/test/configuration/connections_test.rb +439 -0
  86. data/test/configuration/execution_test.rb +175 -0
  87. data/test/configuration/loading_test.rb +148 -0
  88. data/test/configuration/namespace_dsl_test.rb +332 -0
  89. data/test/configuration/roles_test.rb +157 -0
  90. data/test/configuration/servers_test.rb +183 -0
  91. data/test/configuration/variables_test.rb +190 -0
  92. data/test/configuration_test.rb +77 -0
  93. data/test/deploy/local_dependency_test.rb +76 -0
  94. data/test/deploy/remote_dependency_test.rb +146 -0
  95. data/test/deploy/scm/accurev_test.rb +23 -0
  96. data/test/deploy/scm/base_test.rb +55 -0
  97. data/test/deploy/scm/bzr_test.rb +51 -0
  98. data/test/deploy/scm/darcs_test.rb +37 -0
  99. data/test/deploy/scm/git_test.rb +221 -0
  100. data/test/deploy/scm/mercurial_test.rb +134 -0
  101. data/test/deploy/scm/none_test.rb +35 -0
  102. data/test/deploy/scm/perforce_test.rb +23 -0
  103. data/test/deploy/scm/subversion_test.rb +40 -0
  104. data/test/deploy/strategy/copy_test.rb +360 -0
  105. data/test/extensions_test.rb +69 -0
  106. data/test/fixtures/cli_integration.rb +5 -0
  107. data/test/fixtures/config.rb +5 -0
  108. data/test/fixtures/custom.rb +3 -0
  109. data/test/logger_formatting_test.rb +149 -0
  110. data/test/logger_test.rb +134 -0
  111. data/test/recipes_test.rb +25 -0
  112. data/test/role_test.rb +11 -0
  113. data/test/server_definition_test.rb +121 -0
  114. data/test/shell_test.rb +96 -0
  115. data/test/ssh_test.rb +113 -0
  116. data/test/task_definition_test.rb +117 -0
  117. data/test/transfer_test.rb +168 -0
  118. data/test/utils.rb +37 -0
  119. metadata +316 -0
@@ -0,0 +1,46 @@
1
+ require 'capistrano/errors'
2
+
3
+ module Capistrano
4
+ class Configuration
5
+ module Actions
6
+ module Inspect
7
+
8
+ # Streams the result of the command from all servers that are the
9
+ # target of the current task. All these streams will be joined into a
10
+ # single one, so you can, say, watch 10 log files as though they were
11
+ # one. Do note that this is quite expensive from a bandwidth
12
+ # perspective, so use it with care.
13
+ #
14
+ # The command is invoked via #invoke_command.
15
+ #
16
+ # Usage:
17
+ #
18
+ # desc "Run a tail on multiple log files at the same time"
19
+ # task :tail_fcgi, :roles => :app do
20
+ # stream "tail -f #{shared_path}/log/fastcgi.crash.log"
21
+ # end
22
+ def stream(command, options={})
23
+ invoke_command(command, options.merge(:eof => !command.include?(sudo))) do |ch, stream, out|
24
+ puts out if stream == :out
25
+ warn "[err :: #{ch[:server]}] #{out}" if stream == :err
26
+ end
27
+ end
28
+
29
+ # Executes the given command on the first server targetted by the
30
+ # current task, collects it's stdout into a string, and returns the
31
+ # string. The command is invoked via #invoke_command.
32
+ def capture(command, options={})
33
+ output = ""
34
+ invoke_command(command, options.merge(:once => true, :eof => !command.include?(sudo))) do |ch, stream, data|
35
+ case stream
36
+ when :out then output << data
37
+ when :err then warn "[err :: #{ch[:server]}] #{data}"
38
+ end
39
+ end
40
+ output
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,329 @@
1
+ require 'capistrano/command'
2
+
3
+ module Capistrano
4
+ class Configuration
5
+ module Actions
6
+ module Invocation
7
+ def self.included(base) #:nodoc:
8
+ base.extend(ClassMethods)
9
+
10
+ base.send :alias_method, :initialize_without_invocation, :initialize
11
+ base.send :alias_method, :initialize, :initialize_with_invocation
12
+
13
+ base.default_io_proc = Proc.new do |ch, stream, out|
14
+ level = stream == :err ? :important : :info
15
+ ch[:options][:logger].send(level, out, "#{stream} :: #{ch[:server]}")
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ attr_accessor :default_io_proc
21
+ end
22
+
23
+ def initialize_with_invocation(*args) #:nodoc:
24
+ initialize_without_invocation(*args)
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)
80
+ end
81
+
82
+ # Invokes the given command. If a +via+ key is given, it will be used
83
+ # to determine what method to use to invoke the command. It defaults
84
+ # to :run, but may be :sudo, or any other method that conforms to the
85
+ # same interface as run and sudo.
86
+ def invoke_command(cmd, options={}, &block)
87
+ options = options.dup
88
+ via = options.delete(:via) || :run
89
+ send(via, cmd, options, &block)
90
+ end
91
+
92
+ # Execute the given command on all servers that are the target of the
93
+ # current task. If a block is given, it is invoked for all output
94
+ # generated by the command, and should accept three parameters: the SSH
95
+ # channel (which may be used to send data back to the remote process),
96
+ # the stream identifier (<tt>:err</tt> for stderr, and <tt>:out</tt> for
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.
149
+ def run(cmd, options={}, &block)
150
+ if options[:eof].nil? && !cmd.include?(sudo)
151
+ options = options.merge(:eof => !block_given?)
152
+ end
153
+ block ||= self.class.default_io_proc
154
+ tree = Command::Tree.new(self) { |t| t.else(cmd, &block) }
155
+ run_tree(tree, options)
156
+ end
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:
162
+ options = add_default_command_options(options)
163
+
164
+ if tree.branches.any? || tree.fallback
165
+ _, servers = filter_servers(options)
166
+ branches = branches_for_servers(tree,servers)
167
+ case branches.size
168
+ when 0
169
+ branches = tree.branches.dup + [tree.fallback]
170
+ branches.compact!
171
+ case branches.size
172
+ when 1
173
+ logger.debug "no servers for #{branches.first}"
174
+ else
175
+ logger.debug "no servers for commands"
176
+ branches.each{ |branch| logger.trace "-> #{branch.to_s(true)}" }
177
+ end
178
+ when 1
179
+ logger.debug "executing #{branches.first}" unless options[:silent]
180
+ else
181
+ logger.debug "executing multiple commands in parallel"
182
+ branches.each {|branch| logger.trace "-> #{branch.to_s(true)}" }
183
+ end
184
+ else
185
+ raise ArgumentError, "attempt to execute without specifying a command"
186
+ end
187
+
188
+ return if dry_run || (debug && continue_execution(tree) == false)
189
+
190
+ tree.each do |branch|
191
+ if branch.command.include?(sudo)
192
+ branch.callback = sudo_behavior_callback(branch.callback)
193
+ end
194
+ end
195
+
196
+ execute_on_servers(options) do |servers|
197
+ targets = servers.map { |s| sessions[s] }
198
+ Command.process(tree, targets, options.merge(:logger => logger))
199
+ end
200
+ end
201
+
202
+ # Returns the command string used by capistrano to invoke a comamnd via
203
+ # sudo.
204
+ #
205
+ # run "#{sudo :as => 'bob'} mkdir /path/to/dir"
206
+ #
207
+ # It can also be invoked like #run, but executing the command via sudo.
208
+ # This assumes that the sudo password (if required) is the same as the
209
+ # password for logging in to the server.
210
+ #
211
+ # sudo "mkdir /path/to/dir"
212
+ #
213
+ # Also, this method understands a <tt>:sudo</tt> configuration variable,
214
+ # which (if specified) will be used as the full path to the sudo
215
+ # executable on the remote machine:
216
+ #
217
+ # set :sudo, "/opt/local/bin/sudo"
218
+ #
219
+ # If you know what you're doing, you can also set <tt>:sudo_prompt</tt>,
220
+ # which tells capistrano which prompt sudo should use when asking for
221
+ # a password. (This is so that capistrano knows what prompt to look for
222
+ # in the output.) If you set :sudo_prompt to an empty string, Capistrano
223
+ # will not send a preferred prompt.
224
+ def sudo(*parameters, &block)
225
+ options = parameters.last.is_a?(Hash) ? parameters.pop.dup : {}
226
+ command = parameters.first
227
+ user = options[:as] && "-u #{options.delete(:as)}"
228
+
229
+ sudo_prompt_option = "-p '#{sudo_prompt}'" unless sudo_prompt.empty?
230
+ sudo_command = [fetch(:sudo, "sudo"), sudo_prompt_option, user].compact.join(" ")
231
+
232
+ if command
233
+ command = sudo_command + " " + command
234
+ run(command, options, &block)
235
+ else
236
+ return sudo_command
237
+ end
238
+ end
239
+
240
+ # Returns a Proc object that defines the behavior of the sudo
241
+ # callback. The returned Proc will defer to the +fallback+ argument
242
+ # (which should also be a Proc) for any output it does not
243
+ # explicitly handle.
244
+ def sudo_behavior_callback(fallback) #:nodoc:
245
+ # in order to prevent _each host_ from prompting when the password
246
+ # was wrong, let's track which host prompted first and only allow
247
+ # subsequent prompts from that host.
248
+ prompt_host = nil
249
+
250
+ Proc.new do |ch, stream, out|
251
+ if out =~ /^Sorry, try again/
252
+ if prompt_host.nil? || prompt_host == ch[:server]
253
+ prompt_host = ch[:server]
254
+ logger.important out, "#{stream} :: #{ch[:server]}"
255
+ reset! :password
256
+ end
257
+ end
258
+
259
+ if out =~ /^#{Regexp.escape(sudo_prompt)}/
260
+ ch.send_data "#{self[:password]}\n"
261
+ elsif fallback
262
+ fallback.call(ch, stream, out)
263
+ end
264
+ end
265
+ end
266
+
267
+ # Merges the various default command options into the options hash and
268
+ # returns the result. The default command options that are understand
269
+ # are:
270
+ #
271
+ # * :default_environment: If the :env key already exists, the :env
272
+ # key is merged into default_environment and then added back into
273
+ # options.
274
+ # * :default_shell: if the :shell key already exists, it will be used.
275
+ # Otherwise, if the :default_shell key exists in the configuration,
276
+ # it will be used. Otherwise, no :shell key is added.
277
+ def add_default_command_options(options)
278
+ defaults = self[:default_run_options]
279
+ options = defaults.merge(options)
280
+
281
+ env = self[:default_environment]
282
+ env = env.merge(options[:env]) if options[:env]
283
+ options[:env] = env unless env.empty?
284
+
285
+ shell = options[:shell] || self[:default_shell]
286
+ options[:shell] = shell unless shell.nil?
287
+
288
+ options
289
+ end
290
+
291
+ # Returns the prompt text to use with sudo
292
+ def sudo_prompt
293
+ fetch(:sudo_prompt, "sudo password: ")
294
+ end
295
+
296
+ def continue_execution(tree)
297
+ if tree.branches.length == 1
298
+ continue_execution_for_branch(tree.branches.first)
299
+ else
300
+ tree.each { |branch| branch.skip! unless continue_execution_for_branch(branch) }
301
+ tree.any? { |branch| !branch.skip? }
302
+ end
303
+ end
304
+
305
+ def continue_execution_for_branch(branch)
306
+ case Capistrano::CLI.debug_prompt(branch)
307
+ when "y"
308
+ true
309
+ when "n"
310
+ false
311
+ when "a"
312
+ exit(-1)
313
+ end
314
+ end
315
+
316
+ private
317
+ def branches_for_servers(tree,servers)
318
+ servers.inject([]) do |branches,server|
319
+ if server_branches = tree.branches_for(server)
320
+ branches += server_branches
321
+ end
322
+ branches
323
+ end
324
+ end
325
+
326
+ end
327
+ end
328
+ end
329
+ 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
@@ -0,0 +1,147 @@
1
+ require 'capistrano/callback'
2
+
3
+ module Capistrano
4
+ class Configuration
5
+ module Callbacks
6
+ def self.included(base) #:nodoc:
7
+ %w(initialize invoke_task_directly).each do |method|
8
+ base.send :alias_method, "#{method}_without_callbacks", method
9
+ base.send :alias_method, method, "#{method}_with_callbacks"
10
+ end
11
+ end
12
+
13
+ # The hash of callbacks that have been registered for this configuration
14
+ attr_reader :callbacks
15
+
16
+ def initialize_with_callbacks(*args) #:nodoc:
17
+ initialize_without_callbacks(*args)
18
+ @callbacks = {}
19
+ end
20
+
21
+ def invoke_task_directly_with_callbacks(task) #:nodoc:
22
+
23
+ trigger :before, task
24
+
25
+ result = invoke_task_directly_without_callbacks(task)
26
+
27
+ trigger :after, task
28
+
29
+ return result
30
+ end
31
+
32
+ # Defines a callback to be invoked before the given task. You must
33
+ # specify the fully-qualified task name, both for the primary task, and
34
+ # for the task(s) to be executed before. Alternatively, you can pass a
35
+ # block to be executed before the given task.
36
+ #
37
+ # before "deploy:update_code", :record_difference
38
+ # before :deploy, "custom:log_deploy"
39
+ # before :deploy, :this, "then:this", "and:then:this"
40
+ # before :some_task do
41
+ # puts "an anonymous hook!"
42
+ # end
43
+ #
44
+ # This just provides a convenient interface to the more general #on method.
45
+ def before(task_name, *args, &block)
46
+ options = args.last.is_a?(Hash) ? args.pop : {}
47
+ args << options.merge(:only => task_name)
48
+ on :before, *args, &block
49
+ end
50
+
51
+ # Defines a callback to be invoked after the given task. You must
52
+ # specify the fully-qualified task name, both for the primary task, and
53
+ # for the task(s) to be executed after. Alternatively, you can pass a
54
+ # block to be executed after the given task.
55
+ #
56
+ # after "deploy:update_code", :log_difference
57
+ # after :deploy, "custom:announce"
58
+ # after :deploy, :this, "then:this", "and:then:this"
59
+ # after :some_task do
60
+ # puts "an anonymous hook!"
61
+ # end
62
+ #
63
+ # This just provides a convenient interface to the more general #on method.
64
+ def after(task_name, *args, &block)
65
+ options = args.last.is_a?(Hash) ? args.pop : {}
66
+ args << options.merge(:only => task_name)
67
+ on :after, *args, &block
68
+ end
69
+
70
+ # Defines one or more callbacks to be invoked in response to some event.
71
+ # Capistrano currently understands the following events:
72
+ #
73
+ # * :before, triggered before a task is invoked
74
+ # * :after, triggered after a task is invoked
75
+ # * :start, triggered before a top-level task is invoked via the command-line
76
+ # * :finish, triggered when a top-level task completes
77
+ # * :load, triggered after all recipes have loaded
78
+ # * :exit, triggered after all tasks have completed
79
+ #
80
+ # Specify the (fully-qualified) task names that you want invoked in
81
+ # response to the event. Alternatively, you can specify a block to invoke
82
+ # when the event is triggered. You can also pass a hash of options as the
83
+ # last parameter, which may include either of two keys:
84
+ #
85
+ # * :only, should specify an array of task names. Restricts this callback
86
+ # so that it will only fire when the event applies to those tasks.
87
+ # * :except, should specify an array of task names. Restricts this callback
88
+ # so that it will never fire when the event applies to those tasks.
89
+ #
90
+ # Usage:
91
+ #
92
+ # on :before, "some:hook", "another:hook", :only => "deploy:update"
93
+ # on :after, "some:hook", :except => "deploy:create_symlink"
94
+ # on :before, "global:hook"
95
+ # on :after, :only => :deploy do
96
+ # puts "after deploy here"
97
+ # end
98
+ def on(event, *args, &block)
99
+ options = args.last.is_a?(Hash) ? args.pop : {}
100
+ callbacks[event] ||= []
101
+
102
+ if args.empty? && block.nil?
103
+ raise ArgumentError, "please specify either a task name or a block to invoke"
104
+ elsif args.any? && block
105
+ raise ArgumentError, "please specify only a task name or a block, but not both"
106
+ elsif block
107
+ callbacks[event] << ProcCallback.new(block, options)
108
+ else
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 }
128
+ end
129
+
130
+ names
131
+ end
132
+
133
+ # Trigger the named event for the named task. All associated callbacks
134
+ # will be fired, in the order they were defined.
135
+ def trigger(event, task=nil)
136
+ pending = Array(callbacks[event]).select { |c| c.applies_to?(task) }
137
+ if pending.any?
138
+ msg = "triggering #{event} callbacks"
139
+ msg << " for `#{task.fully_qualified_name}'" if task
140
+ logger.trace(msg)
141
+ pending.each { |callback| callback.call }
142
+ end
143
+ end
144
+
145
+ end
146
+ end
147
+ end