capistrano 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/cap +11 -0
- data/examples/sample.rb +113 -0
- data/lib/capistrano.rb +1 -0
- data/lib/capistrano/actor.rb +438 -0
- data/lib/capistrano/cli.rb +295 -0
- data/lib/capistrano/command.rb +90 -0
- data/lib/capistrano/configuration.rb +243 -0
- data/lib/capistrano/extensions.rb +38 -0
- data/lib/capistrano/gateway.rb +118 -0
- data/lib/capistrano/generators/rails/deployment/deployment_generator.rb +25 -0
- data/lib/capistrano/generators/rails/deployment/templates/capistrano.rake +46 -0
- data/lib/capistrano/generators/rails/deployment/templates/deploy.rb +122 -0
- data/lib/capistrano/generators/rails/loader.rb +20 -0
- data/lib/capistrano/logger.rb +59 -0
- data/lib/capistrano/recipes/standard.rb +242 -0
- data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
- data/lib/capistrano/scm/base.rb +62 -0
- data/lib/capistrano/scm/baz.rb +118 -0
- data/lib/capistrano/scm/bzr.rb +70 -0
- data/lib/capistrano/scm/cvs.rb +124 -0
- data/lib/capistrano/scm/darcs.rb +27 -0
- data/lib/capistrano/scm/perforce.rb +139 -0
- data/lib/capistrano/scm/subversion.rb +122 -0
- data/lib/capistrano/ssh.rb +39 -0
- data/lib/capistrano/transfer.rb +90 -0
- data/lib/capistrano/utils.rb +26 -0
- data/lib/capistrano/version.rb +30 -0
- data/test/actor_test.rb +294 -0
- data/test/command_test.rb +43 -0
- data/test/configuration_test.rb +233 -0
- data/test/fixtures/config.rb +5 -0
- data/test/fixtures/custom.rb +3 -0
- data/test/scm/cvs_test.rb +186 -0
- data/test/scm/subversion_test.rb +137 -0
- data/test/ssh_test.rb +104 -0
- data/test/utils.rb +50 -0
- metadata +107 -0
@@ -0,0 +1,186 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + "/../../lib"
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + "/../utils"
|
4
|
+
require 'test/unit'
|
5
|
+
require 'capistrano/scm/cvs'
|
6
|
+
|
7
|
+
class ScmCvsTest < Test::Unit::TestCase
|
8
|
+
class CvsTest < Capistrano::SCM::Cvs
|
9
|
+
attr_accessor :story
|
10
|
+
attr_reader :last_path
|
11
|
+
|
12
|
+
def cvs_log(path,branch)
|
13
|
+
@last_path = path
|
14
|
+
story.shift
|
15
|
+
end
|
16
|
+
|
17
|
+
def cvs_branch(path)
|
18
|
+
"deploy-me"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class MockChannel
|
24
|
+
attr_reader :sent_data
|
25
|
+
|
26
|
+
def send_data(data)
|
27
|
+
@sent_data ||= []
|
28
|
+
@sent_data << data
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](name)
|
32
|
+
"value"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class MockActor
|
37
|
+
attr_reader :command
|
38
|
+
attr_reader :channels
|
39
|
+
attr_accessor :story
|
40
|
+
|
41
|
+
def initialize(config)
|
42
|
+
@config = config
|
43
|
+
end
|
44
|
+
|
45
|
+
def run(command)
|
46
|
+
@command = command
|
47
|
+
@channels ||= []
|
48
|
+
@channels << MockChannel.new
|
49
|
+
story.each { |stream, line| yield @channels.last, stream, line }
|
50
|
+
end
|
51
|
+
|
52
|
+
def release_path
|
53
|
+
(@config[:now] || Time.now.utc).strftime("%Y%m%d%H%M%S")
|
54
|
+
end
|
55
|
+
|
56
|
+
def method_missing(sym, *args)
|
57
|
+
@config.send(sym, *args)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def setup
|
62
|
+
@config = MockConfiguration.new
|
63
|
+
@config[:repository] = ":ext:joetester@rubyforge.org:/hello/world"
|
64
|
+
@config[:cvs] = "/path/to/cvs"
|
65
|
+
@config[:password] = "chocolatebrownies"
|
66
|
+
@config[:now] = Time.utc(2005,8,24,12,0,0)
|
67
|
+
@scm = CvsTest.new(@config)
|
68
|
+
@actor = MockActor.new(@config)
|
69
|
+
@log_msg = <<MSG.strip
|
70
|
+
RCS file: /var/cvs/copland/copland/LICENSE,v
|
71
|
+
Working file: LICENSE
|
72
|
+
head: 1.1
|
73
|
+
branch:
|
74
|
+
locks: strict
|
75
|
+
access list:
|
76
|
+
keyword substitution: kv
|
77
|
+
total revisions: 1; selected revisions: 1
|
78
|
+
description:
|
79
|
+
----------------------------
|
80
|
+
revision 1.1
|
81
|
+
date: 2004/08/29 04:23:36; author: minam; state: Exp;
|
82
|
+
New implementation.
|
83
|
+
=============================================================================
|
84
|
+
|
85
|
+
RCS file: /var/cvs/copland/copland/Rakefile,v
|
86
|
+
Working file: Rakefile
|
87
|
+
head: 1.7
|
88
|
+
branch:
|
89
|
+
locks: strict
|
90
|
+
access list:
|
91
|
+
keyword substitution: kv
|
92
|
+
total revisions: 7; selected revisions: 1
|
93
|
+
description:
|
94
|
+
----------------------------
|
95
|
+
revision 1.7
|
96
|
+
date: 2004/09/15 16:35:01; author: minam; state: Exp; lines: +2 -1
|
97
|
+
Rakefile now publishes package documentation from doc/packages instead of
|
98
|
+
doc/packrat. Updated "latest updates" in manual.
|
99
|
+
=============================================================================
|
100
|
+
|
101
|
+
RCS file: /var/cvs/copland/copland/TODO,v
|
102
|
+
Working file: TODO
|
103
|
+
head: 1.18
|
104
|
+
branch:
|
105
|
+
locks: strict
|
106
|
+
access list:
|
107
|
+
keyword substitution: kv
|
108
|
+
total revisions: 18; selected revisions: 1
|
109
|
+
description:
|
110
|
+
----------------------------
|
111
|
+
revision 1.18
|
112
|
+
date: 2004/10/12 02:21:02; author: minam; state: Exp; lines: +4 -1
|
113
|
+
Added RubyConf 2004 presentation.
|
114
|
+
=============================================================================
|
115
|
+
|
116
|
+
RCS file: /var/cvs/copland/copland/Attic/build-gemspec.rb,v
|
117
|
+
Working file: build-gemspec.rb
|
118
|
+
head: 1.5
|
119
|
+
branch:
|
120
|
+
locks: strict
|
121
|
+
access list:
|
122
|
+
keyword substitution: kv
|
123
|
+
total revisions: 5; selected revisions: 1
|
124
|
+
description:
|
125
|
+
----------------------------
|
126
|
+
revision 1.5
|
127
|
+
date: 2004/08/29 04:10:17; author: minam; state: dead; lines: +0 -0
|
128
|
+
Here we go -- point of no return. Deleting existing implementation to make
|
129
|
+
way for new implementation.
|
130
|
+
=============================================================================
|
131
|
+
|
132
|
+
RCS file: /var/cvs/copland/copland/copland.gemspec,v
|
133
|
+
Working file: copland.gemspec
|
134
|
+
head: 1.12
|
135
|
+
branch:
|
136
|
+
locks: strict
|
137
|
+
access list:
|
138
|
+
keyword substitution: kv
|
139
|
+
total revisions: 13; selected revisions: 1
|
140
|
+
description:
|
141
|
+
----------------------------
|
142
|
+
revision 1.12
|
143
|
+
date: 2004/09/11 21:45:58; author: minam; state: Exp; lines: +4 -4
|
144
|
+
Minor change in how version is communicated to gemspec.
|
145
|
+
=============================================================================
|
146
|
+
MSG
|
147
|
+
@scm.story = [ @log_msg ]
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_latest_revision
|
151
|
+
@config[:local] = "/hello/world"
|
152
|
+
@scm.story = [ @log_msg ]
|
153
|
+
assert_equal "2004-10-12 02:21:02", @scm.latest_revision
|
154
|
+
assert_equal "/hello/world", @scm.last_path
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_latest_with_default_local
|
158
|
+
@config[:local] = nil
|
159
|
+
@scm.story = [ @log_msg ]
|
160
|
+
assert_equal "2004-10-12 02:21:02", @scm.latest_revision
|
161
|
+
assert_equal ".", @scm.last_path
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_checkout
|
165
|
+
@actor.story = []
|
166
|
+
assert_nothing_raised { @scm.checkout(@actor) }
|
167
|
+
assert_nil @actor.channels.last.sent_data
|
168
|
+
assert_match %r{/path/to/cvs}, @actor.command
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_checkout_needs_ssh_password
|
172
|
+
@actor.story = [[:out, "joetester@rubyforge.org's password: "]]
|
173
|
+
assert_nothing_raised { @scm.checkout(@actor) }
|
174
|
+
assert_equal ["chocolatebrownies\n"], @actor.channels.last.sent_data
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_current_branch
|
178
|
+
assert_equal "deploy-me", @scm.current_branch
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_default_current_branch
|
182
|
+
@config[:branch] = "default-branch"
|
183
|
+
@scm = CvsTest.new(@config)
|
184
|
+
assert_equal "default-branch", @scm.current_branch
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + "/../../lib"
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + "/../utils"
|
4
|
+
require 'test/unit'
|
5
|
+
require 'capistrano/scm/subversion'
|
6
|
+
|
7
|
+
class ScmSubversionTest < Test::Unit::TestCase
|
8
|
+
class SubversionTest < Capistrano::SCM::Subversion
|
9
|
+
attr_accessor :story
|
10
|
+
attr_reader :last_path
|
11
|
+
|
12
|
+
def svn_log(path)
|
13
|
+
@last_path = path
|
14
|
+
story.shift
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class MockChannel
|
19
|
+
attr_reader :sent_data
|
20
|
+
|
21
|
+
def send_data(data)
|
22
|
+
@sent_data ||= []
|
23
|
+
@sent_data << data
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](name)
|
27
|
+
"value"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class MockActor
|
32
|
+
attr_reader :command
|
33
|
+
attr_reader :channels
|
34
|
+
attr_accessor :story
|
35
|
+
|
36
|
+
def initialize(config)
|
37
|
+
@config = config
|
38
|
+
end
|
39
|
+
|
40
|
+
def run(command)
|
41
|
+
@command = command
|
42
|
+
@channels ||= []
|
43
|
+
@channels << MockChannel.new
|
44
|
+
story.each { |stream, line| yield @channels.last, stream, line }
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_missing(sym, *args)
|
48
|
+
@config.send(sym, *args)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup
|
53
|
+
@config = MockConfiguration.new
|
54
|
+
@config[:current_path] = "/mwa/ha/ha/current"
|
55
|
+
@config[:repository] = "/hello/world"
|
56
|
+
@config[:svn] = "/path/to/svn"
|
57
|
+
@config[:password] = "chocolatebrownies"
|
58
|
+
@scm = SubversionTest.new(@config)
|
59
|
+
@actor = MockActor.new(@config)
|
60
|
+
@log_msg = <<MSG.strip
|
61
|
+
------------------------------------------------------------------------
|
62
|
+
r1967 | minam | 2005-08-03 06:59:03 -0600 (Wed, 03 Aug 2005) | 2 lines
|
63
|
+
|
64
|
+
Initial commit of the new capistrano utility
|
65
|
+
|
66
|
+
------------------------------------------------------------------------
|
67
|
+
MSG
|
68
|
+
@scm.story = [ @log_msg ]
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_latest_revision
|
72
|
+
@scm.story = [ @log_msg ]
|
73
|
+
assert_equal "1967", @scm.latest_revision
|
74
|
+
assert_equal "/hello/world", @scm.last_path
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_latest_revision_searching_upwards
|
78
|
+
@scm.story = [ "-----------------------------\n", @log_msg ]
|
79
|
+
assert_equal "1967", @scm.latest_revision
|
80
|
+
assert_equal "/hello", @scm.last_path
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_checkout
|
84
|
+
@actor.story = []
|
85
|
+
assert_nothing_raised { @scm.checkout(@actor) }
|
86
|
+
assert_nil @actor.channels.last.sent_data
|
87
|
+
assert_match %r{/path/to/svn co\s+-q}, @actor.command
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_checkout_via_export
|
91
|
+
@actor.story = []
|
92
|
+
@config[:checkout] = "export"
|
93
|
+
assert_nothing_raised { @scm.checkout(@actor) }
|
94
|
+
assert_nil @actor.channels.last.sent_data
|
95
|
+
assert_match %r{/path/to/svn export\s+-q}, @actor.command
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_update
|
99
|
+
@actor.story = []
|
100
|
+
assert_nothing_raised { @scm.update(@actor) }
|
101
|
+
assert_nil @actor.channels.last.sent_data
|
102
|
+
assert_match %r{/path/to/svn up}, @actor.command
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_checkout_needs_ssh_password
|
106
|
+
@actor.story = [[:out, "Password: "]]
|
107
|
+
assert_nothing_raised { @scm.checkout(@actor) }
|
108
|
+
assert_equal ["chocolatebrownies\n"], @actor.channels.last.sent_data
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_checkout_needs_http_password
|
112
|
+
@actor.story = [[:out, "Password for (something): "]]
|
113
|
+
assert_nothing_raised { @scm.checkout(@actor) }
|
114
|
+
assert_equal ["chocolatebrownies\n"], @actor.channels.last.sent_data
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_checkout_needs_alternative_ssh_password
|
118
|
+
@actor.story = [[:out, "someone's password: "]]
|
119
|
+
assert_nothing_raised { @scm.checkout(@actor) }
|
120
|
+
assert_equal ["chocolatebrownies\n"], @actor.channels.last.sent_data
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_svn_password
|
124
|
+
@config[:svn_password] = "butterscotchcandies"
|
125
|
+
@actor.story = [[:out, "Password: "]]
|
126
|
+
assert_nothing_raised { @scm.checkout(@actor) }
|
127
|
+
assert_equal ["butterscotchcandies\n"], @actor.channels.last.sent_data
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_svn_username
|
131
|
+
@actor.story = []
|
132
|
+
@config[:svn_username] = "turtledove"
|
133
|
+
assert_nothing_raised { @scm.checkout(@actor) }
|
134
|
+
assert_nil @actor.channels.last.sent_data
|
135
|
+
assert_match %r{/path/to/svn co --username turtledove}, @actor.command
|
136
|
+
end
|
137
|
+
end
|
data/test/ssh_test.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + "/utils"
|
4
|
+
require 'test/unit'
|
5
|
+
require 'capistrano/ssh'
|
6
|
+
|
7
|
+
class SSHTest < Test::Unit::TestCase
|
8
|
+
class MockSSH
|
9
|
+
AuthenticationFailed = Net::SSH::AuthenticationFailed
|
10
|
+
|
11
|
+
class <<self
|
12
|
+
attr_accessor :story
|
13
|
+
attr_accessor :invocations
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.start(server, opts, &block)
|
17
|
+
@invocations << [server, opts, block]
|
18
|
+
err = story.shift
|
19
|
+
raise err if err
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup
|
24
|
+
@config = MockConfiguration.new
|
25
|
+
@config[:user] = 'demo'
|
26
|
+
@config[:password] = 'c0c0nutfr0st1ng'
|
27
|
+
MockSSH.story = []
|
28
|
+
MockSSH.invocations = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_publickey_auth_succeeds_default_port_no_block
|
32
|
+
Net.const_during(:SSH, MockSSH) do
|
33
|
+
Capistrano::SSH.connect('demo.server.i', @config)
|
34
|
+
end
|
35
|
+
|
36
|
+
assert_equal 1, MockSSH.invocations.length
|
37
|
+
assert_equal 'demo.server.i', MockSSH.invocations.first[0]
|
38
|
+
assert_equal 22, MockSSH.invocations.first[1][:port]
|
39
|
+
assert_equal 'demo', MockSSH.invocations.first[1][:username]
|
40
|
+
assert_nil MockSSH.invocations.first[1][:password]
|
41
|
+
assert_equal %w(publickey hostbased),
|
42
|
+
MockSSH.invocations.first[1][:auth_methods]
|
43
|
+
assert_nil MockSSH.invocations.first[2]
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_publickey_auth_succeeds_explicit_port_no_block
|
47
|
+
Net.const_during(:SSH, MockSSH) do
|
48
|
+
Capistrano::SSH.connect('demo.server.i', @config, 23)
|
49
|
+
end
|
50
|
+
|
51
|
+
assert_equal 1, MockSSH.invocations.length
|
52
|
+
assert_equal 23, MockSSH.invocations.first[1][:port]
|
53
|
+
assert_nil MockSSH.invocations.first[2]
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_publickey_auth_succeeds_with_block
|
57
|
+
Net.const_during(:SSH, MockSSH) do
|
58
|
+
Capistrano::SSH.connect('demo.server.i', @config) do |session|
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
assert_equal 1, MockSSH.invocations.length
|
63
|
+
assert_instance_of Proc, MockSSH.invocations.first[2]
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_publickey_auth_fails
|
67
|
+
MockSSH.story << Net::SSH::AuthenticationFailed
|
68
|
+
|
69
|
+
Net.const_during(:SSH, MockSSH) do
|
70
|
+
Capistrano::SSH.connect('demo.server.i', @config)
|
71
|
+
end
|
72
|
+
|
73
|
+
assert_equal 2, MockSSH.invocations.length
|
74
|
+
|
75
|
+
assert_nil MockSSH.invocations.first[1][:password]
|
76
|
+
assert_equal %w(publickey hostbased),
|
77
|
+
MockSSH.invocations.first[1][:auth_methods]
|
78
|
+
|
79
|
+
assert_equal 'c0c0nutfr0st1ng', MockSSH.invocations.last[1][:password]
|
80
|
+
assert_equal %w(password keyboard-interactive),
|
81
|
+
MockSSH.invocations.last[1][:auth_methods]
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_password_auth_fails
|
85
|
+
MockSSH.story << Net::SSH::AuthenticationFailed
|
86
|
+
MockSSH.story << Net::SSH::AuthenticationFailed
|
87
|
+
|
88
|
+
Net.const_during(:SSH, MockSSH) do
|
89
|
+
assert_raises(Net::SSH::AuthenticationFailed) do
|
90
|
+
Capistrano::SSH.connect('demo.server.i', @config)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
assert_equal 2, MockSSH.invocations.length
|
95
|
+
|
96
|
+
assert_nil MockSSH.invocations.first[1][:password]
|
97
|
+
assert_equal %w(publickey hostbased),
|
98
|
+
MockSSH.invocations.first[1][:auth_methods]
|
99
|
+
|
100
|
+
assert_equal 'c0c0nutfr0st1ng', MockSSH.invocations.last[1][:password]
|
101
|
+
assert_equal %w(password keyboard-interactive),
|
102
|
+
MockSSH.invocations.last[1][:auth_methods]
|
103
|
+
end
|
104
|
+
end
|
data/test/utils.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
class Module
|
2
|
+
def const_during(constant, value)
|
3
|
+
if const_defined?(constant)
|
4
|
+
overridden = true
|
5
|
+
saved = const_get(constant)
|
6
|
+
remove_const(constant)
|
7
|
+
end
|
8
|
+
|
9
|
+
const_set(constant, value)
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
if overridden
|
13
|
+
remove_const(constant)
|
14
|
+
const_set(constant, saved)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class MockLogger
|
20
|
+
def info(msg,pfx=nil) end
|
21
|
+
def debug(msg,pfx=nil) end
|
22
|
+
end
|
23
|
+
|
24
|
+
class MockConfiguration < Hash
|
25
|
+
def initialize(*args)
|
26
|
+
super
|
27
|
+
self[:release_path] = "/path/to/releases/version"
|
28
|
+
self[:ssh_options] = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def logger
|
32
|
+
@logger ||= MockLogger.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def set(variable, value=nil, &block)
|
36
|
+
self[variable] = value
|
37
|
+
end
|
38
|
+
|
39
|
+
def respond_to?(sym)
|
40
|
+
self.has_key?(sym)
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(sym, *args)
|
44
|
+
if args.length == 0
|
45
|
+
self[sym]
|
46
|
+
else
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|