capistrano 2.1.0 → 3.0.0

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 (166) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG.md +89 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +674 -0
  7. data/README.md +226 -0
  8. data/Rakefile +5 -0
  9. data/bin/cap +2 -3
  10. data/bin/capify +7 -77
  11. data/capistrano-public_cert.pem +22 -0
  12. data/capistrano.gemspec +35 -0
  13. data/features/deploy.feature +52 -0
  14. data/features/installation.feature +16 -0
  15. data/features/remote_file_task.feature +14 -0
  16. data/features/step_definitions/assertions.rb +90 -0
  17. data/features/step_definitions/cap_commands.rb +8 -0
  18. data/features/step_definitions/setup.rb +25 -0
  19. data/features/support/env.rb +12 -0
  20. data/features/support/remote_command_helpers.rb +20 -0
  21. data/lib/Capfile +3 -0
  22. data/lib/capistrano/all.rb +16 -0
  23. data/lib/capistrano/application.rb +60 -0
  24. data/lib/capistrano/configuration/question.rb +42 -0
  25. data/lib/capistrano/configuration/server.rb +133 -0
  26. data/lib/capistrano/configuration/servers/role_filter.rb +86 -0
  27. data/lib/capistrano/configuration/servers.rb +53 -58
  28. data/lib/capistrano/configuration.rb +84 -30
  29. data/lib/capistrano/console.rb +1 -0
  30. data/lib/capistrano/defaults.rb +13 -0
  31. data/lib/capistrano/deploy.rb +3 -0
  32. data/lib/capistrano/dotfile.rb +3 -0
  33. data/lib/capistrano/dsl/env.rb +64 -0
  34. data/lib/capistrano/dsl/paths.rb +94 -0
  35. data/lib/capistrano/dsl/stages.rb +15 -0
  36. data/lib/capistrano/dsl/task_enhancements.rb +53 -0
  37. data/lib/capistrano/dsl.rb +48 -0
  38. data/lib/capistrano/git.rb +1 -0
  39. data/lib/capistrano/hg.rb +1 -0
  40. data/lib/capistrano/i18n.rb +34 -0
  41. data/lib/capistrano/install.rb +1 -0
  42. data/lib/capistrano/setup.rb +21 -0
  43. data/lib/capistrano/tasks/console.rake +21 -0
  44. data/lib/capistrano/tasks/deploy.rake +204 -0
  45. data/lib/capistrano/tasks/framework.rake +67 -0
  46. data/lib/capistrano/tasks/git.rake +62 -0
  47. data/lib/capistrano/tasks/hg.rake +39 -0
  48. data/lib/capistrano/tasks/install.rake +39 -0
  49. data/lib/capistrano/templates/Capfile +26 -0
  50. data/lib/capistrano/templates/deploy.rb.erb +40 -0
  51. data/lib/capistrano/templates/stage.rb.erb +42 -0
  52. data/lib/capistrano/version.rb +1 -20
  53. data/lib/capistrano/version_validator.rb +37 -0
  54. data/lib/capistrano.rb +0 -2
  55. data/spec/integration/dsl_spec.rb +344 -0
  56. data/spec/integration_spec_helper.rb +7 -0
  57. data/spec/lib/capistrano/application_spec.rb +61 -0
  58. data/spec/lib/capistrano/configuration/question_spec.rb +54 -0
  59. data/spec/lib/capistrano/configuration/server_spec.rb +249 -0
  60. data/spec/lib/capistrano/configuration/servers/role_filter_spec.rb +140 -0
  61. data/spec/lib/capistrano/configuration/servers_spec.rb +184 -0
  62. data/spec/lib/capistrano/configuration_spec.rb +101 -0
  63. data/spec/lib/capistrano/dsl/env_spec.rb +10 -0
  64. data/spec/lib/capistrano/dsl/paths_spec.rb +69 -0
  65. data/spec/lib/capistrano/dsl_spec.rb +63 -0
  66. data/spec/lib/capistrano/version_validator_spec.rb +103 -0
  67. data/spec/lib/capistrano_spec.rb +8 -0
  68. data/spec/spec_helper.rb +15 -0
  69. data/spec/support/.gitignore +1 -0
  70. data/spec/support/Vagrantfile +13 -0
  71. data/spec/support/matchers.rb +5 -0
  72. data/spec/support/tasks/database.cap +11 -0
  73. data/spec/support/test_app.rb +138 -0
  74. metadata +251 -179
  75. data/CHANGELOG +0 -512
  76. data/MIT-LICENSE +0 -20
  77. data/README +0 -43
  78. data/examples/sample.rb +0 -14
  79. data/lib/capistrano/callback.rb +0 -45
  80. data/lib/capistrano/cli/execute.rb +0 -82
  81. data/lib/capistrano/cli/help.rb +0 -102
  82. data/lib/capistrano/cli/help.txt +0 -53
  83. data/lib/capistrano/cli/options.rb +0 -183
  84. data/lib/capistrano/cli/ui.rb +0 -28
  85. data/lib/capistrano/cli.rb +0 -47
  86. data/lib/capistrano/command.rb +0 -161
  87. data/lib/capistrano/configuration/actions/file_transfer.rb +0 -35
  88. data/lib/capistrano/configuration/actions/inspect.rb +0 -46
  89. data/lib/capistrano/configuration/actions/invocation.rb +0 -134
  90. data/lib/capistrano/configuration/callbacks.rb +0 -148
  91. data/lib/capistrano/configuration/connections.rb +0 -159
  92. data/lib/capistrano/configuration/execution.rb +0 -126
  93. data/lib/capistrano/configuration/loading.rb +0 -198
  94. data/lib/capistrano/configuration/namespaces.rb +0 -196
  95. data/lib/capistrano/configuration/roles.rb +0 -51
  96. data/lib/capistrano/configuration/variables.rb +0 -127
  97. data/lib/capistrano/errors.rb +0 -15
  98. data/lib/capistrano/extensions.rb +0 -57
  99. data/lib/capistrano/gateway.rb +0 -131
  100. data/lib/capistrano/logger.rb +0 -59
  101. data/lib/capistrano/recipes/compat.rb +0 -32
  102. data/lib/capistrano/recipes/deploy/dependencies.rb +0 -44
  103. data/lib/capistrano/recipes/deploy/local_dependency.rb +0 -46
  104. data/lib/capistrano/recipes/deploy/remote_dependency.rb +0 -96
  105. data/lib/capistrano/recipes/deploy/scm/accurev.rb +0 -169
  106. data/lib/capistrano/recipes/deploy/scm/base.rb +0 -192
  107. data/lib/capistrano/recipes/deploy/scm/bzr.rb +0 -86
  108. data/lib/capistrano/recipes/deploy/scm/cvs.rb +0 -151
  109. data/lib/capistrano/recipes/deploy/scm/darcs.rb +0 -85
  110. data/lib/capistrano/recipes/deploy/scm/git.rb +0 -191
  111. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +0 -129
  112. data/lib/capistrano/recipes/deploy/scm/perforce.rb +0 -126
  113. data/lib/capistrano/recipes/deploy/scm/subversion.rb +0 -114
  114. data/lib/capistrano/recipes/deploy/scm.rb +0 -19
  115. data/lib/capistrano/recipes/deploy/strategy/base.rb +0 -64
  116. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +0 -20
  117. data/lib/capistrano/recipes/deploy/strategy/copy.rb +0 -144
  118. data/lib/capistrano/recipes/deploy/strategy/export.rb +0 -20
  119. data/lib/capistrano/recipes/deploy/strategy/remote.rb +0 -52
  120. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +0 -47
  121. data/lib/capistrano/recipes/deploy/strategy.rb +0 -19
  122. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
  123. data/lib/capistrano/recipes/deploy.rb +0 -494
  124. data/lib/capistrano/recipes/standard.rb +0 -37
  125. data/lib/capistrano/recipes/templates/maintenance.rhtml +0 -53
  126. data/lib/capistrano/recipes/upgrade.rb +0 -33
  127. data/lib/capistrano/server_definition.rb +0 -51
  128. data/lib/capistrano/shell.rb +0 -256
  129. data/lib/capistrano/ssh.rb +0 -109
  130. data/lib/capistrano/task_definition.rb +0 -69
  131. data/lib/capistrano/upload.rb +0 -146
  132. data/test/cli/execute_test.rb +0 -132
  133. data/test/cli/help_test.rb +0 -139
  134. data/test/cli/options_test.rb +0 -226
  135. data/test/cli/ui_test.rb +0 -28
  136. data/test/cli_test.rb +0 -17
  137. data/test/command_test.rb +0 -309
  138. data/test/configuration/actions/file_transfer_test.rb +0 -40
  139. data/test/configuration/actions/inspect_test.rb +0 -62
  140. data/test/configuration/actions/invocation_test.rb +0 -202
  141. data/test/configuration/callbacks_test.rb +0 -206
  142. data/test/configuration/connections_test.rb +0 -288
  143. data/test/configuration/execution_test.rb +0 -159
  144. data/test/configuration/loading_test.rb +0 -127
  145. data/test/configuration/namespace_dsl_test.rb +0 -297
  146. data/test/configuration/roles_test.rb +0 -47
  147. data/test/configuration/servers_test.rb +0 -90
  148. data/test/configuration/variables_test.rb +0 -180
  149. data/test/configuration_test.rb +0 -81
  150. data/test/deploy/scm/accurev_test.rb +0 -23
  151. data/test/deploy/scm/base_test.rb +0 -55
  152. data/test/deploy/scm/git_test.rb +0 -112
  153. data/test/deploy/strategy/copy_test.rb +0 -147
  154. data/test/extensions_test.rb +0 -69
  155. data/test/fixtures/cli_integration.rb +0 -5
  156. data/test/fixtures/config.rb +0 -5
  157. data/test/fixtures/custom.rb +0 -3
  158. data/test/gateway_test.rb +0 -167
  159. data/test/logger_test.rb +0 -123
  160. data/test/server_definition_test.rb +0 -108
  161. data/test/shell_test.rb +0 -64
  162. data/test/ssh_test.rb +0 -97
  163. data/test/task_definition_test.rb +0 -101
  164. data/test/upload_test.rb +0 -131
  165. data/test/utils.rb +0 -42
  166. data/test/version_test.rb +0 -24
