capistrano 3.5.0 → 3.6.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -1
  3. data/CHANGELOG.md +55 -10
  4. data/README.md +3 -3
  5. data/RELEASING.md +1 -0
  6. data/UPGRADING-3.7.md +97 -0
  7. data/capistrano.gemspec +1 -1
  8. data/features/deploy.feature +1 -0
  9. data/features/stage_failure.feature +9 -0
  10. data/features/step_definitions/assertions.rb +5 -0
  11. data/features/step_definitions/setup.rb +4 -0
  12. data/lib/capistrano/application.rb +5 -10
  13. data/lib/capistrano/configuration.rb +8 -7
  14. data/lib/capistrano/configuration/filter.rb +4 -5
  15. data/lib/capistrano/configuration/host_filter.rb +1 -1
  16. data/lib/capistrano/configuration/plugin_installer.rb +1 -1
  17. data/lib/capistrano/configuration/server.rb +8 -2
  18. data/lib/capistrano/configuration/validated_variables.rb +75 -0
  19. data/lib/capistrano/configuration/variables.rb +7 -23
  20. data/lib/capistrano/defaults.rb +10 -0
  21. data/lib/capistrano/doctor.rb +1 -0
  22. data/lib/capistrano/doctor/gems_doctor.rb +1 -1
  23. data/lib/capistrano/doctor/servers_doctor.rb +105 -0
  24. data/lib/capistrano/doctor/variables_doctor.rb +6 -7
  25. data/lib/capistrano/dsl.rb +28 -4
  26. data/lib/capistrano/dsl/paths.rb +1 -1
  27. data/lib/capistrano/dsl/stages.rb +15 -1
  28. data/lib/capistrano/dsl/task_enhancements.rb +6 -1
  29. data/lib/capistrano/i18n.rb +2 -0
  30. data/lib/capistrano/proc_helpers.rb +13 -0
  31. data/lib/capistrano/tasks/deploy.rake +9 -1
  32. data/lib/capistrano/tasks/doctor.rake +6 -1
  33. data/lib/capistrano/tasks/git.rake +11 -4
  34. data/lib/capistrano/templates/deploy.rb.erb +2 -15
  35. data/lib/capistrano/version.rb +1 -1
  36. data/spec/lib/capistrano/configuration/empty_filter_spec.rb +1 -1
  37. data/spec/lib/capistrano/configuration/filter_spec.rb +8 -8
  38. data/spec/lib/capistrano/configuration/host_filter_spec.rb +1 -1
  39. data/spec/lib/capistrano/configuration/null_filter_spec.rb +1 -1
  40. data/spec/lib/capistrano/configuration/question_spec.rb +1 -1
  41. data/spec/lib/capistrano/configuration/role_filter_spec.rb +1 -1
  42. data/spec/lib/capistrano/configuration/server_spec.rb +3 -2
  43. data/spec/lib/capistrano/configuration_spec.rb +16 -3
  44. data/spec/lib/capistrano/doctor/gems_doctor_spec.rb +6 -0
  45. data/spec/lib/capistrano/doctor/servers_doctor_spec.rb +86 -0
  46. data/spec/lib/capistrano/doctor/variables_doctor_spec.rb +9 -0
  47. data/spec/lib/capistrano/dsl/paths_spec.rb +9 -9
  48. data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +5 -0
  49. data/spec/lib/capistrano/dsl_spec.rb +37 -3
  50. data/spec/lib/capistrano/version_validator_spec.rb +2 -2
  51. data/spec/support/test_app.rb +10 -0
  52. metadata +12 -4
@@ -0,0 +1,13 @@
1
+ module Capistrano
2
+ module ProcHelpers
3
+ module_function
4
+
5
+ # Tests whether the given object appears to respond to `call` with
6
+ # zero parameters. In Capistrano, such a proc is used to represent a
7
+ # "deferred value". That is, a value that is resolved by invoking `call` at
8
+ # the time it is first needed.
9
+ def callable_without_parameters?(x)
10
+ x.respond_to?(:call) && (!x.respond_to?(:arity) || x.arity.zero?)
11
+ end
12
+ end
13
+ end
@@ -209,7 +209,15 @@ namespace :deploy do
209
209
  error t(:cannot_rollback)
210
210
  exit 1
211
211
  end
212
- last_release = releases[1]
212
+
213
+ rollback_release = ENV["ROLLBACK_RELEASE"]
214
+ index = rollback_release.nil? ? 1 : releases.index(rollback_release)
215
+ if index.nil?
216
+ error t(:cannot_found_rollback_release, release: rollback_release)
217
+ exit 1
218
+ end
219
+
220
+ last_release = releases[index]
213
221
  set_release_path(last_release)
