minmb-capistrano 2.15.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +10 -0
- data/.travis.yml +7 -0
- data/CHANGELOG +1170 -0
- data/Gemfile +13 -0
- data/README.md +94 -0
- data/Rakefile +11 -0
- data/bin/cap +4 -0
- data/bin/capify +92 -0
- data/capistrano.gemspec +40 -0
- data/lib/capistrano.rb +5 -0
- data/lib/capistrano/callback.rb +45 -0
- data/lib/capistrano/cli.rb +47 -0
- data/lib/capistrano/cli/execute.rb +85 -0
- data/lib/capistrano/cli/help.rb +125 -0
- data/lib/capistrano/cli/help.txt +81 -0
- data/lib/capistrano/cli/options.rb +243 -0
- data/lib/capistrano/cli/ui.rb +40 -0
- data/lib/capistrano/command.rb +303 -0
- data/lib/capistrano/configuration.rb +57 -0
- data/lib/capistrano/configuration/actions/file_transfer.rb +50 -0
- data/lib/capistrano/configuration/actions/inspect.rb +46 -0
- data/lib/capistrano/configuration/actions/invocation.rb +329 -0
- data/lib/capistrano/configuration/alias_task.rb +26 -0
- data/lib/capistrano/configuration/callbacks.rb +147 -0
- data/lib/capistrano/configuration/connections.rb +237 -0
- data/lib/capistrano/configuration/execution.rb +142 -0
- data/lib/capistrano/configuration/loading.rb +205 -0
- data/lib/capistrano/configuration/log_formatters.rb +75 -0
- data/lib/capistrano/configuration/namespaces.rb +223 -0
- data/lib/capistrano/configuration/roles.rb +77 -0
- data/lib/capistrano/configuration/servers.rb +116 -0
- data/lib/capistrano/configuration/variables.rb +127 -0
- data/lib/capistrano/errors.rb +19 -0
- data/lib/capistrano/ext/multistage.rb +64 -0
- data/lib/capistrano/ext/string.rb +5 -0
- data/lib/capistrano/extensions.rb +57 -0
- data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
- data/lib/capistrano/logger.rb +166 -0
- data/lib/capistrano/processable.rb +57 -0
- data/lib/capistrano/recipes/compat.rb +32 -0
- data/lib/capistrano/recipes/deploy.rb +625 -0
- data/lib/capistrano/recipes/deploy/assets.rb +201 -0
- data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
- data/lib/capistrano/recipes/deploy/local_dependency.rb +54 -0
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +117 -0
- data/lib/capistrano/recipes/deploy/scm.rb +19 -0
- data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
- data/lib/capistrano/recipes/deploy/scm/base.rb +200 -0
- data/lib/capistrano/recipes/deploy/scm/bzr.rb +86 -0
- data/lib/capistrano/recipes/deploy/scm/cvs.rb +153 -0
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +96 -0
- data/lib/capistrano/recipes/deploy/scm/git.rb +293 -0
- data/lib/capistrano/recipes/deploy/scm/mercurial.rb +137 -0
- data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
- data/lib/capistrano/recipes/deploy/scm/perforce.rb +152 -0
- data/lib/capistrano/recipes/deploy/scm/subversion.rb +121 -0
- data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
- data/lib/capistrano/recipes/deploy/strategy/base.rb +92 -0
- data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +338 -0
- data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
- data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
- data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +57 -0
- data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
- data/lib/capistrano/recipes/standard.rb +37 -0
- data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
- data/lib/capistrano/role.rb +102 -0
- data/lib/capistrano/server_definition.rb +56 -0
- data/lib/capistrano/shell.rb +265 -0
- data/lib/capistrano/ssh.rb +95 -0
- data/lib/capistrano/task_definition.rb +77 -0
- data/lib/capistrano/transfer.rb +218 -0
- data/lib/capistrano/version.rb +11 -0
- data/test/cli/execute_test.rb +132 -0
- data/test/cli/help_test.rb +165 -0
- data/test/cli/options_test.rb +329 -0
- data/test/cli/ui_test.rb +28 -0
- data/test/cli_test.rb +17 -0
- data/test/command_test.rb +322 -0
- data/test/configuration/actions/file_transfer_test.rb +61 -0
- data/test/configuration/actions/inspect_test.rb +76 -0
- data/test/configuration/actions/invocation_test.rb +288 -0
- data/test/configuration/alias_task_test.rb +118 -0
- data/test/configuration/callbacks_test.rb +201 -0
- data/test/configuration/connections_test.rb +439 -0
- data/test/configuration/execution_test.rb +175 -0
- data/test/configuration/loading_test.rb +148 -0
- data/test/configuration/namespace_dsl_test.rb +332 -0
- data/test/configuration/roles_test.rb +157 -0
- data/test/configuration/servers_test.rb +183 -0
- data/test/configuration/variables_test.rb +190 -0
- data/test/configuration_test.rb +77 -0
- 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 +55 -0
- 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 +360 -0
- data/test/extensions_test.rb +69 -0
- data/test/fixtures/cli_integration.rb +5 -0
- data/test/fixtures/config.rb +5 -0
- data/test/fixtures/custom.rb +3 -0
- data/test/logger_formatting_test.rb +149 -0
- data/test/logger_test.rb +134 -0
- data/test/recipes_test.rb +25 -0
- data/test/role_test.rb +11 -0
- data/test/server_definition_test.rb +121 -0
- data/test/shell_test.rb +96 -0
- data/test/ssh_test.rb +113 -0
- data/test/task_definition_test.rb +117 -0
- data/test/transfer_test.rb +168 -0
- data/test/utils.rb +37 -0
- metadata +316 -0
|
@@ -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
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
require 'capistrano/task_definition'
|
|
2
|
+
|
|
3
|
+
module Capistrano
|
|
4
|
+
class Configuration
|
|
5
|
+
module Namespaces
|
|
6
|
+
DEFAULT_TASK = :default
|
|
7
|
+
|
|
8
|
+
def self.included(base) #:nodoc:
|
|
9
|
+
base.send :alias_method, :initialize_without_namespaces, :initialize
|
|
10
|
+
base.send :alias_method, :initialize, :initialize_with_namespaces
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The name of this namespace. Defaults to +nil+ for the top-level
|
|
14
|
+
# namespace.
|
|
15
|
+
attr_reader :name
|
|
16
|
+
|
|
17
|
+
# The parent namespace of this namespace. Returns +nil+ for the top-level
|
|
18
|
+
# namespace.
|
|
19
|
+
attr_reader :parent
|
|
20
|
+
|
|
21
|
+
# The hash of tasks defined for this namespace.
|
|
22
|
+
attr_reader :tasks
|
|
23
|
+
|
|
24
|
+
# The hash of namespaces defined for this namespace.
|
|
25
|
+
attr_reader :namespaces
|
|
26
|
+
|
|
27
|
+
def initialize_with_namespaces(*args) #:nodoc:
|
|
28
|
+
@name = @parent = nil
|
|
29
|
+
initialize_without_namespaces(*args)
|
|
30
|
+
@tasks = {}
|
|
31
|
+
@namespaces = {}
|
|
32
|
+
end
|
|
33
|
+
private :initialize_with_namespaces
|
|
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
|
+
|
|
41
|
+
# Returns the fully-qualified name of this namespace, or nil if the
|
|
42
|
+
# namespace is at the top-level.
|
|
43
|
+
def fully_qualified_name
|
|
44
|
+
return nil if name.nil?
|
|
45
|
+
[parent.fully_qualified_name, name].compact.join(":")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Describe the next task to be defined. The given text will be attached to
|
|
49
|
+
# the next task that is defined and used as its description.
|
|
50
|
+
def desc(text)
|
|
51
|
+
@next_description = text
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Returns the value set by the last, pending "desc" call. If +reset+ is
|
|
55
|
+
# not false, the value will be reset immediately afterwards.
|
|
56
|
+
def next_description(reset=false)
|
|
57
|
+
@next_description
|
|
58
|
+
ensure
|
|
59
|
+
@next_description = nil if reset
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Open a namespace in which to define new tasks. If the namespace was
|
|
63
|
+
# defined previously, it will be reopened, otherwise a new namespace
|
|
64
|
+
# will be created for the given name.
|
|
65
|
+
def namespace(name, &block)
|
|
66
|
+
name = name.to_sym
|
|
67
|
+
raise ArgumentError, "expected a block" unless block_given?
|
|
68
|
+
|
|
69
|
+
namespace_already_defined = namespaces.key?(name)
|
|
70
|
+
if all_methods.any? { |m| m.to_sym == name } && !namespace_already_defined
|
|
71
|
+
thing = tasks.key?(name) ? "task" : "method"
|
|
72
|
+
raise ArgumentError, "defining a namespace named `#{name}' would shadow an existing #{thing} with that name"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
namespaces[name] ||= Namespace.new(name, self)
|
|
76
|
+
namespaces[name].instance_eval(&block)
|
|
77
|
+
|
|
78
|
+
# make sure any open description gets terminated
|
|
79
|
+
namespaces[name].desc(nil)
|
|
80
|
+
|
|
81
|
+
if !namespace_already_defined
|
|
82
|
+
metaclass = class << self; self; end
|
|
83
|
+
metaclass.send(:define_method, name) { namespaces[name] }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Describe a new task. If a description is active (see #desc), it is added
|
|
88
|
+
# to the options under the <tt>:desc</tt> key. The new task is added to
|
|
89
|
+
# the namespace.
|
|
90
|
+
def task(name, options={}, &block)
|
|
91
|
+
name = name.to_sym
|
|
92
|
+
raise ArgumentError, "expected a block" unless block_given?
|
|
93
|
+
|
|
94
|
+
task_already_defined = tasks.key?(name)
|
|
95
|
+
if all_methods.any? { |m| m.to_sym == name } && !task_already_defined
|
|
96
|
+
thing = namespaces.key?(name) ? "namespace" : "method"
|
|
97
|
+
raise ArgumentError, "defining a task named `#{name}' would shadow an existing #{thing} with that name"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
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]) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Find the task with the given name, where name is the fully-qualified
|
|
114
|
+
# name of the task. This will search into the namespaces and return
|
|
115
|
+
# the referenced task, or nil if no such task can be found. If the name
|
|
116
|
+
# refers to a namespace, the task in that namespace named "default"
|
|
117
|
+
# will be returned instead, if one exists.
|
|
118
|
+
def find_task(name)
|
|
119
|
+
parts = name.to_s.split(/:/)
|
|
120
|
+
tail = parts.pop.to_sym
|
|
121
|
+
|
|
122
|
+
ns = self
|
|
123
|
+
until parts.empty?
|
|
124
|
+
next_part = parts.shift
|
|
125
|
+
ns = next_part.empty? ? nil : ns.namespaces[next_part.to_sym]
|
|
126
|
+
return nil if ns.nil?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
if ns.namespaces.key?(tail)
|
|
130
|
+
ns = ns.namespaces[tail]
|
|
131
|
+
tail = DEFAULT_TASK
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
ns.tasks[tail]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Given a task name, this will search the current namespace, and all
|
|
138
|
+
# parent namespaces, looking for a task that matches the name, exactly.
|
|
139
|
+
# It returns the task, if found, or nil, if not.
|
|
140
|
+
def search_task(name)
|
|
141
|
+
name = name.to_sym
|
|
142
|
+
ns = self
|
|
143
|
+
|
|
144
|
+
until ns.nil?
|
|
145
|
+
return ns.tasks[name] if ns.tasks.key?(name)
|
|
146
|
+
ns = ns.parent
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
return nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Returns the default task for this namespace. This will be +nil+ if
|
|
153
|
+
# the namespace is at the top-level, and will otherwise return the
|
|
154
|
+
# task named "default". If no such task exists, +nil+ will be returned.
|
|
155
|
+
def default_task
|
|
156
|
+
return nil if parent.nil?
|
|
157
|
+
return tasks[DEFAULT_TASK]
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Returns the tasks in this namespace as an array of TaskDefinition
|
|
161
|
+
# objects. If a non-false parameter is given, all tasks in all
|
|
162
|
+
# namespaces under this namespace will be returned as well.
|
|
163
|
+
def task_list(all=false)
|
|
164
|
+
list = tasks.values
|
|
165
|
+
namespaces.each { |name,space| list.concat(space.task_list(:all)) } if all
|
|
166
|
+
list
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
private
|
|
170
|
+
|
|
171
|
+
def all_methods
|
|
172
|
+
public_methods.concat(protected_methods).concat(private_methods)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
class Namespace
|
|
176
|
+
def initialize(name, parent)
|
|
177
|
+
@parent = parent
|
|
178
|
+
@name = name
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def role(*args)
|
|
182
|
+
raise NotImplementedError, "roles cannot be defined in a namespace"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def respond_to?(sym, include_priv=false)
|
|
186
|
+
super || parent.respond_to?(sym, include_priv)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def method_missing(sym, *args, &block)
|
|
190
|
+
if parent.respond_to?(sym)
|
|
191
|
+
parent.send(sym, *args, &block)
|
|
192
|
+
else
|
|
193
|
+
super
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
include Capistrano::Configuration::AliasTask
|
|
198
|
+
include Capistrano::Configuration::Namespaces
|
|
199
|
+
undef :desc, :next_description
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
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
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'capistrano/server_definition'
|
|
2
|
+
require 'capistrano/role'
|
|
3
|
+
|
|
4
|
+
module Capistrano
|
|
5
|
+
class Configuration
|
|
6
|
+
module Roles
|
|
7
|
+
def self.included(base) #:nodoc:
|
|
8
|
+
base.send :alias_method, :initialize_without_roles, :initialize
|
|
9
|
+
base.send :alias_method, :initialize, :initialize_with_roles
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# The hash of roles defined for this configuration. Each entry in the
|
|
13
|
+
# hash points to an array of server definitions that belong in that
|
|
14
|
+
# role.
|
|
15
|
+
attr_reader :roles
|
|
16
|
+
|
|
17
|
+
def initialize_with_roles(*args) #:nodoc:
|
|
18
|
+
initialize_without_roles(*args)
|
|
19
|
+
@roles = Hash.new { |h,k| h[k] = Role.new }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Define a new role and its associated servers. You must specify at least
|
|
23
|
+
# one host for each role. Also, you can specify additional information
|
|
24
|
+
# (in the form of a Hash) which can be used to more uniquely specify the
|
|
25
|
+
# subset of servers specified by this specific role definition.
|
|
26
|
+
#
|
|
27
|
+
# Usage:
|
|
28
|
+
#
|
|
29
|
+
# role :db, "db1.example.com", "db2.example.com"
|
|
30
|
+
# role :db, "master.example.com", :primary => true
|
|
31
|
+
# role :app, "app1.example.com", "app2.example.com"
|
|
32
|
+
#
|
|
33
|
+
# You can also encode the username and port number for each host in the
|
|
34
|
+
# server string, if needed:
|
|
35
|
+
#
|
|
36
|
+
# role :web, "www@web1.example.com"
|
|
37
|
+
# role :file, "files.example.com:4144"
|
|
38
|
+
# role :db, "admin@db3.example.com:1234"
|
|
39
|
+
#
|
|
40
|
+
# Lastly, username and port number may be passed as options, if that is
|
|
41
|
+
# preferred; note that the options apply to all servers defined in
|
|
42
|
+
# that call to "role":
|
|
43
|
+
#
|
|
44
|
+
# role :web, "web2", "web3", :user => "www", :port => 2345
|
|
45
|
+
def role(which, *args, &block)
|
|
46
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
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?
|
|
57
|
+
args.each { |host| roles[which] << ServerDefinition.new(host, options) }
|
|
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
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
module Capistrano
|
|
2
|
+
class Configuration
|
|
3
|
+
module Servers
|
|
4
|
+
# Identifies all servers that the given task should be executed on.
|
|
5
|
+
# The options hash accepts the same arguments as #find_servers, and any
|
|
6
|
+
# preexisting options there will take precedence over the options in
|
|
7
|
+
# the task.
|
|
8
|
+
def find_servers_for_task(task, options={})
|
|
9
|
+
find_servers(task.options.merge(options))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Attempts to find all defined servers that match the given criteria.
|
|
13
|
+
# The options hash may include a :hosts option (which should specify
|
|
14
|
+
# an array of host names or ServerDefinition instances), a :roles
|
|
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), and a
|
|
18
|
+
# :skip_hostfilter option to ignore the HOSTFILTER environment variable
|
|
19
|
+
# described below.
|
|
20
|
+
#
|
|
21
|
+
# Additionally, if the HOSTS environment variable is set, it will take
|
|
22
|
+
# precedence over any other options. Similarly, the ROLES environment
|
|
23
|
+
# variable will take precedence over other options. If both HOSTS and
|
|
24
|
+
# ROLES are given, HOSTS wins.
|
|
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
|
+
#
|
|
32
|
+
# Usage:
|
|
33
|
+
#
|
|
34
|
+
# # return all known servers
|
|
35
|
+
# servers = find_servers
|
|
36
|
+
#
|
|
37
|
+
# # find all servers in the app role that are not exempted from
|
|
38
|
+
# # deployment
|
|
39
|
+
# servers = find_servers :roles => :app,
|
|
40
|
+
# :except => { :no_release => true }
|
|
41
|
+
#
|
|
42
|
+
# # returns the given hosts, translated to ServerDefinition objects
|
|
43
|
+
# servers = find_servers :hosts => "jamis@example.host.com"
|
|
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
|
+
|
|
48
|
+
hosts = server_list_from(ENV['HOSTS'] || options[:hosts])
|
|
49
|
+
|
|
50
|
+
if hosts.any?
|
|
51
|
+
if options[:skip_hostfilter]
|
|
52
|
+
hosts.uniq
|
|
53
|
+
else
|
|
54
|
+
filter_server_list(hosts.uniq)
|
|
55
|
+
end
|
|
56
|
+
else
|
|
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] || []) }
|
|
65
|
+
servers = servers.select { |server| only.all? { |key,value| server.options[key] == value } }
|
|
66
|
+
servers = servers.reject { |server| except.any? { |key,value| server.options[key] == value } }
|
|
67
|
+
|
|
68
|
+
if options[:skip_hostfilter]
|
|
69
|
+
servers.uniq
|
|
70
|
+
else
|
|
71
|
+
filter_server_list(servers.uniq)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
protected
|
|
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
|
+
|
|
96
|
+
def server_list_from(hosts)
|
|
97
|
+
hosts = hosts.split(/,/) if String === hosts
|
|
98
|
+
hosts = build_list(hosts)
|
|
99
|
+
hosts.map { |s| String === s ? ServerDefinition.new(s.strip) : s }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def role_list_from(roles)
|
|
103
|
+
roles = roles.split(/,/) if String === roles
|
|
104
|
+
roles = build_list(roles)
|
|
105
|
+
roles.map do |role|
|
|
106
|
+
role = String === role ? role.strip.to_sym : role
|
|
107
|
+
role
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def build_list(list)
|
|
112
|
+
Array(list).map { |item| item.respond_to?(:call) ? item.call : item }.flatten
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|