capistrano 3.4.1 → 3.5.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.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -5
  3. data/.rubocop.yml +49 -0
  4. data/.travis.yml +5 -4
  5. data/CHANGELOG.md +72 -9
  6. data/CONTRIBUTING.md +61 -93
  7. data/DEVELOPMENT.md +122 -0
  8. data/Gemfile +2 -2
  9. data/LICENSE.txt +1 -1
  10. data/README.md +121 -43
  11. data/RELEASING.md +16 -0
  12. data/Rakefile +4 -1
  13. data/bin/cap +1 -1
  14. data/capistrano.gemspec +16 -21
  15. data/features/doctor.feature +11 -0
  16. data/features/step_definitions/assertions.rb +17 -17
  17. data/features/step_definitions/cap_commands.rb +0 -1
  18. data/features/step_definitions/setup.rb +12 -8
  19. data/features/support/env.rb +5 -5
  20. data/features/support/remote_command_helpers.rb +8 -6
  21. data/features/support/vagrant_helpers.rb +5 -4
  22. data/issue_template.md +21 -0
  23. data/lib/Capfile +5 -1
  24. data/lib/capistrano/all.rb +9 -10
  25. data/lib/capistrano/application.rb +36 -26
  26. data/lib/capistrano/configuration.rb +56 -41
  27. data/lib/capistrano/configuration/empty_filter.rb +9 -0
  28. data/lib/capistrano/configuration/filter.rb +18 -47
  29. data/lib/capistrano/configuration/host_filter.rb +30 -0
  30. data/lib/capistrano/configuration/null_filter.rb +9 -0
  31. data/lib/capistrano/configuration/plugin_installer.rb +33 -0
  32. data/lib/capistrano/configuration/question.rb +10 -7
  33. data/lib/capistrano/configuration/role_filter.rb +30 -0
  34. data/lib/capistrano/configuration/server.rb +22 -23
  35. data/lib/capistrano/configuration/servers.rb +6 -7
  36. data/lib/capistrano/configuration/variables.rb +136 -0
  37. data/lib/capistrano/defaults.rb +13 -3
  38. data/lib/capistrano/deploy.rb +1 -1
  39. data/lib/capistrano/doctor.rb +5 -0
  40. data/lib/capistrano/doctor/environment_doctor.rb +19 -0
  41. data/lib/capistrano/doctor/gems_doctor.rb +45 -0
  42. data/lib/capistrano/doctor/output_helpers.rb +79 -0
  43. data/lib/capistrano/doctor/variables_doctor.rb +66 -0
  44. data/lib/capistrano/dotfile.rb +1 -2
  45. data/lib/capistrano/dsl.rb +12 -14
  46. data/lib/capistrano/dsl/env.rb +11 -42
  47. data/lib/capistrano/dsl/paths.rb +12 -13
  48. data/lib/capistrano/dsl/stages.rb +2 -4
  49. data/lib/capistrano/dsl/task_enhancements.rb +5 -7
  50. data/lib/capistrano/framework.rb +1 -1
  51. data/lib/capistrano/git.rb +17 -9
  52. data/lib/capistrano/hg.rb +4 -4
  53. data/lib/capistrano/i18n.rb +24 -24
  54. data/lib/capistrano/immutable_task.rb +29 -0
  55. data/lib/capistrano/install.rb +1 -1
  56. data/lib/capistrano/plugin.rb +95 -0
  57. data/lib/capistrano/scm.rb +7 -20
  58. data/lib/capistrano/setup.rb +19 -5
  59. data/lib/capistrano/svn.rb +9 -5
  60. data/lib/capistrano/tasks/console.rake +4 -8
  61. data/lib/capistrano/tasks/deploy.rake +75 -62
  62. data/lib/capistrano/tasks/doctor.rake +19 -0
  63. data/lib/capistrano/tasks/framework.rake +13 -14
  64. data/lib/capistrano/tasks/git.rake +10 -11
  65. data/lib/capistrano/tasks/hg.rake +7 -7
  66. data/lib/capistrano/tasks/install.rake +14 -15
  67. data/lib/capistrano/tasks/svn.rake +7 -7
  68. data/lib/capistrano/templates/Capfile +3 -3
  69. data/lib/capistrano/templates/deploy.rb.erb +6 -5
  70. data/lib/capistrano/upload_task.rb +1 -1
  71. data/lib/capistrano/version.rb +1 -1
  72. data/lib/capistrano/version_validator.rb +4 -6
  73. data/spec/integration/dsl_spec.rb +286 -239
  74. data/spec/integration_spec_helper.rb +3 -5
  75. data/spec/lib/capistrano/application_spec.rb +22 -14
  76. data/spec/lib/capistrano/configuration/empty_filter_spec.rb +17 -0
  77. data/spec/lib/capistrano/configuration/filter_spec.rb +82 -84
  78. data/spec/lib/capistrano/configuration/host_filter_spec.rb +61 -0
  79. data/spec/lib/capistrano/configuration/null_filter_spec.rb +17 -0
  80. data/spec/lib/capistrano/configuration/question_spec.rb +12 -16
  81. data/spec/lib/capistrano/configuration/role_filter_spec.rb +64 -0
  82. data/spec/lib/capistrano/configuration/server_spec.rb +102 -110
  83. data/spec/lib/capistrano/configuration/servers_spec.rb +124 -141
  84. data/spec/lib/capistrano/configuration_spec.rb +150 -61
  85. data/spec/lib/capistrano/doctor/environment_doctor_spec.rb +44 -0
  86. data/spec/lib/capistrano/doctor/gems_doctor_spec.rb +61 -0
  87. data/spec/lib/capistrano/doctor/output_helpers_spec.rb +47 -0
  88. data/spec/lib/capistrano/doctor/variables_doctor_spec.rb +79 -0
  89. data/spec/lib/capistrano/dsl/paths_spec.rb +58 -50
  90. data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +62 -32
  91. data/spec/lib/capistrano/dsl_spec.rb +6 -8
  92. data/spec/lib/capistrano/git_spec.rb +35 -7
  93. data/spec/lib/capistrano/hg_spec.rb +14 -5
  94. data/spec/lib/capistrano/immutable_task_spec.rb +31 -0
  95. data/spec/lib/capistrano/plugin_spec.rb +84 -0
  96. data/spec/lib/capistrano/scm_spec.rb +6 -7
  97. data/spec/lib/capistrano/svn_spec.rb +40 -14
  98. data/spec/lib/capistrano/upload_task_spec.rb +7 -7
  99. data/spec/lib/capistrano/version_validator_spec.rb +37 -45
  100. data/spec/lib/capistrano_spec.rb +2 -3
  101. data/spec/spec_helper.rb +8 -8
  102. data/spec/support/Vagrantfile +9 -10
  103. data/spec/support/tasks/database.rake +3 -3
  104. data/spec/support/tasks/fail.rake +4 -3
  105. data/spec/support/tasks/failed.rake +2 -2
  106. data/spec/support/tasks/plugin.rake +6 -0
  107. data/spec/support/tasks/root.rake +4 -4
  108. data/spec/support/test_app.rb +31 -30
  109. metadata +93 -14