214
222
  set(:rollback_timestamp, last_release)
215
223
  end
@@ -1,5 +1,5 @@
1
1
  desc "Display a Capistrano troubleshooting report (all doctor: tasks)"
2
- task doctor: ["doctor:environment", "doctor:gems", "doctor:variables"]
2
+ task doctor: ["doctor:environment", "doctor:gems", "doctor:variables", "doctor:servers"]
3
3
 
4
4
  namespace :doctor do
5
5
  desc "Display Ruby environment details"
@@ -16,4 +16,9 @@ namespace :doctor do
16
16
  task :variables do
17
17
  Capistrano::Doctor::VariablesDoctor.new.call
18
18
  end
19
+
20
+ desc "Display the effective servers configuration"
21
+ task :servers do
22
+ Capistrano::Doctor::ServersDoctor.new.call
23
+ end
19
24
  end
@@ -3,19 +3,26 @@ namespace :git do
3
3
  @strategy ||= Capistrano::Git.new(self, fetch(:git_strategy, Capistrano::Git::DefaultStrategy))
4
4
  end
5
5
 
6
+ set :git_wrapper_path, lambda {
7
+ # Try to avoid permissions issues when multiple users deploy the same app
8
+ # by using different file names in the same dir for each deployer and stage.
9
+ suffix = [:application, :stage, :local_user].map { |key| fetch(key).to_s }.join("-")
10
+ "#{fetch(:tmp_dir)}/git-ssh-#{suffix}.sh"
11
+ }
12
+
6
13
  set :git_environmental_variables, lambda {
7
14
  {
8
15
  git_askpass: "/bin/echo",
9
- git_ssh: "#{fetch(:tmp_dir)}/#{fetch(:application)}/git-ssh.sh"
16
+ git_ssh: fetch(:git_wrapper_path)
10
17
  }
11
18
  }
12
19
 
13
20
  desc "Upload the git wrapper script, this script guarantees that we can script git without getting an interactive prompt"
14
21
  task :wrapper do
15
22
  on release_roles :all do
16
- execute :mkdir, "-p", "#{fetch(:tmp_dir)}/#{fetch(:application)}/"
17
- upload! StringIO.new("#!/bin/sh -e\nexec /usr/bin/ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \"$@\"\n"), "#{fetch(:tmp_dir)}/#{fetch(:application)}/git-ssh.sh"
18
- execute :chmod, "+rx", "#{fetch(:tmp_dir)}/#{fetch(:application)}/git-ssh.sh"
23
+ execute :mkdir, "-p", File.dirname(fetch(:git_wrapper_path))
24
+ upload! StringIO.new("#!/bin/sh -e\nexec /usr/bin/ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \"$@\"\n"), fetch(:git_wrapper_path)
25
+ execute :chmod, "700", fetch(:git_wrapper_path)
19
26
  end
20
27
  end
21
28
 
@@ -24,26 +24,13 @@ set :repo_url, 'git@example.com:me/my_repo.git'
24
24
  # set :pty, true
25
25
 
26
26
  # Default value for :linked_files is []
27
- # set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')
27
+ # append :linked_files, 'config/database.yml', 'config/secrets.yml'
28
28
 
29
29
  # Default value for linked_dirs is []
30
- # set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system')
30
+ # append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system'
31
31
 
32
32
  # Default value for default_env is {}
33
33
  # set :default_env, { path: "/opt/ruby/bin:$PATH" }
34
34
 
35
35
  # Default value for keep_releases is 5
36
36
  # set :keep_releases, 5
37
-
38
- namespace :deploy do
39
-
40
- after :restart, :clear_cache do
41
- on roles(:web), in: :groups, limit: 3, wait: 10 do
42
- # Here we can do anything such as:
43
- # within release_path do
44
- # execute :rake, 'cache:clear'
45
- # end
46
- end
47
- end
48
-
49
- end
@@ -1,3 +1,3 @@
1
1
  module Capistrano
2
- VERSION = "3.5.0".freeze
2
+ VERSION = "3.6.0".freeze
3
3
  end
@@ -5,7 +5,7 @@ module Capistrano
5
5
  describe EmptyFilter do
6
6
  subject(:empty_filter) { EmptyFilter.new }
7
7
 
8
- describe '#filter' do
8
+ describe "#filter" do
9
9
  let(:servers) { mock("servers") }
10
10
 
11
11
  it "returns an empty array" do
@@ -13,7 +13,7 @@ module Capistrano
13
13
  ]
14
14
  end
