capistrano 1.1.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/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
|