capistrano 3.4.1 → 3.5.0

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