15
15
 
16
- describe '#new' do
16
+ describe "#new" do
17
17
  it "won't create an invalid type of filter" do
18
18
  expect do
19
19
  Filter.new(:zarg)
@@ -73,12 +73,12 @@ module Capistrano
73
73
  end
74
74
  end
75
75
 
76
- describe '#filter' do
76
+ describe "#filter" do
77
77
  let(:strategy) { filter.instance_variable_get(:@strategy) }
78
78
  let(:results) { mock("result") }
79
79
 
80
- shared_examples 'it calls #filter on its strategy' do
81
- it 'calls #filter on its strategy' do
80
+ shared_examples "it calls #filter on its strategy" do
81
+ it "calls #filter on its strategy" do
82
82
  strategy.expects(:filter).with(available).returns(results)
83
83
  expect(filter.filter(available)).to eq(results)
84
84
  end
@@ -86,22 +86,22 @@ module Capistrano
86
86
 
87
87
  context "for an empty filter" do
88
88
  let(:filter) { Filter.new(:role) }
89
- it_behaves_like 'it calls #filter on its strategy'
89
+ it_behaves_like "it calls #filter on its strategy"
90
90
  end
91
91
 
92
92
  context "for a null filter" do
93
93
  let(:filter) { Filter.new(:role, :all) }
94
- it_behaves_like 'it calls #filter on its strategy'
94
+ it_behaves_like "it calls #filter on its strategy"
95
95
  end
96
96
 
97
97
  context "for a role filter" do
98
98
  let(:filter) { Filter.new(:role, "web") }
99
- it_behaves_like 'it calls #filter on its strategy'
99
+ it_behaves_like "it calls #filter on its strategy"
100
100
  end
101
101
 
102
102
  context "for a host filter" do
103
103
  let(:filter) { Filter.new(:host, "server1") }
104
- it_behaves_like 'it calls #filter on its strategy'
104
+ it_behaves_like "it calls #filter on its strategy"
105
105
  end
106
106
  end
107
107
  end
@@ -20,7 +20,7 @@ module Capistrano
20
20
  end
21
21
  end
22
22
 
23
- describe '#filter' do
23
+ describe "#filter" do
24
24
  context "with a string" do
25
25
  let(:values) { "server1" }
26
26
  it_behaves_like "it filters hosts correctly", %w{server1}
@@ -5,7 +5,7 @@ module Capistrano
5
5
  describe NullFilter do
6
6
  subject(:null_filter) { NullFilter.new }
7
7
 
8
- describe '#filter' do
8
+ describe "#filter" do
9
9
  let(:servers) { mock("servers") }
10
10
 
11
11
  it "returns the servers passed in as arguments" do
@@ -15,7 +15,7 @@ module Capistrano
15
15
  end
16
16
  end
17
17
 
18
- describe '#call' do
18
+ describe "#call" do
19
19
  context "value is entered" do
20
20
  let(:branch) { "branch" }
21
21
 
@@ -23,7 +23,7 @@ module Capistrano
23
23
  end
24
24
  end
25
25
 
26
- describe '#filter' do
26
+ describe "#filter" do
27
27
  context "with a single role string" do
28
28
  let(:values) { "web" }
29
29
  it_behaves_like "it filters roles correctly", 2, %w{server1 server2}
@@ -141,7 +141,7 @@ module Capistrano
141
141
  end
142
142
  end
143
143
 
144
- describe '#include?' do
144
+ describe "#include?" do
145
145
  let(:options) { {} }
146
146
 
147
147
  subject { server.select?(options) }
@@ -269,7 +269,8 @@ module Capistrano
269
269
  user: "another_user",
270
270
  keys: %w(/home/another_user/.ssh/id_rsa),
271
271
  forward_agent: false,
272
- auth_methods: %w(publickey password) } }
272
+ auth_methods: %w(publickey password)
273
+ } }
273
274
  end
274
275
 
275
276
  before do
@@ -150,13 +150,23 @@ module Capistrano
150
150
  end
151
151
  end
152
152
 
153
- it "validates without error" do
153
+ it "validates string without error" do
154
154
  config.set(:key, "longer_value")
155
155
  end
156
156
 
157
- it "raises an exception" do
157
+ it "validates proc without error" do
158
+ config.set(:key) { "longer_value" }
159
+ expect(config.fetch(:key)).to eq "longer_value"
160
+ end
161
+
162
+ it "raises an exception on invalid string" do
158
163
  expect { config.set(:key, "sho") }.to raise_error(Capistrano::ValidationError)
159
164
  end
