capistrano 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -2
  4. data/CHANGELOG.md +36 -3
  5. data/CONTRIBUTING.md +91 -0
  6. data/Gemfile +9 -0
  7. data/README.md +9 -11
  8. data/capistrano.gemspec +5 -8
  9. data/features/configuration.feature +15 -0
  10. data/features/deploy.feature +1 -0
  11. data/features/deploy_failure.feature +17 -0
  12. data/features/step_definitions/assertions.rb +19 -0
  13. data/features/step_definitions/cap_commands.rb +5 -1
  14. data/features/step_definitions/setup.rb +13 -0
  15. data/features/support/remote_command_helpers.rb +4 -0
  16. data/lib/capistrano/application.rb +41 -3
  17. data/lib/capistrano/configuration.rb +8 -0
  18. data/lib/capistrano/configuration/server.rb +28 -5
  19. data/lib/capistrano/configuration/servers.rb +3 -6
  20. data/lib/capistrano/configuration/servers/host_filter.rb +82 -0
  21. data/lib/capistrano/dsl.rb +16 -1
  22. data/lib/capistrano/dsl/env.rb +11 -1
  23. data/lib/capistrano/dsl/paths.rb +8 -0
  24. data/lib/capistrano/dsl/stages.rb +8 -1
  25. data/lib/capistrano/dsl/task_enhancements.rb +13 -2
  26. data/lib/capistrano/git.rb +35 -0
  27. data/lib/capistrano/hg.rb +32 -0
  28. data/lib/capistrano/i18n.rb +6 -2
  29. data/lib/capistrano/scm.rb +116 -0
  30. data/lib/capistrano/setup.rb +4 -3
  31. data/lib/capistrano/tasks/console.rake +9 -1
  32. data/lib/capistrano/tasks/deploy.rake +17 -15
  33. data/lib/capistrano/tasks/framework.rake +1 -0
  34. data/lib/capistrano/tasks/git.rake +16 -10
  35. data/lib/capistrano/tasks/hg.rake +13 -9
  36. data/lib/capistrano/templates/Capfile +1 -2
  37. data/lib/capistrano/templates/deploy.rb.erb +20 -2
  38. data/lib/capistrano/templates/stage.rb.erb +1 -4
  39. data/lib/capistrano/version.rb +1 -1
  40. data/spec/integration/dsl_spec.rb +147 -2
  41. data/spec/lib/capistrano/application_spec.rb +2 -5
  42. data/spec/lib/capistrano/configuration/server_spec.rb +40 -4
  43. data/spec/lib/capistrano/configuration/servers/host_filter_spec.rb +84 -0
  44. data/spec/lib/capistrano/configuration/servers_spec.rb +35 -0
  45. data/spec/lib/capistrano/configuration_spec.rb +8 -0
  46. data/spec/lib/capistrano/dsl_spec.rb +0 -11
  47. data/spec/lib/capistrano/git_spec.rb +70 -0
  48. data/spec/lib/capistrano/hg_spec.rb +70 -0
  49. data/spec/lib/capistrano/scm_spec.rb +104 -0
  50. data/spec/support/tasks/fail.cap +7 -0
  51. data/spec/support/tasks/failed.cap +5 -0
  52. data/spec/support/test_app.rb +33 -3
  53. metadata +29 -52
  54. data/spec/lib/capistrano/dsl/env_spec.rb +0 -10
@@ -7,11 +7,10 @@ describe Capistrano::Application do
7
7
  it "provides a --format option which enables the choice of output formatting"
8
8
 
9
9
  it "identifies itself as cap and not rake" do
10
- pending "Waiting for: https://github.com/jimweirich/rake/pull/204"
11
10
  out, _ = capture_io do
12
11
  flags '--help', '-h'
13
12
  end
14
- out.should match(/\bcap [ -f capfile ]\b/)
13
+ out.lines.first.should match(/cap \[-f rakefile\]/)
15
14
  end
16
15
 
17
16
  it "overrides the rake method, but still prints the rake version" do
@@ -37,9 +36,7 @@ describe Capistrano::Application do
37
36
  def subject.exit(*args)
