capistrano 3.0.1 → 3.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.
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
+