capistrano 2.0.0 → 2.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +7 -0
- data/CHANGELOG +715 -18
- data/Gemfile +12 -0
- data/README.md +94 -0
- data/Rakefile +11 -0
- data/bin/cap +0 -0
- data/bin/capify +37 -22
- data/capistrano.gemspec +40 -0
- data/lib/capistrano/callback.rb +5 -1
- data/lib/capistrano/cli/execute.rb +10 -7
- data/lib/capistrano/cli/help.rb +39 -16
- data/lib/capistrano/cli/help.txt +44 -16
- data/lib/capistrano/cli/options.rb +71 -11
- data/lib/capistrano/cli/ui.rb +13 -1
- data/lib/capistrano/cli.rb +5 -5
- data/lib/capistrano/command.rb +215 -58
- data/lib/capistrano/configuration/actions/file_transfer.rb +29 -14
- data/lib/capistrano/configuration/actions/inspect.rb +3 -3
- data/lib/capistrano/configuration/actions/invocation.rb +212 -22
- data/lib/capistrano/configuration/alias_task.rb +26 -0
- data/lib/capistrano/configuration/callbacks.rb +26 -27
- data/lib/capistrano/configuration/connections.rb +130 -52
- data/lib/capistrano/configuration/execution.rb +34 -18
- data/lib/capistrano/configuration/loading.rb +91 -6
- data/lib/capistrano/configuration/log_formatters.rb +75 -0
- data/lib/capistrano/configuration/namespaces.rb +45 -12
- data/lib/capistrano/configuration/roles.rb +28 -2
- data/lib/capistrano/configuration/servers.rb +51 -10
- data/lib/capistrano/configuration/variables.rb +3 -3
- data/lib/capistrano/configuration.rb +20 -4
- data/lib/capistrano/errors.rb +12 -8
- data/lib/capistrano/ext/multistage.rb +62 -0
- data/lib/capistrano/ext/string.rb +5 -0
- data/lib/capistrano/extensions.rb +1 -1
- data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
- data/lib/capistrano/logger.rb +112 -5
- data/lib/capistrano/processable.rb +55 -0
- data/lib/capistrano/recipes/compat.rb +2 -2
- data/lib/capistrano/recipes/deploy/assets.rb +185 -0
- data/lib/capistrano/recipes/deploy/dependencies.rb +2 -2
- data/lib/capistrano/recipes/deploy/local_dependency.rb +10 -2
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +54 -2
- data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
- data/lib/capistrano/recipes/deploy/scm/base.rb +31 -11
- data/lib/capistrano/recipes/deploy/scm/bzr.rb +14 -14
- data/lib/capistrano/recipes/deploy/scm/cvs.rb +10 -8
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +12 -1
- data/lib/capistrano/recipes/deploy/scm/git.rb +293 -0
- data/lib/capistrano/recipes/deploy/scm/mercurial.rb +23 -15
- data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
- data/lib/capistrano/recipes/deploy/scm/perforce.rb +54 -28
- data/lib/capistrano/recipes/deploy/scm/subversion.rb +35 -17
- data/lib/capistrano/recipes/deploy/scm.rb +1 -1
- data/lib/capistrano/recipes/deploy/strategy/base.rb +32 -4
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +238 -43
- data/lib/capistrano/recipes/deploy/strategy/remote.rb +1 -1
- data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +11 -1
- data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
- data/lib/capistrano/recipes/deploy/strategy.rb +1 -1
- data/lib/capistrano/recipes/deploy.rb +265 -123
- data/lib/capistrano/recipes/standard.rb +1 -1
- data/lib/capistrano/role.rb +102 -0
- data/lib/capistrano/server_definition.rb +6 -1
- data/lib/capistrano/shell.rb +30 -33
- data/lib/capistrano/ssh.rb +46 -60
- data/lib/capistrano/task_definition.rb +16 -8
- data/lib/capistrano/transfer.rb +218 -0
- data/lib/capistrano/version.rb +6 -17
- data/lib/capistrano.rb +4 -1
- data/test/cli/execute_test.rb +3 -3
- data/test/cli/help_test.rb +33 -7
- data/test/cli/options_test.rb +109 -6
- data/test/cli/ui_test.rb +2 -2
- data/test/cli_test.rb +3 -3
- data/test/command_test.rb +144 -124
- data/test/configuration/actions/file_transfer_test.rb +41 -20
- data/test/configuration/actions/inspect_test.rb +21 -7
- data/test/configuration/actions/invocation_test.rb +91 -30
- data/test/configuration/alias_task_test.rb +118 -0
- data/test/configuration/callbacks_test.rb +41 -46
- data/test/configuration/connections_test.rb +187 -36
- data/test/configuration/execution_test.rb +18 -2
- data/test/configuration/loading_test.rb +17 -4
- data/test/configuration/namespace_dsl_test.rb +54 -5
- data/test/configuration/roles_test.rb +114 -4
- data/test/configuration/servers_test.rb +97 -4
- data/test/configuration/variables_test.rb +12 -2
- data/test/configuration_test.rb +9 -13
- data/test/deploy/local_dependency_test.rb +76 -0
- data/test/deploy/remote_dependency_test.rb +146 -0
- data/test/deploy/scm/accurev_test.rb +23 -0
- data/test/deploy/scm/base_test.rb +1 -1
- data/test/deploy/scm/bzr_test.rb +51 -0
- data/test/deploy/scm/darcs_test.rb +37 -0
- data/test/deploy/scm/git_test.rb +221 -0
- data/test/deploy/scm/mercurial_test.rb +134 -0
- data/test/deploy/scm/none_test.rb +35 -0
- data/test/deploy/scm/perforce_test.rb +23 -0
- data/test/deploy/scm/subversion_test.rb +40 -0
- data/test/deploy/strategy/copy_test.rb +240 -26
- data/test/extensions_test.rb +2 -2
- data/test/logger_formatting_test.rb +149 -0
- data/test/logger_test.rb +13 -2
- data/test/recipes_test.rb +25 -0
- data/test/role_test.rb +11 -0
- data/test/server_definition_test.rb +15 -2
- data/test/shell_test.rb +33 -1
- data/test/ssh_test.rb +40 -24
- data/test/task_definition_test.rb +18 -2
- data/test/transfer_test.rb +168 -0
- data/test/utils.rb +27 -33
- metadata +215 -102
- data/MIT-LICENSE +0 -20
- data/README +0 -43
- data/examples/sample.rb +0 -14
- data/lib/capistrano/gateway.rb +0 -131
- data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
- data/lib/capistrano/recipes/templates/maintenance.rhtml +0 -53
- data/lib/capistrano/recipes/upgrade.rb +0 -33
- data/lib/capistrano/upload.rb +0 -146
- data/test/gateway_test.rb +0 -167
- data/test/upload_test.rb +0 -131
- data/test/version_test.rb +0 -24
|
@@ -8,22 +8,11 @@ module Capistrano
|
|
|
8
8
|
base.send :alias_method, :initialize, :initialize_with_execution
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
# The call stack of the tasks. The currently executing task may inspect
|
|
12
|
-
# this to see who its caller was. The current task is always the last
|
|
13
|
-
# element of this stack.
|
|
14
|
-
attr_reader :task_call_frames
|
|
15
|
-
|
|
16
|
-
# The stack of tasks that have registered rollback handlers within the
|
|
17
|
-
# current transaction. If this is nil, then there is no transaction
|
|
18
|
-
# that is currently active.
|
|
19
|
-
attr_reader :rollback_requests
|
|
20
|
-
|
|
21
11
|
# A struct for representing a single instance of an invoked task.
|
|
22
12
|
TaskCallFrame = Struct.new(:task, :rollback)
|
|
23
13
|
|
|
24
14
|
def initialize_with_execution(*args) #:nodoc:
|
|
25
15
|
initialize_without_execution(*args)
|
|
26
|
-
@task_call_frames = []
|
|
27
16
|
end
|
|
28
17
|
private :initialize_with_execution
|
|
29
18
|
|
|
@@ -32,6 +21,25 @@ module Capistrano
|
|
|
32
21
|
!rollback_requests.nil?
|
|
33
22
|
end
|
|
34
23
|
|
|
24
|
+
# The call stack of the tasks. The currently executing task may inspect
|
|
25
|
+
# this to see who its caller was. The current task is always the last
|
|
26
|
+
# element of this stack.
|
|
27
|
+
def task_call_frames
|
|
28
|
+
Thread.current[:task_call_frames] ||= []
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# The stack of tasks that have registered rollback handlers within the
|
|
33
|
+
# current transaction. If this is nil, then there is no transaction
|
|
34
|
+
# that is currently active.
|
|
35
|
+
def rollback_requests
|
|
36
|
+
Thread.current[:rollback_requests]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def rollback_requests=(rollback_requests)
|
|
40
|
+
Thread.current[:rollback_requests] = rollback_requests
|
|
41
|
+
end
|
|
42
|
+
|
|
35
43
|
# Invoke a set of tasks in a transaction. If any task fails (raises an
|
|
36
44
|
# exception), all tasks executed within the transaction are inspected to
|
|
37
45
|
# see if they have an associated on_rollback hook, and if so, that hook
|
|
@@ -44,14 +52,14 @@ module Capistrano
|
|
|
44
52
|
|
|
45
53
|
logger.info "transaction: start"
|
|
46
54
|
begin
|
|
47
|
-
|
|
55
|
+
self.rollback_requests = []
|
|
48
56
|
yield
|
|
49
57
|
logger.info "transaction: commit"
|
|
50
58
|
rescue Object => e
|
|
51
59
|
rollback!
|
|
52
60
|
raise
|
|
53
61
|
ensure
|
|
54
|
-
|
|
62
|
+
self.rollback_requests = nil
|
|
55
63
|
end
|
|
56
64
|
end
|
|
57
65
|
|
|
@@ -60,8 +68,9 @@ module Capistrano
|
|
|
60
68
|
# hook will be executed.
|
|
61
69
|
def on_rollback(&block)
|
|
62
70
|
if transaction?
|
|
71
|
+
# don't note a new rollback request if one has already been set
|
|
72
|
+
rollback_requests << task_call_frames.last unless task_call_frames.last.rollback
|
|
63
73
|
task_call_frames.last.rollback = block
|
|
64
|
-
rollback_requests << task_call_frames.last
|
|
65
74
|
end
|
|
66
75
|
end
|
|
67
76
|
|
|
@@ -72,12 +81,12 @@ module Capistrano
|
|
|
72
81
|
task_call_frames.last.task
|
|
73
82
|
end
|
|
74
83
|
|
|
75
|
-
# Executes the task with the given name,
|
|
76
|
-
#
|
|
84
|
+
# Executes the task with the given name, without invoking any associated
|
|
85
|
+
# callbacks.
|
|
77
86
|
def execute_task(task)
|
|
78
87
|
logger.debug "executing `#{task.fully_qualified_name}'"
|
|
79
88
|
push_task_call_frame(task)
|
|
80
|
-
|
|
89
|
+
invoke_task_directly(task)
|
|
81
90
|
ensure
|
|
82
91
|
pop_task_call_frame
|
|
83
92
|
end
|
|
@@ -98,6 +107,8 @@ module Capistrano
|
|
|
98
107
|
protected
|
|
99
108
|
|
|
100
109
|
def rollback!
|
|
110
|
+
return if Thread.current[:rollback_requests].nil?
|
|
111
|
+
|
|
101
112
|
# throw the task back on the stack so that roles are properly
|
|
102
113
|
# interpreted in the scope of the task in question.
|
|
103
114
|
rollback_requests.reverse.each do |frame|
|
|
@@ -121,6 +132,11 @@ module Capistrano
|
|
|
121
132
|
def pop_task_call_frame
|
|
122
133
|
task_call_frames.pop
|
|
123
134
|
end
|
|
135
|
+
|
|
136
|
+
# Invokes the task's body directly, without setting up the call frame.
|
|
137
|
+
def invoke_task_directly(task)
|
|
138
|
+
task.namespace.instance_eval(&task.body)
|
|
139
|
+
end
|
|
124
140
|
end
|
|
125
141
|
end
|
|
126
|
-
end
|
|
142
|
+
end
|
|
@@ -25,6 +25,28 @@ module Capistrano
|
|
|
25
25
|
def instance=(config)
|
|
26
26
|
Thread.current[:capistrano_configuration] = config
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
# Used internally by Capistrano to track which recipes have been loaded
|
|
30
|
+
# via require, so that they may be successfully reloaded when require
|
|
31
|
+
# is called again.
|
|
32
|
+
def recipes_per_feature
|
|
33
|
+
@recipes_per_feature ||= {}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Used internally to determine what the current "feature" being
|
|
37
|
+
# required is. This is used to track which files load which recipes
|
|
38
|
+
# via require.
|
|
39
|
+
def current_feature
|
|
40
|
+
Thread.current[:capistrano_current_feature]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Used internally to specify the current file being required, so that
|
|
44
|
+
# any recipes loaded by that file can be remembered. This allows
|
|
45
|
+
# recipes loaded via require to be correctly reloaded in different
|
|
46
|
+
# Configuration instances in the same Ruby instance.
|
|
47
|
+
def current_feature=(feature)
|
|
48
|
+
Thread.current[:capistrano_current_feature] = feature
|
|
49
|
+
end
|
|
28
50
|
end
|
|
29
51
|
|
|
30
52
|
# The load paths used for locating recipe files.
|
|
@@ -33,6 +55,7 @@ module Capistrano
|
|
|
33
55
|
def initialize_with_loading(*args) #:nodoc:
|
|
34
56
|
initialize_without_loading(*args)
|
|
35
57
|
@load_paths = [".", File.expand_path(File.join(File.dirname(__FILE__), "../recipes"))]
|
|
58
|
+
@loaded_features = []
|
|
36
59
|
end
|
|
37
60
|
private :initialize_with_loading
|
|
38
61
|
|
|
@@ -66,9 +89,11 @@ module Capistrano
|
|
|
66
89
|
load_from_file(options[:file], options[:name])
|
|
67
90
|
|
|
68
91
|
elsif options[:string]
|
|
92
|
+
remember_load(options) unless options[:reloading]
|
|
69
93
|
instance_eval(options[:string], options[:name] || "<eval>")
|
|
70
94
|
|
|
71
95
|
elsif options[:proc]
|
|
96
|
+
remember_load(options) unless options[:reloading]
|
|
72
97
|
instance_eval(&options[:proc])
|
|
73
98
|
|
|
74
99
|
else
|
|
@@ -80,12 +105,62 @@ module Capistrano
|
|
|
80
105
|
# with the exception that it sets the receiver as the "current" configuration
|
|
81
106
|
# so that third-party task bundles can include themselves relative to
|
|
82
107
|
# that configuration.
|
|
108
|
+
#
|
|
109
|
+
# This is a bit more complicated than an initial review would seem to
|
|
110
|
+
# necessitate, but the use case that complicates things is this: An
|
|
111
|
+
# advanced user wants to embed capistrano, and needs to instantiate
|
|
112
|
+
# more than one capistrano configuration at a time. They also want each
|
|
113
|
+
# configuration to require a third-party capistrano extension. Using a
|
|
114
|
+
# naive require implementation, this would allow the first configuration
|
|
115
|
+
# to successfully load the third-party extension, but the require would
|
|
116
|
+
# fail for the second configuration because the extension has already
|
|
117
|
+
# been loaded.
|
|
118
|
+
#
|
|
119
|
+
# To work around this, we do a few things:
|
|
120
|
+
#
|
|
121
|
+
# 1. Each time a 'require' is invoked inside of a capistrano recipe,
|
|
122
|
+
# we remember the arguments (see "current_feature").
|
|
123
|
+
# 2. Each time a 'load' is invoked inside of a capistrano recipe, and
|
|
124
|
+
# "current_feature" is not nil (meaning we are inside of a pending
|
|
125
|
+
# require) we remember the options (see "remember_load" and
|
|
126
|
+
# "recipes_per_feature").
|
|
127
|
+
# 3. Each time a 'require' is invoked inside of a capistrano recipe,
|
|
128
|
+
# we check to see if this particular configuration has ever seen these
|
|
129
|
+
# arguments to require (see @loaded_features), and if not, we proceed
|
|
130
|
+
# as if the file had never been required. If the superclass' require
|
|
131
|
+
# returns false (meaning, potentially, that the file has already been
|
|
132
|
+
# required), then we look in the recipes_per_feature collection and
|
|
133
|
+
# load any remembered recipes from there.
|
|
134
|
+
#
|
|
135
|
+
# It's kind of a bear, but it works, and works transparently. Note that
|
|
136
|
+
# a simpler implementation would just muck with $", allowing files to be
|
|
137
|
+
# required multiple times, but that will cause warnings (and possibly
|
|
138
|
+
# errors) if the file to be required contains constant definitions and
|
|
139
|
+
# such, alongside (or instead of) capistrano recipe definitions.
|
|
83
140
|
def require(*args) #:nodoc:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
141
|
+
# look to see if this specific configuration instance has ever seen
|
|
142
|
+
# these arguments to require before
|
|
143
|
+
if @loaded_features.include?(args)
|
|
144
|
+
return false
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
@loaded_features << args
|
|
148
|
+
begin
|
|
149
|
+
original_instance, self.class.instance = self.class.instance, self
|
|
150
|
+
original_feature, self.class.current_feature = self.class.current_feature, args
|
|
151
|
+
|
|
152
|
+
result = super
|
|
153
|
+
if !result # file has been required previously, load up the remembered recipes
|
|
154
|
+
list = self.class.recipes_per_feature[args] || []
|
|
155
|
+
list.each { |options| load(options.merge(:reloading => true)) }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
return result
|
|
159
|
+
ensure
|
|
160
|
+
# restore the original, so that require's can be nested
|
|
161
|
+
self.class.instance = original_instance
|
|
162
|
+
self.class.current_feature = original_feature
|
|
163
|
+
end
|
|
89
164
|
end
|
|
90
165
|
|
|
91
166
|
private
|
|
@@ -93,7 +168,7 @@ module Capistrano
|
|
|
93
168
|
# Load a recipe from the named file. If +name+ is given, the file will
|
|
94
169
|
# be reported using that name.
|
|
95
170
|
def load_from_file(file, name=nil)
|
|
96
|
-
file = find_file_in_load_path(file) unless file
|
|
171
|
+
file = find_file_in_load_path(file) unless File.file?(file)
|
|
97
172
|
load :string => File.read(file), :name => name || file
|
|
98
173
|
end
|
|
99
174
|
|
|
@@ -107,6 +182,16 @@ module Capistrano
|
|
|
107
182
|
|
|
108
183
|
raise LoadError, "no such file to load -- #{file}"
|
|
109
184
|
end
|
|
185
|
+
|
|
186
|
+
# If a file is being required, the options associated with loading a
|
|
187
|
+
# recipe are remembered in the recipes_per_feature archive under the
|
|
188
|
+
# name of the file currently being required.
|
|
189
|
+
def remember_load(options)
|
|
190
|
+
if self.class.current_feature
|
|
191
|
+
list = (self.class.recipes_per_feature[self.class.current_feature] ||= [])
|
|
192
|
+
list << options
|
|
193
|
+
end
|
|
194
|
+
end
|
|
110
195
|
end
|
|
111
196
|
end
|
|
112
197
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Add custom log formatters
|
|
2
|
+
#
|
|
3
|
+
# Passing a hash or a array of hashes with custom log formatters.
|
|
4
|
+
#
|
|
5
|
+
# Add the following to your deploy.rb or in your ~/.caprc
|
|
6
|
+
#
|
|
7
|
+
# == Example:
|
|
8
|
+
#
|
|
9
|
+
# capistrano_log_formatters = [
|
|
10
|
+
# { :match => /command finished/, :color => :hide, :priority => 10, :prepend => "$$$" },
|
|
11
|
+
# { :match => /executing command/, :color => :blue, :priority => 10, :style => :underscore, :timestamp => true },
|
|
12
|
+
# { :match => /^transaction: commit$/, :color => :magenta, :priority => 10, :style => :blink },
|
|
13
|
+
# { :match => /git/, :color => :white, :priority => 20, :style => :reverse }
|
|
14
|
+
# ]
|
|
15
|
+
#
|
|
16
|
+
# format_logs capistrano_log_formatters
|
|
17
|
+
#
|
|
18
|
+
# You can call format_logs multiple times, with either a hash or an array of hashes.
|
|
19
|
+
#
|
|
20
|
+
# == Colors:
|
|
21
|
+
#
|
|
22
|
+
# :color can have the following values:
|
|
23
|
+
#
|
|
24
|
+
# * :hide (hides the row completely)
|
|
25
|
+
# * :none
|
|
26
|
+
# * :black
|
|
27
|
+
# * :red
|
|
28
|
+
# * :green
|
|
29
|
+
# * :yellow
|
|
30
|
+
# * :blue
|
|
31
|
+
# * :magenta
|
|
32
|
+
# * :cyan
|
|
33
|
+
# * :white
|
|
34
|
+
#
|
|
35
|
+
# == Styles:
|
|
36
|
+
#
|
|
37
|
+
# :style can have the following values:
|
|
38
|
+
#
|
|
39
|
+
# * :bright
|
|
40
|
+
# * :dim
|
|
41
|
+
# * :underscore
|
|
42
|
+
# * :blink
|
|
43
|
+
# * :reverse
|
|
44
|
+
# * :hidden
|
|
45
|
+
#
|
|
46
|
+
#
|
|
47
|
+
# == Text alterations
|
|
48
|
+
#
|
|
49
|
+
# :prepend gives static text to be prepended to the output
|
|
50
|
+
# :replace replaces the matched text in the output
|
|
51
|
+
# :timestamp adds the current time before the output
|
|
52
|
+
|
|
53
|
+
module Capistrano
|
|
54
|
+
class Configuration
|
|
55
|
+
module LogFormatters
|
|
56
|
+
def log_formatter(options)
|
|
57
|
+
if options.class == Array
|
|
58
|
+
options.each do |option|
|
|
59
|
+
Capistrano::Logger.add_formatter(option)
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
Capistrano::Logger.add_formatter(options)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def default_log_formatters(formatters)
|
|
67
|
+
default_formatters = [*formatters]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def disable_log_formatters
|
|
71
|
+
@logger.disable_formatters = true
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -32,6 +32,12 @@ module Capistrano
|
|
|
32
32
|
end
|
|
33
33
|
private :initialize_with_namespaces
|
|
34
34
|
|
|
35
|
+
# Returns the top-level namespace (the one with no parent).
|
|
36
|
+
def top
|
|
37
|
+
return parent.top if parent
|
|
38
|
+
return self
|
|
39
|
+
end
|
|
40
|
+
|
|
35
41
|
# Returns the fully-qualified name of this namespace, or nil if the
|
|
36
42
|
# namespace is at the top-level.
|
|
37
43
|
def fully_qualified_name
|
|
@@ -61,7 +67,7 @@ module Capistrano
|
|
|
61
67
|
raise ArgumentError, "expected a block" unless block_given?
|
|
62
68
|
|
|
63
69
|
namespace_already_defined = namespaces.key?(name)
|
|
64
|
-
if all_methods.
|
|
70
|
+
if all_methods.any? { |m| m.to_sym == name } && !namespace_already_defined
|
|
65
71
|
thing = tasks.key?(name) ? "task" : "method"
|
|
66
72
|
raise ArgumentError, "defining a namespace named `#{name}' would shadow an existing #{thing} with that name"
|
|
67
73
|
end
|
|
@@ -86,17 +92,22 @@ module Capistrano
|
|
|
86
92
|
raise ArgumentError, "expected a block" unless block_given?
|
|
87
93
|
|
|
88
94
|
task_already_defined = tasks.key?(name)
|
|
89
|
-
if all_methods.
|
|
95
|
+
if all_methods.any? { |m| m.to_sym == name } && !task_already_defined
|
|
90
96
|
thing = namespaces.key?(name) ? "namespace" : "method"
|
|
91
97
|
raise ArgumentError, "defining a task named `#{name}' would shadow an existing #{thing} with that name"
|
|
92
98
|
end
|
|
93
99
|
|
|
94
|
-
tasks[name] = TaskDefinition.new(name, self, {:desc => next_description(:reset)}.merge(options), &block)
|
|
95
100
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
task = TaskDefinition.new(name, self, {:desc => next_description(:reset)}.merge(options), &block)
|
|
102
|
+
|
|
103
|
+
define_task(task)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def define_task(task)
|
|
107
|
+
tasks[task.name] = task
|
|
108
|
+
|
|
109
|
+
metaclass = class << self; self; end
|
|
110
|
+
metaclass.send(:define_method, task.name) { execute_task(tasks[task.name]) }
|
|
100
111
|
end
|
|
101
112
|
|
|
102
113
|
# Find the task with the given name, where name is the fully-qualified
|
|
@@ -110,7 +121,8 @@ module Capistrano
|
|
|
110
121
|
|
|
111
122
|
ns = self
|
|
112
123
|
until parts.empty?
|
|
113
|
-
|
|
124
|
+
next_part = parts.shift
|
|
125
|
+
ns = next_part.empty? ? nil : ns.namespaces[next_part.to_sym]
|
|
114
126
|
return nil if ns.nil?
|
|
115
127
|
end
|
|
116
128
|
|
|
@@ -144,7 +156,7 @@ module Capistrano
|
|
|
144
156
|
return nil if parent.nil?
|
|
145
157
|
return tasks[DEFAULT_TASK]
|
|
146
158
|
end
|
|
147
|
-
|
|
159
|
+
|
|
148
160
|
# Returns the tasks in this namespace as an array of TaskDefinition
|
|
149
161
|
# objects. If a non-false parameter is given, all tasks in all
|
|
150
162
|
# namespaces under this namespace will be returned as well.
|
|
@@ -170,8 +182,8 @@ module Capistrano
|
|
|
170
182
|
raise NotImplementedError, "roles cannot be defined in a namespace"
|
|
171
183
|
end
|
|
172
184
|
|
|
173
|
-
def respond_to?(sym)
|
|
174
|
-
super || parent.respond_to?(sym)
|
|
185
|
+
def respond_to?(sym, include_priv=false)
|
|
186
|
+
super || parent.respond_to?(sym, include_priv)
|
|
175
187
|
end
|
|
176
188
|
|
|
177
189
|
def method_missing(sym, *args, &block)
|
|
@@ -182,9 +194,30 @@ module Capistrano
|
|
|
182
194
|
end
|
|
183
195
|
end
|
|
184
196
|
|
|
197
|
+
include Capistrano::Configuration::AliasTask
|
|
185
198
|
include Capistrano::Configuration::Namespaces
|
|
186
199
|
undef :desc, :next_description
|
|
187
200
|
end
|
|
188
201
|
end
|
|
189
202
|
end
|
|
190
|
-
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
module Kernel
|
|
206
|
+
class << self
|
|
207
|
+
alias_method :method_added_without_capistrano, :method_added
|
|
208
|
+
|
|
209
|
+
# Detect method additions to Kernel and remove them in the Namespace class
|
|
210
|
+
def method_added(name)
|
|
211
|
+
result = method_added_without_capistrano(name)
|
|
212
|
+
return result if self != Kernel
|
|
213
|
+
|
|
214
|
+
namespace = Capistrano::Configuration::Namespaces::Namespace
|
|
215
|
+
|
|
216
|
+
if namespace.method_defined?(name) && namespace.instance_method(name).owner == Kernel
|
|
217
|
+
namespace.send :undef_method, name
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
result
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'capistrano/server_definition'
|
|
2
|
+
require 'capistrano/role'
|
|
2
3
|
|
|
3
4
|
module Capistrano
|
|
4
5
|
class Configuration
|
|
@@ -15,7 +16,7 @@ module Capistrano
|
|
|
15
16
|
|
|
16
17
|
def initialize_with_roles(*args) #:nodoc:
|
|
17
18
|
initialize_without_roles(*args)
|
|
18
|
-
@roles = Hash.new { |h,k| h[k] =
|
|
19
|
+
@roles = Hash.new { |h,k| h[k] = Role.new }
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
# Define a new role and its associated servers. You must specify at least
|
|
@@ -41,11 +42,36 @@ module Capistrano
|
|
|
41
42
|
# that call to "role":
|
|
42
43
|
#
|
|
43
44
|
# role :web, "web2", "web3", :user => "www", :port => 2345
|
|
44
|
-
def role(which, *args)
|
|
45
|
+
def role(which, *args, &block)
|
|
45
46
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
46
47
|
which = which.to_sym
|
|
48
|
+
|
|
49
|
+
# The roles Hash is defined so that unrecognized keys always auto-initialize
|
|
50
|
+
# to a new Role instance (see the assignment in the initialize_with_roles method,
|
|
51
|
+
# above). However, we explicitly assign here so that role declarations will
|
|
52
|
+
# vivify the role object even if there are no server arguments. (Otherwise,
|
|
53
|
+
# role(:app) won't actually instantiate a Role object for :app.)
|
|
54
|
+
roles[which] ||= Role.new
|
|
55
|
+
|
|
56
|
+
roles[which].push(block, options) if block_given?
|
|
47
57
|
args.each { |host| roles[which] << ServerDefinition.new(host, options) }
|
|
48
58
|
end
|
|
59
|
+
|
|
60
|
+
# An alternative way to associate servers with roles. If you have a server
|
|
61
|
+
# that participates in multiple roles, this can be a DRYer way to describe
|
|
62
|
+
# the relationships. Pass the host definition as the first parameter, and
|
|
63
|
+
# the roles as the remaining parameters:
|
|
64
|
+
#
|
|
65
|
+
# server "master.example.com", :web, :app
|
|
66
|
+
def server(host, *roles)
|
|
67
|
+
options = roles.last.is_a?(Hash) ? roles.pop : {}
|
|
68
|
+
raise ArgumentError, "you must associate a server with at least one role" if roles.empty?
|
|
69
|
+
roles.each { |name| role(name, host, options) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def role_names_for_host(host)
|
|
73
|
+
roles.map {|role_name, role| role_name if role.include?(host) }.compact || []
|
|
74
|
+
end
|
|
49
75
|
end
|
|
50
76
|
end
|
|
51
77
|
end
|
|
@@ -13,14 +13,22 @@ module Capistrano
|
|
|
13
13
|
# The options hash may include a :hosts option (which should specify
|
|
14
14
|
# an array of host names or ServerDefinition instances), a :roles
|
|
15
15
|
# option (specifying an array of roles), an :only option (specifying
|
|
16
|
-
# a hash of key/value pairs that any matching server must match),
|
|
17
|
-
# an :exception option (like :only, but the inverse)
|
|
16
|
+
# a hash of key/value pairs that any matching server must match),
|
|
17
|
+
# an :exception option (like :only, but the inverse), and a
|
|
18
|
+
# :skip_hostfilter option to ignore the HOSTFILTER environment variable
|
|
19
|
+
# described below.
|
|
18
20
|
#
|
|
19
21
|
# Additionally, if the HOSTS environment variable is set, it will take
|
|
20
22
|
# precedence over any other options. Similarly, the ROLES environment
|
|
21
23
|
# variable will take precedence over other options. If both HOSTS and
|
|
22
24
|
# ROLES are given, HOSTS wins.
|
|
23
25
|
#
|
|
26
|
+
# Yet additionally, if the HOSTFILTER environment variable is set, it
|
|
27
|
+
# will limit the result to hosts found in that (comma-separated) list.
|
|
28
|
+
#
|
|
29
|
+
# If the HOSTROLEFILTER environment variable is set, it will limit the
|
|
30
|
+
# result to hosts found in that (comma-separated) list of roles
|
|
31
|
+
#
|
|
24
32
|
# Usage:
|
|
25
33
|
#
|
|
26
34
|
# # return all known servers
|
|
@@ -34,23 +42,57 @@ module Capistrano
|
|
|
34
42
|
# # returns the given hosts, translated to ServerDefinition objects
|
|
35
43
|
# servers = find_servers :hosts => "jamis@example.host.com"
|
|
36
44
|
def find_servers(options={})
|
|
45
|
+
return [] if options.key?(:hosts) && (options[:hosts].nil? || [] == options[:hosts])
|
|
46
|
+
return [] if options.key?(:roles) && (options[:roles].nil? || [] == options[:roles])
|
|
47
|
+
|
|
37
48
|
hosts = server_list_from(ENV['HOSTS'] || options[:hosts])
|
|
38
|
-
roles = role_list_from(ENV['ROLES'] || options[:roles] || self.roles.keys)
|
|
39
|
-
only = options[:only] || {}
|
|
40
|
-
except = options[:except] || {}
|
|
41
49
|
|
|
42
50
|
if hosts.any?
|
|
43
|
-
|
|
51
|
+
if options[:skip_hostfilter]
|
|
52
|
+
hosts.uniq
|
|
53
|
+
else
|
|
54
|
+
filter_server_list(hosts.uniq)
|
|
55
|
+
end
|
|
44
56
|
else
|
|
45
|
-
|
|
57
|
+
roles = role_list_from(ENV['ROLES'] || options[:roles] || self.roles.keys)
|
|
58
|
+
roles = roles & Array(options[:roles]) if preserve_roles && !options[:roles].nil?
|
|
59
|
+
|
|
60
|
+
only = options[:only] || {}
|
|
61
|
+
except = options[:except] || {}
|
|
62
|
+
|
|
63
|
+
# If we don't have a def for a role it means its bogus, skip it so higher level can handle
|
|
64
|
+
servers = roles.inject([]) { |list, role| list.concat(self.roles[role] || []) }
|
|
46
65
|
servers = servers.select { |server| only.all? { |key,value| server.options[key] == value } }
|
|
47
66
|
servers = servers.reject { |server| except.any? { |key,value| server.options[key] == value } }
|
|
48
|
-
|
|
67
|
+
|
|
68
|
+
if options[:skip_hostfilter]
|
|
69
|
+
servers.uniq
|
|
70
|
+
else
|
|
71
|
+
filter_server_list(servers.uniq)
|
|
72
|
+
end
|
|
49
73
|
end
|
|
50
74
|
end
|
|
51
75
|
|
|
52
76
|
protected
|
|
53
77
|
|
|
78
|
+
def filter_server_list(servers)
|
|
79
|
+
return servers unless ENV['HOSTFILTER'] or ENV['HOSTROLEFILTER']
|
|
80
|
+
if ENV['HOSTFILTER']
|
|
81
|
+
filters = ENV['HOSTFILTER'].split(/,/)
|
|
82
|
+
servers.select { |server| filters.include?(server.host) }
|
|
83
|
+
elsif ENV['HOSTROLEFILTER']
|
|
84
|
+
filters = ENV['HOSTROLEFILTER'].split(/,/).map do |role|
|
|
85
|
+
local_roles = roles[role.to_sym]
|
|
86
|
+
if local_roles.is_a? Array
|
|
87
|
+
roles[role.to_sym]
|
|
88
|
+
else
|
|
89
|
+
roles[role.to_sym].servers
|
|
90
|
+
end
|
|
91
|
+
end.flatten
|
|
92
|
+
servers.select { |server| filters.include?(server) }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
54
96
|
def server_list_from(hosts)
|
|
55
97
|
hosts = hosts.split(/,/) if String === hosts
|
|
56
98
|
hosts = build_list(hosts)
|
|
@@ -62,7 +104,6 @@ module Capistrano
|
|
|
62
104
|
roles = build_list(roles)
|
|
63
105
|
roles.map do |role|
|
|
64
106
|
role = String === role ? role.strip.to_sym : role
|
|
65
|
-
raise ArgumentError, "unknown role `#{role}'" unless self.roles.key?(role)
|
|
66
107
|
role
|
|
67
108
|
end
|
|
68
109
|
end
|
|
@@ -72,4 +113,4 @@ module Capistrano
|
|
|
72
113
|
end
|
|
73
114
|
end
|
|
74
115
|
end
|
|
75
|
-
end
|
|
116
|
+
end
|
|
@@ -111,8 +111,8 @@ module Capistrano
|
|
|
111
111
|
end
|
|
112
112
|
private :protect
|
|
113
113
|
|
|
114
|
-
def respond_to_with_variables?(sym) #:nodoc:
|
|
115
|
-
@variables.has_key?(sym) || respond_to_without_variables?(sym)
|
|
114
|
+
def respond_to_with_variables?(sym, include_priv=false) #:nodoc:
|
|
115
|
+
@variables.has_key?(sym.to_sym) || respond_to_without_variables?(sym, include_priv)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
def method_missing_with_variables(sym, *args, &block) #:nodoc:
|
|
@@ -124,4 +124,4 @@ module Capistrano
|
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
126
|
end
|
|
127
|
-
end
|
|
127
|
+
end
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
require 'capistrano/logger'
|
|
2
2
|
|
|
3
|
+
require 'capistrano/configuration/alias_task'
|
|
3
4
|
require 'capistrano/configuration/callbacks'
|
|
4
5
|
require 'capistrano/configuration/connections'
|
|
5
6
|
require 'capistrano/configuration/execution'
|
|
6
7
|
require 'capistrano/configuration/loading'
|
|
8
|
+
require 'capistrano/configuration/log_formatters'
|
|
7
9
|
require 'capistrano/configuration/namespaces'
|
|
8
10
|
require 'capistrano/configuration/roles'
|
|
9
11
|
require 'capistrano/configuration/servers'
|
|
@@ -19,10 +21,13 @@ module Capistrano
|
|
|
19
21
|
# define roles, and set configuration variables.
|
|
20
22
|
class Configuration
|
|
21
23
|
# The logger instance defined for this configuration.
|
|
22
|
-
attr_accessor :logger
|
|
24
|
+
attr_accessor :debug, :logger, :dry_run, :preserve_roles
|
|
23
25
|
|
|
24
|
-
def initialize #:nodoc:
|
|
25
|
-
@
|
|
26
|
+
def initialize(options={}) #:nodoc:
|
|
27
|
+
@debug = false
|
|
28
|
+
@dry_run = false
|
|
29
|
+
@preserve_roles = false
|
|
30
|
+
@logger = Logger.new(options)
|
|
26
31
|
end
|
|
27
32
|
|
|
28
33
|
# make the DSL easier to read when using lazy evaluation via lambdas
|
|
@@ -30,12 +35,23 @@ module Capistrano
|
|
|
30
35
|
|
|
31
36
|
# The includes must come at the bottom, since they may redefine methods
|
|
32
37
|
# defined in the base class.
|
|
33
|
-
include Connections, Execution, Loading, Namespaces, Roles, Servers, Variables
|
|
38
|
+
include AliasTask, Connections, Execution, Loading, LogFormatters, Namespaces, Roles, Servers, Variables
|
|
34
39
|
|
|
35
40
|
# Mix in the actions
|
|
36
41
|
include Actions::FileTransfer, Actions::Inspect, Actions::Invocation
|
|
37
42
|
|
|
38
43
|
# Must mix last, because it hooks into previously defined methods
|
|
39
44
|
include Callbacks
|
|
45
|
+
|
|
46
|
+
(self.instance_methods & Kernel.methods).select do |name|
|
|
47
|
+
# Select the instance methods owned by the Configuration class.
|
|
48
|
+
self.instance_method(name).owner.to_s.start_with?("Capistrano::Configuration")
|
|
49
|
+
end.select do |name|
|
|
50
|
+
# Of those, select methods that are being shadowed by the Kernel module in the Namespace class.
|
|
51
|
+
Namespaces::Namespace.method_defined?(name) && Namespaces::Namespace.instance_method(name).owner == Kernel
|
|
52
|
+
end.each do |name|
|
|
53
|
+
# Undefine the shadowed methods, since we want Namespace objects to defer handling to the Configuration object.
|
|
54
|
+
Namespaces::Namespace.send(:undef_method, name)
|
|
55
|
+
end
|
|
40
56
|
end
|
|
41
57
|
end
|