switchtower 0.9.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.
- data/bin/switchtower +11 -0
- data/examples/sample.rb +113 -0
- data/lib/switchtower.rb +1 -0
- data/lib/switchtower/actor.rb +350 -0
- data/lib/switchtower/cli.rb +220 -0
- data/lib/switchtower/command.rb +85 -0
- data/lib/switchtower/configuration.rb +193 -0
- data/lib/switchtower/gateway.rb +106 -0
- data/lib/switchtower/generators/rails/deployment/deployment_generator.rb +25 -0
- data/lib/switchtower/generators/rails/deployment/templates/deploy.rb +116 -0
- data/lib/switchtower/generators/rails/deployment/templates/switchtower.rake +33 -0
- data/lib/switchtower/generators/rails/loader.rb +20 -0
- data/lib/switchtower/logger.rb +56 -0
- data/lib/switchtower/recipes/standard.rb +175 -0
- data/lib/switchtower/recipes/templates/maintenance.rhtml +53 -0
- data/lib/switchtower/scm/base.rb +43 -0
- data/lib/switchtower/scm/cvs.rb +73 -0
- data/lib/switchtower/scm/darcs.rb +27 -0
- data/lib/switchtower/scm/subversion.rb +104 -0
- data/lib/switchtower/ssh.rb +30 -0
- data/lib/switchtower/version.rb +9 -0
- data/test/actor_test.rb +261 -0
- data/test/command_test.rb +43 -0
- data/test/configuration_test.rb +210 -0
- data/test/fixtures/config.rb +5 -0
- data/test/scm/cvs_test.rb +164 -0
- data/test/scm/subversion_test.rb +100 -0
- data/test/ssh_test.rb +104 -0
- data/test/utils.rb +41 -0
- metadata +88 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
|
3
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
4
|
+
|
|
5
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
|
6
|
+
|
|
7
|
+
<head>
|
|
8
|
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
|
9
|
+
<title>System down for maintenance</title>
|
|
10
|
+
|
|
11
|
+
<style type="text/css">
|
|
12
|
+
div.outer {
|
|
13
|
+
position: absolute;
|
|
14
|
+
left: 50%;
|
|
15
|
+
top: 50%;
|
|
16
|
+
width: 500px;
|
|
17
|
+
height: 300px;
|
|
18
|
+
margin-left: -260px;
|
|
19
|
+
margin-top: -150px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.DialogBody {
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 10px;
|
|
25
|
+
text-align: left;
|
|
26
|
+
border: 1px solid #ccc;
|
|
27
|
+
border-right: 1px solid #999;
|
|
28
|
+
border-bottom: 1px solid #999;
|
|
29
|
+
background-color: #fff;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
body { background-color: #fff; }
|
|
33
|
+
</style>
|
|
34
|
+
</head>
|
|
35
|
+
|
|
36
|
+
<body>
|
|
37
|
+
|
|
38
|
+
<div class="outer">
|
|
39
|
+
<div class="DialogBody" style="text-align: center;">
|
|
40
|
+
<div style="text-align: center; width: 200px; margin: 0 auto;">
|
|
41
|
+
<p style="color: red; font-size: 16px; line-height: 20px;">
|
|
42
|
+
The system is down for <%= reason ? reason : "maintenance" %>
|
|
43
|
+
as of <%= Time.now.strftime("%H:%M %Z") %>.
|
|
44
|
+
</p>
|
|
45
|
+
<p style="color: #666;">
|
|
46
|
+
It'll be back <%= deadline ? "by #{deadline}" : "shortly" %>.
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module SwitchTower
|
|
2
|
+
module SCM
|
|
3
|
+
|
|
4
|
+
# The ancestor class of the various SCM module implementations.
|
|
5
|
+
class Base
|
|
6
|
+
attr_reader :configuration
|
|
7
|
+
|
|
8
|
+
def initialize(configuration) #:nodoc:
|
|
9
|
+
@configuration = configuration
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def latest_revision
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def current_revision(actor)
|
|
17
|
+
raise "#{self.class} doesn't support querying the deployed revision"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def diff(actor, from=nil, to=nil)
|
|
21
|
+
raise "#{self.class} doesn't support diff(from, to)"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def run_checkout(actor, guts, &block)
|
|
27
|
+
log = "#{configuration.deploy_to}/revisions.log"
|
|
28
|
+
directory = File.basename(configuration.release_path)
|
|
29
|
+
|
|
30
|
+
command = <<-STR
|
|
31
|
+
if [[ ! -d #{configuration.release_path} ]]; then
|
|
32
|
+
#{guts}
|
|
33
|
+
echo `date +"%Y-%m-%d %H:%M:%S"` $USER #{configuration.revision} #{directory} >> #{log};
|
|
34
|
+
chmod 666 #{log};
|
|
35
|
+
fi
|
|
36
|
+
STR
|
|
37
|
+
|
|
38
|
+
actor.run(command, &block)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require 'time'
|
|
2
|
+
require 'switchtower/scm/base'
|
|
3
|
+
|
|
4
|
+
module SwitchTower
|
|
5
|
+
module SCM
|
|
6
|
+
|
|
7
|
+
# An SCM module for using CVS as your source control tool. You can
|
|
8
|
+
# specify it by placing the following line in your configuration:
|
|
9
|
+
#
|
|
10
|
+
# set :scm, :cvs
|
|
11
|
+
#
|
|
12
|
+
# Also, this module accepts a <tt>:cvs</tt> configuration variable,
|
|
13
|
+
# which (if specified) will be used as the full path to the cvs
|
|
14
|
+
# executable on the remote machine:
|
|
15
|
+
#
|
|
16
|
+
# set :cvs, "/opt/local/bin/cvs"
|
|
17
|
+
#
|
|
18
|
+
# You can specify the location of your local copy (used to query
|
|
19
|
+
# the revisions, etc.) via the <tt>:local</tt> variable, which defaults to
|
|
20
|
+
# ".".
|
|
21
|
+
#
|
|
22
|
+
# Also, you can specify the CVS_RSH variable to use on the remote machine(s)
|
|
23
|
+
# via the <tt>:cvs_rsh</tt> variable. This defaults to the value of the
|
|
24
|
+
# CVS_RSH environment variable locally, or if it is not set, to "ssh".
|
|
25
|
+
class Cvs < Base
|
|
26
|
+
# Return a string representing the date of the last revision (CVS is
|
|
27
|
+
# seriously retarded, in that it does not give you a way to query when
|
|
28
|
+
# the last revision was made to the repository, so this is a fairly
|
|
29
|
+
# expensive operation...)
|
|
30
|
+
def latest_revision
|
|
31
|
+
return @latest_revision if @latest_revision
|
|
32
|
+
configuration.logger.debug "querying latest revision..."
|
|
33
|
+
@latest_revision = cvs_log(configuration.local).
|
|
34
|
+
split(/\r?\n/).
|
|
35
|
+
grep(/^date: (.*?);/) { Time.parse($1).strftime("%F %T") }.
|
|
36
|
+
sort.
|
|
37
|
+
last
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check out (on all servers associated with the current task) the latest
|
|
41
|
+
# revision. Uses the given actor instance to execute the command.
|
|
42
|
+
def checkout(actor)
|
|
43
|
+
cvs = configuration[:cvs] || "cvs"
|
|
44
|
+
cvs_rsh = configuration[:cvs_rsh] || ENV['CVS_RSH'] || "ssh"
|
|
45
|
+
|
|
46
|
+
command = <<-CMD
|
|
47
|
+
cd #{configuration.releases_path};
|
|
48
|
+
CVS_RSH="#{cvs_rsh}" #{cvs} -d #{configuration.repository} -Q co -D "#{configuration.revision}" -d #{File.basename(actor.release_path)} #{actor.application};
|
|
49
|
+
CMD
|
|
50
|
+
|
|
51
|
+
run_checkout(actor, command) do |ch, stream, out|
|
|
52
|
+
prefix = "#{stream} :: #{ch[:host]}"
|
|
53
|
+
actor.logger.info out, prefix
|
|
54
|
+
if out =~ %r{password:}
|
|
55
|
+
actor.logger.info "CVS is asking for a password", prefix
|
|
56
|
+
ch.send_data "#{actor.password}\n"
|
|
57
|
+
elsif out =~ %r{^Enter passphrase}
|
|
58
|
+
message = "CVS needs your key's passphrase and cannot proceed"
|
|
59
|
+
actor.logger.info message, prefix
|
|
60
|
+
raise message
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def cvs_log(path)
|
|
68
|
+
`cd #{path || "."} && cvs -q log -N -rHEAD`
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'switchtower/scm/base'
|
|
2
|
+
|
|
3
|
+
module SwitchTower
|
|
4
|
+
module SCM
|
|
5
|
+
|
|
6
|
+
# An SCM module for using darcs as your source control tool. Use it by
|
|
7
|
+
# specifying the following line in your configuration:
|
|
8
|
+
#
|
|
9
|
+
# set :scm, :darcs
|
|
10
|
+
#
|
|
11
|
+
# Also, this module accepts a <tt>:darcs</tt> configuration variable,
|
|
12
|
+
# which (if specified) will be used as the full path to the darcs
|
|
13
|
+
# executable on the remote machine:
|
|
14
|
+
#
|
|
15
|
+
# set :darcs, "/opt/local/bin/darcs"
|
|
16
|
+
class Darcs < Base
|
|
17
|
+
# Check out (on all servers associated with the current task) the latest
|
|
18
|
+
# revision. Uses the given actor instance to execute the command.
|
|
19
|
+
def checkout(actor)
|
|
20
|
+
darcs = configuration[:darcs] ? configuration[:darcs] : "darcs"
|
|
21
|
+
revision = configuration[:revision] ? %(--to-match "#{configuration.revision}") : ""
|
|
22
|
+
run_checkout(actor, "#{darcs} get -q --set-scripts-executable #{revision} #{configuration.repository} #{actor.release_path};")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'switchtower/scm/base'
|
|
2
|
+
|
|
3
|
+
module SwitchTower
|
|
4
|
+
module SCM
|
|
5
|
+
|
|
6
|
+
# An SCM module for using subversion as your source control tool. This
|
|
7
|
+
# module is used by default, but you can explicitly specify it by
|
|
8
|
+
# placing the following line in your configuration:
|
|
9
|
+
#
|
|
10
|
+
# set :scm, :subversion
|
|
11
|
+
#
|
|
12
|
+
# Also, this module accepts a <tt>:svn</tt> configuration variable,
|
|
13
|
+
# which (if specified) will be used as the full path to the svn
|
|
14
|
+
# executable on the remote machine:
|
|
15
|
+
#
|
|
16
|
+
# set :svn, "/opt/local/bin/svn"
|
|
17
|
+
class Subversion < Base
|
|
18
|
+
# Return an integer identifying the last known revision in the svn
|
|
19
|
+
# repository. (This integer is currently the revision number.) If latest
|
|
20
|
+
# revision does not exist in the given repository, this routine will
|
|
21
|
+
# walk up the directory tree until it finds it.
|
|
22
|
+
def latest_revision
|
|
23
|
+
configuration.logger.debug "querying latest revision..." unless @latest_revision
|
|
24
|
+
repo = configuration.repository
|
|
25
|
+
until @latest_revision
|
|
26
|
+
match = svn_log(repo).scan(/r(\d+)/).first
|
|
27
|
+
@latest_revision = match ? match.first : nil
|
|
28
|
+
if @latest_revision.nil?
|
|
29
|
+
# if a revision number was not reported, move up a level in the path
|
|
30
|
+
# and try again.
|
|
31
|
+
repo = File.dirname(repo)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
@latest_revision
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Return the number of the revision currently deployed.
|
|
38
|
+
def current_revision(actor)
|
|
39
|
+
latest = actor.releases.last
|
|
40
|
+
grep = %(grep " #{latest}$" #{configuration.deploy_to}/revisions.log)
|
|
41
|
+
result = ""
|
|
42
|
+
actor.run(grep, :once => true) do |ch, str, out|
|
|
43
|
+
result << out if str == :out
|
|
44
|
+
raise "could not determine current revision" if str == :err
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
date, time, user, rev, dir = result.split
|
|
48
|
+
raise "current revision not found in revisions.log" unless dir == latest
|
|
49
|
+
|
|
50
|
+
rev.to_i
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Return a string containing the diff between the two revisions. +from+
|
|
54
|
+
# and +to+ may be in any format that svn recognizes as a valid revision
|
|
55
|
+
# identifier. If +from+ is +nil+, it defaults to the last deployed
|
|
56
|
+
# revision. If +to+ is +nil+, it defaults to HEAD.
|
|
57
|
+
def diff(actor, from=nil, to=nil)
|
|
58
|
+
from ||= current_revision(actor)
|
|
59
|
+
to ||= "HEAD"
|
|
60
|
+
|
|
61
|
+
`svn diff #{configuration.repository}@#{from} #{configuration.repository}@#{to}`
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Check out (on all servers associated with the current task) the latest
|
|
65
|
+
# revision. Uses the given actor instance to execute the command. If
|
|
66
|
+
# svn asks for a password this will automatically provide it (assuming
|
|
67
|
+
# the requested password is the same as the password for logging into the
|
|
68
|
+
# remote server.)
|
|
69
|
+
def checkout(actor)
|
|
70
|
+
svn = configuration[:svn] ? configuration[:svn] : "svn"
|
|
71
|
+
|
|
72
|
+
command = "#{svn} co -q -r#{configuration.revision} #{configuration.repository} #{actor.release_path};"
|
|
73
|
+
|
|
74
|
+
run_checkout(actor, command) do |ch, stream, out|
|
|
75
|
+
prefix = "#{stream} :: #{ch[:host]}"
|
|
76
|
+
actor.logger.info out, prefix
|
|
77
|
+
if out =~ /^Password.*:/
|
|
78
|
+
actor.logger.info "subversion is asking for a password", prefix
|
|
79
|
+
ch.send_data "#{actor.password}\n"
|
|
80
|
+
elsif out =~ %r{\(yes/no\)}
|
|
81
|
+
actor.logger.info "subversion is asking whether to connect or not",
|
|
82
|
+
prefix
|
|
83
|
+
ch.send_data "yes\n"
|
|
84
|
+
elsif out =~ %r{passphrase}
|
|
85
|
+
message = "subversion needs your key's passphrase, sending empty string"
|
|
86
|
+
actor.logger.info message, prefix
|
|
87
|
+
ch.send_data "\n"
|
|
88
|
+
elsif out =~ %r{The entry \'(\w+)\' is no longer a directory}
|
|
89
|
+
message = "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
|
|
90
|
+
actor.logger.info message, prefix
|
|
91
|
+
raise message
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def svn_log(path)
|
|
99
|
+
`svn log -q -rhead #{path}`
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'net/ssh'
|
|
2
|
+
|
|
3
|
+
module SwitchTower
|
|
4
|
+
# A helper class for dealing with SSH connections.
|
|
5
|
+
class SSH
|
|
6
|
+
# An abstraction to make it possible to connect to the server via public key
|
|
7
|
+
# without prompting for the password. If the public key authentication fails
|
|
8
|
+
# this will fall back to password authentication.
|
|
9
|
+
#
|
|
10
|
+
# If a block is given, the new session is yielded to it, otherwise the new
|
|
11
|
+
# session is returned.
|
|
12
|
+
def self.connect(server, config, port=22, &block)
|
|
13
|
+
methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
|
|
14
|
+
password_value = nil
|
|
15
|
+
|
|
16
|
+
begin
|
|
17
|
+
Net::SSH.start(server,
|
|
18
|
+
:username => config.user,
|
|
19
|
+
:password => password_value,
|
|
20
|
+
:port => port,
|
|
21
|
+
:auth_methods => methods.shift,
|
|
22
|
+
&block)
|
|
23
|
+
rescue Net::SSH::AuthenticationFailed
|
|
24
|
+
raise if methods.empty?
|
|
25
|
+
password_value = config.password
|
|
26
|
+
retry
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/test/actor_test.rb
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
2
|
+
|
|
3
|
+
require 'stringio'
|
|
4
|
+
require 'test/unit'
|
|
5
|
+
require 'switchtower/actor'
|
|
6
|
+
require 'switchtower/logger'
|
|
7
|
+
|
|
8
|
+
class ActorTest < Test::Unit::TestCase
|
|
9
|
+
|
|
10
|
+
class TestingConnectionFactory
|
|
11
|
+
def initialize(config)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def connect_to(server)
|
|
15
|
+
server
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class GatewayConnectionFactory
|
|
20
|
+
def connect_to(server)
|
|
21
|
+
server
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class TestingCommand
|
|
26
|
+
def self.invoked!
|
|
27
|
+
@invoked = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.invoked?
|
|
31
|
+
@invoked
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.reset!
|
|
35
|
+
@invoked = nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initialize(*args)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def process!
|
|
42
|
+
self.class.invoked!
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class TestActor < SwitchTower::Actor
|
|
47
|
+
attr_reader :factory
|
|
48
|
+
|
|
49
|
+
self.connection_factory = TestingConnectionFactory
|
|
50
|
+
self.command_factory = TestingCommand
|
|
51
|
+
|
|
52
|
+
def establish_gateway
|
|
53
|
+
GatewayConnectionFactory.new
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class MockConfiguration
|
|
58
|
+
Role = Struct.new(:host, :options)
|
|
59
|
+
|
|
60
|
+
attr_accessor :gateway, :pretend
|
|
61
|
+
|
|
62
|
+
def delegated_method
|
|
63
|
+
"result of method"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
ROLES = { :db => [ Role.new("01.example.com", :primary => true),
|
|
67
|
+
Role.new("02.example.com", {}),
|
|
68
|
+
Role.new("all.example.com", {})],
|
|
69
|
+
:web => [ Role.new("03.example.com", {}),
|
|
70
|
+
Role.new("04.example.com", {}),
|
|
71
|
+
Role.new("all.example.com", {})],
|
|
72
|
+
:app => [ Role.new("05.example.com", {}),
|
|
73
|
+
Role.new("06.example.com", {}),
|
|
74
|
+
Role.new("07.example.com", {}),
|
|
75
|
+
Role.new("all.example.com", {})] }
|
|
76
|
+
|
|
77
|
+
def roles
|
|
78
|
+
ROLES
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def logger
|
|
82
|
+
@logger ||= SwitchTower::Logger.new(:output => StringIO.new)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def setup
|
|
87
|
+
TestingCommand.reset!
|
|
88
|
+
@actor = TestActor.new(MockConfiguration.new)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_define_task_creates_method
|
|
92
|
+
@actor.define_task :hello do
|
|
93
|
+
"result"
|
|
94
|
+
end
|
|
95
|
+
assert @actor.respond_to?(:hello)
|
|
96
|
+
assert_equal "result", @actor.hello
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def test_define_task_with_successful_transaction
|
|
100
|
+
class << @actor
|
|
101
|
+
attr_reader :rolled_back
|
|
102
|
+
attr_reader :history
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
@actor.define_task :hello do
|
|
106
|
+
(@history ||= []) << :hello
|
|
107
|
+
on_rollback { @rolled_back = true }
|
|
108
|
+
"hello"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
@actor.define_task :goodbye do
|
|
112
|
+
(@history ||= []) << :goodbye
|
|
113
|
+
transaction do
|
|
114
|
+
hello
|
|
115
|
+
end
|
|
116
|
+
"goodbye"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
assert_nothing_raised { @actor.goodbye }
|
|
120
|
+
assert !@actor.rolled_back
|
|
121
|
+
assert_equal [:goodbye, :hello], @actor.history
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_define_task_with_failed_transaction
|
|
125
|
+
class << @actor
|
|
126
|
+
attr_reader :rolled_back
|
|
127
|
+
attr_reader :history
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
@actor.define_task :hello do
|
|
131
|
+
(@history ||= []) << :hello
|
|
132
|
+
on_rollback { @rolled_back = true }
|
|
133
|
+
"hello"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
@actor.define_task :goodbye do
|
|
137
|
+
(@history ||= []) << :goodbye
|
|
138
|
+
transaction do
|
|
139
|
+
hello
|
|
140
|
+
raise "ouch"
|
|
141
|
+
end
|
|
142
|
+
"goodbye"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
assert_raise(RuntimeError) do
|
|
146
|
+
@actor.goodbye
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
assert @actor.rolled_back
|
|
150
|
+
assert_equal [:goodbye, :hello], @actor.history
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def test_delegates_to_configuration
|
|
154
|
+
@actor.define_task :hello do
|
|
155
|
+
delegated_method
|
|
156
|
+
end
|
|
157
|
+
assert_equal "result of method", @actor.hello
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_task_servers_with_duplicates
|
|
161
|
+
@actor.define_task :foo do
|
|
162
|
+
run "do this"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
assert_equal %w(01.example.com 02.example.com 03.example.com 04.example.com 05.example.com 06.example.com 07.example.com all.example.com), @actor.tasks[:foo].servers(@actor.configuration).sort
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_run_in_task_without_explicit_roles_selects_all_roles
|
|
169
|
+
@actor.define_task :foo do
|
|
170
|
+
run "do this"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
@actor.foo
|
|
174
|
+
assert_equal %w(01.example.com 02.example.com 03.example.com 04.example.com 05.example.com 06.example.com 07.example.com all.example.com), @actor.sessions.keys.sort
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def test_run_in_task_with_single_role_selects_that_role
|
|
178
|
+
@actor.define_task :foo, :roles => :db do
|
|
179
|
+
run "do this"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
@actor.foo
|
|
183
|
+
assert_equal %w(01.example.com 02.example.com all.example.com), @actor.sessions.keys.sort
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_run_in_task_with_multiple_roles_selects_those_roles
|
|
187
|
+
@actor.define_task :foo, :roles => [:db, :web] do
|
|
188
|
+
run "do this"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
@actor.foo
|
|
192
|
+
assert_equal %w(01.example.com 02.example.com 03.example.com 04.example.com all.example.com), @actor.sessions.keys.sort
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def test_run_in_task_with_only_restricts_selected_roles
|
|
196
|
+
@actor.define_task :foo, :roles => :db, :only => { :primary => true } do
|
|
197
|
+
run "do this"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
@actor.foo
|
|
201
|
+
assert_equal %w(01.example.com), @actor.sessions.keys.sort
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def test_establish_connection_uses_gateway_if_specified
|
|
205
|
+
@actor.configuration.gateway = "10.example.com"
|
|
206
|
+
@actor.define_task :foo, :roles => :db do
|
|
207
|
+
run "do this"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
@actor.foo
|
|
211
|
+
assert_instance_of GatewayConnectionFactory, @actor.factory
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def test_run_when_not_pretend
|
|
215
|
+
@actor.define_task :foo do
|
|
216
|
+
run "do this"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
@actor.configuration.pretend = false
|
|
220
|
+
@actor.foo
|
|
221
|
+
assert TestingCommand.invoked?
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def test_run_when_pretend
|
|
225
|
+
@actor.define_task :foo do
|
|
226
|
+
run "do this"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
@actor.configuration.pretend = true
|
|
230
|
+
@actor.foo
|
|
231
|
+
assert !TestingCommand.invoked?
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def test_task_before_hook
|
|
235
|
+
history = []
|
|
236
|
+
@actor.define_task :foo do
|
|
237
|
+
history << "foo"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
@actor.define_task :before_foo do
|
|
241
|
+
history << "before_foo"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
@actor.foo
|
|
245
|
+
assert_equal %w(before_foo foo), history
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def test_task_after_hook
|
|
249
|
+
history = []
|
|
250
|
+
@actor.define_task :foo do
|
|
251
|
+
history << "foo"
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
@actor.define_task :after_foo do
|
|
255
|
+
history << "after_foo"
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
@actor.foo
|
|
259
|
+
assert_equal %w(foo after_foo), history
|
|
260
|
+
end
|
|
261
|
+
end
|