@@ -1,55 +1,26 @@
1
- require 'capistrano/configuration'
1
+ require "capistrano/configuration"
2
+ require "capistrano/configuration/empty_filter"
3
+ require "capistrano/configuration/host_filter"
4
+ require "capistrano/configuration/null_filter"
5
+ require "capistrano/configuration/role_filter"
2
6
 
3
7
  module Capistrano
4
8
  class Configuration
5
9
  class Filter
6
- def initialize type, values = nil
7
- raise "Invalid filter type #{type}" unless [:host,:role].include? type
8
- av = Array(values).dup
9
- @mode = case
10
- when av.size == 0 then :none
11
- when av.include?(:all) then :all
12
- else type
13
- end
14
- @rex = case @mode
15
- when :host
16
- av.map!{|v| (v.is_a?(String) && v =~ /^(?<name>[-A-Za-z0-9.]+)(,\g<name>)*$/) ? v.split(',') : v }
17
- av.flatten!
18
- av.map! do |v|
19
- case v
20
- when Regexp then v
21
- else
22
- vs = v.to_s
23
- vs =~ /^[-A-Za-z0-9.]+$/ ? vs : Regexp.new(vs)
24
- end
25
- end
26
- Regexp.union av
27
- when :role
28
- av.map!{|v| v.is_a?(String) ? v.split(',') : v }
29
- av.flatten!
30
- av.map! do |v|
31
- case v
32
- when Regexp then v
33
- else
34
- vs = v.to_s
35
- vs =~ %r{^/(.+)/$} ? Regexp.new($1) : %r{^#{vs}$}
36
- end
37
- end
38
- Regexp.union av
39
- else
40
- nil
41
- end
10
+ def initialize(type, values=nil)
11
+ raise "Invalid filter type #{type}" unless [:host, :role].include? type
12
+ av = Array(values)
13
+ @strategy = case
14
+ when av.empty? then EmptyFilter.new
15
+ when av.include?(:all), av.include?("all") then NullFilter.new
16
+ when type == :host then HostFilter.new(values)
17
+ when type == :role then RoleFilter.new(values)
18
+ else NullFilter.new
19
+ end
42
20
  end
43
- def filter servers
44
- as = Array(servers)
45
- case @mode
46
- when :none then return []
47
- when :all then return servers
48
- when :host
49
- as.select {|s| @rex.match s.hostname}
50
- when :role
51
- as.select {|s| s.roles.any? {|r| @rex.match r} }
52
- end
21
+
22
+ def filter(servers)
23
+ @strategy.filter servers
53
24
  end
54
25
  end
55
26
  end
@@ -0,0 +1,30 @@
1
+ module Capistrano
2
+ class Configuration
3
+ class HostFilter
4
+ def initialize(values)
5
+ av = Array(values).dup
6
+ av.map! { |v| (v.is_a?(String) && v =~ /^(?<name>[-A-Za-z0-9.]+)(,\g<name>)*$/) ? v.split(",") : v }
7
+ av.flatten!
8
+ @rex = regex_matcher(av)
9
+ end
10
+
11
+ def filter(servers)
12
+ Array(servers).select { |s| @rex.match s.to_s }
13
+ end
14
+
15
+ private
16
+
17
+ def regex_matcher(values)
18
+ values.map! do |v|
19
+ case v
20
+ when Regexp then v
21
+ else
22
+ vs = v.to_s
23
+ vs =~ /^[-A-Za-z0-9.]+$/ ? vs : Regexp.new(vs)
24
+ end
25
+ end
26
+ Regexp.union values
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ module Capistrano
2
+ class Configuration
3
+ class NullFilter
4
+ def filter(servers)
5
+ servers
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ # Encapsulates the logic for installing plugins into Capistrano. Plugins must
2
+ # simply conform to a basic API; the PluginInstaller takes care of invoking the
3
+ # API at appropriate times.
4
+ #
5
+ # This class is not used directly; instead it is typically accessed via the
6
+ # `install_plugin` method of the Capistrano DSL.
7
+ #
8
+ module Capistrano
9
+ class Configuration
10
+ class PluginInstaller
11
+ # "Installs" a Plugin into Capistrano by loading its tasks, hooks, and
12
+ # defaults at the appropriate time. The hooks in particular can be
13
+ # skipped, if you want full control over when and how the plugin's tasks
14
+ # are executed. Simply pass `load_hooks:false` to opt out.
15
+ #
16
+ # The plugin class or instance may be provided. These are equivalent:
17
+ #
18
+ # install(Capistrano::SCM::Git)
19
+ # install(Capistrano::SCM::Git.new)
20
+ #
21
+ def install(plugin, load_hooks:true)
22
+ plugin = plugin.is_a?(Class) ? plugin.new : plugin
23
+
24
+ plugin.define_tasks
25
+ plugin.register_hooks if load_hooks
26
+
27
+ Rake::Task.define_task("load:defaults") do
28
+ plugin.set_defaults
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,9 +1,10 @@
1
1
  module Capistrano
2
2
  class Configuration
3
3
  class Question
4
-
5
- def initialize(key, default, options = {})
6
- @key, @default, @options = key, default, options
4
+ def initialize(key, default, options={})
5
+ @key = key
6
+ @default = default
7
+ @options = options
7
8
  end
8
9
 
9
10
  def call
@@ -12,6 +13,7 @@ module Capistrano
12
13
  end
13
14
 
14
15
  private
16
+
15
17
  attr_reader :key, :default, :options
16
18
 
17
19
  def ask_question
@@ -28,20 +30,21 @@ module Capistrano
28
30
 
29
31
  def response
30
32
  return @response if defined? @response
31
-
33
+
32
34
  @response = (gets || "").chomp
33
35
  end
34
-
36
+
35
37
  def gets
36
38
  if echo?
37
39
  $stdin.gets
38
40
  else
39
- $stdin.noecho(&:gets).tap{ $stdout.print "\n" }
41
+ $stdin.noecho(&:gets).tap { $stdout.print "\n" }
40
42
  end
41
43
  rescue Errno::EIO
42
44
  # when stdio gets closed
45
+ return
43
46
  end
44
-
47
+
45
48
  def question
46
49
  I18n.t(:question, key: key, default_value: default, scope: :capistrano)
47
50
  end
@@ -0,0 +1,30 @@
1
+ module Capistrano
2
+ class Configuration
3
+ class RoleFilter
4
+ def initialize(values)
5
+ av = Array(values).dup
6
+ av.map! { |v| v.is_a?(String) ? v.split(",") : v }
7
+ av.flatten!
8
+ @rex = regex_matcher(av)
9
+ end
10
+
11
+ def filter(servers)
12
+ Array(servers).select { |s| s.is_a?(String) ? false : s.roles.any? { |r| @rex.match r } }
13
+ end
14
+
15
+ private
16
+
17
+ def regex_matcher(values)
18
+ values.map! do |v|
19
+ case v
20
+ when Regexp then v
21
+ else
22
+ vs = v.to_s
23
+ vs =~ %r{^/(.+)/$} ? Regexp.new($1) : /^#{vs}$/
24
+ end
25
+ end
26
+ Regexp.union values
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,4 +1,4 @@
1
- require 'set'
1
+ require "set"
2
2
  module Capistrano
3
3
  class Configuration
4
4
  class Server < SSHKit::Host
@@ -25,19 +25,21 @@ module Capistrano
25
25
  end
26
26
 
27
27
  def select?(options)
28
- options.each do |k,v|
29
- callable = v.respond_to?(:call) ? v: ->(server){server.fetch(v)}
30
- result = case k
31
- when :filter, :select
32
- callable.call(self)
33
- when :exclude
34
- !callable.call(self)
35
- else
36
- self.fetch(k) == v
37
- end
28
+ options.each do |k, v|
29
+ callable = v.respond_to?(:call) ? v : ->(server) { server.fetch(v) }
30
+ result = \
31
+ case k
32
+ when :filter, :select
33
+ callable.call(self)
34
+ when :exclude
35
+ !callable.call(self)
36
+ else
37
+ fetch(k) == v
38
+ end
38
39
  return false unless result
39
40
  end
40
- return true
41
+
42
+ true
41
43
  end
42
44
 
43
45
  def primary
@@ -54,7 +56,7 @@ module Capistrano
54
56
  end
55
57
 
56
58
  def netssh_options
57
- @netssh_options ||= super.merge( fetch(:ssh_options) || {} )
59
+ @netssh_options ||= super.merge(fetch(:ssh_options) || {})
58
60
  end
59
61
 
60
62
  def roles_array
@@ -62,7 +64,7 @@ module Capistrano
62
64
  end
63
65
 
64
66
  def matches?(other)
65
- hostname == other.hostname
67
+ hostname == other.hostname && port == other.port
66
68
  end
67
69
 
68
70
  private
@@ -76,18 +78,17 @@ module Capistrano
76
78
  end
77
79
 
78
80
  class Properties
79
-
80
81
  def initialize
81
82
  @properties = {}
82
83
  end
83
84
 
84
85
  def set(key, value)
85
86
  pval = @properties[key]
86
- if pval.is_a? Hash and value.is_a? Hash
87
+ if pval.is_a?(Hash) && value.is_a?(Hash)
87
88
  pval.merge!(value)
88
- elsif pval.is_a? Set and value.is_a? Set
89
+ elsif pval.is_a?(Set) && value.is_a?(Set)
89
90
  pval.merge(value)
90
- elsif pval.is_a? Array and value.is_a? Array
91
+ elsif pval.is_a?(Array) && value.is_a?(Array)
91
92
  pval.concat value
92
93
  else
93
94
  @properties[key] = value
@@ -98,8 +99,8 @@ module Capistrano
98
99
  @properties[key]
99
100
  end
100
101
 
101
- def respond_to?(method, include_all=false)
102
- @properties.has_key?(method)
102
+ def respond_to?(method, _include_all=false)
103
+ @properties.key?(method)
103
104
  end
104
105
 
105
106
  def roles
@@ -121,11 +122,9 @@ module Capistrano
121
122
  private
122
123
 
123
124
  def lvalue(key)
124
- key.to_s.chomp('=').to_sym
125
+ key.to_s.chomp("=").to_sym
125
126
  end
126
-
127
127
  end
128
-
129
128
  end
130
129
  end
131
130
  end
@@ -1,6 +1,6 @@
1
- require 'set'
2
- require 'capistrano/configuration'
3
- require 'capistrano/configuration/filter'
1
+ require "set"
2
+ require "capistrano/configuration"
3
+ require "capistrano/configuration/filter"
4
4
 
5
5
  module Capistrano
6
6
  class Configuration
@@ -9,9 +9,8 @@ module Capistrano
9
9
 
10
10
  def add_host(host, properties={})
11
11
  new_host = Server[host]
12
- if server = servers.find { |s| s.matches? new_host }
12
+ if (server = servers.find { |s| s.matches? new_host })
13
13
  server.user = new_host.user if new_host.user
14
- server.port = new_host.port if new_host.port
15
14
  server.with(properties)
16
15
  else
17
16
  servers << new_host.with(properties)
@@ -38,12 +37,12 @@ module Capistrano
38
37
  if block_given?
39
38
  yield host, role, props
40
39
  else
41
- rps << (props || {}).merge( role: role, hostname: host.hostname )
40
+ rps << (props || {}).merge(role: role, hostname: host.hostname)
42
41
  end
43
42
  end
44
43
  end
45
44
  end
46
- block_given? ? nil: rps
45
+ block_given? ? nil : rps
47
46
  end
48
47
 
49
48
  def fetch_primary(role)
@@ -0,0 +1,136 @@
1
+ module Capistrano
2
+ class Configuration
3
+ # Holds the variables assigned at Capistrano runtime via `set` and retrieved
4
+ # with `fetch`. Does internal bookkeeping to help identify user mistakes
5
+ # like spelling errors or unused variables that may lead to unexpected
6
+ # behavior. Also allows validation rules to be registered with `validate`.
7
+ class Variables
8
+ CAPISTRANO_LOCATION = File.expand_path("../..", __FILE__).freeze
9
+ IGNORED_LOCATIONS = [
10
+ "#{CAPISTRANO_LOCATION}/configuration/variables.rb:",
11
+ "#{CAPISTRANO_LOCATION}/configuration.rb:",
12
+ "#{CAPISTRANO_LOCATION}/dsl/env.rb:",
13
+ "/dsl.rb:",
14
+ "/forwardable.rb:"
15
+ ].freeze
16
+ private_constant :CAPISTRANO_LOCATION, :IGNORED_LOCATIONS
17
+
18
+ def initialize(values={})
19
+ @trusted_keys = []
20
+ @fetched_keys = []
21
+ @locations = {}
22
+ @values = values
23
+ @trusted = true
24
+ end
25
+
26
+ def untrusted!
27
+ @trusted = false
28
+ yield
29
+ ensure
30
+ @trusted = true
31
+ end
32
+
33
+ def set(key, value=nil, &block)
34
+ invoke_validations(key, value, &block)
35
+ @trusted_keys << key if trusted?
36
+ remember_location(key)
37
+ values[key] = block || value
38
+ trace_set(key)
39
+ values[key]
40
+ end
41
+
42
+ def fetch(key, default=nil, &block)
43
+ fetched_keys << key
44
+ peek(key, default, &block)
45
+ end
46
+
47
+ # Internal use only.
48
+ def peek(key, default=nil, &block)
49
+ value = fetch_for(key, default, &block)
50
+ while callable_without_parameters?(value)
51
+ value = (values[key] = value.call)
52
+ end
53
+ value
54
+ end
55
+
56
+ def fetch_for(key, default, &block)
57
+ block ? values.fetch(key, &block) : values.fetch(key, default)
58
+ end
59
+
60
+ def delete(key)
61
+ values.delete(key)
62
+ end
63
+
64
+ def validate(key, &validator)
65
+ vs = (validators[key] || [])
66
+ vs << validator
67
+ validators[key] = vs
68
+ end
69
+
70
+ def trusted_keys
71
+ @trusted_keys.dup
72
+ end
73
+
74
+ def untrusted_keys
75
+ keys - @trusted_keys
76
+ end
77
+
78
+ def keys
79
+ values.keys
80
+ end
81
+
82
+ # Keys that have been set, but which have never been fetched.
83
+ def unused_keys
84
+ keys - fetched_keys
85
+ end
86
+
87
+ # Returns an array of source file location(s) where the given key was
88
+ # assigned (i.e. where `set` was called). If the key was never assigned,
89
+ # returns `nil`.
90
+ def source_locations(key)
91
+ locations[key]
92
+ end
93
+
94
+ private
95
+
96
+ attr_reader :locations, :values, :fetched_keys
97
+
98
+ def trusted?
99
+ @trusted
100
+ end
101
+
102
+ def remember_location(key)
103
+ location = caller.find do |line|
104
+ IGNORED_LOCATIONS.none? { |i| line.include?(i) }
105
+ end
106
+ (locations[key] ||= []) << location
107
+ end
108
+
109
+ def callable_without_parameters?(x)
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)
118
+ unless value.nil? || block.nil?
119
+ raise Capistrano::ValidationError,
120
+ "Value and block both passed to Configuration#set"
121
+ end
122
+
123
+ return unless validators.key? key
124
+
125
+ validators[key].each do |validator|
126
+ validator.call(key, block || value)
127
+ end
128
+ end
129
+
130
+ def trace_set(key)
131
+ return unless fetch(:print_config_variables, false)
132
+ puts "Config variable set: #{key.inspect} => #{values[key].inspect}"
133
+ end
134
+ end
135
+ end
136
+ end