38
37
  throw(:system_exit, :exit)
39
38
  end
40
- subject.instance_eval do
41
- handle_options
42
- end
39
+ subject.run
43
40
  subject.options
44
41
  end
45
42
 
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  module Capistrano
4
4
  class Configuration
5
5
  describe Server do
6
- let(:server) { Server.new('hostname') }
6
+ let(:server) { Server.new('hostname:1234') }
7
7
 
8
8
  describe 'adding a role' do
9
9
  subject { server.add_role(:test) }
@@ -33,18 +33,23 @@ module Capistrano
33
33
  end
34
34
 
35
35
  describe 'comparing identity' do
36
- subject { server.matches? hostname }
36
+ subject { server.matches? Server[hostname] }
37
37
 
38
38
  context 'with the same hostname' do
39
- let(:hostname) { 'hostname' }
39
+ let(:hostname) { 'hostname:1234' }
40
40
  it { should be_true }
41
41
  end
42
42
 
43
43
  context 'with the same hostname and a user' do
44
- let(:hostname) { 'user@hostname' }
44
+ let(:hostname) { 'user@hostname:1234' }
45
45
  it { should be_true }
46
46
  end
47
47
 
48
+ context 'with the same hostname but different port' do
49
+ let(:hostname) { 'hostname:5678' }
50
+ it { should be_false }
51
+ end
52
+
48
53
  context 'with a different hostname' do
49
54
  let(:hostname) { 'otherserver' }
50
55
  it { should be_false }
@@ -159,6 +164,11 @@ module Capistrano
159
164
  let(:options) { { select: :active }}
160
165
  it { should be_true }
161
166
  end
167
+
168
+ context 'with :exclude' do
169
+ let(:options) { { exclude: :active }}
170
+ it { should be_false }
171
+ end
162
172
  end
163
173
 
164
174
  context 'value does not match server properly' do
@@ -171,6 +181,11 @@ module Capistrano
171
181
  let(:options) { { select: :inactive }}
172
182
  it { should be_false }
173
183
  end
184
+
185
+ context 'with :exclude' do
186
+ let(:options) { { exclude: :inactive }}
187
+ it { should be_true }
188
+ end
174
189
  end
175
190
  end
176
191
 
@@ -186,6 +201,12 @@ module Capistrano
186
201
  let(:options) { { select: ->(s) { s.properties.active } } }
187
202
  it { should be_true }
188
203
  end
204
+
205
+ context 'with :exclude' do
206
+ let(:options) { { exclude: ->(s) { s.properties.active } } }
207
+ it { should be_false }
208
+ end
209
+
189
210
  end
190
211
 
191
212
  context 'value does not match server properly' do
@@ -198,6 +219,12 @@ module Capistrano
198
219
  let(:options) { { select: ->(s) { s.properties.inactive } } }
199
220
  it { should be_false }
200
221
  end
222
+
223
+ context 'with :exclude' do
224
+ let(:options) { { exclude: ->(s) { s.properties.inactive } } }
225
+ it { should be_true }
226
+ end
227
+
201
228
  end
202
229
  end
203
230
 
@@ -244,6 +271,15 @@ module Capistrano
244
271
 
245
272
  end
246
273
 
274
+ describe ".[]" do
275
+ it 'creates a server if its argument is not already a server' do
276
+ expect(Server['hostname:1234']).to be_a Server
277
+ end
278
+
279
+ it 'returns its argument if it is already a server' do
280
+ expect(Server[server]).to be server
281
+ end
282
+ end
247
283
  end
248
284
  end
249
285
  end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ module Capistrano
