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,75 @@
|
|
1
|
+
require "capistrano/proc_helpers"
|
2
|
+
require "delegate"
|
3
|
+
|
4
|
+
module Capistrano
|
5
|
+
class Configuration
|
6
|
+
# Decorates a Variables object to additionally perform an optional set of
|
7
|
+
# user-supplied validation rules. Each rule for a given key is invoked
|
8
|
+
# immediately whenever `set` is called with a value for that key.
|
9
|
+
#
|
10
|
+
# If `set` is called with a block, validation is not performed immediately.
|
11
|
+
# Instead, the validation rules are invoked the first time `fetch` is used
|
12
|
+
# to access the value.
|
13
|
+
#
|
14
|
+
# A rule is simply a block that accepts two arguments: key and value. It is
|
15
|
+
# up to the rule to raise an exception when it deems the value is invalid
|
16
|
+
# (or just print a warning).
|
17
|
+
#
|
18
|
+
# Rules can be registered using the DSL like this:
|
19
|
+
#
|
20
|
+
# validate(:my_key) do |key, value|
|
21
|
+
# # rule goes here
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
class ValidatedVariables < SimpleDelegator
|
25
|
+
include Capistrano::ProcHelpers
|
26
|
+
|
27
|
+
def initialize(variables)
|
28
|
+
super(variables)
|
29
|
+
@validators = {}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Decorate Variables#set to add validation behavior.
|
33
|
+
def set(key, value=nil, &block)
|
34
|
+
if value.nil? && callable_without_parameters?(block)
|
35
|
+
super(key, nil, &assert_valid_later(key, &block))
|
36
|
+
else
|
37
|
+
assert_valid_now(key, block || value)
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Register a validation rule for the given key.
|
43
|
+
def validate(key, &validator)
|
44
|
+
vs = (validators[key] || [])
|
45
|
+
vs << validator
|
46
|
+
validators[key] = vs
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :validators
|
52
|
+
|
53
|
+
# Wrap a block with a proc that validates the value of the block. This
|
54
|
+
# allows us to defer validation until the time the value is requested.
|
55
|
+
def assert_valid_later(key)
|
56
|
+
lambda do
|
57
|
+
value = yield
|
58
|
+
assert_valid_now(key, value)
|
59
|
+
value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Runs all validation rules registered for the given key against the
|
64
|
+
# user-supplied value for that variable. If no validator raises an
|
65
|
+
# exception, the value is assumed to be valid.
|
66
|
+
def assert_valid_now(key, value)
|
67
|
+
return unless validators.key?(key)
|
68
|
+
|
69
|
+
validators[key].each do |validator|
|
70
|
+
validator.call(key, value)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
require "capistrano/proc_helpers"
|
2
|
+
|
1
3
|
module Capistrano
|
2
4
|
class Configuration
|
3
5
|
# Holds the variables assigned at Capistrano runtime via `set` and retrieved
|
4
6
|
# with `fetch`. Does internal bookkeeping to help identify user mistakes
|
5
7
|
# like spelling errors or unused variables that may lead to unexpected
|
6
|
-
# behavior.
|
8
|
+
# behavior.
|
7
9
|
class Variables
|
8
10
|
CAPISTRANO_LOCATION = File.expand_path("../..", __FILE__).freeze
|
9
11
|
IGNORED_LOCATIONS = [
|
@@ -15,6 +17,8 @@ module Capistrano
|
|
15
17
|
].freeze
|
16
18
|
private_constant :CAPISTRANO_LOCATION, :IGNORED_LOCATIONS
|
17
19
|
|
20
|
+
include Capistrano::ProcHelpers
|
21
|
+
|
18
22
|
def initialize(values={})
|
19
23
|
@trusted_keys = []
|
20
24
|
@fetched_keys = []
|
@@ -31,7 +35,7 @@ module Capistrano
|
|
31
35
|
end
|
32
36
|
|
33
37
|
def set(key, value=nil, &block)
|
34
|
-
|
38
|
+
assert_value_or_block_not_both(value, block)
|
35
39
|
@trusted_keys << key if trusted?
|
36
40
|
remember_location(key)
|
37
41
|
values[key] = block || value
|
@@ -61,12 +65,6 @@ module Capistrano
|
|
61
65
|
values.delete(key)
|
62
66
|
end
|
63
67
|
|
64
|
-
def validate(key, &validator)
|
65
|
-
vs = (validators[key] || [])
|
66
|
-
vs << validator
|
67
|
-
validators[key] = vs
|
68
|
-
end
|
69
|
-
|
70
68
|
def trusted_keys
|
71
69
|
@trusted_keys.dup
|
72
70
|
end
|
@@ -106,25 +104,11 @@ module Capistrano
|
|
106
104
|
(locations[key] ||= []) << location
|
107
105
|
end
|
108
106
|
|
109
|
-
def
|
110
|
-
x.respond_to?(:call) && (!x.respond_to?(:arity) || x.arity == 0)
|
111
|
-
end
|
112
|
-
|
113
|
-
def validators
|
114
|
-
@validators ||= {}
|
115
|
-
end
|
116
|
-
|
117
|
-
def invoke_validations(key, value, &block)
|
107
|
+
def assert_value_or_block_not_both(value, block)
|
118
108
|
unless value.nil? || block.nil?
|
119
109
|
raise Capistrano::ValidationError,
|
120
110
|
"Value and block both passed to Configuration#set"
|
121
111
|
end
|
122
|
-
|
123
|
-
return unless validators.key? key
|
124
|
-
|
125
|
-
validators[key].each do |validator|
|
126
|
-
validator.call(key, block || value)
|
127
|
-
end
|
128
112
|
end
|
129
113
|
|
130
114
|
def trace_set(key)
|
data/lib/capistrano/defaults.rb
CHANGED
@@ -8,6 +8,16 @@ validate :application do |_key, value|
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
+
[:git_strategy, :hg_strategy, :svn_strategy].each do |strategy|
|
12
|
+
validate(strategy) do |key, _value|
|
13
|
+
warn(
|
14
|
+
"[Deprecation Warning] #{key} is deprecated and will be removed in "\
|
15
|
+
"Capistrano 3.7.0.\n"\
|
16
|
+
"https://github.com/capistrano/capistrano/blob/master/UPGRADING-3.7.md"
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
11
21
|
set_if_empty :scm, :git
|
12
22
|
set_if_empty :branch, "master"
|
13
23
|
set_if_empty :deploy_to, -> { "/var/www/#{fetch(:application)}" }
|
data/lib/capistrano/doctor.rb
CHANGED
@@ -0,0 +1,105 @@
|
|
1
|
+
require "capistrano/doctor/output_helpers"
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Doctor
|
5
|
+
class ServersDoctor
|
6
|
+
include Capistrano::Doctor::OutputHelpers
|
7
|
+
|
8
|
+
def initialize(env=Capistrano::Configuration.env)
|
9
|
+
@servers = env.servers.to_a
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
title("Servers (#{servers.size})")
|
14
|
+
rwc = RoleWhitespaceChecker.new(servers)
|
15
|
+
|
16
|
+
table(servers) do |server, row|
|
17
|
+
sd = ServerDecorator.new(server)
|
18
|
+
|
19
|
+
row << sd.uri_form
|
20
|
+
row << sd.roles
|
21
|
+
row << sd.properties
|
22
|
+
row.yellow if rwc.any_has_whitespace?(server.roles)
|
23
|
+
end
|
24
|
+
|
25
|
+
if rwc.whitespace_roles.any?
|
26
|
+
warning "\nWhitespace detected in role(s) #{rwc.whitespace_roles_decorated}. " \
|
27
|
+
"This might be a result of a mistyped \"%w()\" array literal."
|
28
|
+
end
|
29
|
+
puts
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :servers
|
35
|
+
|
36
|
+
class RoleWhitespaceChecker
|
37
|
+
attr_reader :whitespace_roles, :servers
|
38
|
+
|
39
|
+
def initialize(servers)
|
40
|
+
@servers = servers
|
41
|
+
@whitespace_roles = find_whitespace_roles
|
42
|
+
end
|
43
|
+
|
44
|
+
def any_has_whitespace?(roles)
|
45
|
+
roles.any? { |role| include_whitespace?(role) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def include_whitespace?(role)
|
49
|
+
role =~ /\s/
|
50
|
+
end
|
51
|
+
|
52
|
+
def whitespace_roles_decorated
|
53
|
+
whitespace_roles.map(&:inspect).join(", ")
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def find_whitespace_roles
|
59
|
+
servers.map(&:roles).map(&:to_a).flatten.uniq
|
60
|
+
.select { |role| include_whitespace?(role) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class ServerDecorator
|
65
|
+
def initialize(server)
|
66
|
+
@server = server
|
67
|
+
end
|
68
|
+
|
69
|
+
def uri_form
|
70
|
+
[
|
71
|
+
server.user,
|
72
|
+
server.user && "@",
|
73
|
+
server.hostname,
|
74
|
+
server.port && ":",
|
75
|
+
server.port
|
76
|
+
].compact.join
|
77
|
+
end
|
78
|
+
|
79
|
+
def roles
|
80
|
+
server.roles.to_a.inspect
|
81
|
+
end
|
82
|
+
|
83
|
+
def properties
|
84
|
+
return "" unless server.properties.keys.any?
|
85
|
+
pretty_inspect(server.properties.to_h)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
attr_reader :server
|
91
|
+
|
92
|
+
# Hashes with proper padding
|
93
|
+
def pretty_inspect(element)
|
94
|
+
return element.inspect unless element.is_a?(Hash)
|
95
|
+
|
96
|
+
pairs_string = element.keys.map do |key|
|
97
|
+
[pretty_inspect(key), pretty_inspect(element.fetch(key))].join(" => ")
|
98
|
+
end.join(", ")
|
99
|
+
|
100
|
+
"{ #{pairs_string} }"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -7,7 +7,7 @@ module Capistrano
|
|
7
7
|
class VariablesDoctor
|
8
8
|
# These are keys that have no default values in Capistrano, but are
|
9
9
|
# nonetheless expected to be set.
|
10
|
-
WHITELIST = [:application, :repo_url].freeze
|
10
|
+
WHITELIST = [:application, :repo_url, :git_strategy, :hg_strategy, :svn_strategy].freeze
|
11
11
|
private_constant :WHITELIST
|
12
12
|
|
13
13
|
include Capistrano::Doctor::OutputHelpers
|
@@ -20,18 +20,17 @@ module Capistrano
|
|
20
20
|
title("Variables")
|
21
21
|
values = inspect_all_values
|
22
22
|
|
23
|
-
table(variables.keys.
|
23
|
+
table(variables.keys.sort_by(&:to_s)) do |key, row|
|
24
24
|
row.yellow if suspicious_keys.include?(key)
|
25
|
-
row <<
|
25
|
+
row << key.inspect
|
26
26
|
row << values[key]
|
27
27
|
end
|
28
28
|
|
29
29
|
puts if suspicious_keys.any?
|
30
30
|
|
31
|
-
suspicious_keys.
|
32
|
-
warning(
|
33
|
-
|
34
|
-
)
|
31
|
+
suspicious_keys.sort_by(&:to_s).each do |key|
|
32
|
+
warning("#{key.inspect} is not a recognized Capistrano setting "\
|
33
|
+
"(#{location(key)})")
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
data/lib/capistrano/dsl.rb
CHANGED
@@ -11,8 +11,18 @@ module Capistrano
|
|
11
11
|
include Paths
|
12
12
|
include Stages
|
13
13
|
|
14
|
-
def invoke(
|
15
|
-
Rake::Task[
|
14
|
+
def invoke(task_name, *args)
|
15
|
+
task = Rake::Task[task_name]
|
16
|
+
if task && task.already_invoked
|
17
|
+
file, line, = caller.first.split(":")
|
18
|
+
colors = SSHKit::Color.new($stderr)
|
19
|
+
$stderr.puts colors.colorize("Skipping task `#{task_name}'.", :yellow)
|
20
|
+
$stderr.puts "Capistrano tasks may only be invoked once. Since task `#{task}' was previously invoked, invoke(\"#{task_name}\") at #{file}:#{line} will be skipped."
|
21
|
+
$stderr.puts "If you really meant to run this task again, first call Rake::Task[\"#{task_name}\"].reenable"
|
22
|
+
$stderr.puts colors.colorize("THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF CAPISTRANO. Please join the conversation here if this affects you.", :red)
|
23
|
+
$stderr.puts colors.colorize("https://github.com/capistrano/capistrano/issues/1686", :red)
|
24
|
+
end
|
25
|
+
task.invoke(*args)
|
16
26
|
end
|
17
27
|
|
18
28
|
def t(key, options={})
|
@@ -33,8 +43,7 @@ module Capistrano
|
|
33
43
|
branch: fetch(:branch),
|
34
44
|
user: local_user,
|
35
45
|
sha: fetch(:current_revision),
|
36
|
-
release: fetch(:release_timestamp))
|
37
|
-
)
|
46
|
+
release: fetch(:release_timestamp)))
|
38
47
|
end
|
39
48
|
|
40
49
|
def rollback_log_message
|
@@ -57,6 +66,21 @@ module Capistrano
|
|
57
66
|
def run_locally(&block)
|
58
67
|
SSHKit::Backend::Local.new(&block).run
|
59
68
|
end
|
69
|
+
|
70
|
+
# Catch common beginner mistake and give a helpful error message on stderr
|
71
|
+
def execute(*)
|
72
|
+
file, line, = caller.first.split(":")
|
73
|
+
colors = SSHKit::Color.new($stderr)
|
74
|
+
$stderr.puts colors.colorize("Warning: `execute' should be wrapped in an `on' scope in #{file}:#{line}.", :red)
|
75
|
+
$stderr.puts
|
76
|
+
$stderr.puts " task :example do"
|
77
|
+
$stderr.puts colors.colorize(" on roles(:app) do", :yellow)
|
78
|
+
$stderr.puts " execute 'whoami'"
|
79
|
+
$stderr.puts colors.colorize(" end", :yellow)
|
80
|
+
$stderr.puts " end"
|
81
|
+
$stderr.puts
|
82
|
+
raise NoMethodError, "undefined method `execute' for main:Object"
|
83
|
+
end
|
60
84
|
end
|
61
85
|
end
|
62
86
|
extend Capistrano::DSL
|
data/lib/capistrano/dsl/paths.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
module Capistrano
|
2
2
|
module DSL
|
3
3
|
module Stages
|
4
|
+
RESERVED_NAMES = %w(deploy doctor install).freeze
|
5
|
+
private_constant :RESERVED_NAMES
|
6
|
+
|
4
7
|
def stages
|
5
|
-
Dir[stage_definitions].map { |f| File.basename(f, ".rb") }
|
8
|
+
names = Dir[stage_definitions].map { |f| File.basename(f, ".rb") }
|
9
|
+
assert_valid_stage_names(names)
|
10
|
+
names
|
6
11
|
end
|
7
12
|
|
8
13
|
def stage_definitions
|
@@ -12,6 +17,15 @@ module Capistrano
|
|
12
17
|
def stage_set?
|
13
18
|
!!fetch(:stage, false)
|
14
19
|
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def assert_valid_stage_names(names)
|
24
|
+
invalid = names.find { |n| RESERVED_NAMES.include?(n) }
|
25
|
+
return if invalid.nil?
|
26
|
+
|
27
|
+
raise t("error.invalid_stage_name", name: invalid, path: stage_config_path.join("#{invalid}.rb"))
|
28
|
+
end
|
15
29
|
end
|
16
30
|
end
|
17
31
|
end
|
@@ -11,11 +11,16 @@ module Capistrano
|
|
11
11
|
Rake::Task.define_task(post_task, *args, &block) if block_given?
|
12
12
|
task = Rake::Task[task]
|
13
13
|
task.enhance do
|
14
|
-
Rake.application.lookup(post_task, task.scope)
|
14
|
+
post = Rake.application.lookup(post_task, task.scope)
|
15
|
+
raise ArgumentError, "Task #{post_task.inspect} not found" unless post
|
16
|
+
post.invoke
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
20
|
def remote_file(task)
|
21
|
+
warn("[Deprecation Warning] `remote_file` is deprecated and will be "\
|
22
|
+
"removed in Capistrano 3.7.0")
|
23
|
+
|
19
24
|
target_roles = task.delete(:roles) { :all }
|
20
25
|
define_remote_file_task(task, target_roles)
|
21
26
|
end
|
data/lib/capistrano/i18n.rb
CHANGED
@@ -15,6 +15,7 @@ en = {
|
|
15
15
|
no_old_releases: "No old releases (keeping newest %{keep_releases}) on %{host}",
|
16
16
|
linked_file_does_not_exist: "linked file %{file} does not exist on %{host}",
|
17
17
|
cannot_rollback: "There are no older releases to rollback to",
|
18
|
+
cannot_found_rollback_release: "Cannot rollback because release %{release} does not exist",
|
18
19
|
mirror_exists: "The repository mirror is at %{at}",
|
19
20
|
revision_log_message: "Branch %{branch} (at %{sha}) deployed as release %{release} by %{user}",
|
20
21
|
rollback_log_message: "%{user} rolled back to release %{release}",
|
@@ -24,6 +25,7 @@ en = {
|
|
24
25
|
bye: "bye"
|
25
26
|
},
|
26
27
|
error: {
|
28
|
+
invalid_stage_name: '"%{name}" is a reserved word and cannot be used as a stage. Rename "%{path}" to something else.',
|
27
29
|
user: {
|
28
30
|
does_not_exist: "User %{user} does not exists",
|
29
31
|
cannot_switch: "Cannot switch to user %{user}"
|