capistrano 2.0.0 → 2.15.9
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 +9 -0
- data/CHANGELOG +755 -18
- data/Gemfile +13 -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 +224 -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 +99 -6
- data/lib/capistrano/configuration/log_formatters.rb +75 -0
- data/lib/capistrano/configuration/namespaces.rb +45 -12
- data/lib/capistrano/configuration/roles.rb +34 -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 +67 -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 +202 -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 +11 -9
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +12 -1
- data/lib/capistrano/recipes/deploy/scm/git.rb +299 -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 +36 -18
- 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 +4 -3
- 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 +8 -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 +141 -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 +33 -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 +274 -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 +68 -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 +28 -33
- data/test/version_test.rb +4 -17
- metadata +215 -100
- 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/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
|
@@ -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,42 @@ 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
|
+
#
|
|
67
|
+
# Also, you can specify additional information
|
|
68
|
+
# (in the form of a Hash) which can be used to more uniquely specify the
|
|
69
|
+
# subset of servers specified by this specific definition:
|
|
70
|
+
#
|
|
71
|
+
# server "master.example.com", :db, :primary => true
|
|
72
|
+
def server(host, *roles)
|
|
73
|
+
options = roles.last.is_a?(Hash) ? roles.pop : {}
|
|
74
|
+
raise ArgumentError, "you must associate a server with at least one role" if roles.empty?
|
|
75
|
+
roles.each { |name| role(name, host, options) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def role_names_for_host(host)
|
|
79
|
+
roles.map {|role_name, role| role_name if role.include?(host) }.compact || []
|
|
80
|
+
end
|
|
49
81
|
end
|
|
50
82
|
end
|
|
51
83
|
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 :
|
|
16
|
+
# a hash of key/value pairs that any matching server must match),
|
|
17
|
+
# an :except 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
|
data/lib/capistrano/errors.rb
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
module Capistrano
|
|
2
|
-
class Error < RuntimeError; end
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Error = Class.new(RuntimeError)
|
|
4
|
+
|
|
5
|
+
CaptureError = Class.new(Capistrano::Error)
|
|
6
|
+
NoSuchTaskError = Class.new(Capistrano::Error)
|
|
7
|
+
NoMatchingServersError = Class.new(Capistrano::Error)
|
|
8
|
+
|
|
8
9
|
class RemoteError < Error
|
|
9
10
|
attr_accessor :hosts
|
|
10
11
|
end
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
ConnectionError = Class.new(Capistrano::RemoteError)
|
|
14
|
+
TransferError = Class.new(Capistrano::RemoteError)
|
|
15
|
+
CommandError = Class.new(Capistrano::RemoteError)
|
|
16
|
+
|
|
17
|
+
LocalArgumentError = Class.new(Capistrano::Error)
|
|
18
|
+
|
|
15
19
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
unless Capistrano::Configuration.respond_to?(:instance)
|
|
4
|
+
abort "capistrano/ext/multistage requires Capistrano 2"
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
Capistrano::Configuration.instance.load do
|
|
8
|
+
location = fetch(:stage_dir, "config/deploy")
|
|
9
|
+
|
|
10
|
+
unless exists?(:stages)
|
|
11
|
+
set :stages, Dir["#{location}/*.rb"].map { |f| File.basename(f, ".rb") }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
stages.each do |name|
|
|
15
|
+
desc "Set the target stage to `#{name}'."
|
|
16
|
+
task(name) do
|
|
17
|
+
set :stage, name.to_sym
|
|
18
|
+
|
|
19
|
+
file = "#{location}/#{stage}"
|
|
20
|
+
load file if file_in_load_path?(file)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
on :load do
|
|
25
|
+
# The first non option argument
|
|
26
|
+
env = ARGV.detect { |a| a.to_s !~ /\A-/ && a.to_s !~ /=/ }
|
|
27
|
+
|
|
28
|
+
if stages.include?(env)
|
|
29
|
+
# Execute the specified stage so that recipes required in stage can contribute to task list
|
|
30
|
+
find_and_execute_task(env) if ARGV.any?{ |option| option =~ /-T|--tasks|-e|--explain/ }
|
|
31
|
+
else
|
|
32
|
+
# Execute the default stage so that recipes required in stage can contribute tasks
|
|
33
|
+
find_and_execute_task(default_stage) if exists?(:default_stage)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
namespace :multistage do
|
|
38
|
+
desc "[internal] Ensure that a stage has been selected."
|
|
39
|
+
task :ensure do
|
|
40
|
+
if !exists?(:stage)
|
|
41
|
+
if exists?(:default_stage)
|
|
42
|
+
logger.important "Defaulting to `#{default_stage}'"
|
|
43
|
+
find_and_execute_task(default_stage)
|
|
44
|
+
else
|
|
45
|
+
abort "No stage specified. Please specify one of: #{stages.join(', ')} (e.g. `cap #{stages.first} #{ARGV.last}')"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
desc "Stub out the staging config files."
|
|
51
|
+
task :prepare do
|
|
52
|
+
FileUtils.mkdir_p(location)
|
|
53
|
+
stages.each do |name|
|
|
54
|
+
file = File.join(location, name + ".rb")
|
|
55
|
+
unless File.exists?(file)
|
|
56
|
+
File.open(file, "w") do |f|
|
|
57
|
+
f.puts "# #{name.upcase}-specific deployment configuration"
|
|
58
|
+
f.puts "# please put general deployment config in config/deploy.rb"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
on :start, "multistage:ensure", :except => stages + ['multistage:prepare']
|
|
66
|
+
|
|
67
|
+
end
|
|
@@ -25,7 +25,7 @@ module Capistrano
|
|
|
25
25
|
Capistrano::Configuration.protected_instance_methods +
|
|
26
26
|
Capistrano::Configuration.private_instance_methods
|
|
27
27
|
|
|
28
|
-
if methods.
|
|
28
|
+
if methods.any? { |m| m.to_sym == name }
|
|
29
29
|
raise Capistrano::Error, "registering a plugin named `#{name}' would shadow a method on Capistrano::Configuration with the same name"
|
|
30
30
|
end
|
|
31
31
|
|
data/lib/capistrano/logger.rb
CHANGED
|
@@ -1,15 +1,80 @@
|
|
|
1
1
|
module Capistrano
|
|
2
2
|
class Logger #:nodoc:
|
|
3
|
-
attr_accessor :level
|
|
4
|
-
attr_reader :device
|
|
3
|
+
attr_accessor :level, :device, :disable_formatters
|
|
5
4
|
|
|
6
5
|
IMPORTANT = 0
|
|
7
6
|
INFO = 1
|
|
8
7
|
DEBUG = 2
|
|
9
8
|
TRACE = 3
|
|
10
|
-
|
|
9
|
+
|
|
11
10
|
MAX_LEVEL = 3
|
|
12
11
|
|
|
12
|
+
COLORS = {
|
|
13
|
+
:none => "0",
|
|
14
|
+
:black => "30",
|
|
15
|
+
:red => "31",
|
|
16
|
+
:green => "32",
|
|
17
|
+
:yellow => "33",
|
|
18
|
+
:blue => "34",
|
|
19
|
+
:magenta => "35",
|
|
20
|
+
:cyan => "36",
|
|
21
|
+
:white => "37"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
STYLES = {
|
|
25
|
+
:bright => 1,
|
|
26
|
+
:dim => 2,
|
|
27
|
+
:underscore => 4,
|
|
28
|
+
:blink => 5,
|
|
29
|
+
:reverse => 7,
|
|
30
|
+
:hidden => 8
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Set up default formatters
|
|
34
|
+
@default_formatters = [
|
|
35
|
+
# TRACE
|
|
36
|
+
{ :match => /command finished/, :color => :white, :style => :dim, :level => 3, :priority => -10 },
|
|
37
|
+
{ :match => /executing locally/, :color => :yellow, :level => 3, :priority => -20 },
|
|
38
|
+
|
|
39
|
+
# DEBUG
|
|
40
|
+
{ :match => /executing `.*/, :color => :green, :level => 2, :priority => -10, :timestamp => true },
|
|
41
|
+
{ :match => /.*/, :color => :yellow, :level => 2, :priority => -30 },
|
|
42
|
+
|
|
43
|
+
# INFO
|
|
44
|
+
{ :match => /.*out\] (fatal:|ERROR:).*/, :color => :red, :level => 1, :priority => -10 },
|
|
45
|
+
{ :match => /Permission denied/, :color => :red, :level => 1, :priority => -20 },
|
|
46
|
+
{ :match => /sh: .+: command not found/, :color => :magenta, :level => 1, :priority => -30 },
|
|
47
|
+
|
|
48
|
+
# IMPORTANT
|
|
49
|
+
{ :match => /^err ::/, :color => :red, :level => 0, :priority => -10 },
|
|
50
|
+
{ :match => /.*/, :color => :blue, :level => 0, :priority => -20 }
|
|
51
|
+
]
|
|
52
|
+
@formatters = @default_formatters
|
|
53
|
+
|
|
54
|
+
class << self
|
|
55
|
+
def default_formatters
|
|
56
|
+
@default_formatters
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def default_formatters=(defaults=nil)
|
|
60
|
+
@default_formatters = [defaults].flatten
|
|
61
|
+
|
|
62
|
+
# reset the formatters
|
|
63
|
+
@formatters = @default_formatters
|
|
64
|
+
@sorted_formatters = nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def add_formatter(options) #:nodoc:
|
|
68
|
+
@formatters.push(options)
|
|
69
|
+
@sorted_formatters = nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def sorted_formatters
|
|
73
|
+
# Sort matchers in reverse order so we can break if we found a match.
|
|
74
|
+
@sorted_formatters ||= @formatters.sort_by { |i| -(i[:priority] || i[:prio] || 0) }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
13
78
|
def initialize(options={})
|
|
14
79
|
output = options[:output] || $stderr
|
|
15
80
|
if output.respond_to?(:puts)
|
|
@@ -20,7 +85,8 @@ module Capistrano
|
|
|
20
85
|
end
|
|
21
86
|
|
|
22
87
|
@options = options
|
|
23
|
-
@level = 0
|
|
88
|
+
@level = options[:level] || 0
|
|
89
|
+
@disable_formatters = options[:disable_formatters]
|
|
24
90
|
end
|
|
25
91
|
|
|
26
92
|
def close
|
|
@@ -29,8 +95,44 @@ module Capistrano
|
|
|
29
95
|
|
|
30
96
|
def log(level, message, line_prefix=nil)
|
|
31
97
|
if level <= self.level
|
|
98
|
+
# Only format output if device is a TTY and formatters are not disabled
|
|
99
|
+
if device.tty? && !@disable_formatters
|
|
100
|
+
color = :none
|
|
101
|
+
style = nil
|
|
102
|
+
|
|
103
|
+
Logger.sorted_formatters.each do |formatter|
|
|
104
|
+
if (formatter[:level] == level || formatter[:level].nil?)
|
|
105
|
+
if message =~ formatter[:match] || line_prefix =~ formatter[:match]
|
|
106
|
+
color = formatter[:color] if formatter[:color]
|
|
107
|
+
style = formatter[:style] || formatter[:attribute] # (support original cap colors)
|
|
108
|
+
message.gsub!(formatter[:match], formatter[:replace]) if formatter[:replace]
|
|
109
|
+
message = formatter[:prepend] + message unless formatter[:prepend].nil?
|
|
110
|
+
message = message + formatter[:append] unless formatter[:append].nil?
|
|
111
|
+
message = Time.now.strftime('%Y-%m-%d %T') + ' ' + message if formatter[:timestamp]
|
|
112
|
+
break unless formatter[:replace]
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if color == :hide
|
|
118
|
+
# Don't do anything if color is set to :hide
|
|
119
|
+
return false
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
term_color = COLORS[color]
|
|
123
|
+
term_style = STYLES[style]
|
|
124
|
+
|
|
125
|
+
# Don't format message if no color or style
|
|
126
|
+
unless color == :none and style.nil?
|
|
127
|
+
unless line_prefix.nil?
|
|
128
|
+
line_prefix = format(line_prefix, term_color, term_style, nil)
|
|
129
|
+
end
|
|
130
|
+
message = format(message, term_color, term_style)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
32
134
|
indent = "%*s" % [MAX_LEVEL, "*" * (MAX_LEVEL - level)]
|
|
33
|
-
message.each do |line|
|
|
135
|
+
(RUBY_VERSION >= "1.9" ? message.lines : message).each do |line|
|
|
34
136
|
if line_prefix
|
|
35
137
|
device.puts "#{indent} [#{line_prefix}] #{line.strip}\n"
|
|
36
138
|
else
|
|
@@ -55,5 +157,10 @@ module Capistrano
|
|
|
55
157
|
def trace(message, line_prefix=nil)
|
|
56
158
|
log(TRACE, message, line_prefix)
|
|
57
159
|
end
|
|
160
|
+
|
|
161
|
+
def format(message, color, style, nl = "\n")
|
|
162
|
+
style = "#{style};" if style
|
|
163
|
+
"\e[#{style}#{color}m" + message.to_s.strip + "\e[0m#{nl}"
|
|
164
|
+
end
|
|
58
165
|
end
|
|
59
166
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Capistrano
|
|
2
|
+
module Processable
|
|
3
|
+
module SessionAssociation
|
|
4
|
+
def self.on(exception, session)
|
|
5
|
+
unless exception.respond_to?(:session)
|
|
6
|
+
exception.extend(self)
|
|
7
|
+
exception.session = session
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
return exception
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_accessor :session
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def process_iteration(wait=nil, &block)
|
|
17
|
+
ensure_each_session { |session| session.preprocess }
|
|
18
|
+
|
|
19
|
+
return false if block && !block.call(self)
|
|
20
|
+
|
|
21
|
+
readers = sessions.map { |session| session.listeners.keys }.flatten.reject { |io| io.closed? }
|
|
22
|
+
writers = readers.select { |io| io.respond_to?(:pending_write?) && io.pending_write? }
|
|
23
|
+
|
|
24
|
+
if readers.any? || writers.any?
|
|
25
|
+
readers, writers, = IO.select(readers, writers, nil, wait)
|
|
26
|
+
else
|
|
27
|
+
return false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if readers
|
|
31
|
+
ensure_each_session do |session|
|
|
32
|
+
ios = session.listeners.keys
|
|
33
|
+
session.postprocess(ios & readers, ios & writers)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ensure_each_session
|
|
41
|
+
errors = []
|
|
42
|
+
|
|
43
|
+
sessions.each do |session|
|
|
44
|
+
begin
|
|
45
|
+
yield session
|
|
46
|
+
rescue Exception => error
|
|
47
|
+
errors << SessionAssociation.on(error, session)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
raise errors.first if errors.any?
|
|
52
|
+
sessions
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|