4
+ class Configuration
5
+ class Servers
6
+
7
+ describe HostFilter do
8
+ let(:host_filter) { HostFilter.new(available) }
9
+ let(:available) { [ Server.new('server1'), Server.new('server2'), Server.new('server3') ] }
10
+
11
+ describe '#new' do
12
+ it 'takes one array of hostnames' do
13
+ expect(host_filter)
14
+ end
15
+ end
16
+
17
+ describe '.for' do
18
+
19
+ subject { HostFilter.for(available) }
20
+
21
+ context 'without env vars' do
22
+ it 'returns all available hosts' do
23
+ expect(subject).to eq available
24
+ end
25
+ end
26
+
27
+ context 'with ENV vars' do
28
+ before do
29
+ ENV.stubs(:[]).with('HOSTS').returns('server1,server2')
30
+ end
31
+
32
+ it 'returns all required hosts defined in HOSTS' do
33
+ expect(subject).to eq [Server.new('server1'), Server.new('server2')]
34
+ end
35
+ end
36
+
37
+ context 'with configuration filters' do
38
+ before do
39
+ Configuration.env.set(:filter, hosts: %w{server1 server2})
40
+ end
41
+
42
+ it 'returns all required hosts defined in the filter' do
43
+ expect(subject).to eq [Server.new('server1'), Server.new('server2')]
44
+ end
45
+
46
+ after do
47
+ Configuration.env.delete(:filter)
48
+ end
49
+ end
50
+
51
+ context 'with a single configuration filter' do
52
+ before do
53
+ Configuration.env.set(:filter, hosts: 'server3')
54
+ end
55
+
56
+ it 'returns all required hosts defined in the filter' do
57
+ expect(subject).to eq [Server.new('server3')]
58
+ end
59
+
60
+ after do
61
+ Configuration.env.delete(:filter)
62
+ end
63
+ end
64
+
65
+ context 'with configuration filters and ENV vars' do
66
+ before do
67
+ Configuration.env.set(:filter, hosts: 'server1')
68
+ ENV.stubs(:[]).with('HOSTS').returns('server3')
69
+ end
70
+
71
+ it 'returns all required hosts defined in the filter' do
72
+ expect(subject).to eq [Server.new('server1'), Server.new('server3')]
73
+ end
74
+
75
+ after do
76
+ Configuration.env.delete(:filter)
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -106,6 +106,11 @@ module Capistrano
106
106
  expect(servers.roles_for([:all]).first.properties.test).to eq :value
107
107
  end
108
108
 
109
+ it 'can accept multiple servers with the same hostname but different ports' do
110
+ servers.add_host('2', roles: [:app, 'web'], test: :value, port: 12)
111
+ servers.add_host('2', roles: [:app, 'web'], test: :value, port: 34)
112
+ expect(servers.count { |server| server.hostname == '2' }).to eq 2
113
+ end
109
114
  end
110
115
 
111
116
  describe 'selecting roles' do
@@ -141,10 +146,40 @@ module Capistrano
141
146
 
142
147
  end
143
148
 
149
+ describe 'excluding by property' do
150
+
151
+ before do
152
+ servers.add_host('1', roles: :app, active: true)
153
+ servers.add_host('2', roles: :app, active: true, no_release: true)
154
+ end
155
+
156
+ it 'is empty if the filter would remove all matching hosts' do
157
+ hosts = servers.roles_for([:app, exclude: :active])
158
+ expect(hosts.map(&:hostname)).to be_empty
159
+ end
160
+
161
+ it 'returns the servers without the attributes specified' do
162
+ hosts = servers.roles_for([:app, exclude: :no_release])
163
+ expect(hosts.map(&:hostname)).to eq %w{1}
164
+ end
165
+
166
+ it 'can exclude hosts by properties on the host using a regular proc' do
167
+ hosts = servers.roles_for([:app, exclude: ->(h) { h.properties.no_release }])
168
+ expect(hosts.map(&:hostname)).to eq %w{1}
169
+ end
170
+
171
+ it 'is empty if the regular proc filter would remove all matching hosts' do
172
+ hosts = servers.roles_for([:app, exclude: ->(h) { h.properties.active }])
173
+ expect(hosts.map(&:hostname)).to be_empty
174
+ end
175
+
176
+ end
177
+
144
178
  describe 'filtering roles' do
145
179
 
146
180
  before do
147
181
  ENV.stubs(:[]).with('ROLES').returns('web,db')
182
+ ENV.stubs(:[]).with('HOSTS').returns(nil)
148
183
  servers.add_host('1', roles: :app, active: true)
149
184
  servers.add_host('2', roles: :app)
150
185
  servers.add_host('3', roles: :web)
