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.
- checksums.yaml +4 -4
- data/.travis.yml +5 -1
- data/CHANGELOG.md +55 -10
- data/README.md +3 -3
- data/RELEASING.md +1 -0
- data/UPGRADING-3.7.md +97 -0
- data/capistrano.gemspec +1 -1
- data/features/deploy.feature +1 -0
- data/features/stage_failure.feature +9 -0
- data/features/step_definitions/assertions.rb +5 -0
- data/features/step_definitions/setup.rb +4 -0
- data/lib/capistrano/application.rb +5 -10
- data/lib/capistrano/configuration.rb +8 -7
- data/lib/capistrano/configuration/filter.rb +4 -5
- data/lib/capistrano/configuration/host_filter.rb +1 -1
- data/lib/capistrano/configuration/plugin_installer.rb +1 -1
- data/lib/capistrano/configuration/server.rb +8 -2
- data/lib/capistrano/configuration/validated_variables.rb +75 -0
- data/lib/capistrano/configuration/variables.rb +7 -23
- data/lib/capistrano/defaults.rb +10 -0
- data/lib/capistrano/doctor.rb +1 -0
- data/lib/capistrano/doctor/gems_doctor.rb +1 -1
- data/lib/capistrano/doctor/servers_doctor.rb +105 -0
- data/lib/capistrano/doctor/variables_doctor.rb +6 -7
- data/lib/capistrano/dsl.rb +28 -4
- data/lib/capistrano/dsl/paths.rb +1 -1
- data/lib/capistrano/dsl/stages.rb +15 -1
- data/lib/capistrano/dsl/task_enhancements.rb +6 -1
- data/lib/capistrano/i18n.rb +2 -0
- data/lib/capistrano/proc_helpers.rb +13 -0
- data/lib/capistrano/tasks/deploy.rake +9 -1
- data/lib/capistrano/tasks/doctor.rake +6 -1
- data/lib/capistrano/tasks/git.rake +11 -4
- data/lib/capistrano/templates/deploy.rb.erb +2 -15
- data/lib/capistrano/version.rb +1 -1
- data/spec/lib/capistrano/configuration/empty_filter_spec.rb +1 -1
- data/spec/lib/capistrano/configuration/filter_spec.rb +8 -8
- data/spec/lib/capistrano/configuration/host_filter_spec.rb +1 -1
- data/spec/lib/capistrano/configuration/null_filter_spec.rb +1 -1
- data/spec/lib/capistrano/configuration/question_spec.rb +1 -1
- data/spec/lib/capistrano/configuration/role_filter_spec.rb +1 -1
- data/spec/lib/capistrano/configuration/server_spec.rb +3 -2
- data/spec/lib/capistrano/configuration_spec.rb +16 -3
- data/spec/lib/capistrano/doctor/gems_doctor_spec.rb +6 -0
- data/spec/lib/capistrano/doctor/servers_doctor_spec.rb +86 -0
- data/spec/lib/capistrano/doctor/variables_doctor_spec.rb +9 -0
- data/spec/lib/capistrano/dsl/paths_spec.rb +9 -9
- data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +5 -0
- data/spec/lib/capistrano/dsl_spec.rb +37 -3
- data/spec/lib/capistrano/version_validator_spec.rb +2 -2
- data/spec/support/test_app.rb +10 -0
- 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
|
-
|
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:
|
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",
|
17
|
-
upload! StringIO.new("#!/bin/sh -e\nexec /usr/bin/ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \"$@\"\n"),
|
18
|
-
execute :chmod, "
|
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
|
-
#
|
27
|
+
# append :linked_files, 'config/database.yml', 'config/secrets.yml'
|
28
28
|
|
29
29
|
# Default value for linked_dirs is []
|
30
|
-
#
|
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
|
data/lib/capistrano/version.rb
CHANGED
@@ -13,7 +13,7 @@ module Capistrano
|
|
13
13
|
]
|
14
14
|
end
|
15
15
|
|
16
|
-
describe
|
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
|
76
|
+
describe "#filter" do
|
77
77
|
let(:strategy) { filter.instance_variable_get(:@strategy) }
|
78
78
|
let(:results) { mock("result") }
|
79
79
|
|
80
|
-
shared_examples
|
81
|
-
it
|
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
|
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
|
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
|
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
|
104
|
+
it_behaves_like "it calls #filter on its strategy"
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|
@@ -141,7 +141,7 @@ module Capistrano
|
|
141
141
|
end
|
142
142
|
end
|
143
143
|
|
144
|
-
describe
|
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 "
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
174
|
+
describe "#repo_path" do
|
175
175
|
subject { dsl.repo_path.to_s }
|
176
176
|
|
177
177
|
context "when not specified" do
|