@@ -1,256 +0,0 @@
1
- require 'thread'
2
-
3
- module Capistrano
4
- # The Capistrano::Shell class is the guts of the "shell" task. It implements
5
- # an interactive REPL interface that users can employ to execute tasks and
6
- # commands. It makes for a GREAT way to monitor systems, and perform quick
7
- # maintenance on one or more machines.
8
- class Shell
9
- # A Readline replacement for platforms where readline is either
10
- # unavailable, or has not been installed.
11
- class ReadlineFallback #:nodoc:
12
- HISTORY = []
13
-
14
- def self.readline(prompt)
15
- STDOUT.print(prompt)
16
- STDOUT.flush
17
- STDIN.gets
18
- end
19
- end
20
-
21
- # The configuration instance employed by this shell
22
- attr_reader :configuration
23
-
24
- # Instantiate a new shell and begin executing it immediately.
25
- def self.run(config)
26
- new(config).run!
27
- end
28
-
29
- # Instantiate a new shell
30
- def initialize(config)
31
- @configuration = config
32
- end
33
-
34
- # Start the shell running. This method will block until the shell
35
- # terminates.
36
- def run!
37
- setup
38
-
39
- puts <<-INTRO
40
- ====================================================================
41
- Welcome to the interactive Capistrano shell! This is an experimental
42
- feature, and is liable to change in future releases. Type 'help' for
43
- a summary of how to use the shell.
44
- --------------------------------------------------------------------
45
- INTRO
46
-
47
- loop do
48
- break if !read_and_execute
49
- end
50
-
51
- @bgthread.kill
52
- end
53
-
54
- def read_and_execute
55
- command = read_line
56
-
57
- case command
58
- when "?", "help" then help
59
- when "quit", "exit" then
60
- puts "exiting"
61
- return false
62
- when /^set -(\w)\s*(\S+)/
63
- set_option($1, $2)
64
- when /^(?:(with|on)\s*(\S+))?\s*(\S.*)?/i
65
- process_command($1, $2, $3)
66
- else
67
- raise "eh?"
68
- end
69
-
70
- return true
71
- end
72
-
73
- private
74
-
75
- # Present the prompt and read a single line from the console. It also
76
- # detects ^D and returns "exit" in that case. Adds the input to the
77
- # history, unless the input is empty. Loops repeatedly until a non-empty
78
- # line is input.
79
- def read_line
80
- loop do
81
- command = reader.readline("cap> ")
82
-
83
- if command.nil?
84
- command = "exit"
85
- puts(command)
86
- else
87
- command.strip!
88
- end
89
-
90
- unless command.empty?
91
- reader::HISTORY << command
92
- return command
93
- end
94
- end
95
- end
96
-
97
- # Display a verbose help message.
98
- def help
99
- puts <<-HELP
100
- --- HELP! ---------------------------------------------------
101
- "Get me out of this thing. I just want to quit."
102
- -> Easy enough. Just type "exit", or "quit". Or press ctrl-D.
103
-
104
- "I want to execute a command on all servers."
105
- -> Just type the command, and press enter. It will be passed,
106
- verbatim, to all defined servers.
107
-
108
- "What if I only want it to execute on a subset of them?"
109
- -> No problem, just specify the list of servers, separated by
110
- commas, before the command, with the `on' keyword:
111
-
112
- cap> on app1.foo.com,app2.foo.com echo ping
113
-
114
- "Nice, but can I specify the servers by role?"
115
- -> You sure can. Just use the `with' keyword, followed by the
116
- comma-delimited list of role names:
117
-
118
- cap> with app,db echo ping
119
-
120
- "Can I execute a Capistrano task from within this shell?"
121
- -> Yup. Just prefix the task with an exclamation mark:
122
-
123
- cap> !deploy
124
- HELP
125
- end
126
-
127
- # Determine which servers the given task requires a connection to, and
128
- # establish connections to them if necessary. Return the list of
129
- # servers (names).
130
- def connect(task)
131
- servers = configuration.find_servers_for_task(task)
132
- needing_connections = servers - configuration.sessions.keys
133
- unless needing_connections.empty?
134
- puts "[establishing connection(s) to #{needing_connections.join(', ')}]"
135
- configuration.establish_connections_to(needing_connections)
136
- end
137
- servers
138
- end
139
-
140
- # Execute the given command. If the command is prefixed by an exclamation
141
- # mark, it is assumed to refer to another capistrano task, which will
142
- # be invoked. Otherwise, it is executed as a command on all associated
143
- # servers.
144
- def exec(command)
145
- if command[0] == ?!
146
- exec_tasks(command[1..-1].split)
147
- else
148
- servers = connect(configuration.current_task)
149
- exec_command(command, servers)
150
- end
151
- ensure
152
- STDOUT.flush
153
- end
154
-
155
- # Given an array of task names, invoke them in sequence.
156
- def exec_tasks(list)
157
- list.each do |task_name|
158
- task = configuration.find_task(task_name)
159
- raise Capistrano::NoSuchTaskError, "no such task `#{task_name}'" unless task
160
- connect(task)
161
- configuration.execute_task(task)
162
- end
163
- rescue Capistrano::NoMatchingServersError, Capistrano::NoSuchTaskError => error
164
- warn "error: #{error.message}"
165
- end
166
-
167
- # Execute a command on the given list of servers.
168
- def exec_command(command, servers)
169
- command = command.gsub(/\bsudo\b/, "sudo -p '#{configuration.sudo_prompt}'")
170
- processor = configuration.sudo_behavior_callback(Configuration.default_io_proc)
171
- sessions = servers.map { |server| configuration.sessions[server] }
172
- cmd = Command.new(command, sessions, :logger => configuration.logger, &processor)
173
- previous = trap("INT") { cmd.stop! }
174
- cmd.process!
175
- rescue Capistrano::Error => error
176
- warn "error: #{error.message}"
177
- ensure
178
- trap("INT", previous)
179
- end
180
-
181
- # Return the object that will be used to query input from the console.
182
- # The returned object will quack (more or less) like Readline.
183
- def reader
184
- @reader ||= begin
185
- require 'readline'
186
- Readline
187
- rescue LoadError
188
- ReadlineFallback
189
- end
190
- end
191
-
192
- # Prepare every little thing for the shell. Starts the background
193
- # thread and generally gets things ready for the REPL.
194
- def setup
195
- configuration.logger.level = Capistrano::Logger::INFO
196
-
197
- @mutex = Mutex.new
198
- @bgthread = Thread.new do
199
- loop do
200
- ready = configuration.sessions.values.select { |sess| sess.connection.reader_ready? }
201
- if ready.empty?
202
- sleep 0.1
203
- else
204
- @mutex.synchronize do
205
- ready.each { |session| session.connection.process(true) }
206
- end
207
- end
208
- end
209
- end
210
- end
211
-
212
- # Set the given option to +value+.
213
- def set_option(opt, value)
214
- case opt
215
- when "v" then
216
- puts "setting log verbosity to #{value.to_i}"
217
- configuration.logger.level = value.to_i
218
- when "o" then
219
- case value
220
- when "vi" then
221
- puts "using vi edit mode"
222
- reader.vi_editing_mode
223
- when "emacs" then
224
- puts "using emacs edit mode"
225
- reader.emacs_editing_mode
226
- else
227
- puts "unknown -o option #{value.inspect}"
228
- end
229
- else
230
- puts "unknown setting #{opt.inspect}"
231
- end
232
- end
233
-
234
- # Process a command. Interprets the scope_type (must be nil, "with", or
235
- # "on") and the command. If no command is given, then the scope is made
236
- # effective for all subsequent commands. If the scope value is "all",
237
- # then the scope is unrestricted.
238
- def process_command(scope_type, scope_value, command)
239
- env_var = case scope_type
240
- when "with" then "ROLES"
241
- when "on" then "HOSTS"
242
- end
243
-
244
- old_var, ENV[env_var] = ENV[env_var], (scope_value == "all" ? nil : scope_value) if env_var
245
- if command
246
- begin
247
- @mutex.synchronize { exec(command) }
248
- ensure
249
- ENV[env_var] = old_var if env_var
250
- end
251
- else
252
- puts "scoping #{scope_type} #{scope_value}"
253
- end
254
- end
255
- end
256
- end
@@ -1,109 +0,0 @@
1
- require 'net/ssh'
2
-
3
- module Capistrano
4
- unless ENV['SKIP_VERSION_CHECK']
5
- require 'capistrano/version'
6
- require 'net/ssh/version'
7
- ssh_version = [Net::SSH::Version::MAJOR, Net::SSH::Version::MINOR, Net::SSH::Version::TINY]
8
- if !Version.check(Version::SSH_REQUIRED, ssh_version)
9
- raise "You have Net::SSH #{ssh_version.join(".")}, but you need at least #{Version::SSH_REQUIRED.join(".")}"
10
- end
11
- end
12
-
13
- # Now, Net::SSH is kind of silly, and tries to lazy-load everything. This
14
- # wreaks havoc with the parallel connection trick that Capistrano wants to
15
- # use, so we're going to do something hideously ugly here and force all the
16
- # files that Net::SSH uses to load RIGHT NOW, rather than lazily.
17
-
18
- net_ssh_dependencies = %w(connection/services connection/channel connection/driver
19
- service/agentforward/services service/agentforward/driver
20
- service/process/driver util/prompter
21
- service/forward/services service/forward/driver service/forward/local-network-handler service/forward/remote-network-handler
22
- service/shell/services service/shell/driver
23
- lenient-host-key-verifier
24
- transport/compress/services transport/compress/zlib-compressor transport/compress/none-compressor transport/compress/zlib-decompressor transport/compress/none-decompressor
25
- transport/kex/services transport/kex/dh transport/kex/dh-gex
26
- transport/ossl/services
27
- transport/ossl/hmac/services transport/ossl/hmac/sha1 transport/ossl/hmac/sha1-96 transport/ossl/hmac/md5 transport/ossl/hmac/md5-96 transport/ossl/hmac/none
28
- transport/ossl/cipher-factory transport/ossl/hmac-factory transport/ossl/buffer-factory transport/ossl/key-factory transport/ossl/digest-factory
29
- transport/identity-cipher transport/packet-stream transport/version-negotiator transport/algorithm-negotiator transport/session
30
- userauth/methods/services userauth/methods/password userauth/methods/keyboard-interactive userauth/methods/publickey userauth/methods/hostbased
31
- userauth/services userauth/agent userauth/userkeys userauth/driver
32
- transport/services service/services
33
- )
34
-
35
- net_ssh_dependencies << "userauth/pageant" if File::ALT_SEPARATOR
36
- net_ssh_dependencies.each do |path|
37
- begin
38
- require "net/ssh/#{path}"
39
- rescue LoadError
40
- # Ignore load errors from this, since some files are in the list which
41
- # do not exist in different (supported) versions of Net::SSH. We know
42
- # (by this point) that Net::SSH is installed, though, since we do a
43
- # require 'net/ssh' at the very top of this file, and we know the
44
- # installed version meets the minimum version requirements because of
45
- # the version check, also at the top of this file. So, if we get a
46
- # LoadError, it's simply because the file in question does not exist in
47
- # the version of Net::SSH that is installed.
48
- #
49
- # Whew!
50
- end
51
- end
52
-
53
- # A helper class for dealing with SSH connections.
54
- class SSH
55
- # Patch an accessor onto an SSH connection so that we can record the server
56
- # definition object that defines the connection. This is useful because
57
- # the gateway returns connections whose "host" is 127.0.0.1, instead of
58
- # the host on the other side of the tunnel.
59
- module Server #:nodoc:
60
- def self.apply_to(connection, server)
61
- connection.extend(Server)
62
- connection.xserver = server
63
- connection
64
- end
65
-
66
- attr_accessor :xserver
67
- end
68
-
69
- # The default port for SSH.
70
- DEFAULT_PORT = 22
71
-
72
- # An abstraction to make it possible to connect to the server via public key
73
- # without prompting for the password. If the public key authentication fails
74
- # this will fall back to password authentication.
75
- #
76
- # +server+ must be an instance of ServerDefinition.
77
- #
78
- # If a block is given, the new session is yielded to it, otherwise the new
79
- # session is returned.
80
- #
81
- # If an :ssh_options key exists in +options+, it is passed to the Net::SSH
82
- # constructor. Values in +options+ are then merged into it, and any
83
- # connection information in +server+ is added last, so that +server+ info
84
- # takes precedence over +options+, which takes precendence over ssh_options.
85
- def self.connect(server, options={}, &block)
86
- methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
87
- password_value = nil
88
-
89
- ssh_options = (options[:ssh_options] || {}).dup
90
- ssh_options[:username] = server.user || options[:user] || ssh_options[:username]
91
- ssh_options[:port] = server.port || options[:port] || ssh_options[:port] || DEFAULT_PORT
92
-
93
- begin
94
- connection_options = ssh_options.merge(
95
- :password => password_value,
96
- :auth_methods => ssh_options[:auth_methods] || methods.shift
97
- )
98
-
99
- connection = Net::SSH.start(server.host, connection_options, &block)
100
- Server.apply_to(connection, server)
101
-
102
- rescue Net::SSH::AuthenticationFailed
103
- raise if methods.empty? || ssh_options[:auth_methods]
104
- password_value = options[:password]
105
- retry
106
- end
107
- end
108
- end
109
- end
@@ -1,69 +0,0 @@
1
- require 'capistrano/server_definition'
2
-
3
- module Capistrano
4
- # Represents the definition of a single task.
5
- class TaskDefinition
6
- attr_reader :name, :namespace, :options, :body, :desc, :on_error
7
-
8
- def initialize(name, namespace, options={}, &block)
9
- @name, @namespace, @options = name, namespace, options
10
- @desc = @options.delete(:desc)
11
- @on_error = options.delete(:on_error)
12
- @body = block or raise ArgumentError, "a task requires a block"
13
- @servers = nil
14
- end
15
-
16
- # Returns the task's fully-qualified name, including the namespace
17
- def fully_qualified_name
18
- @fully_qualified_name ||= begin
19
- if namespace.default_task == self
20
- namespace.fully_qualified_name
21
- else
22
- [namespace.fully_qualified_name, name].compact.join(":")
23
- end
24
- end
25
- end
26
-
27
- # Returns the description for this task, with newlines collapsed and
28
- # whitespace stripped. Returns the empty string if there is no
29
- # description for this task.
30
- def description(rebuild=false)
31
- @description = nil if rebuild
32
- @description ||= begin
33
- description = @desc || ""
34
-
35
- indentation = description[/\A\s+/]
36
- if indentation
37
- reformatted_description = ""
38
- description.strip.each_line do |line|
39
- line = line.chomp.sub(/^#{indentation}/, "")
40
- line = line.gsub(/#{indentation}\s*/, " ") if line[/^\S/]
41
- reformatted_description << line << "\n"
42
- end
43
- description = reformatted_description
44
- end
45
-
46
- description.strip.gsub(/\r\n/, "\n")
47
- end
48
- end
49
-
50
- # Returns the first sentence of the full description. If +max_length+ is
51
- # given, the result will be truncated if it is longer than +max_length+,
52
- # and an ellipsis appended.
53
- def brief_description(max_length=nil)
54
- brief = description[/^.*?\.(?=\s|$)/] || description
55
-
56
- if max_length && brief.length > max_length
57
- brief = brief[0,max_length-3] + "..."
58
- end
59
-
60
- brief
61
- end
62
-
63
- # Indicates whether the task wants to continue, even if a server has failed
64
- # previously
65
- def continue_on_error?
66
- @on_error == :continue
67
- end
68
- end
69
- end
@@ -1,146 +0,0 @@
1
- require 'net/sftp'
2
- require 'net/sftp/operations/errors'
3
- require 'capistrano/errors'
4
-
5
- module Capistrano
6
- unless ENV['SKIP_VERSION_CHECK']
7
- require 'capistrano/version'
8
- require 'net/sftp/version'
9
- sftp_version = [Net::SFTP::Version::MAJOR, Net::SFTP::Version::MINOR, Net::SFTP::Version::TINY]
10
- required_version = [1,1,0]
11
- if !Capistrano::Version.check(required_version, sftp_version)
12
- raise "You have Net::SFTP #{sftp_version.join(".")}, but you need at least #{required_version.join(".")}. Net::SFTP will not be used."
13
- end
14
- end
15
-
16
- # This class encapsulates a single file upload to be performed in parallel
17
- # across multiple machines, using the SFTP protocol. Although it is intended
18
- # to be used primarily from within Capistrano, it may also be used standalone
19
- # if you need to simply upload a file to multiple servers.
20
- #
21
- # Basic Usage:
22
- #
23
- # begin
24
- # uploader = Capistrano::Upload.new(sessions, "remote-file.txt",
25
- # :data => "the contents of the file to upload")
26
- # uploader.process!
27
- # rescue Capistrano::UploadError => e
28
- # warn "Could not upload the file: #{e.message}"
29
- # end
30
- class Upload
31
- def self.process(sessions, filename, options)
32
- new(sessions, filename, options).process!
33
- end
34
-
35
- attr_reader :sessions, :filename, :options
36
- attr_reader :failed, :completed
37
-
38
- # Creates and prepares a new Upload instance. The +sessions+ parameter
39
- # must be an array of open Net::SSH sessions. The +filename+ is the name
40
- # (including path) of the destination file on the remote server. The
41
- # +options+ hash accepts the following keys (as symbols):
42
- #
43
- # * data: required. Should refer to a String containing the contents of
44
- # the file to upload.
45
- # * mode: optional. The "mode" of the destination file. Defaults to 0660.
46
- # * logger: optional. Should point to a Capistrano::Logger instance, if
47
- # given.
48
- def initialize(sessions, filename, options)
49
- raise ArgumentError, "you must specify the data to upload via the :data option" unless options[:data]
50
-
51
- @sessions = sessions
52
- @filename = filename
53
- @options = options
54
-
55
- @completed = @failed = 0
56
- @sftps = setup_sftp
57
- end
58
-
59
- # Uploads to all specified servers in parallel. If any one of the servers
60
- # fails, an exception will be raised (UploadError).
61
- def process!
62
- logger.debug "uploading #{filename}" if logger
63
- while running?
64
- @sftps.each do |sftp|
65
- next if sftp.channel[:done]
66
- begin
67
- sftp.channel.connection.process(true)
68
- rescue Net::SFTP::Operations::StatusException => error
69
- logger.important "uploading failed: #{error.description}", sftp.channel[:server] if logger
70
- failed!(sftp)
71
- end
72
- end
73
- sleep 0.01 # a brief respite, to keep the CPU from going crazy
74
- end
75
- logger.trace "upload finished" if logger
76
-
77
- if (failed = @sftps.select { |sftp| sftp.channel[:failed] }).any?
78
- hosts = failed.map { |sftp| sftp.channel[:server] }
79
- error = UploadError.new("upload of #{filename} failed on #{hosts.join(',')}")
80
- error.hosts = hosts
81
- raise error
82
- end
83
-
84
- self
85
- end
86
-
87
- private
88
-
89
- def logger
90
- options[:logger]
91
- end
92
-
93
- def setup_sftp
94
- sessions.map do |session|
95
- server = session.xserver
96
- sftp = session.sftp
97
- sftp.connect unless sftp.state == :open
98
-
99
- sftp.channel[:server] = server
100
- sftp.channel[:done] = false
101
- sftp.channel[:failed] = false
102
-
103
- real_filename = filename.gsub(/\$CAPISTRANO:HOST\$/, server.host)
104
- sftp.open(real_filename, IO::WRONLY | IO::CREAT | IO::TRUNC, options[:mode] || 0664) do |status, handle|
105
- break unless check_status(sftp, "open #{real_filename}", server, status)
106
-
107
- logger.info "uploading data to #{server}:#{real_filename}" if logger
108
- sftp.write(handle, options[:data] || "") do |status|
109
- break unless check_status(sftp, "write to #{server}:#{real_filename}", server, status)
110
- sftp.close_handle(handle) do
111
- logger.debug "done uploading data to #{server}:#{real_filename}" if logger
112
- completed!(sftp)
113
- end
114
- end
115
- end
116
-
117
- sftp
118
- end
119
- end
120
-
121
- def check_status(sftp, action, server, status)
122
- return true if status.code == Net::SFTP::Session::FX_OK
123
-
124
- logger.error "could not #{action} on #{server} (#{status.message})" if logger
125
- failed!(sftp)
126
-
127
- return false
128
- end
129
-
130
- def running?
131
- completed < @sftps.length
132
- end
133
-
134
- def failed!(sftp)
135
- completed!(sftp)
136
- @failed += 1
137
- sftp.channel[:failed] = true
138
- end
139
-
140
- def completed!(sftp)
141
- @completed += 1
142
- sftp.channel[:done] = true
143
- end
144
- end
145
-
146
- end