mbailey-capistrano 2.5.5
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/CHANGELOG.rdoc +761 -0
- data/Manifest +104 -0
- data/README.rdoc +66 -0
- data/Rakefile +34 -0
- data/bin/cap +4 -0
- data/bin/capify +78 -0
- data/examples/sample.rb +14 -0
- data/lib/capistrano/callback.rb +45 -0
- data/lib/capistrano/cli/execute.rb +84 -0
- data/lib/capistrano/cli/help.rb +125 -0
- data/lib/capistrano/cli/help.txt +75 -0
- data/lib/capistrano/cli/options.rb +224 -0
- data/lib/capistrano/cli/ui.rb +40 -0
- data/lib/capistrano/cli.rb +47 -0
- data/lib/capistrano/command.rb +283 -0
- data/lib/capistrano/configuration/actions/file_transfer.rb +47 -0
- data/lib/capistrano/configuration/actions/inspect.rb +46 -0
- data/lib/capistrano/configuration/actions/invocation.rb +293 -0
- data/lib/capistrano/configuration/callbacks.rb +148 -0
- data/lib/capistrano/configuration/connections.rb +200 -0
- data/lib/capistrano/configuration/execution.rb +132 -0
- data/lib/capistrano/configuration/loading.rb +197 -0
- data/lib/capistrano/configuration/namespaces.rb +197 -0
- data/lib/capistrano/configuration/roles.rb +73 -0
- data/lib/capistrano/configuration/servers.rb +85 -0
- data/lib/capistrano/configuration/variables.rb +127 -0
- data/lib/capistrano/configuration.rb +43 -0
- data/lib/capistrano/errors.rb +15 -0
- data/lib/capistrano/extensions.rb +57 -0
- data/lib/capistrano/logger.rb +59 -0
- data/lib/capistrano/processable.rb +53 -0
- data/lib/capistrano/recipes/compat.rb +32 -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 +105 -0
- data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
- data/lib/capistrano/recipes/deploy/scm/base.rb +196 -0
- data/lib/capistrano/recipes/deploy/scm/bzr.rb +83 -0
- data/lib/capistrano/recipes/deploy/scm/cvs.rb +152 -0
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
- data/lib/capistrano/recipes/deploy/scm/git.rb +271 -0
- data/lib/capistrano/recipes/deploy/scm/mercurial.rb +137 -0
- data/lib/capistrano/recipes/deploy/scm/none.rb +44 -0
- data/lib/capistrano/recipes/deploy/scm/perforce.rb +133 -0
- data/lib/capistrano/recipes/deploy/scm/subversion.rb +121 -0
- data/lib/capistrano/recipes/deploy/scm.rb +19 -0
- data/lib/capistrano/recipes/deploy/strategy/base.rb +79 -0
- data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +210 -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 +56 -0
- data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
- data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
- data/lib/capistrano/recipes/deploy.rb +562 -0
- data/lib/capistrano/recipes/standard.rb +37 -0
- data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
- data/lib/capistrano/recipes/upgrade.rb +33 -0
- data/lib/capistrano/role.rb +102 -0
- data/lib/capistrano/server_definition.rb +56 -0
- data/lib/capistrano/shell.rb +260 -0
- data/lib/capistrano/ssh.rb +99 -0
- data/lib/capistrano/task_definition.rb +70 -0
- data/lib/capistrano/transfer.rb +216 -0
- data/lib/capistrano/version.rb +18 -0
- data/lib/capistrano.rb +2 -0
- data/setup.rb +1346 -0
- data/test/cli/execute_test.rb +132 -0
- data/test/cli/help_test.rb +165 -0
- data/test/cli/options_test.rb +317 -0
- data/test/cli/ui_test.rb +28 -0
- data/test/cli_test.rb +17 -0
- data/test/command_test.rb +286 -0
- data/test/configuration/actions/file_transfer_test.rb +61 -0
- data/test/configuration/actions/inspect_test.rb +65 -0
- data/test/configuration/actions/invocation_test.rb +224 -0
- data/test/configuration/callbacks_test.rb +220 -0
- data/test/configuration/connections_test.rb +349 -0
- data/test/configuration/execution_test.rb +175 -0
- data/test/configuration/loading_test.rb +132 -0
- data/test/configuration/namespace_dsl_test.rb +311 -0
- data/test/configuration/roles_test.rb +144 -0
- data/test/configuration/servers_test.rb +121 -0
- data/test/configuration/variables_test.rb +184 -0
- data/test/configuration_test.rb +88 -0
- data/test/deploy/local_dependency_test.rb +76 -0
- data/test/deploy/remote_dependency_test.rb +114 -0
- data/test/deploy/scm/accurev_test.rb +23 -0
- data/test/deploy/scm/base_test.rb +55 -0
- data/test/deploy/scm/git_test.rb +167 -0
- data/test/deploy/scm/mercurial_test.rb +129 -0
- data/test/deploy/strategy/copy_test.rb +258 -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_test.rb +123 -0
- data/test/role_test.rb +11 -0
- data/test/server_definition_test.rb +121 -0
- data/test/shell_test.rb +90 -0
- data/test/ssh_test.rb +104 -0
- data/test/task_definition_test.rb +101 -0
- data/test/transfer_test.rb +160 -0
- data/test/utils.rb +38 -0
- metadata +205 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
class Configuration
|
5
|
+
module Variables
|
6
|
+
def self.included(base) #:nodoc:
|
7
|
+
%w(initialize respond_to? method_missing).each do |m|
|
8
|
+
base_name = m[/^\w+/]
|
9
|
+
punct = m[/\W+$/]
|
10
|
+
base.send :alias_method, "#{base_name}_without_variables#{punct}", m
|
11
|
+
base.send :alias_method, m, "#{base_name}_with_variables#{punct}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# The hash of variables that have been defined in this configuration
|
16
|
+
# instance.
|
17
|
+
attr_reader :variables
|
18
|
+
|
19
|
+
# Set a variable to the given value.
|
20
|
+
def set(variable, *args, &block)
|
21
|
+
if variable.to_s !~ /^[_a-z]/
|
22
|
+
raise ArgumentError, "invalid variable `#{variable}' (variables must begin with an underscore, or a lower-case letter)"
|
23
|
+
end
|
24
|
+
|
25
|
+
if !block_given? && args.empty? || block_given? && !args.empty?
|
26
|
+
raise ArgumentError, "you must specify exactly one of either a value or a block"
|
27
|
+
end
|
28
|
+
|
29
|
+
if args.length > 1
|
30
|
+
raise ArgumentError, "wrong number of arguments (#{args.length} for 1)"
|
31
|
+
end
|
32
|
+
|
33
|
+
value = args.empty? ? block : args.first
|
34
|
+
sym = variable.to_sym
|
35
|
+
protect(sym) { @variables[sym] = value }
|
36
|
+
end
|
37
|
+
|
38
|
+
alias :[]= :set
|
39
|
+
|
40
|
+
# Removes any trace of the given variable.
|
41
|
+
def unset(variable)
|
42
|
+
sym = variable.to_sym
|
43
|
+
protect(sym) do
|
44
|
+
@original_procs.delete(sym)
|
45
|
+
@variables.delete(sym)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns true if the variable has been defined, and false otherwise.
|
50
|
+
def exists?(variable)
|
51
|
+
@variables.key?(variable.to_sym)
|
52
|
+
end
|
53
|
+
|
54
|
+
# If the variable was originally a proc value, it will be reset to it's
|
55
|
+
# original proc value. Otherwise, this method does nothing. It returns
|
56
|
+
# true if the variable was actually reset.
|
57
|
+
def reset!(variable)
|
58
|
+
sym = variable.to_sym
|
59
|
+
protect(sym) do
|
60
|
+
if @original_procs.key?(sym)
|
61
|
+
@variables[sym] = @original_procs.delete(sym)
|
62
|
+
true
|
63
|
+
else
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Access a named variable. If the value of the variable responds_to? :call,
|
70
|
+
# #call will be invoked (without parameters) and the return value cached
|
71
|
+
# and returned.
|
72
|
+
def fetch(variable, *args)
|
73
|
+
if !args.empty? && block_given?
|
74
|
+
raise ArgumentError, "you must specify either a default value or a block, but not both"
|
75
|
+
end
|
76
|
+
|
77
|
+
sym = variable.to_sym
|
78
|
+
protect(sym) do
|
79
|
+
if !@variables.key?(sym)
|
80
|
+
return args.first unless args.empty?
|
81
|
+
return yield(variable) if block_given?
|
82
|
+
raise IndexError, "`#{variable}' not found"
|
83
|
+
end
|
84
|
+
|
85
|
+
if @variables[sym].respond_to?(:call)
|
86
|
+
@original_procs[sym] = @variables[sym]
|
87
|
+
@variables[sym] = @variables[sym].call
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
@variables[sym]
|
92
|
+
end
|
93
|
+
|
94
|
+
def [](variable)
|
95
|
+
fetch(variable, nil)
|
96
|
+
end
|
97
|
+
|
98
|
+
def initialize_with_variables(*args) #:nodoc:
|
99
|
+
initialize_without_variables(*args)
|
100
|
+
@variables = {}
|
101
|
+
@original_procs = {}
|
102
|
+
@variable_locks = Hash.new { |h,k| h[k] = Mutex.new }
|
103
|
+
|
104
|
+
set :ssh_options, {}
|
105
|
+
set :logger, logger
|
106
|
+
end
|
107
|
+
private :initialize_with_variables
|
108
|
+
|
109
|
+
def protect(variable)
|
110
|
+
@variable_locks[variable.to_sym].synchronize { yield }
|
111
|
+
end
|
112
|
+
private :protect
|
113
|
+
|
114
|
+
def respond_to_with_variables?(sym, include_priv=false) #:nodoc:
|
115
|
+
@variables.has_key?(sym) || respond_to_without_variables?(sym, include_priv)
|
116
|
+
end
|
117
|
+
|
118
|
+
def method_missing_with_variables(sym, *args, &block) #:nodoc:
|
119
|
+
if args.length == 0 && block.nil? && @variables.has_key?(sym)
|
120
|
+
self[sym]
|
121
|
+
else
|
122
|
+
method_missing_without_variables(sym, *args, &block)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'capistrano/logger'
|
2
|
+
|
3
|
+
require 'capistrano/configuration/callbacks'
|
4
|
+
require 'capistrano/configuration/connections'
|
5
|
+
require 'capistrano/configuration/execution'
|
6
|
+
require 'capistrano/configuration/loading'
|
7
|
+
require 'capistrano/configuration/namespaces'
|
8
|
+
require 'capistrano/configuration/roles'
|
9
|
+
require 'capistrano/configuration/servers'
|
10
|
+
require 'capistrano/configuration/variables'
|
11
|
+
|
12
|
+
require 'capistrano/configuration/actions/file_transfer'
|
13
|
+
require 'capistrano/configuration/actions/inspect'
|
14
|
+
require 'capistrano/configuration/actions/invocation'
|
15
|
+
|
16
|
+
module Capistrano
|
17
|
+
# Represents a specific Capistrano configuration. A Configuration instance
|
18
|
+
# may be used to load multiple recipe files, define and describe tasks,
|
19
|
+
# define roles, and set configuration variables.
|
20
|
+
class Configuration
|
21
|
+
# The logger instance defined for this configuration.
|
22
|
+
attr_accessor :debug, :logger, :dry_run
|
23
|
+
|
24
|
+
def initialize #:nodoc:
|
25
|
+
@debug = false
|
26
|
+
@dry_run = false
|
27
|
+
@logger = Logger.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# make the DSL easier to read when using lazy evaluation via lambdas
|
31
|
+
alias defer lambda
|
32
|
+
|
33
|
+
# The includes must come at the bottom, since they may redefine methods
|
34
|
+
# defined in the base class.
|
35
|
+
include Connections, Execution, Loading, Namespaces, Roles, Servers, Variables
|
36
|
+
|
37
|
+
# Mix in the actions
|
38
|
+
include Actions::FileTransfer, Actions::Inspect, Actions::Invocation
|
39
|
+
|
40
|
+
# Must mix last, because it hooks into previously defined methods
|
41
|
+
include Callbacks
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Capistrano
|
2
|
+
class Error < RuntimeError; end
|
3
|
+
|
4
|
+
class CaptureError < Error; end
|
5
|
+
class NoSuchTaskError < Error; end
|
6
|
+
class NoMatchingServersError < Error; end
|
7
|
+
|
8
|
+
class RemoteError < Error
|
9
|
+
attr_accessor :hosts
|
10
|
+
end
|
11
|
+
|
12
|
+
class ConnectionError < RemoteError; end
|
13
|
+
class TransferError < RemoteError; end
|
14
|
+
class CommandError < RemoteError; end
|
15
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Capistrano
|
2
|
+
class ExtensionProxy #:nodoc:
|
3
|
+
def initialize(config, mod)
|
4
|
+
@config = config
|
5
|
+
extend(mod)
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(sym, *args, &block)
|
9
|
+
@config.send(sym, *args, &block)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Holds the set of registered plugins, keyed by name (where the name is a
|
14
|
+
# symbol).
|
15
|
+
EXTENSIONS = {}
|
16
|
+
|
17
|
+
# Register the given module as a plugin with the given name. It will henceforth
|
18
|
+
# be available via a proxy object on Configuration instances, accessible by
|
19
|
+
# a method with the given name.
|
20
|
+
def self.plugin(name, mod)
|
21
|
+
name = name.to_sym
|
22
|
+
return false if EXTENSIONS.has_key?(name)
|
23
|
+
|
24
|
+
methods = Capistrano::Configuration.public_instance_methods +
|
25
|
+
Capistrano::Configuration.protected_instance_methods +
|
26
|
+
Capistrano::Configuration.private_instance_methods
|
27
|
+
|
28
|
+
if methods.any? { |m| m.to_sym == name }
|
29
|
+
raise Capistrano::Error, "registering a plugin named `#{name}' would shadow a method on Capistrano::Configuration with the same name"
|
30
|
+
end
|
31
|
+
|
32
|
+
Capistrano::Configuration.class_eval <<-STR, __FILE__, __LINE__+1
|
33
|
+
def #{name}
|
34
|
+
@__#{name}_proxy ||= Capistrano::ExtensionProxy.new(self, Capistrano::EXTENSIONS[#{name.inspect}])
|
35
|
+
end
|
36
|
+
STR
|
37
|
+
|
38
|
+
EXTENSIONS[name] = mod
|
39
|
+
return true
|
40
|
+
end
|
41
|
+
|
42
|
+
# Unregister the plugin with the given name.
|
43
|
+
def self.remove_plugin(name)
|
44
|
+
name = name.to_sym
|
45
|
+
if EXTENSIONS.delete(name)
|
46
|
+
Capistrano::Configuration.send(:remove_method, name)
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.configuration(*args) #:nodoc:
|
54
|
+
warn "[DEPRECATION] Capistrano.configuration is deprecated. Use Capistrano::Configuration.instance instead"
|
55
|
+
Capistrano::Configuration.instance(*args)
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Capistrano
|
2
|
+
class Logger #:nodoc:
|
3
|
+
attr_accessor :level
|
4
|
+
attr_reader :device
|
5
|
+
|
6
|
+
IMPORTANT = 0
|
7
|
+
INFO = 1
|
8
|
+
DEBUG = 2
|
9
|
+
TRACE = 3
|
10
|
+
|
11
|
+
MAX_LEVEL = 3
|
12
|
+
|
13
|
+
def initialize(options={})
|
14
|
+
output = options[:output] || $stderr
|
15
|
+
if output.respond_to?(:puts)
|
16
|
+
@device = output
|
17
|
+
else
|
18
|
+
@device = File.open(output.to_str, "a")
|
19
|
+
@needs_close = true
|
20
|
+
end
|
21
|
+
|
22
|
+
@options = options
|
23
|
+
@level = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def close
|
27
|
+
device.close if @needs_close
|
28
|
+
end
|
29
|
+
|
30
|
+
def log(level, message, line_prefix=nil)
|
31
|
+
if level <= self.level
|
32
|
+
indent = "%*s" % [MAX_LEVEL, "*" * (MAX_LEVEL - level)]
|
33
|
+
(RUBY_VERSION >= "1.9" ? message.lines : message).each do |line|
|
34
|
+
if line_prefix
|
35
|
+
device.puts "#{indent} [#{line_prefix}] #{line.strip}\n"
|
36
|
+
else
|
37
|
+
device.puts "#{indent} #{line.strip}\n"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def important(message, line_prefix=nil)
|
44
|
+
log(IMPORTANT, message, line_prefix)
|
45
|
+
end
|
46
|
+
|
47
|
+
def info(message, line_prefix=nil)
|
48
|
+
log(INFO, message, line_prefix)
|
49
|
+
end
|
50
|
+
|
51
|
+
def debug(message, line_prefix=nil)
|
52
|
+
log(DEBUG, message, line_prefix)
|
53
|
+
end
|
54
|
+
|
55
|
+
def trace(message, line_prefix=nil)
|
56
|
+
log(TRACE, message, line_prefix)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Capistrano
|
2
|
+
module Processable
|
3
|
+
module SessionAssociation
|
4
|
+
def self.on(exception, session)
|
5
|
+
unless exception.respond_to?(:session)
|
6
|
+
exception.extend(self)
|
7
|
+
exception.session = session
|
8
|
+
end
|
9
|
+
|
10
|
+
return exception
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :session
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_iteration(wait=nil, &block)
|
17
|
+
ensure_each_session { |session| session.preprocess }
|
18
|
+
|
19
|
+
return false if block && !block.call(self)
|
20
|
+
|
21
|
+
readers = sessions.map { |session| session.listeners.keys }.flatten.reject { |io| io.closed? }
|
22
|
+
writers = readers.select { |io| io.respond_to?(:pending_write?) && io.pending_write? }
|
23
|
+
|
24
|
+
if readers.any? || writers.any?
|
25
|
+
readers, writers, = IO.select(readers, writers, nil, wait)
|
26
|
+
end
|
27
|
+
|
28
|
+
if readers
|
29
|
+
ensure_each_session do |session|
|
30
|
+
ios = session.listeners.keys
|
31
|
+
session.postprocess(ios & readers, ios & writers)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def ensure_each_session
|
39
|
+
errors = []
|
40
|
+
|
41
|
+
sessions.each do |session|
|
42
|
+
begin
|
43
|
+
yield session
|
44
|
+
rescue Exception => error
|
45
|
+
errors << SessionAssociation.on(error, session)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
raise errors.first if errors.any?
|
50
|
+
sessions
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# A collection of compatibility scripts, to ease the transition between
|
2
|
+
# Capistrano 1.x and Capistrano 2.x.
|
3
|
+
|
4
|
+
# Depends on the deployment system
|
5
|
+
load 'deploy'
|
6
|
+
|
7
|
+
map = { "diff_from_last_deploy" => "deploy:pending:diff",
|
8
|
+
"update" => "deploy:update",
|
9
|
+
"update_code" => "deploy:update_code",
|
10
|
+
"symlink" => "deploy:symlink",
|
11
|
+
"restart" => "deploy:restart",
|
12
|
+
"rollback" => "deploy:rollback",
|
13
|
+
"cleanup" => "deploy:cleanup",
|
14
|
+
"disable_web" => "deploy:web:disable",
|
15
|
+
"enable_web" => "deploy:web:enable",
|
16
|
+
"cold_deploy" => "deploy:cold",
|
17
|
+
"deploy_with_migrations" => "deploy:migrations" }
|
18
|
+
|
19
|
+
map.each do |old, new|
|
20
|
+
desc "DEPRECATED: See #{new}."
|
21
|
+
eval "task(#{old.inspect}) do
|
22
|
+
warn \"[DEPRECATED] `#{old}' is deprecated. Use `#{new}' instead.\"
|
23
|
+
find_and_execute_task(#{new.inspect})
|
24
|
+
end"
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "DEPRECATED: See deploy:start."
|
28
|
+
task :spinner do
|
29
|
+
warn "[DEPRECATED] `spinner' is deprecated. Use `deploy:start' instead."
|
30
|
+
set :runner, fetch(:spinner_user, "app")
|
31
|
+
deploy.start
|
32
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'capistrano/recipes/deploy/local_dependency'
|
2
|
+
require 'capistrano/recipes/deploy/remote_dependency'
|
3
|
+
|
4
|
+
module Capistrano
|
5
|
+
module Deploy
|
6
|
+
class Dependencies
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
attr_reader :configuration
|
10
|
+
|
11
|
+
def initialize(configuration)
|
12
|
+
@configuration = configuration
|
13
|
+
@dependencies = []
|
14
|
+
yield self if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
def check
|
18
|
+
yield self
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def remote
|
23
|
+
dep = RemoteDependency.new(configuration)
|
24
|
+
@dependencies << dep
|
25
|
+
dep
|
26
|
+
end
|
27
|
+
|
28
|
+
def local
|
29
|
+
dep = LocalDependency.new(configuration)
|
30
|
+
@dependencies << dep
|
31
|
+
dep
|
32
|
+
end
|
33
|
+
|
34
|
+
def each
|
35
|
+
@dependencies.each { |d| yield d }
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def pass?
|
40
|
+
all? { |d| d.pass? }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Capistrano
|
2
|
+
module Deploy
|
3
|
+
class LocalDependency
|
4
|
+
attr_reader :configuration
|
5
|
+
attr_reader :message
|
6
|
+
|
7
|
+
def initialize(configuration)
|
8
|
+
@configuration = configuration
|
9
|
+
@success = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def command(command)
|
13
|
+
@message ||= "`#{command}' could not be found in the path on the local host"
|
14
|
+
@success = find_in_path(command)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def or(message)
|
19
|
+
@message = message
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def pass?
|
24
|
+
@success
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Searches the path, looking for the given utility. If an executable
|
30
|
+
# file is found that matches the parameter, this returns true.
|
31
|
+
def find_in_path(utility)
|
32
|
+
path = (ENV['PATH'] || "").split(File::PATH_SEPARATOR)
|
33
|
+
suffixes = self.class.on_windows? ? self.class.windows_executable_extensions : [""]
|
34
|
+
|
35
|
+
path.each do |dir|
|
36
|
+
suffixes.each do |sfx|
|
37
|
+
file = File.join(dir, utility + sfx)
|
38
|
+
return true if File.executable?(file)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.on_windows?
|
46
|
+
RUBY_PLATFORM =~ /mswin|mingw/
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.windows_executable_extensions
|
50
|
+
%w(.exe .bat .com .cmd)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'capistrano/errors'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Deploy
|
5
|
+
class RemoteDependency
|
6
|
+
attr_reader :configuration
|
7
|
+
attr_reader :hosts
|
8
|
+
|
9
|
+
def initialize(configuration)
|
10
|
+
@configuration = configuration
|
11
|
+
@success = true
|
12
|
+
@hosts = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def directory(path, options={})
|
16
|
+
@message ||= "`#{path}' is not a directory"
|
17
|
+
try("test -d #{path}", options)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def file(path, options={})
|
22
|
+
@message ||= "`#{path}' is not a file"
|
23
|
+
try("test -f #{path}", options)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def writable(path, options={})
|
28
|
+
@message ||= "`#{path}' is not writable"
|
29
|
+
try("test -w #{path}", options)
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def command(command, options={})
|
34
|
+
@message ||= "`#{command}' could not be found in the path"
|
35
|
+
try("which #{command}", options)
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def gem(name, version, options={})
|
40
|
+
@message ||= "gem `#{name}' #{version} could not be found"
|
41
|
+
gem_cmd = configuration.fetch(:gem_command, "gem")
|
42
|
+
try("#{gem_cmd} specification --version '#{version}' #{name} 2>&1 | awk 'BEGIN { s = 0 } /^name:/ { s = 1; exit }; END { if(s == 0) exit 1 }'", options)
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def match(command, expect, options={})
|
47
|
+
expect = Regexp.new(Regexp.escape(expect.to_s)) unless expect.is_a?(Regexp)
|
48
|
+
|
49
|
+
output_per_server = {}
|
50
|
+
try("#{command} ", options) do |ch, stream, out|
|
51
|
+
output_per_server[ch[:server]] ||= ''
|
52
|
+
output_per_server[ch[:server]] += out
|
53
|
+
end
|
54
|
+
|
55
|
+
# It is possible for some of these commands to return a status != 0
|
56
|
+
# (for example, rake --version exits with a 1). For this check we
|
57
|
+
# just care if the output matches, so we reset the success flag.
|
58
|
+
@success = true
|
59
|
+
|
60
|
+
errored_hosts = []
|
61
|
+
output_per_server.each_pair do |server, output|
|
62
|
+
next if output =~ expect
|
63
|
+
errored_hosts << server
|
64
|
+
end
|
65
|
+
|
66
|
+
if errored_hosts.any?
|
67
|
+
@hosts = errored_hosts.join(', ')
|
68
|
+
output = output_per_server[errored_hosts.first]
|
69
|
+
@message = "the output #{output.inspect} from #{command.inspect} did not match #{expect.inspect}"
|
70
|
+
@success = false
|
71
|
+
end
|
72
|
+
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def or(message)
|
77
|
+
@message = message
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
def pass?
|
82
|
+
@success
|
83
|
+
end
|
84
|
+
|
85
|
+
def message
|
86
|
+
s = @message.dup
|
87
|
+
s << " (#{@hosts})" if @hosts
|
88
|
+
s
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def try(command, options)
|
94
|
+
return unless @success # short-circuit evaluation
|
95
|
+
configuration.invoke_command(command, options) do |ch,stream,out|
|
96
|
+
warn "#{ch[:server]}: #{out}" if stream == :err
|
97
|
+
yield ch, stream, out if block_given?
|
98
|
+
end
|
99
|
+
rescue Capistrano::CommandError => e
|
100
|
+
@success = false
|
101
|
+
@hosts = e.hosts.join(', ')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|