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