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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +89 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +674 -0
- data/README.md +226 -0
- data/Rakefile +5 -0
- data/bin/cap +2 -3
- data/bin/capify +7 -77
- data/capistrano-public_cert.pem +22 -0
- data/capistrano.gemspec +35 -0
- data/features/deploy.feature +52 -0
- data/features/installation.feature +16 -0
- data/features/remote_file_task.feature +14 -0
- data/features/step_definitions/assertions.rb +90 -0
- data/features/step_definitions/cap_commands.rb +8 -0
- data/features/step_definitions/setup.rb +25 -0
- data/features/support/env.rb +12 -0
- data/features/support/remote_command_helpers.rb +20 -0
- data/lib/Capfile +3 -0
- data/lib/capistrano/all.rb +16 -0
- data/lib/capistrano/application.rb +60 -0
- data/lib/capistrano/configuration/question.rb +42 -0
- data/lib/capistrano/configuration/server.rb +133 -0
- data/lib/capistrano/configuration/servers/role_filter.rb +86 -0
- data/lib/capistrano/configuration/servers.rb +53 -58
- data/lib/capistrano/configuration.rb +84 -30
- data/lib/capistrano/console.rb +1 -0
- data/lib/capistrano/defaults.rb +13 -0
- data/lib/capistrano/deploy.rb +3 -0
- data/lib/capistrano/dotfile.rb +3 -0
- data/lib/capistrano/dsl/env.rb +64 -0
- data/lib/capistrano/dsl/paths.rb +94 -0
- data/lib/capistrano/dsl/stages.rb +15 -0
- data/lib/capistrano/dsl/task_enhancements.rb +53 -0
- data/lib/capistrano/dsl.rb +48 -0
- data/lib/capistrano/git.rb +1 -0
- data/lib/capistrano/hg.rb +1 -0
- data/lib/capistrano/i18n.rb +34 -0
- data/lib/capistrano/install.rb +1 -0
- data/lib/capistrano/setup.rb +21 -0
- data/lib/capistrano/tasks/console.rake +21 -0
- data/lib/capistrano/tasks/deploy.rake +204 -0
- data/lib/capistrano/tasks/framework.rake +67 -0
- data/lib/capistrano/tasks/git.rake +62 -0
- data/lib/capistrano/tasks/hg.rake +39 -0
- data/lib/capistrano/tasks/install.rake +39 -0
- data/lib/capistrano/templates/Capfile +26 -0
- data/lib/capistrano/templates/deploy.rb.erb +40 -0
- data/lib/capistrano/templates/stage.rb.erb +42 -0
- data/lib/capistrano/version.rb +1 -20
- data/lib/capistrano/version_validator.rb +37 -0
- data/lib/capistrano.rb +0 -2
- data/spec/integration/dsl_spec.rb +344 -0
- data/spec/integration_spec_helper.rb +7 -0
- data/spec/lib/capistrano/application_spec.rb +61 -0
- data/spec/lib/capistrano/configuration/question_spec.rb +54 -0
- data/spec/lib/capistrano/configuration/server_spec.rb +249 -0
- data/spec/lib/capistrano/configuration/servers/role_filter_spec.rb +140 -0
- data/spec/lib/capistrano/configuration/servers_spec.rb +184 -0
- data/spec/lib/capistrano/configuration_spec.rb +101 -0
- data/spec/lib/capistrano/dsl/env_spec.rb +10 -0
- data/spec/lib/capistrano/dsl/paths_spec.rb +69 -0
- data/spec/lib/capistrano/dsl_spec.rb +63 -0
- data/spec/lib/capistrano/version_validator_spec.rb +103 -0
- data/spec/lib/capistrano_spec.rb +8 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/.gitignore +1 -0
- data/spec/support/Vagrantfile +13 -0
- data/spec/support/matchers.rb +5 -0
- data/spec/support/tasks/database.cap +11 -0
- data/spec/support/test_app.rb +138 -0
- metadata +251 -179
- data/CHANGELOG +0 -512
- data/MIT-LICENSE +0 -20
- data/README +0 -43
- data/examples/sample.rb +0 -14
- data/lib/capistrano/callback.rb +0 -45
- data/lib/capistrano/cli/execute.rb +0 -82
- data/lib/capistrano/cli/help.rb +0 -102
- data/lib/capistrano/cli/help.txt +0 -53
- data/lib/capistrano/cli/options.rb +0 -183
- data/lib/capistrano/cli/ui.rb +0 -28
- data/lib/capistrano/cli.rb +0 -47
- data/lib/capistrano/command.rb +0 -161
- data/lib/capistrano/configuration/actions/file_transfer.rb +0 -35
- data/lib/capistrano/configuration/actions/inspect.rb +0 -46
- data/lib/capistrano/configuration/actions/invocation.rb +0 -134
- data/lib/capistrano/configuration/callbacks.rb +0 -148
- data/lib/capistrano/configuration/connections.rb +0 -159
- data/lib/capistrano/configuration/execution.rb +0 -126
- data/lib/capistrano/configuration/loading.rb +0 -198
- data/lib/capistrano/configuration/namespaces.rb +0 -196
- data/lib/capistrano/configuration/roles.rb +0 -51
- data/lib/capistrano/configuration/variables.rb +0 -127
- data/lib/capistrano/errors.rb +0 -15
- data/lib/capistrano/extensions.rb +0 -57
- data/lib/capistrano/gateway.rb +0 -131
- data/lib/capistrano/logger.rb +0 -59
- data/lib/capistrano/recipes/compat.rb +0 -32
- data/lib/capistrano/recipes/deploy/dependencies.rb +0 -44
- data/lib/capistrano/recipes/deploy/local_dependency.rb +0 -46
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +0 -96
- data/lib/capistrano/recipes/deploy/scm/accurev.rb +0 -169
- data/lib/capistrano/recipes/deploy/scm/base.rb +0 -192
- data/lib/capistrano/recipes/deploy/scm/bzr.rb +0 -86
- data/lib/capistrano/recipes/deploy/scm/cvs.rb +0 -151
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +0 -85
- data/lib/capistrano/recipes/deploy/scm/git.rb +0 -191
- data/lib/capistrano/recipes/deploy/scm/mercurial.rb +0 -129
- data/lib/capistrano/recipes/deploy/scm/perforce.rb +0 -126
- data/lib/capistrano/recipes/deploy/scm/subversion.rb +0 -114
- data/lib/capistrano/recipes/deploy/scm.rb +0 -19
- data/lib/capistrano/recipes/deploy/strategy/base.rb +0 -64
- data/lib/capistrano/recipes/deploy/strategy/checkout.rb +0 -20
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +0 -144
- data/lib/capistrano/recipes/deploy/strategy/export.rb +0 -20
- data/lib/capistrano/recipes/deploy/strategy/remote.rb +0 -52
- data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +0 -47
- data/lib/capistrano/recipes/deploy/strategy.rb +0 -19
- data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
- data/lib/capistrano/recipes/deploy.rb +0 -494
- data/lib/capistrano/recipes/standard.rb +0 -37
- data/lib/capistrano/recipes/templates/maintenance.rhtml +0 -53
- data/lib/capistrano/recipes/upgrade.rb +0 -33
- data/lib/capistrano/server_definition.rb +0 -51
- data/lib/capistrano/shell.rb +0 -256
- data/lib/capistrano/ssh.rb +0 -109
- data/lib/capistrano/task_definition.rb +0 -69
- data/lib/capistrano/upload.rb +0 -146
- data/test/cli/execute_test.rb +0 -132
- data/test/cli/help_test.rb +0 -139
- data/test/cli/options_test.rb +0 -226
- data/test/cli/ui_test.rb +0 -28
- data/test/cli_test.rb +0 -17
- data/test/command_test.rb +0 -309
- data/test/configuration/actions/file_transfer_test.rb +0 -40
- data/test/configuration/actions/inspect_test.rb +0 -62
- data/test/configuration/actions/invocation_test.rb +0 -202
- data/test/configuration/callbacks_test.rb +0 -206
- data/test/configuration/connections_test.rb +0 -288
- data/test/configuration/execution_test.rb +0 -159
- data/test/configuration/loading_test.rb +0 -127
- data/test/configuration/namespace_dsl_test.rb +0 -297
- data/test/configuration/roles_test.rb +0 -47
- data/test/configuration/servers_test.rb +0 -90
- data/test/configuration/variables_test.rb +0 -180
- data/test/configuration_test.rb +0 -81
- data/test/deploy/scm/accurev_test.rb +0 -23
- data/test/deploy/scm/base_test.rb +0 -55
- data/test/deploy/scm/git_test.rb +0 -112
- data/test/deploy/strategy/copy_test.rb +0 -147
- data/test/extensions_test.rb +0 -69
- data/test/fixtures/cli_integration.rb +0 -5
- data/test/fixtures/config.rb +0 -5
- data/test/fixtures/custom.rb +0 -3
- data/test/gateway_test.rb +0 -167
- data/test/logger_test.rb +0 -123
- data/test/server_definition_test.rb +0 -108
- data/test/shell_test.rb +0 -64
- data/test/ssh_test.rb +0 -97
- data/test/task_definition_test.rb +0 -101
- data/test/upload_test.rb +0 -131
- data/test/utils.rb +0 -42
- data/test/version_test.rb +0 -24
data/lib/capistrano/shell.rb
DELETED
@@ -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
|
data/lib/capistrano/ssh.rb
DELETED
@@ -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
|
data/lib/capistrano/upload.rb
DELETED
@@ -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
|