capistrano 2.0.0 → 2.15.11
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 +763 -18
- data/Gemfile +15 -0
- data/README.md +94 -0
- data/Rakefile +11 -0
- data/bin/cap +0 -0
- data/bin/capify +40 -25
- 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 +214 -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
|
@@ -24,9 +24,14 @@ module Capistrano
|
|
|
24
24
|
# line switches for Capistrano, and what their corresponding behaviors
|
|
25
25
|
# are.
|
|
26
26
|
def option_parser #:nodoc:
|
|
27
|
+
@logger = Logger.new
|
|
27
28
|
@option_parser ||= OptionParser.new do |opts|
|
|
28
29
|
opts.banner = "Usage: #{File.basename($0)} [options] action ..."
|
|
29
30
|
|
|
31
|
+
opts.on("-d", "--debug",
|
|
32
|
+
"Prompts before each remote command execution."
|
|
33
|
+
) { |value| options[:debug] = true }
|
|
34
|
+
|
|
30
35
|
opts.on("-e", "--explain TASK",
|
|
31
36
|
"Displays help (if available) for the task."
|
|
32
37
|
) { |value| options[:explain] = value }
|
|
@@ -39,7 +44,7 @@ module Capistrano
|
|
|
39
44
|
"A recipe file to load. May be given more than once."
|
|
40
45
|
) { |value| options[:recipes] << value }
|
|
41
46
|
|
|
42
|
-
opts.on("-H", "--long-help", "Explain these options.") do
|
|
47
|
+
opts.on("-H", "--long-help", "Explain these options and environment variables.") do
|
|
43
48
|
long_help
|
|
44
49
|
exit
|
|
45
50
|
end
|
|
@@ -49,14 +54,35 @@ module Capistrano
|
|
|
49
54
|
exit
|
|
50
55
|
end
|
|
51
56
|
|
|
57
|
+
opts.on("-l", "--logger [STDERR|STDOUT|file]",
|
|
58
|
+
"Choose logger method. STDERR used by default."
|
|
59
|
+
) do |value|
|
|
60
|
+
options[:output] = if value.nil? || value.upcase == 'STDERR'
|
|
61
|
+
# Using default logger.
|
|
62
|
+
nil
|
|
63
|
+
elsif value.upcase == 'STDOUT'
|
|
64
|
+
$stdout
|
|
65
|
+
else
|
|
66
|
+
value
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
opts.on("-n", "--dry-run",
|
|
71
|
+
"Prints out commands without running them."
|
|
72
|
+
) { |value| options[:dry_run] = true }
|
|
73
|
+
|
|
52
74
|
opts.on("-p", "--password",
|
|
53
75
|
"Immediately prompt for the password."
|
|
54
76
|
) { options[:password] = nil }
|
|
55
77
|
|
|
56
78
|
opts.on("-q", "--quiet",
|
|
57
|
-
"Make the output as quiet as possible
|
|
79
|
+
"Make the output as quiet as possible."
|
|
58
80
|
) { options[:verbose] = 0 }
|
|
59
81
|
|
|
82
|
+
opts.on("-r", "--preserve-roles",
|
|
83
|
+
"Preserve task roles"
|
|
84
|
+
) { options[:preserve_roles] = true }
|
|
85
|
+
|
|
60
86
|
opts.on("-S", "--set-before NAME=VALUE",
|
|
61
87
|
"Set a variable before the recipes are loaded."
|
|
62
88
|
) do |pair|
|
|
@@ -71,24 +97,35 @@ module Capistrano
|
|
|
71
97
|
options[:vars][name.to_sym] = value
|
|
72
98
|
end
|
|
73
99
|
|
|
74
|
-
opts.on("-T", "--tasks",
|
|
75
|
-
"List all tasks in the loaded recipe files."
|
|
76
|
-
) do
|
|
77
|
-
options[:tasks] =
|
|
100
|
+
opts.on("-T", "--tasks [PATTERN]",
|
|
101
|
+
"List all tasks (matching optional PATTERN) in the loaded recipe files."
|
|
102
|
+
) do |value|
|
|
103
|
+
options[:tasks] = if value
|
|
104
|
+
value
|
|
105
|
+
else
|
|
106
|
+
true
|
|
107
|
+
end
|
|
78
108
|
options[:verbose] ||= 0
|
|
79
109
|
end
|
|
80
110
|
|
|
111
|
+
opts.on("-t", "--tool",
|
|
112
|
+
"Abbreviates the output of -T for tool integration."
|
|
113
|
+
) { options[:tool] = true }
|
|
114
|
+
|
|
81
115
|
opts.on("-V", "--version",
|
|
82
116
|
"Display the Capistrano version, and exit."
|
|
83
117
|
) do
|
|
84
118
|
require 'capistrano/version'
|
|
85
|
-
puts "Capistrano v#{Capistrano::Version
|
|
119
|
+
puts "Capistrano v#{Capistrano::Version}"
|
|
86
120
|
exit
|
|
87
121
|
end
|
|
88
122
|
|
|
89
123
|
opts.on("-v", "--verbose",
|
|
90
124
|
"Be more verbose. May be given more than once."
|
|
91
|
-
)
|
|
125
|
+
) do
|
|
126
|
+
options[:verbose] ||= 0
|
|
127
|
+
options[:verbose] += 1
|
|
128
|
+
end
|
|
92
129
|
|
|
93
130
|
opts.on("-X", "--skip-system-config",
|
|
94
131
|
"Don't load the system config file (capistrano.conf)"
|
|
@@ -116,6 +153,8 @@ module Capistrano
|
|
|
116
153
|
|
|
117
154
|
option_parser.parse!(args)
|
|
118
155
|
|
|
156
|
+
coerce_variable_types!
|
|
157
|
+
|
|
119
158
|
# if no verbosity has been specified, be verbose
|
|
120
159
|
options[:verbose] = 3 if !options.has_key?(:verbose)
|
|
121
160
|
|
|
@@ -146,6 +185,7 @@ module Capistrano
|
|
|
146
185
|
%w(Capfile capfile).each do |file|
|
|
147
186
|
if File.file?(file)
|
|
148
187
|
options[:recipes] << file
|
|
188
|
+
@logger.info "Using recipes from #{File.join(current,file)}"
|
|
149
189
|
return
|
|
150
190
|
end
|
|
151
191
|
end
|
|
@@ -161,7 +201,7 @@ module Capistrano
|
|
|
161
201
|
def default_sysconf #:nodoc:
|
|
162
202
|
File.join(sysconf_directory, "capistrano.conf")
|
|
163
203
|
end
|
|
164
|
-
|
|
204
|
+
|
|
165
205
|
def default_dotfile #:nodoc:
|
|
166
206
|
File.join(home_directory, ".caprc")
|
|
167
207
|
end
|
|
@@ -171,13 +211,33 @@ module Capistrano
|
|
|
171
211
|
# appropriate location for this file in Windows.
|
|
172
212
|
ENV["SystemRoot"] || '/etc'
|
|
173
213
|
end
|
|
174
|
-
|
|
214
|
+
|
|
175
215
|
def home_directory #:nodoc:
|
|
176
216
|
ENV["HOME"] ||
|
|
177
217
|
(ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") ||
|
|
178
218
|
"/"
|
|
179
219
|
end
|
|
180
220
|
|
|
221
|
+
def coerce_variable_types!
|
|
222
|
+
[:pre_vars, :vars].each do |collection|
|
|
223
|
+
options[collection].keys.each do |key|
|
|
224
|
+
options[collection][key] = coerce_variable(options[collection][key])
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def coerce_variable(value)
|
|
230
|
+
case value
|
|
231
|
+
when /^"(.*)"$/ then $1
|
|
232
|
+
when /^'(.*)'$/ then $1
|
|
233
|
+
when /^\d+$/ then value.to_i
|
|
234
|
+
when /^\d+\.\d*$/ then value.to_f
|
|
235
|
+
when "true" then true
|
|
236
|
+
when "false" then false
|
|
237
|
+
when "nil" then nil
|
|
238
|
+
else value
|
|
239
|
+
end
|
|
240
|
+
end
|
|
181
241
|
end
|
|
182
242
|
end
|
|
183
|
-
end
|
|
243
|
+
end
|
data/lib/capistrano/cli/ui.rb
CHANGED
|
@@ -22,7 +22,19 @@ module Capistrano
|
|
|
22
22
|
def password_prompt(prompt="Password: ")
|
|
23
23
|
ui.ask(prompt) { |q| q.echo = false }
|
|
24
24
|
end
|
|
25
|
+
|
|
26
|
+
# Debug mode prompt
|
|
27
|
+
def debug_prompt(cmd)
|
|
28
|
+
ui.say("Preparing to execute command: #{cmd}")
|
|
29
|
+
prompt = "Execute ([Yes], No, Abort) "
|
|
30
|
+
ui.ask("#{prompt}? ") do |q|
|
|
31
|
+
q.overwrite = false
|
|
32
|
+
q.default = 'y'
|
|
33
|
+
q.validate = /(y(es)?)|(no?)|(a(bort)?|\n)/i
|
|
34
|
+
q.responses[:not_valid] = prompt
|
|
35
|
+
end
|
|
36
|
+
end
|
|
25
37
|
end
|
|
26
38
|
end
|
|
27
39
|
end
|
|
28
|
-
end
|
|
40
|
+
end
|
data/lib/capistrano/cli.rb
CHANGED
|
@@ -17,17 +17,16 @@ module Capistrano
|
|
|
17
17
|
# different set of parameters (such as when embedded cap in a program):
|
|
18
18
|
#
|
|
19
19
|
# require 'capistrano/cli'
|
|
20
|
-
# Capistrano::CLI.parse(%
|
|
20
|
+
# Capistrano::CLI.parse(%W(-vvvv -f config/deploy update_code)).execute!
|
|
21
21
|
#
|
|
22
22
|
# Note that you can also embed cap directly by creating a new Configuration
|
|
23
|
-
# instance and setting it up,
|
|
24
|
-
#
|
|
25
|
-
# class directly, would look like:
|
|
23
|
+
# instance and setting it up, The above snippet, redone using the
|
|
24
|
+
# Configuration class directly, would look like:
|
|
26
25
|
#
|
|
27
26
|
# require 'capistrano'
|
|
28
27
|
# require 'capistrano/cli'
|
|
29
28
|
# config = Capistrano::Configuration.new
|
|
30
|
-
# config.
|
|
29
|
+
# config.logger.level = Capistrano::Logger::TRACE
|
|
31
30
|
# config.set(:password) { Capistrano::CLI.password_prompt }
|
|
32
31
|
# config.load "config/deploy"
|
|
33
32
|
# config.update_code
|
|
@@ -43,5 +42,6 @@ module Capistrano
|
|
|
43
42
|
# Mix-in the actual behavior
|
|
44
43
|
include Execute, Options, UI
|
|
45
44
|
include Help # needs to be included last, because it overrides some methods
|
|
45
|
+
|
|
46
46
|
end
|
|
47
47
|
end
|
data/lib/capistrano/command.rb
CHANGED
|
@@ -1,14 +1,143 @@
|
|
|
1
|
+
require 'benchmark'
|
|
1
2
|
require 'capistrano/errors'
|
|
3
|
+
require 'capistrano/processable'
|
|
2
4
|
|
|
3
5
|
module Capistrano
|
|
4
6
|
|
|
5
7
|
# This class encapsulates a single command to be executed on a set of remote
|
|
6
8
|
# machines, in parallel.
|
|
7
9
|
class Command
|
|
8
|
-
|
|
10
|
+
include Processable
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
class Tree
|
|
13
|
+
attr_reader :configuration
|
|
14
|
+
attr_reader :branches
|
|
15
|
+
attr_reader :fallback
|
|
16
|
+
|
|
17
|
+
include Enumerable
|
|
18
|
+
|
|
19
|
+
class Branch
|
|
20
|
+
attr_accessor :command, :callback, :condition
|
|
21
|
+
attr_reader :options
|
|
22
|
+
|
|
23
|
+
def initialize(command, options, callback)
|
|
24
|
+
@command = command.strip.gsub(/\r?\n/, "\\\n")
|
|
25
|
+
@callback = callback || Capistrano::Configuration.default_io_proc
|
|
26
|
+
@options = options
|
|
27
|
+
@skip = false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def last?
|
|
31
|
+
options[:last]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def skip?
|
|
35
|
+
@skip
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def skip!
|
|
39
|
+
@skip = true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def match(server)
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s(parallel=false)
|
|
47
|
+
if parallel && @condition
|
|
48
|
+
"#{condition.inspect} :: #{command.inspect}"
|
|
49
|
+
else
|
|
50
|
+
command.inspect
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class ConditionBranch < Branch
|
|
56
|
+
attr_accessor :configuration
|
|
57
|
+
|
|
58
|
+
class Evaluator
|
|
59
|
+
attr_reader :configuration, :condition, :server
|
|
60
|
+
|
|
61
|
+
def initialize(config, condition, server)
|
|
62
|
+
@configuration = config
|
|
63
|
+
@condition = condition
|
|
64
|
+
@server = server
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def in?(role)
|
|
68
|
+
configuration.roles[role].include?(server)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def result
|
|
72
|
+
eval(condition, binding)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def method_missing(sym, *args, &block)
|
|
76
|
+
if server.respond_to?(sym)
|
|
77
|
+
server.send(sym, *args, &block)
|
|
78
|
+
elsif configuration.respond_to?(sym)
|
|
79
|
+
configuration.send(sym, *args, &block)
|
|
80
|
+
else
|
|
81
|
+
super
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def initialize(configuration, condition, command, options, callback)
|
|
87
|
+
@configuration = configuration
|
|
88
|
+
@condition = condition
|
|
89
|
+
super(command, options, callback)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def match(server)
|
|
93
|
+
Evaluator.new(configuration, condition, server).result
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class ElseBranch < Branch
|
|
98
|
+
def initialize(command, options, callback)
|
|
99
|
+
@condition = "else"
|
|
100
|
+
super(command, options, callback)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def initialize(config)
|
|
105
|
+
@configuration = config
|
|
106
|
+
@branches = []
|
|
107
|
+
yield self if block_given?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def when(condition, command, options={}, &block)
|
|
111
|
+
branches << ConditionBranch.new(configuration, condition, command, options, block)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def else(command, &block)
|
|
115
|
+
@fallback = ElseBranch.new(command, {}, block)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def branches_for(server)
|
|
119
|
+
seen_last = false
|
|
120
|
+
matches = branches.select do |branch|
|
|
121
|
+
success = !seen_last && !branch.skip? && branch.match(server)
|
|
122
|
+
seen_last = success && branch.last?
|
|
123
|
+
success
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
matches << fallback if matches.empty? && fallback
|
|
127
|
+
return matches
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def each
|
|
131
|
+
branches.each { |branch| yield branch }
|
|
132
|
+
yield fallback if fallback
|
|
133
|
+
return self
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
attr_reader :tree, :sessions, :options
|
|
138
|
+
|
|
139
|
+
def self.process(tree, sessions, options={})
|
|
140
|
+
new(tree, sessions, options).process!
|
|
12
141
|
end
|
|
13
142
|
|
|
14
143
|
# Instantiates a new command object. The +command+ must be a string
|
|
@@ -20,11 +149,16 @@ module Capistrano
|
|
|
20
149
|
# * +data+: (optional), a string to be sent to the command via it's stdin
|
|
21
150
|
# * +env+: (optional), a string or hash to be interpreted as environment
|
|
22
151
|
# variables that should be defined for this command invocation.
|
|
23
|
-
def initialize(
|
|
24
|
-
|
|
152
|
+
def initialize(tree, sessions, options={}, &block)
|
|
153
|
+
if String === tree
|
|
154
|
+
tree = Tree.new(nil) { |t| t.else(tree, &block) }
|
|
155
|
+
elsif block
|
|
156
|
+
raise ArgumentError, "block given with tree argument"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
@tree = tree
|
|
25
160
|
@sessions = sessions
|
|
26
161
|
@options = options
|
|
27
|
-
@callback = block
|
|
28
162
|
@channels = open_channels
|
|
29
163
|
end
|
|
30
164
|
|
|
@@ -32,29 +166,19 @@ module Capistrano
|
|
|
32
166
|
# fails (non-zero return code) on any of the hosts, this will raise a
|
|
33
167
|
# Capistrano::CommandError.
|
|
34
168
|
def process!
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@channels.each do |ch|
|
|
39
|
-
next if ch[:closed]
|
|
40
|
-
active += 1
|
|
41
|
-
ch.connection.process(true)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
break if active == 0
|
|
45
|
-
if Time.now - since >= 1
|
|
46
|
-
since = Time.now
|
|
47
|
-
@channels.each { |ch| ch.connection.ping! }
|
|
169
|
+
elapsed = Benchmark.realtime do
|
|
170
|
+
loop do
|
|
171
|
+
break unless process_iteration { @channels.any? { |ch| !ch[:closed] } }
|
|
48
172
|
end
|
|
49
|
-
sleep 0.01 # a brief respite, to keep the CPU from going crazy
|
|
50
173
|
end
|
|
51
174
|
|
|
52
|
-
logger.trace "command finished" if logger
|
|
175
|
+
logger.trace "command finished in #{(elapsed * 1000).round}ms" if logger
|
|
53
176
|
|
|
54
177
|
if (failed = @channels.select { |ch| ch[:status] != 0 }).any?
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
error
|
|
178
|
+
commands = failed.inject({}) { |map, ch| (map[ch[:command]] ||= []) << ch[:server]; map }
|
|
179
|
+
message = commands.map { |command, list| "#{command.inspect} on #{list.join(',')}" }.join("; ")
|
|
180
|
+
error = CommandError.new("failed: #{message}")
|
|
181
|
+
error.hosts = commands.values.flatten
|
|
58
182
|
raise error
|
|
59
183
|
end
|
|
60
184
|
|
|
@@ -77,51 +201,84 @@ module Capistrano
|
|
|
77
201
|
|
|
78
202
|
def open_channels
|
|
79
203
|
sessions.map do |session|
|
|
80
|
-
session.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
channel.on_success do |ch|
|
|
89
|
-
logger.trace "executing command", ch[:server] if logger
|
|
90
|
-
escaped = replace_placeholders(command, ch).gsub(/[$\\`"]/) { |m| "\\#{m}" }
|
|
91
|
-
command_line = [environment, options[:shell] || "sh", "-c", "\"#{escaped}\""].compact.join(" ")
|
|
92
|
-
ch.exec(command_line)
|
|
93
|
-
ch.send_data(options[:data]) if options[:data]
|
|
94
|
-
end
|
|
204
|
+
server = session.xserver
|
|
205
|
+
tree.branches_for(server).map do |branch|
|
|
206
|
+
session.open_channel do |channel|
|
|
207
|
+
channel[:server] = server
|
|
208
|
+
channel[:host] = server.host
|
|
209
|
+
channel[:options] = options
|
|
210
|
+
channel[:branch] = branch
|
|
95
211
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
logger.important "could not open channel", ch[:server] if logger
|
|
101
|
-
ch.close
|
|
102
|
-
end
|
|
212
|
+
request_pty_if_necessary(channel) do |ch, success|
|
|
213
|
+
if success
|
|
214
|
+
logger.trace "executing command", ch[:server] if logger
|
|
215
|
+
cmd = replace_placeholders(channel[:branch].command, ch)
|
|
103
216
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
217
|
+
if options[:shell] == false
|
|
218
|
+
shell = nil
|
|
219
|
+
else
|
|
220
|
+
shell = "#{options[:shell] || "sh"} -c"
|
|
221
|
+
cmd = cmd.gsub(/'/) { |m| "'\\''" }
|
|
222
|
+
cmd = "'#{cmd}'"
|
|
223
|
+
end
|
|
107
224
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
end
|
|
225
|
+
command_line = [environment, shell, cmd].compact.join(" ")
|
|
226
|
+
ch[:command] = command_line
|
|
111
227
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
228
|
+
ch.exec(command_line)
|
|
229
|
+
ch.send_data(options[:data]) if options[:data]
|
|
230
|
+
ch.eof! if options[:eof]
|
|
231
|
+
else
|
|
232
|
+
# just log it, don't actually raise an exception, since the
|
|
233
|
+
# process method will see that the status is not zero and will
|
|
234
|
+
# raise an exception then.
|
|
235
|
+
logger.important "could not open channel", ch[:server] if logger
|
|
236
|
+
ch.close
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
channel.on_data do |ch, data|
|
|
241
|
+
ch[:branch].callback[ch, :out, data]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
channel.on_extended_data do |ch, type, data|
|
|
245
|
+
ch[:branch].callback[ch, :err, data]
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
channel.on_request("exit-status") do |ch, data|
|
|
249
|
+
ch[:status] = data.read_long
|
|
250
|
+
end
|
|
115
251
|
|
|
116
|
-
|
|
117
|
-
|
|
252
|
+
channel.on_request("exit-signal") do |ch, data|
|
|
253
|
+
if logger
|
|
254
|
+
exit_signal = data.read_string
|
|
255
|
+
logger.important "command received signal #{exit_signal}", ch[:server]
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
channel.on_close do |ch|
|
|
260
|
+
ch[:closed] = true
|
|
261
|
+
end
|
|
118
262
|
end
|
|
119
263
|
end
|
|
264
|
+
end.flatten
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def request_pty_if_necessary(channel)
|
|
268
|
+
if options[:pty]
|
|
269
|
+
channel.request_pty do |ch, success|
|
|
270
|
+
yield ch, success
|
|
271
|
+
end
|
|
272
|
+
else
|
|
273
|
+
yield channel, true
|
|
120
274
|
end
|
|
121
275
|
end
|
|
122
276
|
|
|
123
277
|
def replace_placeholders(command, channel)
|
|
124
|
-
|
|
278
|
+
roles = @tree.configuration && @tree.configuration.role_names_for_host(channel[:server])
|
|
279
|
+
command = command.gsub(/\$CAPISTRANO:HOST\$/, channel[:host])
|
|
280
|
+
command.gsub!(/\$CAPISTRANO:HOSTROLES\$/, roles.join(',')) if roles
|
|
281
|
+
command
|
|
125
282
|
end
|
|
126
283
|
|
|
127
284
|
# prepare a space-separated sequence of variables assignments
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require 'capistrano/
|
|
1
|
+
require 'capistrano/transfer'
|
|
2
2
|
|
|
3
3
|
module Capistrano
|
|
4
4
|
class Configuration
|
|
@@ -9,23 +9,38 @@ module Capistrano
|
|
|
9
9
|
# by the current task. If <tt>:mode</tt> is specified it is used to
|
|
10
10
|
# set the mode on the file.
|
|
11
11
|
def put(data, path, options={})
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Upload.process(targets, path, :data => data, :mode => options[:mode], :logger => logger)
|
|
15
|
-
end
|
|
12
|
+
opts = options.dup
|
|
13
|
+
upload(StringIO.new(data), path, opts)
|
|
16
14
|
end
|
|
17
|
-
|
|
18
|
-
# Get file remote_path from FIRST server
|
|
15
|
+
|
|
16
|
+
# Get file remote_path from FIRST server targeted by
|
|
19
17
|
# the current task and transfer it to local machine as path.
|
|
20
18
|
#
|
|
21
19
|
# get "#{deploy_to}/current/log/production.log", "log/production.log.web"
|
|
22
|
-
def get(remote_path, path, options
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
def get(remote_path, path, options={}, &block)
|
|
21
|
+
download(remote_path, path, options.merge(:once => true), &block)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def upload(from, to, options={}, &block)
|
|
25
|
+
mode = options.delete(:mode)
|
|
26
|
+
transfer(:up, from, to, options, &block)
|
|
27
|
+
if mode
|
|
28
|
+
mode = mode.is_a?(Numeric) ? mode.to_s(8) : mode.to_s
|
|
29
|
+
run "chmod #{mode} #{to}", options
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def download(from, to, options={}, &block)
|
|
34
|
+
transfer(:down, from, to, options, &block)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def transfer(direction, from, to, options={}, &block)
|
|
38
|
+
if dry_run
|
|
39
|
+
return logger.debug "transfering: #{[direction, from, to] * ', '}"
|
|
40
|
+
end
|
|
41
|
+
execute_on_servers(options) do |servers|
|
|
42
|
+
targets = servers.map { |s| sessions[s] }
|
|
43
|
+
Transfer.process(direction, from, to, targets, options.merge(:logger => logger), &block)
|
|
29
44
|
end
|
|
30
45
|
end
|
|
31
46
|
|
|
@@ -20,7 +20,7 @@ module Capistrano
|
|
|
20
20
|
# stream "tail -f #{shared_path}/log/fastcgi.crash.log"
|
|
21
21
|
# end
|
|
22
22
|
def stream(command, options={})
|
|
23
|
-
invoke_command(command, options) do |ch, stream, out|
|
|
23
|
+
invoke_command(command, options.merge(:eof => !command.include?(sudo))) do |ch, stream, out|
|
|
24
24
|
puts out if stream == :out
|
|
25
25
|
warn "[err :: #{ch[:server]}] #{out}" if stream == :err
|
|
26
26
|
end
|
|
@@ -31,10 +31,10 @@ module Capistrano
|
|
|
31
31
|
# string. The command is invoked via #invoke_command.
|
|
32
32
|
def capture(command, options={})
|
|
33
33
|
output = ""
|
|
34
|
-
invoke_command(command, options.merge(:once => true)) do |ch, stream, data|
|
|
34
|
+
invoke_command(command, options.merge(:once => true, :eof => !command.include?(sudo))) do |ch, stream, data|
|
|
35
35
|
case stream
|
|
36
36
|
when :out then output << data
|
|
37
|
-
when :err then
|
|
37
|
+
when :err then warn "[err :: #{ch[:server]}] #{data}"
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
output
|