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,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}"
|