capistrano 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/cap +11 -0
- data/examples/sample.rb +113 -0
- data/lib/capistrano.rb +1 -0
- data/lib/capistrano/actor.rb +438 -0
- data/lib/capistrano/cli.rb +295 -0
- data/lib/capistrano/command.rb +90 -0
- data/lib/capistrano/configuration.rb +243 -0
- data/lib/capistrano/extensions.rb +38 -0
- data/lib/capistrano/gateway.rb +118 -0
- data/lib/capistrano/generators/rails/deployment/deployment_generator.rb +25 -0
- data/lib/capistrano/generators/rails/deployment/templates/capistrano.rake +46 -0
- data/lib/capistrano/generators/rails/deployment/templates/deploy.rb +122 -0
- data/lib/capistrano/generators/rails/loader.rb +20 -0
- data/lib/capistrano/logger.rb +59 -0
- data/lib/capistrano/recipes/standard.rb +242 -0
- data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
- data/lib/capistrano/scm/base.rb +62 -0
- data/lib/capistrano/scm/baz.rb +118 -0
- data/lib/capistrano/scm/bzr.rb +70 -0
- data/lib/capistrano/scm/cvs.rb +124 -0
- data/lib/capistrano/scm/darcs.rb +27 -0
- data/lib/capistrano/scm/perforce.rb +139 -0
- data/lib/capistrano/scm/subversion.rb +122 -0
- data/lib/capistrano/ssh.rb +39 -0
- data/lib/capistrano/transfer.rb +90 -0
- data/lib/capistrano/utils.rb +26 -0
- data/lib/capistrano/version.rb +30 -0
- data/test/actor_test.rb +294 -0
- data/test/command_test.rb +43 -0
- data/test/configuration_test.rb +233 -0
- data/test/fixtures/config.rb +5 -0
- data/test/fixtures/custom.rb +3 -0
- data/test/scm/cvs_test.rb +186 -0
- data/test/scm/subversion_test.rb +137 -0
- data/test/ssh_test.rb +104 -0
- data/test/utils.rb +50 -0
- metadata +107 -0
data/bin/cap
ADDED
data/examples/sample.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# You must always specify the application and repository for every recipe. The
|
2
|
+
# repository must be the URL of the repository you want this recipe to
|
3
|
+
# correspond to. The deploy_to path must be the path on each machine that will
|
4
|
+
# form the root of the application path.
|
5
|
+
|
6
|
+
set :application, "sample"
|
7
|
+
set :repository, "http://svn.example.com/#{application}/trunk"
|
8
|
+
|
9
|
+
# The deploy_to path is optional, defaulting to "/u/apps/#{application}".
|
10
|
+
|
11
|
+
set :deploy_to, "/path/to/app/root"
|
12
|
+
|
13
|
+
# The user value is optional, defaulting to user-name of the current user. This
|
14
|
+
# is the user name that will be used when logging into the deployment boxes.
|
15
|
+
|
16
|
+
set :user, "flippy"
|
17
|
+
|
18
|
+
# By default, the source control module (scm) is set to "subversion". You can
|
19
|
+
# set it to any supported scm:
|
20
|
+
|
21
|
+
set :scm, :subversion
|
22
|
+
|
23
|
+
# gateway is optional, but allows you to specify the address of a computer that
|
24
|
+
# will be used to tunnel other requests through, such as when your machines are
|
25
|
+
# all behind a VPN or something
|
26
|
+
|
27
|
+
set :gateway, "gateway.example.com"
|
28
|
+
|
29
|
+
# You can define any number of roles, each of which contains any number of
|
30
|
+
# machines. Roles might include such things as :web, or :app, or :db, defining
|
31
|
+
# what the purpose of each machine is. You can also specify options that can
|
32
|
+
# be used to single out a specific subset of boxes in a particular role, like
|
33
|
+
# :primary => true.
|
34
|
+
|
35
|
+
role :web, "www01.example.com", "www02.example.com"
|
36
|
+
role :app, "app01.example.com", "app02.example.com", "app03.example.com"
|
37
|
+
role :db, "db01.example.com", :primary => true
|
38
|
+
role :db, "db02.example.com", "db03.example.com"
|
39
|
+
|
40
|
+
# Define tasks that run on all (or only some) of the machines. You can specify
|
41
|
+
# a role (or set of roles) that each task should be executed on. You can also
|
42
|
+
# narrow the set of servers to a subset of a role by specifying options, which
|
43
|
+
# must match the options given for the servers to select (like :primary => true)
|
44
|
+
|
45
|
+
desc <<DESC
|
46
|
+
An imaginary backup task. (Execute the 'show_tasks' task to display all
|
47
|
+
available tasks.)
|
48
|
+
DESC
|
49
|
+
|
50
|
+
task :backup, :roles => :db, :only => { :primary => true } do
|
51
|
+
# the on_rollback handler is only executed if this task is executed within
|
52
|
+
# a transaction (see below), AND it or a subsequent task fails.
|
53
|
+
on_rollback { delete "/tmp/dump.sql" }
|
54
|
+
|
55
|
+
run "mysqldump -u theuser -p thedatabase > /tmp/dump.sql" do |ch, stream, out|
|
56
|
+
ch.send_data "thepassword\n" if out =~ /^Enter password:/
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Tasks may take advantage of several different helper methods to interact
|
61
|
+
# with the remote server(s). These are:
|
62
|
+
#
|
63
|
+
# * run(command, options={}, &block): execute the given command on all servers
|
64
|
+
# associated with the current task, in parallel. The block, if given, should
|
65
|
+
# accept three parameters: the communication channel, a symbol identifying the
|
66
|
+
# type of stream (:err or :out), and the data. The block is invoked for all
|
67
|
+
# output from the command, allowing you to inspect output and act
|
68
|
+
# accordingly.
|
69
|
+
# * sudo(command, options={}, &block): same as run, but it executes the command
|
70
|
+
# via sudo.
|
71
|
+
# * delete(path, options={}): deletes the given file or directory from all
|
72
|
+
# associated servers. If :recursive => true is given in the options, the
|
73
|
+
# delete uses "rm -rf" instead of "rm -f".
|
74
|
+
# * put(buffer, path, options={}): creates or overwrites a file at "path" on
|
75
|
+
# all associated servers, populating it with the contents of "buffer". You
|
76
|
+
# can specify :mode as an integer value, which will be used to set the mode
|
77
|
+
# on the file.
|
78
|
+
# * render(template, options={}) or render(options={}): renders the given
|
79
|
+
# template and returns a string. Alternatively, if the :template key is given,
|
80
|
+
# it will be treated as the contents of the template to render. Any other keys
|
81
|
+
# are treated as local variables, which are made available to the (ERb)
|
82
|
+
# template.
|
83
|
+
|
84
|
+
desc "Demonstrates the various helper methods available to recipes."
|
85
|
+
task :helper_demo do
|
86
|
+
# "setup" is a standard task which sets up the directory structure on the
|
87
|
+
# remote servers. It is a good idea to run the "setup" task at least once
|
88
|
+
# at the beginning of your app's lifetime (it is non-destructive).
|
89
|
+
setup
|
90
|
+
|
91
|
+
buffer = render("maintenance.rhtml", :deadline => ENV['UNTIL'])
|
92
|
+
put buffer, "#{shared_path}/system/maintenance.html", :mode => 0644
|
93
|
+
sudo "killall -USR1 dispatch.fcgi"
|
94
|
+
run "#{release_path}/script/spin"
|
95
|
+
delete "#{shared_path}/system/maintenance.html"
|
96
|
+
end
|
97
|
+
|
98
|
+
# You can use "transaction" to indicate that if any of the tasks within it fail,
|
99
|
+
# all should be rolled back (for each task that specifies an on_rollback
|
100
|
+
# handler).
|
101
|
+
|
102
|
+
desc "A task demonstrating the use of transactions."
|
103
|
+
task :long_deploy do
|
104
|
+
transaction do
|
105
|
+
update_code
|
106
|
+
disable_web
|
107
|
+
symlink
|
108
|
+
migrate
|
109
|
+
end
|
110
|
+
|
111
|
+
restart
|
112
|
+
enable_web
|
113
|
+
end
|
data/lib/capistrano.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'capistrano/configuration'
|
@@ -0,0 +1,438 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'capistrano/command'
|
3
|
+
require 'capistrano/transfer'
|
4
|
+
require 'capistrano/gateway'
|
5
|
+
require 'capistrano/ssh'
|
6
|
+
require 'capistrano/utils'
|
7
|
+
|
8
|
+
module Capistrano
|
9
|
+
|
10
|
+
# An Actor is the entity that actually does the work of determining which
|
11
|
+
# servers should be the target of a particular task, and of executing the
|
12
|
+
# task on each of them in parallel. An Actor is never instantiated
|
13
|
+
# directly--rather, you create a new Configuration instance, and access the
|
14
|
+
# new actor via Configuration#actor.
|
15
|
+
class Actor
|
16
|
+
|
17
|
+
# An adaptor for making the SSH interface look and act like that of the
|
18
|
+
# Gateway class.
|
19
|
+
class DefaultConnectionFactory #:nodoc:
|
20
|
+
def initialize(config)
|
21
|
+
@config= config
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect_to(server)
|
25
|
+
SSH.connect(server, @config)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class <<self
|
30
|
+
attr_accessor :connection_factory
|
31
|
+
attr_accessor :command_factory
|
32
|
+
attr_accessor :transfer_factory
|
33
|
+
attr_accessor :default_io_proc
|
34
|
+
end
|
35
|
+
|
36
|
+
self.connection_factory = DefaultConnectionFactory
|
37
|
+
self.command_factory = Command
|
38
|
+
self.transfer_factory = Transfer
|
39
|
+
|
40
|
+
self.default_io_proc = Proc.new do |ch, stream, out|
|
41
|
+
level = out == :error ? :important : :info
|
42
|
+
ch[:actor].logger.send(level, out, "#{stream} :: #{ch[:host]}")
|
43
|
+
end
|
44
|
+
|
45
|
+
# The configuration instance associated with this actor.
|
46
|
+
attr_reader :configuration
|
47
|
+
|
48
|
+
# A hash of the tasks known to this actor, keyed by name. The values are
|
49
|
+
# instances of Actor::Task.
|
50
|
+
attr_reader :tasks
|
51
|
+
|
52
|
+
# A hash of the SSH sessions that are currently open and available.
|
53
|
+
# Because sessions are constructed lazily, this will only contain
|
54
|
+
# connections to those servers that have been the targets of one or more
|
55
|
+
# executed tasks.
|
56
|
+
attr_reader :sessions
|
57
|
+
|
58
|
+
# The call stack of the tasks. The currently executing task may inspect
|
59
|
+
# this to see who its caller was. The current task is always the last
|
60
|
+
# element of this stack.
|
61
|
+
attr_reader :task_call_frames
|
62
|
+
|
63
|
+
# The history of executed tasks. This will be an array of all tasks that
|
64
|
+
# have been executed, in the order in which they were called.
|
65
|
+
attr_reader :task_call_history
|
66
|
+
|
67
|
+
# A struct for representing a single instance of an invoked task.
|
68
|
+
TaskCallFrame = Struct.new(:name, :rollback)
|
69
|
+
|
70
|
+
# Represents the definition of a single task.
|
71
|
+
class Task #:nodoc:
|
72
|
+
attr_reader :name, :actor, :options
|
73
|
+
|
74
|
+
def initialize(name, actor, options)
|
75
|
+
@name, @actor, @options = name, actor, options
|
76
|
+
@servers = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the list of servers (_not_ connections to servers) that are
|
80
|
+
# the target of this task.
|
81
|
+
def servers
|
82
|
+
unless @servers
|
83
|
+
roles = [*(@options[:roles] || actor.configuration.roles.keys)].
|
84
|
+
map { |name|
|
85
|
+
actor.configuration.roles[name] or
|
86
|
+
raise ArgumentError, "task #{self.name.inspect} references non-existant role #{name.inspect}"
|
87
|
+
}.flatten
|
88
|
+
only = @options[:only] || {}
|
89
|
+
|
90
|
+
unless only.empty?
|
91
|
+
roles = roles.delete_if do |role|
|
92
|
+
catch(:done) do
|
93
|
+
only.keys.each do |key|
|
94
|
+
throw(:done, true) if role.options[key] != only[key]
|
95
|
+
end
|
96
|
+
false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
@servers = roles.map { |role| role.host }.uniq
|
102
|
+
end
|
103
|
+
|
104
|
+
@servers
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def initialize(config) #:nodoc:
|
109
|
+
@configuration = config
|
110
|
+
@tasks = {}
|
111
|
+
@task_call_frames = []
|
112
|
+
@sessions = {}
|
113
|
+
@factory = self.class.connection_factory.new(configuration)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Define a new task for this actor. The block will be invoked when this
|
117
|
+
# task is called.
|
118
|
+
def define_task(name, options={}, &block)
|
119
|
+
@tasks[name] = (options[:task_class] || Task).new(name, self, options)
|
120
|
+
define_method(name) do
|
121
|
+
send "before_#{name}" if respond_to? "before_#{name}"
|
122
|
+
logger.debug "executing task #{name}"
|
123
|
+
begin
|
124
|
+
push_task_call_frame name
|
125
|
+
result = instance_eval(&block)
|
126
|
+
ensure
|
127
|
+
pop_task_call_frame
|
128
|
+
end
|
129
|
+
send "after_#{name}" if respond_to? "after_#{name}"
|
130
|
+
result
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Iterates over each task, in alphabetical order. A hash object is
|
135
|
+
# yielded for each task, which includes the task's name (:name), the
|
136
|
+
# length of the longest task name (:longest), and the task's description,
|
137
|
+
# reformatted as a single line (:desc).
|
138
|
+
def each_task
|
139
|
+
keys = tasks.keys.sort_by { |a| a.to_s }
|
140
|
+
longest = keys.inject(0) { |len,key| key.to_s.length > len ? key.to_s.length : len } + 2
|
141
|
+
|
142
|
+
keys.sort_by { |a| a.to_s }.each do |key|
|
143
|
+
desc = (tasks[key].options[:desc] || "").gsub(/(?:\r?\n)+[ \t]*/, " ").strip
|
144
|
+
info = { :task => key, :longest => longest, :desc => desc }
|
145
|
+
yield info
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Dump all tasks and (brief) descriptions in YAML format for consumption
|
150
|
+
# by other processes. Returns a string containing the YAML-formatted data.
|
151
|
+
def dump_tasks
|
152
|
+
data = ""
|
153
|
+
each_task do |info|
|
154
|
+
desc = info[:desc].split(/\. /).first || ""
|
155
|
+
desc << "." if !desc.empty? && desc[-1] != ?.
|
156
|
+
data << "#{info[:task]}: #{desc}\n"
|
157
|
+
end
|
158
|
+
data
|
159
|
+
end
|
160
|
+
|
161
|
+
# Execute the given command on all servers that are the target of the
|
162
|
+
# current task. If a block is given, it is invoked for all output
|
163
|
+
# generated by the command, and should accept three parameters: the SSH
|
164
|
+
# channel (which may be used to send data back to the remote process),
|
165
|
+
# the stream identifier (<tt>:err</tt> for stderr, and <tt>:out</tt> for
|
166
|
+
# stdout), and the data that was received.
|
167
|
+
#
|
168
|
+
# If +pretend+ mode is active, this does nothing.
|
169
|
+
def run(cmd, options={}, &block)
|
170
|
+
block ||= default_io_proc
|
171
|
+
logger.debug "executing #{cmd.strip.inspect}"
|
172
|
+
|
173
|
+
execute_on_servers(options) do |servers|
|
174
|
+
# execute the command on each server in parallel
|
175
|
+
command = self.class.command_factory.new(servers, cmd, block, options, self)
|
176
|
+
command.process! # raises an exception if command fails on any server
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Deletes the given file from all servers targetted by the current task.
|
181
|
+
# If <tt>:recursive => true</tt> is specified, it may be used to remove
|
182
|
+
# directories.
|
183
|
+
def delete(path, options={})
|
184
|
+
cmd = "rm -%sf #{path}" % (options[:recursive] ? "r" : "")
|
185
|
+
run(cmd, options)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Store the given data at the given location on all servers targetted by
|
189
|
+
# the current task. If <tt>:mode</tt> is specified it is used to set the
|
190
|
+
# mode on the file.
|
191
|
+
def put(data, path, options={})
|
192
|
+
if Capistrano::SFTP
|
193
|
+
execute_on_servers(options) do |servers|
|
194
|
+
transfer = self.class.transfer_factory.new(servers, self, path, :data => data,
|
195
|
+
:mode => options[:mode])
|
196
|
+
transfer.process!
|
197
|
+
end
|
198
|
+
else
|
199
|
+
# Poor-man's SFTP... just run a cat on the remote end, and send data
|
200
|
+
# to it.
|
201
|
+
|
202
|
+
cmd = "cat > #{path}"
|
203
|
+
cmd << " && chmod #{options[:mode].to_s(8)} #{path}" if options[:mode]
|
204
|
+
run(cmd, options.merge(:data => data + "\n\4")) do |ch, stream, out|
|
205
|
+
logger.important out, "#{stream} :: #{ch[:host]}" if stream == :err
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Like #run, but executes the command via <tt>sudo</tt>. This assumes that
|
211
|
+
# the sudo password (if required) is the same as the password for logging
|
212
|
+
# in to the server.
|
213
|
+
def sudo(command, options={}, &block)
|
214
|
+
block ||= default_io_proc
|
215
|
+
|
216
|
+
# in order to prevent _each host_ from prompting when the password was
|
217
|
+
# wrong, let's track which host prompted first and only allow subsequent
|
218
|
+
# prompts from that host.
|
219
|
+
prompt_host = nil
|
220
|
+
|
221
|
+
run "sudo #{command}", options do |ch, stream, out|
|
222
|
+
if out =~ /^Password:/
|
223
|
+
ch.send_data "#{password}\n"
|
224
|
+
elsif out =~ /try again/
|
225
|
+
if prompt_host.nil? || prompt_host == ch[:host]
|
226
|
+
prompt_host = ch[:host]
|
227
|
+
logger.important out, "#{stream} :: #{ch[:host]}"
|
228
|
+
# reset the password to it's original value and prepare for another
|
229
|
+
# pass (the reset allows the password prompt to be attempted again
|
230
|
+
# if the password variable was originally a proc (the default)
|
231
|
+
set :password, self[:original_value][:password] || self[:password]
|
232
|
+
end
|
233
|
+
else
|
234
|
+
block.call(ch, stream, out)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Renders an ERb template and returns the result. This is useful for
|
240
|
+
# dynamically building documents to store on the remote servers.
|
241
|
+
#
|
242
|
+
# Usage:
|
243
|
+
#
|
244
|
+
# render("something", :foo => "hello")
|
245
|
+
# look for "something.rhtml" in the current directory, or in the
|
246
|
+
# capistrano/recipes/templates directory, and render it with
|
247
|
+
# foo defined as a local variable with the value "hello".
|
248
|
+
#
|
249
|
+
# render(:file => "something", :foo => "hello")
|
250
|
+
# same as above
|
251
|
+
#
|
252
|
+
# render(:template => "<%= foo %> world", :foo => "hello")
|
253
|
+
# treat the given string as an ERb template and render it with
|
254
|
+
# the given hash of local variables active.
|
255
|
+
def render(*args)
|
256
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
257
|
+
options[:file] = args.shift if args.first.is_a?(String)
|
258
|
+
raise ArgumentError, "too many parameters" unless args.empty?
|
259
|
+
|
260
|
+
case
|
261
|
+
when options[:file]
|
262
|
+
file = options.delete :file
|
263
|
+
unless file[0] == ?/
|
264
|
+
dirs = [".",
|
265
|
+
File.join(File.dirname(__FILE__), "recipes", "templates")]
|
266
|
+
dirs.each do |dir|
|
267
|
+
if File.file?(File.join(dir, file))
|
268
|
+
file = File.join(dir, file)
|
269
|
+
break
|
270
|
+
elsif File.file?(File.join(dir, file + ".rhtml"))
|
271
|
+
file = File.join(dir, file + ".rhtml")
|
272
|
+
break
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
render options.merge(:template => File.read(file))
|
278
|
+
|
279
|
+
when options[:template]
|
280
|
+
erb = ERB.new(options[:template])
|
281
|
+
b = Proc.new { binding }.call
|
282
|
+
options.each do |key, value|
|
283
|
+
next if key == :template
|
284
|
+
eval "#{key} = options[:#{key}]", b
|
285
|
+
end
|
286
|
+
erb.result(b)
|
287
|
+
|
288
|
+
else
|
289
|
+
raise ArgumentError, "no file or template given for rendering"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Inspects the remote servers to determine the list of all released versions
|
294
|
+
# of the software. Releases are sorted with the most recent release last.
|
295
|
+
def releases
|
296
|
+
unless @releases
|
297
|
+
buffer = ""
|
298
|
+
run "ls -x1 #{releases_path}", :once => true do |ch, str, out|
|
299
|
+
buffer << out if str == :out
|
300
|
+
raise "could not determine releases #{out.inspect}" if str == :err
|
301
|
+
end
|
302
|
+
@releases = buffer.split.sort
|
303
|
+
end
|
304
|
+
|
305
|
+
@releases
|
306
|
+
end
|
307
|
+
|
308
|
+
# Returns the most recent deployed release
|
309
|
+
def current_release
|
310
|
+
release_path(releases.last)
|
311
|
+
end
|
312
|
+
|
313
|
+
# Returns the release immediately before the currently deployed one
|
314
|
+
def previous_release
|
315
|
+
release_path(releases[-2])
|
316
|
+
end
|
317
|
+
|
318
|
+
# Invoke a set of tasks in a transaction. If any task fails (raises an
|
319
|
+
# exception), all tasks executed within the transaction are inspected to
|
320
|
+
# see if they have an associated on_rollback hook, and if so, that hook
|
321
|
+
# is called.
|
322
|
+
def transaction
|
323
|
+
if task_call_history
|
324
|
+
yield
|
325
|
+
else
|
326
|
+
logger.info "transaction: start"
|
327
|
+
begin
|
328
|
+
@task_call_history = []
|
329
|
+
yield
|
330
|
+
logger.info "transaction: commit"
|
331
|
+
rescue Object => e
|
332
|
+
current = task_call_history.last
|
333
|
+
logger.important "transaction: rollback", current ? current.name : "transaction start"
|
334
|
+
task_call_history.reverse.each do |task|
|
335
|
+
begin
|
336
|
+
logger.debug "rolling back", task.name
|
337
|
+
task.rollback.call if task.rollback
|
338
|
+
rescue Object => e
|
339
|
+
logger.info "exception while rolling back: #{e.class}, #{e.message}", task.name
|
340
|
+
end
|
341
|
+
end
|
342
|
+
raise
|
343
|
+
ensure
|
344
|
+
@task_call_history = nil
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# Specifies an on_rollback hook for the currently executing task. If this
|
350
|
+
# or any subsequent task then fails, and a transaction is active, this
|
351
|
+
# hook will be executed.
|
352
|
+
def on_rollback(&block)
|
353
|
+
task_call_frames.last.rollback = block
|
354
|
+
end
|
355
|
+
|
356
|
+
# An instance-level reader for the class' #default_io_proc attribute.
|
357
|
+
def default_io_proc
|
358
|
+
self.class.default_io_proc
|
359
|
+
end
|
360
|
+
|
361
|
+
# Used to force connections to be made to the current task's servers.
|
362
|
+
# Connections are normally made lazily in Capistrano--you can use this
|
363
|
+
# to force them open before performing some operation that might be
|
364
|
+
# time-sensitive.
|
365
|
+
def connect!(options={})
|
366
|
+
execute_on_servers(options) { }
|
367
|
+
end
|
368
|
+
|
369
|
+
def current_task
|
370
|
+
return nil if task_call_frames.empty?
|
371
|
+
tasks[task_call_frames.last.name]
|
372
|
+
end
|
373
|
+
|
374
|
+
def metaclass
|
375
|
+
class << self; self; end
|
376
|
+
end
|
377
|
+
|
378
|
+
private
|
379
|
+
|
380
|
+
def define_method(name, &block)
|
381
|
+
metaclass.send(:define_method, name, &block)
|
382
|
+
end
|
383
|
+
|
384
|
+
def push_task_call_frame(name)
|
385
|
+
frame = TaskCallFrame.new(name)
|
386
|
+
task_call_frames.push frame
|
387
|
+
task_call_history.push frame if task_call_history
|
388
|
+
end
|
389
|
+
|
390
|
+
def pop_task_call_frame
|
391
|
+
task_call_frames.pop
|
392
|
+
end
|
393
|
+
|
394
|
+
def establish_connections(servers)
|
395
|
+
@factory = establish_gateway if needs_gateway?
|
396
|
+
servers.each do |server|
|
397
|
+
@sessions[server] ||= @factory.connect_to(server)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def establish_gateway
|
402
|
+
logger.debug "establishing connection to gateway #{gateway}"
|
403
|
+
@established_gateway = true
|
404
|
+
Gateway.new(gateway, configuration)
|
405
|
+
end
|
406
|
+
|
407
|
+
def needs_gateway?
|
408
|
+
gateway && !@established_gateway
|
409
|
+
end
|
410
|
+
|
411
|
+
def execute_on_servers(options)
|
412
|
+
task = current_task
|
413
|
+
servers = task.servers
|
414
|
+
|
415
|
+
if servers.empty?
|
416
|
+
raise "The #{task.name} task is only run for servers matching #{task.options.inspect}, but no servers matched"
|
417
|
+
end
|
418
|
+
|
419
|
+
servers = [servers.first] if options[:once]
|
420
|
+
logger.trace "servers: #{servers.inspect}"
|
421
|
+
|
422
|
+
if !pretend
|
423
|
+
# establish connections to those servers, as necessary
|
424
|
+
establish_connections(servers)
|
425
|
+
yield servers
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
def method_missing(sym, *args, &block)
|
430
|
+
if @configuration.respond_to?(sym)
|
431
|
+
@configuration.send(sym, *args, &block)
|
432
|
+
else
|
433
|
+
super
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
end
|
438
|
+
end
|