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.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG +763 -18
  5. data/Gemfile +15 -0
  6. data/README.md +94 -0
  7. data/Rakefile +11 -0
  8. data/bin/cap +0 -0
  9. data/bin/capify +40 -25
  10. data/capistrano.gemspec +40 -0
  11. data/lib/capistrano/callback.rb +5 -1
  12. data/lib/capistrano/cli/execute.rb +10 -7
  13. data/lib/capistrano/cli/help.rb +39 -16
  14. data/lib/capistrano/cli/help.txt +44 -16
  15. data/lib/capistrano/cli/options.rb +71 -11
  16. data/lib/capistrano/cli/ui.rb +13 -1
  17. data/lib/capistrano/cli.rb +5 -5
  18. data/lib/capistrano/command.rb +215 -58
  19. data/lib/capistrano/configuration/actions/file_transfer.rb +29 -14
  20. data/lib/capistrano/configuration/actions/inspect.rb +3 -3
  21. data/lib/capistrano/configuration/actions/invocation.rb +224 -22
  22. data/lib/capistrano/configuration/alias_task.rb +26 -0
  23. data/lib/capistrano/configuration/callbacks.rb +26 -27
  24. data/lib/capistrano/configuration/connections.rb +130 -52
  25. data/lib/capistrano/configuration/execution.rb +34 -18
  26. data/lib/capistrano/configuration/loading.rb +99 -6
  27. data/lib/capistrano/configuration/log_formatters.rb +75 -0
  28. data/lib/capistrano/configuration/namespaces.rb +45 -12
  29. data/lib/capistrano/configuration/roles.rb +34 -2
  30. data/lib/capistrano/configuration/servers.rb +51 -10
  31. data/lib/capistrano/configuration/variables.rb +3 -3
  32. data/lib/capistrano/configuration.rb +20 -4
  33. data/lib/capistrano/errors.rb +12 -8
  34. data/lib/capistrano/ext/multistage.rb +67 -0
  35. data/lib/capistrano/ext/string.rb +5 -0
  36. data/lib/capistrano/extensions.rb +1 -1
  37. data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
  38. data/lib/capistrano/logger.rb +112 -5
  39. data/lib/capistrano/processable.rb +55 -0
  40. data/lib/capistrano/recipes/compat.rb +2 -2
  41. data/lib/capistrano/recipes/deploy/assets.rb +202 -0
  42. data/lib/capistrano/recipes/deploy/dependencies.rb +2 -2
  43. data/lib/capistrano/recipes/deploy/local_dependency.rb +10 -2
  44. data/lib/capistrano/recipes/deploy/remote_dependency.rb +54 -2
  45. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  46. data/lib/capistrano/recipes/deploy/scm/base.rb +31 -11
  47. data/lib/capistrano/recipes/deploy/scm/bzr.rb +14 -14
  48. data/lib/capistrano/recipes/deploy/scm/cvs.rb +11 -9
  49. data/lib/capistrano/recipes/deploy/scm/darcs.rb +12 -1
  50. data/lib/capistrano/recipes/deploy/scm/git.rb +299 -0
  51. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +23 -15
  52. data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
  53. data/lib/capistrano/recipes/deploy/scm/perforce.rb +54 -28
  54. data/lib/capistrano/recipes/deploy/scm/subversion.rb +36 -18
  55. data/lib/capistrano/recipes/deploy/scm.rb +1 -1
  56. data/lib/capistrano/recipes/deploy/strategy/base.rb +32 -4
  57. data/lib/capistrano/recipes/deploy/strategy/copy.rb +238 -43
  58. data/lib/capistrano/recipes/deploy/strategy/remote.rb +1 -1
  59. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +11 -1
  60. data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
  61. data/lib/capistrano/recipes/deploy/strategy.rb +4 -3
  62. data/lib/capistrano/recipes/deploy.rb +265 -123
  63. data/lib/capistrano/recipes/standard.rb +1 -1
  64. data/lib/capistrano/role.rb +102 -0
  65. data/lib/capistrano/server_definition.rb +6 -1
  66. data/lib/capistrano/shell.rb +30 -33
  67. data/lib/capistrano/ssh.rb +46 -60
  68. data/lib/capistrano/task_definition.rb +16 -8
  69. data/lib/capistrano/transfer.rb +218 -0
  70. data/lib/capistrano/version.rb +8 -17
  71. data/lib/capistrano.rb +4 -1
  72. data/test/cli/execute_test.rb +3 -3
  73. data/test/cli/help_test.rb +33 -7
  74. data/test/cli/options_test.rb +109 -6
  75. data/test/cli/ui_test.rb +2 -2
  76. data/test/cli_test.rb +3 -3
  77. data/test/command_test.rb +144 -124
  78. data/test/configuration/actions/file_transfer_test.rb +41 -20
  79. data/test/configuration/actions/inspect_test.rb +21 -7
  80. data/test/configuration/actions/invocation_test.rb +141 -30
  81. data/test/configuration/alias_task_test.rb +118 -0
  82. data/test/configuration/callbacks_test.rb +41 -46
  83. data/test/configuration/connections_test.rb +187 -36
  84. data/test/configuration/execution_test.rb +18 -2
  85. data/test/configuration/loading_test.rb +33 -4
  86. data/test/configuration/namespace_dsl_test.rb +54 -5
  87. data/test/configuration/roles_test.rb +114 -4
  88. data/test/configuration/servers_test.rb +97 -4
  89. data/test/configuration/variables_test.rb +12 -2
  90. data/test/configuration_test.rb +9 -13
  91. data/test/deploy/local_dependency_test.rb +76 -0
  92. data/test/deploy/remote_dependency_test.rb +146 -0
  93. data/test/deploy/scm/accurev_test.rb +23 -0
  94. data/test/deploy/scm/base_test.rb +1 -1
  95. data/test/deploy/scm/bzr_test.rb +51 -0
  96. data/test/deploy/scm/darcs_test.rb +37 -0
  97. data/test/deploy/scm/git_test.rb +274 -0
  98. data/test/deploy/scm/mercurial_test.rb +134 -0
  99. data/test/deploy/scm/none_test.rb +35 -0
  100. data/test/deploy/scm/perforce_test.rb +23 -0
  101. data/test/deploy/scm/subversion_test.rb +68 -0
  102. data/test/deploy/strategy/copy_test.rb +240 -26
  103. data/test/extensions_test.rb +2 -2
  104. data/test/logger_formatting_test.rb +149 -0
  105. data/test/logger_test.rb +13 -2
  106. data/test/recipes_test.rb +25 -0
  107. data/test/role_test.rb +11 -0
  108. data/test/server_definition_test.rb +15 -2
  109. data/test/shell_test.rb +33 -1
  110. data/test/ssh_test.rb +40 -24
  111. data/test/task_definition_test.rb +18 -2
  112. data/test/transfer_test.rb +168 -0
  113. data/test/utils.rb +28 -33
  114. data/test/version_test.rb +4 -17
  115. metadata +214 -100
  116. data/MIT-LICENSE +0 -20
  117. data/README +0 -43
  118. data/examples/sample.rb +0 -14
  119. data/lib/capistrano/gateway.rb +0 -131
  120. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
  121. data/lib/capistrano/recipes/upgrade.rb +0 -33
  122. data/lib/capistrano/upload.rb +0 -146
  123. data/test/gateway_test.rb +0 -167
  124. 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 (default)"
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] = true
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::STRING}"
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
- ) { options[:verbose] ||= 0; options[:verbose] += 1 }
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
@@ -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
@@ -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(%w(-vvvv -r config/deploy update_code)).execute!
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, but you'll often wind up duplicating logic
24
- # defined in the CLI class. The above snippet, redone using the Configuration
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.logger_level = Capistrano::Logger::TRACE
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
@@ -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
- attr_reader :command, :sessions, :options
10
+ include Processable
9
11
 
