capistrano 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
@@ -0,0 +1,295 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'capistrano'
|
3
|
+
|
4
|
+
module Capistrano
|
5
|
+
# The CLI class encapsulates the behavior of capistrano when it is invoked
|
6
|
+
# as a command-line utility. This allows other programs to embed ST and
|
7
|
+
# preserve it's command-line semantics.
|
8
|
+
class CLI
|
9
|
+
# Invoke capistrano using the ARGV array as the option parameters. This
|
10
|
+
# is what the command-line capistrano utility does.
|
11
|
+
def self.execute!
|
12
|
+
new.execute!
|
13
|
+
end
|
14
|
+
|
15
|
+
# The following determines whether or not echo-suppression is available.
|
16
|
+
# This requires the termios library to be installed (which, unfortunately,
|
17
|
+
# is not available for Windows).
|
18
|
+
begin
|
19
|
+
if !defined?(USE_TERMIOS) || USE_TERMIOS
|
20
|
+
require 'termios'
|
21
|
+
else
|
22
|
+
raise LoadError
|
23
|
+
end
|
24
|
+
|
25
|
+
# Enable or disable stdin echoing to the terminal.
|
26
|
+
def self.echo(enable)
|
27
|
+
term = Termios::getattr(STDIN)
|
28
|
+
|
29
|
+
if enable
|
30
|
+
term.c_lflag |= (Termios::ECHO | Termios::ICANON)
|
31
|
+
else
|
32
|
+
term.c_lflag &= ~Termios::ECHO
|
33
|
+
end
|
34
|
+
|
35
|
+
Termios::setattr(STDIN, Termios::TCSANOW, term)
|
36
|
+
end
|
37
|
+
rescue LoadError
|
38
|
+
def self.echo(enable)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# execute the associated block with echo-suppression enabled. Note that
|
43
|
+
# if termios is not available, echo suppression will not be available
|
44
|
+
# either.
|
45
|
+
def self.with_echo
|
46
|
+
echo(false)
|
47
|
+
yield
|
48
|
+
ensure
|
49
|
+
echo(true)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Prompt for a password using echo suppression.
|
53
|
+
def self.password_prompt(prompt="Password: ")
|
54
|
+
sync = STDOUT.sync
|
55
|
+
begin
|
56
|
+
with_echo do
|
57
|
+
STDOUT.sync = true
|
58
|
+
print(prompt)
|
59
|
+
STDIN.gets.chomp
|
60
|
+
end
|
61
|
+
ensure
|
62
|
+
STDOUT.sync = sync
|
63
|
+
puts
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# The array of (unparsed) command-line options
|
68
|
+
attr_reader :args
|
69
|
+
|
70
|
+
# The hash of (parsed) command-line options
|
71
|
+
attr_reader :options
|
72
|
+
|
73
|
+
# Create a new CLI instance using the given array of command-line parameters
|
74
|
+
# to initialize it. By default, +ARGV+ is used, but you can specify a
|
75
|
+
# different set of parameters (such as when embedded ST in a program):
|
76
|
+
#
|
77
|
+
# require 'capistrano/cli'
|
78
|
+
# Capistrano::CLI.new(%w(-vvvv -r config/deploy -a update_code)).execute!
|
79
|
+
#
|
80
|
+
# Note that you can also embed ST directly by creating a new Configuration
|
81
|
+
# instance and setting it up, but you'll often wind up duplicating logic
|
82
|
+
# defined in the CLI class. The above snippet, redone using the Configuration
|
83
|
+
# class directly, would look like:
|
84
|
+
#
|
85
|
+
# require 'capistrano'
|
86
|
+
# require 'capistrano/cli'
|
87
|
+
# config = Capistrano::Configuration.new
|
88
|
+
# config.logger_level = Capistrano::Logger::TRACE
|
89
|
+
# config.set(:password) { Capistrano::CLI.password_prompt }
|
90
|
+
# config.load "standard", "config/deploy"
|
91
|
+
# config.actor.update_code
|
92
|
+
#
|
93
|
+
# There may be times that you want/need the additional control offered by
|
94
|
+
# manipulating the Configuration directly, but generally interfacing with
|
95
|
+
# the CLI class is recommended.
|
96
|
+
def initialize(args = ARGV)
|
97
|
+
@args = args
|
98
|
+
@options = { :recipes => [], :actions => [], :vars => {},
|
99
|
+
:pre_vars => {} }
|
100
|
+
|
101
|
+
OptionParser.new do |opts|
|
102
|
+
opts.banner = "Usage: #{$0} [options] [args]"
|
103
|
+
|
104
|
+
opts.separator ""
|
105
|
+
opts.separator "Recipe Options -----------------------"
|
106
|
+
opts.separator ""
|
107
|
+
|
108
|
+
opts.on("-a", "--action ACTION",
|
109
|
+
"An action to execute. Multiple actions may",
|
110
|
+
"be specified, and are loaded in the given order."
|
111
|
+
) { |value| @options[:actions] << value }
|
112
|
+
|
113
|
+
opts.on("-p", "--password [PASSWORD]",
|
114
|
+
"The password to use when connecting. If the switch",
|
115
|
+
"is given without a password, the password will be",
|
116
|
+
"prompted for immediately. (Default: prompt for password",
|
117
|
+
"the first time it is needed.)"
|
118
|
+
) { |value| @options[:password] = value }
|
119
|
+
|
120
|
+
opts.on("-r", "--recipe RECIPE",
|
121
|
+
"A recipe file to load. Multiple recipes may",
|
122
|
+
"be specified, and are loaded in the given order."
|
123
|
+
) { |value| @options[:recipes] << value }
|
124
|
+
|
125
|
+
opts.on("-s", "--set NAME=VALUE",
|
126
|
+
"Specify a variable and it's value to set. This",
|
127
|
+
"will be set after loading all recipe files."
|
128
|
+
) do |pair|
|
129
|
+
name, value = pair.split(/=/, 2)
|
130
|
+
@options[:vars][name.to_sym] = value
|
131
|
+
end
|
132
|
+
|
133
|
+
opts.on("-S", "--set-before NAME=VALUE",
|
134
|
+
"Specify a variable and it's value to set. This",
|
135
|
+
"will be set BEFORE loading all recipe files."
|
136
|
+
) do |pair|
|
137
|
+
name, value = pair.split(/=/, 2)
|
138
|
+
@options[:pre_vars][name.to_sym] = value
|
139
|
+
end
|
140
|
+
|
141
|
+
opts.separator ""
|
142
|
+
opts.separator "Framework Integration Options --------"
|
143
|
+
opts.separator ""
|
144
|
+
|
145
|
+
opts.on("-A", "--apply-to DIRECTORY",
|
146
|
+
"Create a minimal set of scripts and recipes to use",
|
147
|
+
"capistrano with the application at the given",
|
148
|
+
"directory. (Currently only works with Rails apps.)"
|
149
|
+
) { |value| @options[:apply_to] = value }
|
150
|
+
|
151
|
+
opts.separator ""
|
152
|
+
opts.separator "Miscellaneous Options ----------------"
|
153
|
+
opts.separator ""
|
154
|
+
|
155
|
+
opts.on("-h", "--help", "Display this help message") do
|
156
|
+
puts opts
|
157
|
+
exit
|
158
|
+
end
|
159
|
+
|
160
|
+
opts.on("-P", "--[no-]pretend",
|
161
|
+
"Run the task(s), but don't actually connect to or",
|
162
|
+
"execute anything on the servers. (For various reasons",
|
163
|
+
"this will not necessarily be an accurate depiction",
|
164
|
+
"of the work that will actually be performed.",
|
165
|
+
"Default: don't pretend.)"
|
166
|
+
) { |value| @options[:pretend] = value }
|
167
|
+
|
168
|
+
opts.on("-q", "--quiet",
|
169
|
+
"Make the output as quiet as possible (the default)"
|
170
|
+
) { @options[:verbose] = 0 }
|
171
|
+
|
172
|
+
opts.on("-v", "--verbose",
|
173
|
+
"Specify the verbosity of the output.",
|
174
|
+
"May be given multiple times. (Default: silent)"
|
175
|
+
) { @options[:verbose] ||= 0; @options[:verbose] += 1 }
|
176
|
+
|
177
|
+
opts.on("-V", "--version",
|
178
|
+
"Display the version info for this utility"
|
179
|
+
) do
|
180
|
+
require 'capistrano/version'
|
181
|
+
puts "Capistrano v#{Capistrano::Version::STRING}"
|
182
|
+
exit
|
183
|
+
end
|
184
|
+
|
185
|
+
opts.separator ""
|
186
|
+
opts.separator <<-DETAIL.split(/\n/)
|
187
|
+
You can use the --apply-to switch to generate a minimal set of capistrano
|
188
|
+
scripts and recipes for an application. Just specify the path to the application
|
189
|
+
as the argument to --apply-to, like this:
|
190
|
+
|
191
|
+
capistrano --apply-to ~/projects/myapp
|
192
|
+
|
193
|
+
You'll wind up with a sample deployment recipe in config/deploy.rb, some new
|
194
|
+
rake tasks in config/tasks, and a capistrano script in your script directory.
|
195
|
+
|
196
|
+
(Currently, --apply-to only works with Rails applications.)
|
197
|
+
DETAIL
|
198
|
+
|
199
|
+
if args.empty?
|
200
|
+
puts opts
|
201
|
+
exit
|
202
|
+
else
|
203
|
+
opts.parse!(args)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
check_options!
|
208
|
+
|
209
|
+
password_proc = Proc.new { self.class.password_prompt }
|
210
|
+
|
211
|
+
if !@options.has_key?(:password)
|
212
|
+
@options[:password] = password_proc
|
213
|
+
elsif !@options[:password]
|
214
|
+
@options[:password] = password_proc.call
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Beginning running Capistrano based on the configured options.
|
219
|
+
def execute!
|
220
|
+
if !@options[:recipes].empty?
|
221
|
+
execute_recipes!
|
222
|
+
elsif @options[:apply_to]
|
223
|
+
execute_apply_to!
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
# Load the recipes specified by the options, and execute the actions
|
230
|
+
# specified.
|
231
|
+
def execute_recipes!
|
232
|
+
config = Capistrano::Configuration.new
|
233
|
+
config.logger.level = options[:verbose]
|
234
|
+
config.set :password, options[:password]
|
235
|
+
config.set :pretend, options[:pretend]
|
236
|
+
|
237
|
+
options[:pre_vars].each { |name, value| config.set(name, value) }
|
238
|
+
|
239
|
+
# load the standard recipe definition
|
240
|
+
config.load "standard"
|
241
|
+
|
242
|
+
options[:recipes].each { |recipe| config.load(recipe) }
|
243
|
+
options[:vars].each { |name, value| config.set(name, value) }
|
244
|
+
|
245
|
+
actor = config.actor
|
246
|
+
options[:actions].each { |action| actor.send action }
|
247
|
+
end
|
248
|
+
|
249
|
+
# Load the Rails generator and apply it to the specified directory.
|
250
|
+
def execute_apply_to!
|
251
|
+
require 'capistrano/generators/rails/loader'
|
252
|
+
Generators::RailsLoader.load! @options
|
253
|
+
end
|
254
|
+
|
255
|
+
APPLY_TO_OPTIONS = [:apply_to]
|
256
|
+
RECIPE_OPTIONS = [:password]
|
257
|
+
DEFAULT_RECIPES = %w(Capfile capfile config/deploy.rb)
|
258
|
+
|
259
|
+
# A sanity check to ensure that a valid operation is specified.
|
260
|
+
def check_options!
|
261
|
+
# if no verbosity has been specified, be verbose
|
262
|
+
@options[:verbose] = 3 if !@options.has_key?(:verbose)
|
263
|
+
|
264
|
+
apply_to_given = !(@options.keys & APPLY_TO_OPTIONS).empty?
|
265
|
+
recipe_given = !(@options.keys & RECIPE_OPTIONS).empty? ||
|
266
|
+
!@options[:recipes].empty? ||
|
267
|
+
!@options[:actions].empty?
|
268
|
+
|
269
|
+
if apply_to_given && recipe_given
|
270
|
+
abort "You cannot specify both recipe options and framework integration options."
|
271
|
+
elsif !apply_to_given
|
272
|
+
look_for_default_recipe_file! if @options[:recipes].empty?
|
273
|
+
look_for_raw_actions!
|
274
|
+
abort "You must specify at least one recipe" if @options[:recipes].empty?
|
275
|
+
abort "You must specify at least one action" if @options[:actions].empty?
|
276
|
+
else
|
277
|
+
@options[:application] = args.shift
|
278
|
+
@options[:recipe_file] = args.shift
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def look_for_default_recipe_file!
|
283
|
+
DEFAULT_RECIPES.each do |file|
|
284
|
+
if File.exist?(file)
|
285
|
+
@options[:recipes] << file
|
286
|
+
break
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def look_for_raw_actions!
|
292
|
+
@options[:actions].concat(@args)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Capistrano
|
2
|
+
|
3
|
+
# This class encapsulates a single command to be executed on a set of remote
|
4
|
+
# machines, in parallel.
|
5
|
+
class Command
|
6
|
+
attr_reader :servers, :command, :options, :actor
|
7
|
+
|
8
|
+
def initialize(servers, command, callback, options, actor) #:nodoc:
|
9
|
+
@servers = servers
|
10
|
+
@command = command.strip.gsub(/\r?\n/, "\\\n")
|
11
|
+
@callback = callback
|
12
|
+
@options = options
|
13
|
+
@actor = actor
|
14
|
+
@channels = open_channels
|
15
|
+
end
|
16
|
+
|
17
|
+
def logger #:nodoc:
|
18
|
+
actor.logger
|
19
|
+
end
|
20
|
+
|
21
|
+
# Processes the command in parallel on all specified hosts. If the command
|
22
|
+
# fails (non-zero return code) on any of the hosts, this will raise a
|
23
|
+
# RuntimeError.
|
24
|
+
def process!
|
25
|
+
since = Time.now
|
26
|
+
loop do
|
27
|
+
active = 0
|
28
|
+
@channels.each do |ch|
|
29
|
+
next if ch[:closed]
|
30
|
+
active += 1
|
31
|
+
ch.connection.process(true)
|
32
|
+
end
|
33
|
+
|
34
|
+
break if active == 0
|
35
|
+
if Time.now - since >= 1
|
36
|
+
since = Time.now
|
37
|
+
@channels.each { |ch| ch.connection.ping! }
|
38
|
+
end
|
39
|
+
sleep 0.01 # a brief respite, to keep the CPU from going crazy
|
40
|
+
end
|
41
|
+
|
42
|
+
logger.trace "command finished"
|
43
|
+
|
44
|
+
if failed = @channels.detect { |ch| ch[:status] != 0 }
|
45
|
+
raise "command #{@command.inspect} failed on #{failed[:host]}"
|
46
|
+
end
|
47
|
+
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def open_channels
|
54
|
+
@servers.map do |server|
|
55
|
+
@actor.sessions[server].open_channel do |channel|
|
56
|
+
channel[:host] = server
|
57
|
+
channel[:actor] = @actor # so callbacks can access the actor instance
|
58
|
+
channel.request_pty :want_reply => true
|
59
|
+
|
60
|
+
channel.on_success do |ch|
|
61
|
+
logger.trace "executing command", ch[:host]
|
62
|
+
ch.exec command
|
63
|
+
ch.send_data options[:data] if options[:data]
|
64
|
+
end
|
65
|
+
|
66
|
+
channel.on_failure do |ch|
|
67
|
+
logger.important "could not open channel", ch[:host]
|
68
|
+
ch.close
|
69
|
+
end
|
70
|
+
|
71
|
+
channel.on_data do |ch, data|
|
72
|
+
@callback[ch, :out, data] if @callback
|
73
|
+
end
|
74
|
+
|
75
|
+
channel.on_extended_data do |ch, type, data|
|
76
|
+
@callback[ch, :err, data] if @callback
|
77
|
+
end
|
78
|
+
|
79
|
+
channel.on_request do |ch, request, reply, data|
|
80
|
+
ch[:status] = data.read_long if request == "exit-status"
|
81
|
+
end
|
82
|
+
|
83
|
+
channel.on_close do |ch|
|
84
|
+
ch[:closed] = true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'capistrano/actor'
|
2
|
+
require 'capistrano/logger'
|
3
|
+
require 'capistrano/scm/subversion'
|
4
|
+
require 'capistrano/extensions'
|
5
|
+
|
6
|
+
module Capistrano
|
7
|
+
# Represents a specific Capistrano configuration. A Configuration instance
|
8
|
+
# may be used to load multiple recipe files, define and describe tasks,
|
9
|
+
# define roles, create an actor, and set configuration variables.
|
10
|
+
class Configuration
|
11
|
+
Role = Struct.new(:host, :options)
|
12
|
+
|
13
|
+
DEFAULT_VERSION_DIR_NAME = "releases" #:nodoc:
|
14
|
+
DEFAULT_CURRENT_DIR_NAME = "current" #:nodoc:
|
15
|
+
DEFAULT_SHARED_DIR_NAME = "shared" #:nodoc:
|
16
|
+
|
17
|
+
# The actor created for this configuration instance.
|
18
|
+
attr_reader :actor
|
19
|
+
|
20
|
+
# The list of Role instances defined for this configuration.
|
21
|
+
attr_reader :roles
|
22
|
+
|
23
|
+
# The logger instance defined for this configuration.
|
24
|
+
attr_reader :logger
|
25
|
+
|
26
|
+
# The load paths used for locating recipe files.
|
27
|
+
attr_reader :load_paths
|
28
|
+
|
29
|
+
# The time (in UTC) at which this configuration was created, used for
|
30
|
+
# determining the release path.
|
31
|
+
attr_reader :now
|
32
|
+
|
33
|
+
# The has of variables currently known by the configuration
|
34
|
+
attr_reader :variables
|
35
|
+
|
36
|
+
def initialize(actor_class=Actor) #:nodoc:
|
37
|
+
@roles = Hash.new { |h,k| h[k] = [] }
|
38
|
+
@actor = actor_class.new(self)
|
39
|
+
@logger = Logger.new
|
40
|
+
@load_paths = [".", File.join(File.dirname(__FILE__), "recipes")]
|
41
|
+
@variables = {}
|
42
|
+
@now = Time.now.utc
|
43
|
+
|
44
|
+
# for preserving the original value of Proc-valued variables
|
45
|
+
set :original_value, Hash.new
|
46
|
+
|
47
|
+
set :application, nil
|
48
|
+
set :repository, nil
|
49
|
+
set :gateway, nil
|
50
|
+
set :user, nil
|
51
|
+
set :password, nil
|
52
|
+
|
53
|
+
set :ssh_options, Hash.new
|
54
|
+
|
55
|
+
set(:deploy_to) { "/u/apps/#{application}" }
|
56
|
+
|
57
|
+
set :version_dir, DEFAULT_VERSION_DIR_NAME
|
58
|
+
set :current_dir, DEFAULT_CURRENT_DIR_NAME
|
59
|
+
set :shared_dir, DEFAULT_SHARED_DIR_NAME
|
60
|
+
set :scm, :subversion
|
61
|
+
|
62
|
+
set(:revision) { source.latest_revision }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Set a variable to the given value.
|
66
|
+
def set(variable, value=nil, &block)
|
67
|
+
# if the variable is uppercase, then we add it as a constant to the
|
68
|
+
# actor. This is to allow uppercase "variables" to be set and referenced
|
69
|
+
# in recipes.
|
70
|
+
if variable.to_s[0].between?(?A, ?Z)
|
71
|
+
klass = @actor.metaclass
|
72
|
+
klass.send(:remove_const, variable) if klass.const_defined?(variable)
|
73
|
+
klass.const_set(variable, value)
|
74
|
+
end
|
75
|
+
|
76
|
+
value = block if value.nil? && block_given?
|
77
|
+
@variables[variable] = value
|
78
|
+
end
|
79
|
+
|
80
|
+
alias :[]= :set
|
81
|
+
|
82
|
+
# Access a named variable. If the value of the variable responds_to? :call,
|
83
|
+
# #call will be invoked (without parameters) and the return value cached
|
84
|
+
# and returned.
|
85
|
+
def [](variable)
|
86
|
+
if @variables[variable].respond_to?(:call)
|
87
|
+
self[:original_value][variable] = @variables[variable]
|
88
|
+
set variable, @variables[variable].call
|
89
|
+
end
|
90
|
+
@variables[variable]
|
91
|
+
end
|
92
|
+
|
93
|
+
# Based on the current value of the <tt>:scm</tt> variable, instantiate and
|
94
|
+
# return an SCM module representing the desired source control behavior.
|
95
|
+
def source
|
96
|
+
@source ||= case scm
|
97
|
+
when Class then
|
98
|
+
scm.new(self)
|
99
|
+
when String, Symbol then
|
100
|
+
require "capistrano/scm/#{scm.to_s.downcase}"
|
101
|
+
Capistrano::SCM.const_get(scm.to_s.downcase.capitalize).new(self)
|
102
|
+
else
|
103
|
+
raise "invalid scm specification: #{scm.inspect}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Load a configuration file or string into this configuration.
|
108
|
+
#
|
109
|
+
# Usage:
|
110
|
+
#
|
111
|
+
# load("recipe"):
|
112
|
+
# Look for and load the contents of 'recipe.rb' into this
|
113
|
+
# configuration.
|
114
|
+
#
|
115
|
+
# load(:file => "recipe"):
|
116
|
+
# same as above
|
117
|
+
#
|
118
|
+
# load(:string => "set :scm, :subversion"):
|
119
|
+
# Load the given string as a configuration specification.
|
120
|
+
#
|
121
|
+
# load { ... }
|
122
|
+
# Load the block in the context of the configuration.
|
123
|
+
def load(*args, &block)
|
124
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
125
|
+
args.each { |arg| load options.merge(:file => arg) }
|
126
|
+
return unless args.empty?
|
127
|
+
|
128
|
+
if block
|
129
|
+
raise "loading a block requires 0 parameters" unless args.empty?
|
130
|
+
load(options.merge(:proc => block))
|
131
|
+
|
132
|
+
elsif options[:file]
|
133
|
+
file = options[:file]
|
134
|
+
unless file[0] == ?/
|
135
|
+
load_paths.each do |path|
|
136
|
+
if File.file?(File.join(path, file))
|
137
|
+
file = File.join(path, file)
|
138
|
+
break
|
139
|
+
elsif File.file?(File.join(path, file) + ".rb")
|
140
|
+
file = File.join(path, file + ".rb")
|
141
|
+
break
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
load :string => File.read(file), :name => options[:name] || file
|
147
|
+
|
148
|
+
elsif options[:string]
|
149
|
+
logger.trace "loading configuration #{options[:name] || "<eval>"}"
|
150
|
+
instance_eval(options[:string], options[:name] || "<eval>")
|
151
|
+
|
152
|
+
elsif options[:proc]
|
153
|
+
logger.trace "loading configuration #{options[:proc].inspect}"
|
154
|
+
instance_eval(&options[:proc])
|
155
|
+
|
156
|
+
else
|
157
|
+
raise ArgumentError, "don't know how to load #{options.inspect}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Define a new role and its associated servers. You must specify at least
|
162
|
+
# one host for each role. Also, you can specify additional information
|
163
|
+
# (in the form of a Hash) which can be used to more uniquely specify the
|
164
|
+
# subset of servers specified by this specific role definition.
|
165
|
+
#
|
166
|
+
# Usage:
|
167
|
+
#
|
168
|
+
# role :db, "db1.example.com", "db2.example.com"
|
169
|
+
# role :db, "master.example.com", :primary => true
|
170
|
+
# role :app, "app1.example.com", "app2.example.com"
|
171
|
+
def role(which, *args)
|
172
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
173
|
+
raise ArgumentError, "must give at least one host" if args.empty?
|
174
|
+
args.each { |host| roles[which] << Role.new(host, options) }
|
175
|
+
end
|
176
|
+
|
177
|
+
# Describe the next task to be defined. The given text will be attached to
|
178
|
+
# the next task that is defined and used as its description.
|
179
|
+
def desc(text)
|
180
|
+
@next_description = text
|
181
|
+
end
|
182
|
+
|
183
|
+
# Define a new task. If a description is active (see #desc), it is added to
|
184
|
+
# the options under the <tt>:desc</tt> key. This method ultimately
|
185
|
+
# delegates to Actor#define_task.
|
186
|
+
def task(name, options={}, &block)
|
187
|
+
raise ArgumentError, "expected a block" unless block
|
188
|
+
|
189
|
+
if @next_description
|
190
|
+
options = options.merge(:desc => @next_description)
|
191
|
+
@next_description = nil
|
192
|
+
end
|
193
|
+
|
194
|
+
actor.define_task(name, options, &block)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Require another file. This is identical to the standard require method,
|
198
|
+
# with the exception that it sets the reciever as the "current" configuration
|
199
|
+
# so that third-party task bundles can include themselves relative to
|
200
|
+
# that configuration.
|
201
|
+
def require(*args) #:nodoc:
|
202
|
+
original, Capistrano.configuration = Capistrano.configuration, self
|
203
|
+
super
|
204
|
+
ensure
|
205
|
+
# restore the original, so that require's can be nested
|
206
|
+
Capistrano.configuration = original
|
207
|
+
end
|
208
|
+
|
209
|
+
# Return the path into which releases should be deployed.
|
210
|
+
def releases_path
|
211
|
+
File.join(deploy_to, version_dir)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Return the path identifying the +current+ symlink, used to identify the
|
215
|
+
# current release.
|
216
|
+
def current_path
|
217
|
+
File.join(deploy_to, current_dir)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Return the path into which shared files should be stored.
|
221
|
+
def shared_path
|
222
|
+
File.join(deploy_to, shared_dir)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Return the full path to the named release. If a release is not specified,
|
226
|
+
# +now+ is used (the time at which the configuration was created).
|
227
|
+
def release_path(release=now.strftime("%Y%m%d%H%M%S"))
|
228
|
+
File.join(releases_path, release)
|
229
|
+
end
|
230
|
+
|
231
|
+
def respond_to?(sym) #:nodoc:
|
232
|
+
@variables.has_key?(sym) || super
|
233
|
+
end
|
234
|
+
|
235
|
+
def method_missing(sym, *args, &block) #:nodoc:
|
236
|
+
if args.length == 0 && block.nil? && @variables.has_key?(sym)
|
237
|
+
self[sym]
|
238
|
+
else
|
239
|
+
super
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|