@@ -12,6 +12,14 @@ module Capistrano
12
12
  end
13
13
  end
14
14
 
15
+ describe '.reset!' do
16
+ it 'blows away the existing `env` and creates a new one' do
17
+ old_env = Configuration.env
18
+ Configuration.reset!
19
+ expect(Configuration.env).not_to be old_env
20
+ end
21
+ end
22
+
15
23
  describe 'roles' do
16
24
  context 'adding a role' do
17
25
  subject { config.role(:app, %w{server1 server2}) }
@@ -20,17 +20,6 @@ module Capistrano
20
20
  end
21
21
  end
22
22
 
23
- describe '#stages' do
24
- before do
25
- Dir.expects(:[]).with('config/deploy/*.rb').
26
- returns(['config/deploy/staging.rb', 'config/deploy/production.rb'])
27
- end
28
-
29
- it 'returns a list of defined stages' do
30
- expect(dsl.stages).to eq %w{staging production}
31
- end
32
- end
33
-
34
23
  describe '#stage_set?' do
35
24
  subject { dsl.stage_set? }
36
25
 
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ require 'capistrano/git'
4
+
5
+ module Capistrano
6
+ describe Git do
7
+ let(:context) { Class.new.new }
8
+ subject { Capistrano::Git.new(context, Capistrano::Git::DefaultStrategy) }
9
+
10
+ describe "#git" do
11
+ it "should call execute git in the context, with arguments" do
12
+ context.expects(:execute).with(:git, :init)
13
+ subject.git(:init)
14
+ end
15
+ end
16
+ end
17
+
18
+ describe Git::DefaultStrategy do
19
+ let(:context) { Class.new.new }
20
+ subject { Capistrano::Git.new(context, Capistrano::Git::DefaultStrategy) }
21
+
22
+ describe "#test" do
23
+ it "should call test for repo HEAD" do
24
+ context.expects(:repo_path).returns("/path/to/repo")
25
+ context.expects(:test).with " [ -f /path/to/repo/HEAD ] "
26
+
27
+ subject.test
28
+ end
29
+ end
30
+
31
+ describe "#check" do
32
+ it "should test the repo url" do
33
+ context.expects(:repo_url).returns(:url)
34
+ context.expects(:test).with(:git, :'ls-remote', :url).returns(true)
35
+
36
+ subject.check
37
+ end
38
+ end
39
+
40
+ describe "#clone" do
41
+ it "should run git clone" do
42
+ context.expects(:repo_url).returns(:url)
43
+ context.expects(:repo_path).returns(:path)
44
+
45
+ context.expects(:execute).with(:git, :clone, '--mirror', :url, :path)
46
+
47
+ subject.clone
48
+ end
49
+ end
50
+
51
+ describe "#update" do
52
+ it "should run git update" do
53
+ context.expects(:execute).with(:git, :remote, :update)
54
+
55
+ subject.update
56
+ end
57
+ end
58
+
59
+ describe "#release" do
60
+ it "should run git archive" do
61
+ context.expects(:fetch).returns(:branch)
62
+ context.expects(:release_path).returns(:path)
63
+
64
+ context.expects(:execute).with(:git, :archive, :branch, '| tar -x -C', :path)
65
+
66
+ subject.release
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ require 'capistrano/hg'
4
+
5
+ module Capistrano
6
+ describe Hg do
7
+ let(:context) { Class.new.new }
8
+ subject { Capistrano::Hg.new(context, Capistrano::Hg::DefaultStrategy) }
9
+
10
+ describe "#hg" do
11
+ it "should call execute hg in the context, with arguments" do
12
+ context.expects(:execute).with(:hg, :init)
13
+ subject.hg(:init)
14
+ end
15
+ end
16
+ end
17
+
18
+ describe Hg::DefaultStrategy do
19
+ let(:context) { Class.new.new }
20
+ subject { Capistrano::Hg.new(context, Capistrano::Hg::DefaultStrategy) }
21
+
22
+ describe "#test" do
23
+ it "should call test for repo HEAD" do
24
+ context.expects(:repo_path).returns("/path/to/repo")
25
+ context.expects(:test).with " [ -d /path/to/repo/.hg ] "
26
+
27
+ subject.test
28
+ end
29
+ end
30
+
31
+ describe "#check" do
32
+ it "should test the repo url" do
33
+ context.expects(:repo_url).returns(:url)
34
+ context.expects(:execute).with(:hg, "id", :url)
35
+
36
+ subject.check
37
+ end
38
+ end
39
+
40
+ describe "#clone" do
41
+ it "should run hg clone" do
42
+ context.expects(:repo_url).returns(:url)
43
+ context.expects(:repo_path).returns(:path)
44
+
45
+ context.expects(:execute).with(:hg, "clone", '--noupdate', :url, :path)
46
+
47
+ subject.clone
48
+ end
49
+ end
50
+
51
+ describe "#update" do
52
+ it "should run hg update" do
53
+ context.expects(:execute).with(:hg, "pull")
54
+
55
+ subject.update
56
+ end
57
+ end
58
+
59
+ describe "#release" do
60
+ it "should run hg archive" do
61
+ context.expects(:fetch).returns(:branch)
62
+ context.expects(:release_path).returns(:path)
63
+
64
+ context.expects(:execute).with(:hg, "archive", :path, "--rev", :branch)
65
+
66
+ subject.release
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ require 'capistrano/scm'
4
+
5
+ module RaiseNotImplementedMacro
6
+ def raise_not_implemented_on(method)
7
+ it "should raise NotImplemented on #{method}" do
8
+ expect {
9
+ subject.send(method)
10
+ }.to raise_error(NotImplementedError)
11
+ end
12
+ end
13
+ end
14
+
15
+ RSpec.configure do
16
+ include RaiseNotImplementedMacro
17
+ end
18
+
19
+ module DummyStrategy
20
+ def test
21
+ test!("you dummy!")
22
+ end
23
+ end
24
+
25
+ module BlindStrategy; end
26
+
27
+ module Capistrano
28
+ describe SCM do
29
+ let(:context) { Class.new.new }
30
+
31
+ describe "#initialize" do
32
+ subject { Capistrano::SCM.new(context, DummyStrategy) }
33
+
34
+ it "should load the provided strategy" do
35
+ context.expects(:test).with("you dummy!")
36
+ subject.test
37
+ end
38
+ end
39
+
40
+ describe "Convenience methods" do
41
+ subject { Capistrano::SCM.new(context, BlindStrategy) }
42
+
43
+ describe "#test!" do
44
+ it "should return call test on the context" do
45
+ context.expects(:test).with(:x)
46
+ subject.test!(:x)
47
+ end
48
+ end
49
+
50
+ describe "#repo_url" do
51
+ it "should return the repo url according to the context" do
52
+ context.expects(:repo_url).returns(:url)
53
+ subject.repo_url.should == :url
54
+ end
55
+ end
56
+
57
+ describe "#repo_path" do
58
+ it "should return the repo path according to the context" do
59
+ context.expects(:repo_path).returns(:path)
60
+ subject.repo_path.should == :path
61
+ end
62
+ end
63
+
64
+ describe "#release_path" do
65
+ it "should return the release path according to the context" do
66
+ context.expects(:release_path).returns('/path/to/nowhere')
67
+ subject.release_path.should == '/path/to/nowhere'
68
+ end
69
+ end
70
+
71
+ describe "#fetch" do
72
+ it "should call fetch on the context" do
73
+ context.expects(:fetch)
74
+ subject.fetch(:branch)
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "With a 'blind' strategy" do
80
+ subject { Capistrano::SCM.new(context, BlindStrategy) }
81
+
82
+ describe "#test" do
83
+ raise_not_implemented_on(:test)
84
+ end
85
+
86
+ describe "#check" do
87
+ raise_not_implemented_on(:check)
88
+ end
89
+
90
+ describe "#clone" do
91
+ raise_not_implemented_on(:clone)
92
+ end
93
+
94
+ describe "#update" do
95
+ raise_not_implemented_on(:update)
96
+ end
97
+
98
+ describe "#release" do
99
+ raise_not_implemented_on(:release)
100
+ end
101
+ end
102
+ end
103
+ end
104
+