capistrano 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
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