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
@@ -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
|