minmb-capistrano 2.15.4
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.
- data/.gitignore +10 -0
- data/.travis.yml +7 -0
- data/CHANGELOG +1170 -0
- data/Gemfile +13 -0
- data/README.md +94 -0
- data/Rakefile +11 -0
- data/bin/cap +4 -0
- data/bin/capify +92 -0
- data/capistrano.gemspec +40 -0
- data/lib/capistrano.rb +5 -0
- data/lib/capistrano/callback.rb +45 -0
- data/lib/capistrano/cli.rb +47 -0
- data/lib/capistrano/cli/execute.rb +85 -0
- data/lib/capistrano/cli/help.rb +125 -0
- data/lib/capistrano/cli/help.txt +81 -0
- data/lib/capistrano/cli/options.rb +243 -0
- data/lib/capistrano/cli/ui.rb +40 -0
- data/lib/capistrano/command.rb +303 -0
- data/lib/capistrano/configuration.rb +57 -0
- data/lib/capistrano/configuration/actions/file_transfer.rb +50 -0
- data/lib/capistrano/configuration/actions/inspect.rb +46 -0
- data/lib/capistrano/configuration/actions/invocation.rb +329 -0
- data/lib/capistrano/configuration/alias_task.rb +26 -0
- data/lib/capistrano/configuration/callbacks.rb +147 -0
- data/lib/capistrano/configuration/connections.rb +237 -0
- data/lib/capistrano/configuration/execution.rb +142 -0
- data/lib/capistrano/configuration/loading.rb +205 -0
- data/lib/capistrano/configuration/log_formatters.rb +75 -0
- data/lib/capistrano/configuration/namespaces.rb +223 -0
- data/lib/capistrano/configuration/roles.rb +77 -0
- data/lib/capistrano/configuration/servers.rb +116 -0
- data/lib/capistrano/configuration/variables.rb +127 -0
- data/lib/capistrano/errors.rb +19 -0
- data/lib/capistrano/ext/multistage.rb +64 -0
- data/lib/capistrano/ext/string.rb +5 -0
- data/lib/capistrano/extensions.rb +57 -0
- data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
- data/lib/capistrano/logger.rb +166 -0
- data/lib/capistrano/processable.rb +57 -0
- data/lib/capistrano/recipes/compat.rb +32 -0
- data/lib/capistrano/recipes/deploy.rb +625 -0
- data/lib/capistrano/recipes/deploy/assets.rb +201 -0
- data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
- data/lib/capistrano/recipes/deploy/local_dependency.rb +54 -0
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +117 -0
- data/lib/capistrano/recipes/deploy/scm.rb +19 -0
- data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
- data/lib/capistrano/recipes/deploy/scm/base.rb +200 -0
- data/lib/capistrano/recipes/deploy/scm/bzr.rb +86 -0
- data/lib/capistrano/recipes/deploy/scm/cvs.rb +153 -0
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +96 -0
- data/lib/capistrano/recipes/deploy/scm/git.rb +293 -0
- data/lib/capistrano/recipes/deploy/scm/mercurial.rb +137 -0
- data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
- data/lib/capistrano/recipes/deploy/scm/perforce.rb +152 -0
- data/lib/capistrano/recipes/deploy/scm/subversion.rb +121 -0
- data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
- data/lib/capistrano/recipes/deploy/strategy/base.rb +92 -0
- data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +338 -0
- data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
- data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
- data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +57 -0
- data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
- data/lib/capistrano/recipes/standard.rb +37 -0
- data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
- data/lib/capistrano/role.rb +102 -0
- data/lib/capistrano/server_definition.rb +56 -0
- data/lib/capistrano/shell.rb +265 -0
- data/lib/capistrano/ssh.rb +95 -0
- data/lib/capistrano/task_definition.rb +77 -0
- data/lib/capistrano/transfer.rb +218 -0
- data/lib/capistrano/version.rb +11 -0
- data/test/cli/execute_test.rb +132 -0
- data/test/cli/help_test.rb +165 -0
- data/test/cli/options_test.rb +329 -0
- data/test/cli/ui_test.rb +28 -0
- data/test/cli_test.rb +17 -0
- data/test/command_test.rb +322 -0
- data/test/configuration/actions/file_transfer_test.rb +61 -0
- data/test/configuration/actions/inspect_test.rb +76 -0
- data/test/configuration/actions/invocation_test.rb +288 -0
- data/test/configuration/alias_task_test.rb +118 -0
- data/test/configuration/callbacks_test.rb +201 -0
- data/test/configuration/connections_test.rb +439 -0
- data/test/configuration/execution_test.rb +175 -0
- data/test/configuration/loading_test.rb +148 -0
- data/test/configuration/namespace_dsl_test.rb +332 -0
- data/test/configuration/roles_test.rb +157 -0
- data/test/configuration/servers_test.rb +183 -0
- data/test/configuration/variables_test.rb +190 -0
- data/test/configuration_test.rb +77 -0
- 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 +55 -0
- 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 +221 -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 +40 -0
- data/test/deploy/strategy/copy_test.rb +360 -0
- data/test/extensions_test.rb +69 -0
- data/test/fixtures/cli_integration.rb +5 -0
- data/test/fixtures/config.rb +5 -0
- data/test/fixtures/custom.rb +3 -0
- data/test/logger_formatting_test.rb +149 -0
- data/test/logger_test.rb +134 -0
- data/test/recipes_test.rb +25 -0
- data/test/role_test.rb +11 -0
- data/test/server_definition_test.rb +121 -0
- data/test/shell_test.rb +96 -0
- data/test/ssh_test.rb +113 -0
- data/test/task_definition_test.rb +117 -0
- data/test/transfer_test.rb +168 -0
- data/test/utils.rb +37 -0
- metadata +316 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'net/ssh'
|
|
2
|
+
|
|
3
|
+
module Capistrano
|
|
4
|
+
# A helper class for dealing with SSH connections.
|
|
5
|
+
class SSH
|
|
6
|
+
# Patch an accessor onto an SSH connection so that we can record the server
|
|
7
|
+
# definition object that defines the connection. This is useful because
|
|
8
|
+
# the gateway returns connections whose "host" is 127.0.0.1, instead of
|
|
9
|
+
# the host on the other side of the tunnel.
|
|
10
|
+
module Server #:nodoc:
|
|
11
|
+
def self.apply_to(connection, server)
|
|
12
|
+
connection.extend(Server)
|
|
13
|
+
connection.xserver = server
|
|
14
|
+
connection
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_accessor :xserver
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# An abstraction to make it possible to connect to the server via public key
|
|
21
|
+
# without prompting for the password. If the public key authentication fails
|
|
22
|
+
# this will fall back to password authentication.
|
|
23
|
+
#
|
|
24
|
+
# +server+ must be an instance of ServerDefinition.
|
|
25
|
+
#
|
|
26
|
+
# If a block is given, the new session is yielded to it, otherwise the new
|
|
27
|
+
# session is returned.
|
|
28
|
+
#
|
|
29
|
+
# If an :ssh_options key exists in +options+, it is passed to the Net::SSH
|
|
30
|
+
# constructor. Values in +options+ are then merged into it, and any
|
|
31
|
+
# connection information in +server+ is added last, so that +server+ info
|
|
32
|
+
# takes precedence over +options+, which takes precendence over ssh_options.
|
|
33
|
+
def self.connect(server, options={})
|
|
34
|
+
connection_strategy(server, options) do |host, user, connection_options|
|
|
35
|
+
connection = Net::SSH.start(host, user, connection_options)
|
|
36
|
+
Server.apply_to(connection, server)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Abstracts the logic for establishing an SSH connection (which includes
|
|
41
|
+
# testing for connection failures and retrying with a password, and so forth,
|
|
42
|
+
# mostly made complicated because of the fact that some of these variables
|
|
43
|
+
# might be lazily evaluated and try to do something like prompt the user,
|
|
44
|
+
# which should only happen when absolutely necessary.
|
|
45
|
+
#
|
|
46
|
+
# This will yield the hostname, username, and a hash of connection options
|
|
47
|
+
# to the given block, which should return a new connection.
|
|
48
|
+
def self.connection_strategy(server, options={}, &block)
|
|
49
|
+
methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
|
|
50
|
+
password_value = nil
|
|
51
|
+
|
|
52
|
+
# construct the hash of ssh options that should be passed more-or-less
|
|
53
|
+
# directly to Net::SSH. This will be the general ssh options, merged with
|
|
54
|
+
# the server-specific ssh-options.
|
|
55
|
+
ssh_options = (options[:ssh_options] || {}).merge(server.options[:ssh_options] || {})
|
|
56
|
+
|
|
57
|
+
# load any SSH configuration files that were specified in the SSH options. This
|
|
58
|
+
# will load from ~/.ssh/config and /etc/ssh_config by default (see Net::SSH
|
|
59
|
+
# for details). Merge the explicitly given ssh_options over the top of the info
|
|
60
|
+
# from the config file.
|
|
61
|
+
ssh_options = Net::SSH.configuration_for(server.host, ssh_options.fetch(:config, true)).merge(ssh_options)
|
|
62
|
+
|
|
63
|
+
# Once we've loaded the config, we don't need Net::SSH to do it again.
|
|
64
|
+
ssh_options[:config] = false
|
|
65
|
+
|
|
66
|
+
ssh_options[:verbose] = :debug if options[:verbose] && options[:verbose] > 0
|
|
67
|
+
|
|
68
|
+
user = server.user || options[:user] || ssh_options[:username] ||
|
|
69
|
+
ssh_options[:user] || ServerDefinition.default_user
|
|
70
|
+
port = server.port || options[:port] || ssh_options[:port]
|
|
71
|
+
|
|
72
|
+
# the .ssh/config file might have changed the host-name on us
|
|
73
|
+
host = ssh_options.fetch(:host_name, server.host)
|
|
74
|
+
|
|
75
|
+
ssh_options[:port] = port if port
|
|
76
|
+
|
|
77
|
+
# delete these, since we've determined which username to use by this point
|
|
78
|
+
ssh_options.delete(:username)
|
|
79
|
+
ssh_options.delete(:user)
|
|
80
|
+
|
|
81
|
+
begin
|
|
82
|
+
connection_options = ssh_options.merge(
|
|
83
|
+
:password => password_value,
|
|
84
|
+
:auth_methods => ssh_options[:auth_methods] || methods.shift
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
yield host, user, connection_options
|
|
88
|
+
rescue Net::SSH::AuthenticationFailed
|
|
89
|
+
raise if methods.empty? || ssh_options[:auth_methods]
|
|
90
|
+
password_value = options[:password]
|
|
91
|
+
retry
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'capistrano/server_definition'
|
|
2
|
+
|
|
3
|
+
module Capistrano
|
|
4
|
+
|
|
5
|
+
class TaskDefinition
|
|
6
|
+
|
|
7
|
+
attr_reader :name, :namespace, :options, :body, :desc, :on_error, :max_hosts
|
|
8
|
+
|
|
9
|
+
def initialize(name, namespace, options={}, &block)
|
|
10
|
+
@name, @namespace, @options = name, namespace, options
|
|
11
|
+
@desc = @options.delete(:desc)
|
|
12
|
+
@on_error = options.delete(:on_error)
|
|
13
|
+
@max_hosts = options[:max_hosts] && options[:max_hosts].to_i
|
|
14
|
+
@body = block or raise ArgumentError, "a task requires a block"
|
|
15
|
+
@servers = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns the task's fully-qualified name, including the namespace
|
|
19
|
+
def fully_qualified_name
|
|
20
|
+
@fully_qualified_name ||= begin
|
|
21
|
+
if namespace.default_task == self
|
|
22
|
+
namespace.fully_qualified_name
|
|
23
|
+
else
|
|
24
|
+
[namespace.fully_qualified_name, name].compact.join(":")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def name=(value)
|
|
30
|
+
raise ArgumentError, "expected a valid task name" if !value.respond_to?(:to_sym)
|
|
31
|
+
@name = value.to_sym
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns the description for this task, with newlines collapsed and
|
|
35
|
+
# whitespace stripped. Returns the empty string if there is no
|
|
36
|
+
# description for this task.
|
|
37
|
+
def description(rebuild=false)
|
|
38
|
+
@description = nil if rebuild
|
|
39
|
+
@description ||= begin
|
|
40
|
+
description = @desc || ""
|
|
41
|
+
|
|
42
|
+
indentation = description[/\A\s+/]
|
|
43
|
+
if indentation
|
|
44
|
+
reformatted_description = ""
|
|
45
|
+
description.strip.each_line do |line|
|
|
46
|
+
line = line.chomp.sub(/^#{indentation}/, "")
|
|
47
|
+
line = line.gsub(/#{indentation}\s*/, " ") if line[/^\S/]
|
|
48
|
+
reformatted_description << line << "\n"
|
|
49
|
+
end
|
|
50
|
+
description = reformatted_description
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
description.strip.gsub(/\r\n/, "\n")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns the first sentence of the full description. If +max_length+ is
|
|
58
|
+
# given, the result will be truncated if it is longer than +max_length+,
|
|
59
|
+
# and an ellipsis appended.
|
|
60
|
+
def brief_description(max_length=nil)
|
|
61
|
+
brief = description[/^.*?\.(?=\s|$)/] || description
|
|
62
|
+
|
|
63
|
+
if max_length && brief.length > max_length
|
|
64
|
+
brief = brief[0,max_length-3] + "..."
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
brief
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Indicates whether the task wants to continue, even if a server has failed
|
|
71
|
+
# previously
|
|
72
|
+
def continue_on_error?
|
|
73
|
+
@on_error == :continue
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
require 'net/scp'
|
|
2
|
+
require 'net/sftp'
|
|
3
|
+
|
|
4
|
+
require 'capistrano/processable'
|
|
5
|
+
|
|
6
|
+
module Capistrano
|
|
7
|
+
class Transfer
|
|
8
|
+
include Processable
|
|
9
|
+
|
|
10
|
+
def self.process(direction, from, to, sessions, options={}, &block)
|
|
11
|
+
new(direction, from, to, sessions, options, &block).process!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :sessions
|
|
15
|
+
attr_reader :options
|
|
16
|
+
attr_reader :callback
|
|
17
|
+
|
|
18
|
+
attr_reader :transport
|
|
19
|
+
attr_reader :direction
|
|
20
|
+
attr_reader :from
|
|
21
|
+
attr_reader :to
|
|
22
|
+
|
|
23
|
+
attr_reader :logger
|
|
24
|
+
attr_reader :transfers
|
|
25
|
+
|
|
26
|
+
def initialize(direction, from, to, sessions, options={}, &block)
|
|
27
|
+
@direction = direction
|
|
28
|
+
@from = from
|
|
29
|
+
@to = to
|
|
30
|
+
@sessions = sessions
|
|
31
|
+
@options = options
|
|
32
|
+
@callback = block
|
|
33
|
+
|
|
34
|
+
@transport = options.fetch(:via, :sftp)
|
|
35
|
+
@logger = options.delete(:logger)
|
|
36
|
+
|
|
37
|
+
@session_map = {}
|
|
38
|
+
|
|
39
|
+
prepare_transfers
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def process!
|
|
43
|
+
loop do
|
|
44
|
+
begin
|
|
45
|
+
break unless process_iteration { active? }
|
|
46
|
+
rescue Exception => error
|
|
47
|
+
if error.respond_to?(:session)
|
|
48
|
+
handle_error(error)
|
|
49
|
+
else
|
|
50
|
+
raise
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
failed = transfers.select { |txfr| txfr[:failed] }
|
|
56
|
+
if failed.any?
|
|
57
|
+
hosts = failed.map { |txfr| txfr[:server] }
|
|
58
|
+
errors = failed.map { |txfr| "#{txfr[:error]} (#{txfr[:error].message})" }.uniq.join(", ")
|
|
59
|
+
error = TransferError.new("#{operation} via #{transport} failed on #{hosts.join(',')}: #{errors}")
|
|
60
|
+
error.hosts = hosts
|
|
61
|
+
|
|
62
|
+
logger.important(error.message) if logger
|
|
63
|
+
raise error
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
logger.debug "#{transport} #{operation} complete" if logger
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def active?
|
|
71
|
+
transfers.any? { |transfer| transfer.active? }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def operation
|
|
75
|
+
"#{direction}load"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def sanitized_from
|
|
79
|
+
if from.responds_to?(:read)
|
|
80
|
+
"#<#{from.class}>"
|
|
81
|
+
else
|
|
82
|
+
from
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def sanitized_to
|
|
87
|
+
if to.responds_to?(:read)
|
|
88
|
+
"#<#{to.class}>"
|
|
89
|
+
else
|
|
90
|
+
to
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def session_map
|
|
97
|
+
@session_map
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def prepare_transfers
|
|
101
|
+
logger.info "#{transport} #{operation} #{from} -> #{to}" if logger
|
|
102
|
+
|
|
103
|
+
@transfers = sessions.map do |session|
|
|
104
|
+
session_from = normalize(from, session)
|
|
105
|
+
session_to = normalize(to, session)
|
|
106
|
+
|
|
107
|
+
session_map[session] = case transport
|
|
108
|
+
when :sftp
|
|
109
|
+
prepare_sftp_transfer(session_from, session_to, session)
|
|
110
|
+
when :scp
|
|
111
|
+
prepare_scp_transfer(session_from, session_to, session)
|
|
112
|
+
else
|
|
113
|
+
raise ArgumentError, "unsupported transport type: #{transport.inspect}"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def prepare_scp_transfer(from, to, session)
|
|
119
|
+
real_callback = callback || Proc.new do |channel, name, sent, total|
|
|
120
|
+
logger.trace "[#{channel[:host]}] #{name}" if logger && sent == 0
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
channel = case direction
|
|
124
|
+
when :up
|
|
125
|
+
session.scp.upload(from, to, options, &real_callback)
|
|
126
|
+
when :down
|
|
127
|
+
session.scp.download(from, to, options, &real_callback)
|
|
128
|
+
else
|
|
129
|
+
raise ArgumentError, "unsupported transfer direction: #{direction.inspect}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
channel[:server] = session.xserver
|
|
133
|
+
channel[:host] = session.xserver.host
|
|
134
|
+
|
|
135
|
+
return channel
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
class SFTPTransferWrapper
|
|
139
|
+
attr_reader :operation
|
|
140
|
+
|
|
141
|
+
def initialize(session, &callback)
|
|
142
|
+
session.sftp(false).connect do |sftp|
|
|
143
|
+
@operation = callback.call(sftp)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def active?
|
|
148
|
+
@operation.nil? || @operation.active?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def [](key)
|
|
152
|
+
@operation[key]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def []=(key, value)
|
|
156
|
+
@operation[key] = value
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def abort!
|
|
160
|
+
@operation.abort!
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def prepare_sftp_transfer(from, to, session)
|
|
165
|
+
SFTPTransferWrapper.new(session) do |sftp|
|
|
166
|
+
real_callback = Proc.new do |event, op, *args|
|
|
167
|
+
if callback
|
|
168
|
+
callback.call(event, op, *args)
|
|
169
|
+
elsif event == :open
|
|
170
|
+
logger.trace "[#{op[:host]}] #{args[0].remote}"
|
|
171
|
+
elsif event == :finish
|
|
172
|
+
logger.trace "[#{op[:host]}] done"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
opts = options.dup
|
|
177
|
+
opts[:properties] = (opts[:properties] || {}).merge(
|
|
178
|
+
:server => session.xserver,
|
|
179
|
+
:host => session.xserver.host)
|
|
180
|
+
|
|
181
|
+
case direction
|
|
182
|
+
when :up
|
|
183
|
+
sftp.upload(from, to, opts, &real_callback)
|
|
184
|
+
when :down
|
|
185
|
+
sftp.download(from, to, opts, &real_callback)
|
|
186
|
+
else
|
|
187
|
+
raise ArgumentError, "unsupported transfer direction: #{direction.inspect}"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def normalize(argument, session)
|
|
193
|
+
if argument.is_a?(String)
|
|
194
|
+
argument.gsub(/\$CAPISTRANO:HOST\$/, session.xserver.host)
|
|
195
|
+
elsif argument.respond_to?(:read)
|
|
196
|
+
pos = argument.pos
|
|
197
|
+
clone = StringIO.new(argument.read)
|
|
198
|
+
clone.pos = argument.pos = pos
|
|
199
|
+
clone
|
|
200
|
+
else
|
|
201
|
+
argument
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def handle_error(error)
|
|
206
|
+
raise error if error.message.include?('expected a file to upload')
|
|
207
|
+
|
|
208
|
+
transfer = session_map[error.session]
|
|
209
|
+
transfer[:error] = error
|
|
210
|
+
transfer[:failed] = true
|
|
211
|
+
|
|
212
|
+
case transport
|
|
213
|
+
when :sftp then transfer.abort!
|
|
214
|
+
when :scp then transfer.close
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
require "utils"
|
|
2
|
+
require 'capistrano/cli/execute'
|
|
3
|
+
|
|
4
|
+
class CLIExecuteTest < Test::Unit::TestCase
|
|
5
|
+
class MockCLI
|
|
6
|
+
attr_reader :options
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@options = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
include Capistrano::CLI::Execute
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def setup
|
|
16
|
+
@cli = MockCLI.new
|
|
17
|
+
@logger = stub_everything
|
|
18
|
+
@config = stub(:logger => @logger, :debug= => nil, :dry_run= => nil, :preserve_roles= => nil)
|
|
19
|
+
@config.stubs(:set)
|
|
20
|
+
@config.stubs(:load)
|
|
21
|
+
@config.stubs(:trigger)
|
|
22
|
+
@cli.stubs(:instantiate_configuration).returns(@config)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_execute_should_set_logger_verbosity
|
|
26
|
+
@cli.options[:verbose] = 7
|
|
27
|
+
@logger.expects(:level=).with(7)
|
|
28
|
+
@cli.execute!
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_execute_should_set_password
|
|
32
|
+
@cli.options[:password] = "nosoup4u"
|
|
33
|
+
@config.expects(:set).with(:password, "nosoup4u")
|
|
34
|
+
@cli.execute!
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_execute_should_set_prevars_before_loading
|
|
38
|
+
@config.expects(:load).never
|
|
39
|
+
@config.expects(:set).with(:stage, "foobar")
|
|
40
|
+
@config.expects(:load).with("standard")
|
|
41
|
+
@cli.options[:pre_vars] = { :stage => "foobar" }
|
|
42
|
+
@cli.execute!
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_execute_should_load_sysconf_if_sysconf_set_and_exists
|
|
46
|
+
@cli.options[:sysconf] = "/etc/capistrano.conf"
|
|
47
|
+
@config.expects(:load).with("/etc/capistrano.conf")
|
|
48
|
+
File.expects(:file?).with("/etc/capistrano.conf").returns(true)
|
|
49
|
+
@cli.execute!
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_execute_should_not_load_sysconf_when_sysconf_set_and_not_exists
|
|
53
|
+
@cli.options[:sysconf] = "/etc/capistrano.conf"
|
|
54
|
+
File.expects(:file?).with("/etc/capistrano.conf").returns(false)
|
|
55
|
+
@cli.execute!
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_execute_should_load_dotfile_if_dotfile_set_and_exists
|
|
59
|
+
@cli.options[:dotfile] = "/home/jamis/.caprc"
|
|
60
|
+
@config.expects(:load).with("/home/jamis/.caprc")
|
|
61
|
+
File.expects(:file?).with("/home/jamis/.caprc").returns(true)
|
|
62
|
+
@cli.execute!
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_execute_should_not_load_dotfile_when_dotfile_set_and_not_exists
|
|
66
|
+
@cli.options[:dotfile] = "/home/jamis/.caprc"
|
|
67
|
+
File.expects(:file?).with("/home/jamis/.caprc").returns(false)
|
|
68
|
+
@cli.execute!
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_execute_should_load_recipes_when_recipes_are_given
|
|
72
|
+
@cli.options[:recipes] = %w(config/deploy path/to/extra)
|
|
73
|
+
@config.expects(:load).with("config/deploy")
|
|
74
|
+
@config.expects(:load).with("path/to/extra")
|
|
75
|
+
@cli.execute!
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_execute_should_set_vars_and_execute_tasks
|
|
79
|
+
@cli.options[:vars] = { :foo => "bar", :baz => "bang" }
|
|
80
|
+
@cli.options[:actions] = %w(first second)
|
|
81
|
+
@config.expects(:set).with(:foo, "bar")
|
|
82
|
+
@config.expects(:set).with(:baz, "bang")
|
|
83
|
+
@config.expects(:find_and_execute_task).with("first", :before => :start, :after => :finish)
|
|
84
|
+
@config.expects(:find_and_execute_task).with("second", :before => :start, :after => :finish)
|
|
85
|
+
@cli.execute!
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_execute_should_call_load_and_exit_triggers
|
|
89
|
+
@cli.options[:actions] = %w(first second)
|
|
90
|
+
@config.expects(:find_and_execute_task).with("first", :before => :start, :after => :finish)
|
|
91
|
+
@config.expects(:find_and_execute_task).with("second", :before => :start, :after => :finish)
|
|
92
|
+
@config.expects(:trigger).never
|
|
93
|
+
@config.expects(:trigger).with(:load)
|
|
94
|
+
@config.expects(:trigger).with(:exit)
|
|
95
|
+
@cli.execute!
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test_execute_should_call_handle_error_when_exceptions_occur
|
|
99
|
+
@config.expects(:load).raises(Exception, "boom")
|
|
100
|
+
@cli.expects(:handle_error).with { |e,| Exception === e }
|
|
101
|
+
@cli.execute!
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_execute_should_return_config_instance
|
|
105
|
+
assert_equal @config, @cli.execute!
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_instantiate_configuration_should_return_new_configuration_instance
|
|
109
|
+
assert_instance_of Capistrano::Configuration, MockCLI.new.instantiate_configuration
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_handle_error_with_auth_error_should_abort_with_message_including_user_name
|
|
113
|
+
@cli.expects(:abort).with { |s| s.include?("jamis") }
|
|
114
|
+
@cli.handle_error(Net::SSH::AuthenticationFailed.new("jamis"))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_handle_error_with_cap_error_should_abort_with_message
|
|
118
|
+
@cli.expects(:abort).with("Wish you were here")
|
|
119
|
+
@cli.handle_error(Capistrano::Error.new("Wish you were here"))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def test_handle_error_with_other_errors_should_reraise_error
|
|
123
|
+
other_error = Class.new(RuntimeError)
|
|
124
|
+
assert_raises(other_error) { @cli.handle_error(other_error.new("boom")) }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def test_class_execute_method_should_call_parse_and_execute_with_ARGV
|
|
128
|
+
cli = mock(:execute! => nil)
|
|
129
|
+
MockCLI.expects(:parse).with(ARGV).returns(cli)
|
|
130
|
+
MockCLI.execute
|
|
131
|
+
end
|
|
132
|
+
end
|