10
- def self.process(command, sessions, options={}, &block)
11
- new(command, sessions, options, &block).process!
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(command, sessions, options={}, &block)
24
- @command = command.strip.gsub(/\r?\n/, "\\\n")
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
- since = Time.now
36
- loop do
37
- active = 0
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
- hosts = failed.map { |ch| ch[:server] }
56
- error = CommandError.new("command #{command.inspect} failed on #{hosts.join(',')}")
57
- error.hosts = hosts
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.open_channel do |channel|
81
- server = session.xserver
82
-
83
- channel[:server] = server
84
- channel[:host] = server.host
85
- channel[:options] = options
86
- channel.request_pty :want_reply => true
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
- channel.on_failure do |ch|
97
- # just log it, don't actually raise an exception, since the
98
- # process method will see that the status is not zero and will
99
- # raise an exception then.
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
- channel.on_data do |ch, data|
105
- @callback[ch, :out, data] if @callback
106
- end
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
- channel.on_extended_data do |ch, type, data|
109
- @callback[ch, :err, data] if @callback
110
- end
225
+ command_line = [environment, shell, cmd].compact.join(" ")
226
+ ch[:command] = command_line
111
227
 
112
- channel.on_request do |ch, request, reply, data|
113
- ch[:status] = data.read_long if request == "exit-status"
114
- end
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
- channel.on_close do |ch|
117
- ch[:closed] = true
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
- command.gsub(/\$CAPISTRANO:HOST\$/, channel[:host])
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/upload'
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
- execute_on_servers(options) do |servers|
13
- targets = servers.map { |s| sessions[s] }
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 targetted by
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
- execute_on_servers(options.merge(:once => true)) do |servers|
24
- logger.info "downloading `#{servers.first.host}:#{remote_path}' to `#{path}'"
25
- sftp = sessions[servers.first].sftp
26
- sftp.connect unless sftp.state == :open
27
- sftp.get_file remote_path, path
28
- logger.debug "download finished"
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 raise CaptureError, "error processing #{command.inspect}: #{data.inspect}"
37
+ when :err then warn "[err :: #{ch[:server]}] #{data}"
38
38
  end
39
39
  end
40
40
  output