165
+
166
+ it "raises an exception on invalid string provided by proc" do
167
+ config.set(:key) { "sho" }
168
+ expect { config.fetch(:key) }.to raise_error(Capistrano::ValidationError)
169
+ end
160
170
  end
161
171
 
162
172
  context "appending" do
@@ -265,7 +275,10 @@ module Capistrano
265
275
  config.set :ssh_options, user: "albert"
266
276
  SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = { password: "einstein" } }
267
277
  config.configure_backend
268
- expect(config.backend.config.backend.config.ssh_options).to eq(user: "albert", password: "einstein")
278
+
279
+ expect(
280
+ config.backend.config.backend.config.ssh_options
281
+ ).to include(user: "albert", password: "einstein")
269
282
  end
270
283
  end
271
284
  end
@@ -2,6 +2,7 @@ require "spec_helper"
2
2
  require "capistrano/doctor/gems_doctor"
3
3
  require "airbrussh/version"
4
4
  require "sshkit/version"
5
+ require "net/ssh/version"
5
6
 
6
7
  module Capistrano
7
8
  module Doctor
@@ -32,6 +33,11 @@ module Capistrano
32
33
  output(/airbrussh\s+#{Regexp.quote(Airbrussh::VERSION)}/).to_stdout
33
34
  end
34
35
 
36
+ it "prints the net-ssh version" do
37
+ expect { doc.call }.to\
38
+ output(/net-ssh\s+#{Regexp.quote(Net::SSH::Version::STRING)}/).to_stdout
39
+ end
40
+
35
41
  it "warns that new version is available" do
36
42
  Gem.stubs(:latest_version_for).returns(Gem::Version.new("99.0.0"))
37
43
  expect { doc.call }.to output(/\(update available\)/).to_stdout
@@ -0,0 +1,86 @@
1
+ require "spec_helper"
2
+ require "capistrano/doctor/servers_doctor"
3
+
4
+ module Capistrano
5
+ module Doctor
6
+ describe ServersDoctor do
7
+ include Capistrano::DSL
8
+ let(:doc) { ServersDoctor.new }
9
+
10
+ before { Capistrano::Configuration.reset! }
11
+ after { Capistrano::Configuration.reset! }
12
+
13
+ it "prints using 4-space indentation" do
14
+ expect { doc.call }.to output(/^ {4}/).to_stdout
15
+ end
16
+
17
+ it "prints the number of defined servers" do
18
+ role :app, %w(example.com)
19
+ server "www@example.com:22"
20
+
21
+ expect { doc.call }.to output(/Servers \(2\)/).to_stdout
22
+ end
23
+
24
+ describe "prints the server's details" do
25
+ it "including username" do
26
+ server "www@example.com"
27
+ expect { doc.call }.to output(/www@example.com/).to_stdout
28
+ end
29
+
30
+ it "including port" do
31
+ server "www@example.com:22"
32
+ expect { doc.call }.to output(/www@example.com:22/).to_stdout
33
+ end
34
+
35
+ it "including roles" do
36
+ role :app, %w(example.com)
37
+ expect { doc.call }.to output(/example.com\s+\[:app\]/).to_stdout
38
+ end
39
+
40
+ it "including empty roles" do
41
+ server "example.com"
42
+ expect { doc.call }.to output(/example.com\s+\[\]/).to_stdout
43
+ end
44
+
45
+ it "including properties" do
46
+ server "example.com", roles: %w(app db), primary: true
47
+ expect { doc.call }.to \
48
+ output(/example.com\s+\[:app, :db\]\s+\{ :primary => true \}/).to_stdout
49
+ end
50
+
51
+ it "including misleading role name alert" do
52
+ server "example.com", roles: ["web app db"]
53
+ warning_msg = 'Whitespace detected in role(s) :"web app db". ' \
54
+ 'This might be a result of a mistyped "%w()" array literal'
55
+
56
+ expect { doc.call }.to output(/#{Regexp.escape(warning_msg)}/).to_stdout
57
+ end
58
+ end
59
+
60
+ it "doesn't fail for no servers" do
61
+ expect { doc.call }.to output("\nServers (0)\n \n").to_stdout
62
+ end
63
+
64
+ describe "Rake" do
65
+ before do
66
+ load File.expand_path("../../../../../lib/capistrano/doctor.rb",
67
+ __FILE__)
68
+ end
69
+
70
+ after do
71
+ Rake::Task.clear
72
+ end
73
+
74
+ it "has an doctor:servers task that calls ServersDoctor" do
75
+ ServersDoctor.any_instance.expects(:call)
76
+ Rake::Task["doctor:servers"].invoke
77
+ end
78
+
79
+ it "has a doctor task that depends on doctor:servers" do
80
+ expect(Rake::Task["doctor"].prerequisites).to \
81
+ include("doctor:servers")
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -15,8 +15,10 @@ module Capistrano
15
15
  env.variables.untrusted! do
16
16
  set :application, "my_app"
17
17
  set :repo_url, ".git"
18
+ set :git_strategy, "Capistrano::Git::DefaultStrategy"
18
19
  set :copy_strategy, :scp
19
20
  set :custom_setting, "hello"
21
+ set "string_setting", "hello"
20
22
  ask :secret
21
23
  end
22
24
 
@@ -36,6 +38,7 @@ module Capistrano
36
38
  expect { doc.call }.to output(/:repo_url\s+".git"$/).to_stdout
37
39
  expect { doc.call }.to output(/:copy_strategy\s+:scp$/).to_stdout
38
40
  expect { doc.call }.to output(/:custom_setting\s+"hello"$/).to_stdout
41
+ expect { doc.call }.to output(/"string_setting"\s+"hello"$/).to_stdout
39
42
  end
40
43
 
41
44
  it "prints unanswered question variable as <ask>" do
@@ -54,6 +57,12 @@ module Capistrano
54
57
  .to_stdout
55
58
  end
56
59
 
60
+ it "does not print warning for the whitelisted git_strategy variable" do
61
+ expect { doc.call }.not_to \
62
+ output(/:git_strategy is not a recognized Capistrano setting/)\
63
+ .to_stdout
64
+ end
65
+
57
66
  describe "Rake" do
58
67
  before do
59
68
  load File.expand_path("../../../../../lib/capistrano/doctor.rb",
@@ -12,7 +12,7 @@ describe Capistrano::DSL::Paths do
12
12
  dsl.set(:deploy_to, "/var/www")
13
13
  end
14
14
 
15
- describe '#linked_dirs' do
15
+ describe "#linked_dirs" do
16
16
  subject { paths.linked_dirs(parent) }
17
17
 
18
18
  before do
@@ -27,7 +27,7 @@ describe Capistrano::DSL::Paths do
27
27
  end
28
28
  end
29
29
 
30
- describe '#linked_files' do
30
+ describe "#linked_files" do
31
31
  subject { paths.linked_files(parent) }
32
32
 
33
33
  before do
@@ -43,7 +43,7 @@ describe Capistrano::DSL::Paths do
43
43
  end
44
44
  end
45
45
 
46
- describe '#linked_file_dirs' do
46
+ describe "#linked_file_dirs" do
47
47
  subject { paths.linked_file_dirs(parent) }
48
48
 
49
49
  before do
@@ -58,7 +58,7 @@ describe Capistrano::DSL::Paths do
58
58
  end
59
59
  end
60
60
 
61
- describe '#linked_dir_parents' do
61
+ describe "#linked_dir_parents" do
62
62
  subject { paths.linked_dir_parents(parent) }
63
63
 
64
64
  before do
@@ -73,7 +73,7 @@ describe Capistrano::DSL::Paths do
73
73
  end
74
74
  end
75
75
 
76
- describe '#release path' do
76
+ describe "#release path" do
77
77
  subject { dsl.release_path }
78
78
 
79
79
  context "where no release path has been set" do
@@ -97,7 +97,7 @@ describe Capistrano::DSL::Paths do
97
97
  end
98
98
  end
99
99
 
100
- describe '#set_release_path' do
100
+ describe "#set_release_path" do
101
101
  let(:now) { Time.parse("Oct 21 16:29:00 2015") }
102
102
  subject { dsl.release_path }
103
103
 
@@ -123,7 +123,7 @@ describe Capistrano::DSL::Paths do
123
123
  end
124
124
  end
125
125
 
126
- describe '#deploy_config_path' do
126
+ describe "#deploy_config_path" do
127
127
  subject { dsl.deploy_config_path.to_s }
128
128
 
129
129
  context "when not specified" do
@@ -147,7 +147,7 @@ describe Capistrano::DSL::Paths do
147
147
  end
148
148
  end
149
149
 
150
- describe '#stage_config_path' do
150
+ describe "#stage_config_path" do
151
151
  subject { dsl.stage_config_path.to_s }
152
152
 
153
153
  context "when not specified" do
@@ -171,7 +171,7 @@ describe Capistrano::DSL::Paths do
171
171
  end
172
172
  end
173
173
 
174
- describe '#repo_path' do
174
+ describe "#repo_path" do
175
175
  subject { dsl.repo_path.to_s }
176
176
 
177
177
  context "when not specified" do