capistrano 3.4.0 → 3.17.1

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 (138) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +129 -0
  3. data/.github/issue_template.md +19 -0
  4. data/.github/pull_request_template.md +22 -0
  5. data/.github/release-drafter.yml +17 -0
  6. data/.github/workflows/push.yml +12 -0
  7. data/.gitignore +8 -5
  8. data/.rubocop.yml +62 -0
  9. data/CHANGELOG.md +1 -307
  10. data/CONTRIBUTING.md +63 -93
  11. data/DEVELOPMENT.md +127 -0
  12. data/Dangerfile +1 -0
  13. data/Gemfile +40 -3
  14. data/LICENSE.txt +1 -1
  15. data/README.md +127 -44
  16. data/RELEASING.md +17 -0
  17. data/Rakefile +13 -2
  18. data/UPGRADING-3.7.md +86 -0
  19. data/bin/cap +1 -1
  20. data/capistrano.gemspec +21 -24
  21. data/features/deploy.feature +35 -1
  22. data/features/doctor.feature +11 -0
  23. data/features/installation.feature +8 -3
  24. data/features/stage_failure.feature +9 -0
  25. data/features/step_definitions/assertions.rb +51 -18
  26. data/features/step_definitions/cap_commands.rb +9 -0
  27. data/features/step_definitions/setup.rb +53 -9
  28. data/features/subdirectory.feature +9 -0
  29. data/features/support/env.rb +5 -5
  30. data/features/support/remote_command_helpers.rb +12 -6
  31. data/features/support/vagrant_helpers.rb +17 -11
  32. data/lib/Capfile +1 -1
  33. data/lib/capistrano/all.rb +10 -10
  34. data/lib/capistrano/application.rb +47 -34
  35. data/lib/capistrano/configuration/empty_filter.rb +9 -0
  36. data/lib/capistrano/configuration/filter.rb +17 -47
  37. data/lib/capistrano/configuration/host_filter.rb +29 -0
  38. data/lib/capistrano/configuration/null_filter.rb +9 -0
  39. data/lib/capistrano/configuration/plugin_installer.rb +51 -0
  40. data/lib/capistrano/configuration/question.rb +31 -9
  41. data/lib/capistrano/configuration/role_filter.rb +29 -0
  42. data/lib/capistrano/configuration/scm_resolver.rb +149 -0
  43. data/lib/capistrano/configuration/server.rb +29 -23
  44. data/lib/capistrano/configuration/servers.rb +21 -14
  45. data/lib/capistrano/configuration/validated_variables.rb +110 -0
  46. data/lib/capistrano/configuration/variables.rb +112 -0
  47. data/lib/capistrano/configuration.rb +91 -44
  48. data/lib/capistrano/defaults.rb +26 -4
  49. data/lib/capistrano/deploy.rb +1 -1
  50. data/lib/capistrano/doctor/environment_doctor.rb +19 -0
  51. data/lib/capistrano/doctor/gems_doctor.rb +45 -0
  52. data/lib/capistrano/doctor/output_helpers.rb +79 -0
  53. data/lib/capistrano/doctor/servers_doctor.rb +105 -0
  54. data/lib/capistrano/doctor/variables_doctor.rb +74 -0
  55. data/lib/capistrano/doctor.rb +6 -0
  56. data/lib/capistrano/dotfile.rb +1 -2
  57. data/lib/capistrano/dsl/env.rb +9 -47
  58. data/lib/capistrano/dsl/paths.rb +11 -25
  59. data/lib/capistrano/dsl/stages.rb +14 -2
  60. data/lib/capistrano/dsl/task_enhancements.rb +7 -12
  61. data/lib/capistrano/dsl.rb +47 -16
  62. data/lib/capistrano/framework.rb +1 -1
  63. data/lib/capistrano/i18n.rb +32 -24
  64. data/lib/capistrano/immutable_task.rb +30 -0
  65. data/lib/capistrano/install.rb +1 -1
  66. data/lib/capistrano/plugin.rb +95 -0
  67. data/lib/capistrano/proc_helpers.rb +13 -0
  68. data/lib/capistrano/scm/git.rb +100 -0
  69. data/lib/capistrano/scm/hg.rb +55 -0
  70. data/lib/capistrano/scm/plugin.rb +13 -0
  71. data/lib/capistrano/scm/svn.rb +56 -0
  72. data/lib/capistrano/scm/tasks/git.rake +73 -0
  73. data/lib/capistrano/scm/tasks/hg.rake +53 -0
  74. data/lib/capistrano/scm/tasks/svn.rake +53 -0
  75. data/lib/capistrano/scm.rb +7 -20
  76. data/lib/capistrano/setup.rb +20 -6
  77. data/lib/capistrano/tasks/console.rake +4 -8
  78. data/lib/capistrano/tasks/deploy.rake +105 -73
  79. data/lib/capistrano/tasks/doctor.rake +24 -0
  80. data/lib/capistrano/tasks/framework.rake +13 -14
  81. data/lib/capistrano/tasks/install.rake +14 -15
  82. data/lib/capistrano/templates/Capfile +21 -10
  83. data/lib/capistrano/templates/deploy.rb.erb +17 -26
  84. data/lib/capistrano/templates/stage.rb.erb +9 -9
  85. data/lib/capistrano/upload_task.rb +1 -1
  86. data/lib/capistrano/version.rb +1 -1
  87. data/lib/capistrano/version_validator.rb +5 -10
  88. data/spec/integration/dsl_spec.rb +289 -240
  89. data/spec/integration_spec_helper.rb +3 -5
  90. data/spec/lib/capistrano/application_spec.rb +23 -39
  91. data/spec/lib/capistrano/configuration/empty_filter_spec.rb +17 -0
  92. data/spec/lib/capistrano/configuration/filter_spec.rb +83 -85
  93. data/spec/lib/capistrano/configuration/host_filter_spec.rb +71 -0
  94. data/spec/lib/capistrano/configuration/null_filter_spec.rb +17 -0
  95. data/spec/lib/capistrano/configuration/plugin_installer_spec.rb +98 -0
  96. data/spec/lib/capistrano/configuration/question_spec.rb +58 -26
  97. data/spec/lib/capistrano/configuration/role_filter_spec.rb +80 -0
  98. data/spec/lib/capistrano/configuration/scm_resolver_spec.rb +55 -0
  99. data/spec/lib/capistrano/configuration/server_spec.rb +106 -113
  100. data/spec/lib/capistrano/configuration/servers_spec.rb +129 -145
  101. data/spec/lib/capistrano/configuration_spec.rb +224 -63
  102. data/spec/lib/capistrano/doctor/environment_doctor_spec.rb +44 -0
  103. data/spec/lib/capistrano/doctor/gems_doctor_spec.rb +67 -0
  104. data/spec/lib/capistrano/doctor/output_helpers_spec.rb +47 -0
  105. data/spec/lib/capistrano/doctor/servers_doctor_spec.rb +86 -0
  106. data/spec/lib/capistrano/doctor/variables_doctor_spec.rb +89 -0
  107. data/spec/lib/capistrano/dsl/paths_spec.rb +97 -59
  108. data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +57 -37
  109. data/spec/lib/capistrano/dsl_spec.rb +84 -11
  110. data/spec/lib/capistrano/immutable_task_spec.rb +31 -0
  111. data/spec/lib/capistrano/plugin_spec.rb +84 -0
  112. data/spec/lib/capistrano/scm/git_spec.rb +184 -0
  113. data/spec/lib/capistrano/scm/hg_spec.rb +109 -0
  114. data/spec/lib/capistrano/scm/svn_spec.rb +137 -0
  115. data/spec/lib/capistrano/scm_spec.rb +7 -8
  116. data/spec/lib/capistrano/upload_task_spec.rb +7 -7
  117. data/spec/lib/capistrano/version_validator_spec.rb +61 -46
  118. data/spec/lib/capistrano_spec.rb +2 -3
  119. data/spec/spec_helper.rb +21 -8
  120. data/spec/support/Vagrantfile +9 -10
  121. data/spec/support/tasks/database.rake +3 -3
  122. data/spec/support/tasks/fail.rake +4 -3
  123. data/spec/support/tasks/failed.rake +2 -2
  124. data/spec/support/tasks/plugin.rake +6 -0
  125. data/spec/support/tasks/root.rake +4 -4
  126. data/spec/support/test_app.rb +64 -39
  127. metadata +100 -55
  128. data/.travis.yml +0 -13
  129. data/features/remote_file_task.feature +0 -14
  130. data/lib/capistrano/git.rb +0 -46
  131. data/lib/capistrano/hg.rb +0 -43
  132. data/lib/capistrano/svn.rb +0 -38
  133. data/lib/capistrano/tasks/git.rake +0 -81
  134. data/lib/capistrano/tasks/hg.rake +0 -52
  135. data/lib/capistrano/tasks/svn.rake +0 -52
  136. data/spec/lib/capistrano/git_spec.rb +0 -81
  137. data/spec/lib/capistrano/hg_spec.rb +0 -81
  138. data/spec/lib/capistrano/svn_spec.rb +0 -79
@@ -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,8 @@ module Capistrano
62
64
  end
63
65
 
64
66
  def matches?(other)
65
- hostname == other.hostname
67
+ # This matching logic must stay in sync with `Servers#add_host`.
68
+ hostname == other.hostname && port == other.port
66
69
  end
67
70
 
68
71
  private
@@ -76,18 +79,17 @@ module Capistrano
76
79
  end
77
80
 
78
81
  class Properties
79
-
80
82
  def initialize
81
83
  @properties = {}
82
84
  end
83
85
 
84
86
  def set(key, value)
85
87
  pval = @properties[key]
86
- if pval.is_a? Hash and value.is_a? Hash
88
+ if pval.is_a?(Hash) && value.is_a?(Hash)
87
89
  pval.merge!(value)
88
- elsif pval.is_a? Set and value.is_a? Set
90
+ elsif pval.is_a?(Set) && value.is_a?(Set)
89
91
  pval.merge(value)
90
- elsif pval.is_a? Array and value.is_a? Array
92
+ elsif pval.is_a?(Array) && value.is_a?(Array)
91
93
  pval.concat value
92
94
  else
93
95
  @properties[key] = value
@@ -98,8 +100,8 @@ module Capistrano
98
100
  @properties[key]
99
101
  end
100
102
 
101
- def respond_to?(method, include_all=false)
102
- @properties.has_key?(method)
103
+ def respond_to_missing?(method, _include_all=false)
104
+ @properties.key?(method) || super
103
105
  end
104
106
 
105
107
  def roles
@@ -110,6 +112,7 @@ module Capistrano
110
112
  @properties.keys
111
113
  end
112
114
 
115
+ # rubocop:disable Style/MethodMissing
113
116
  def method_missing(key, value=nil)
114
117
  if value
115
118
  set(lvalue(key), value)
@@ -117,15 +120,18 @@ module Capistrano
117
120
  fetch(key)
118
121
  end
119
122
  end
123
+ # rubocop:enable Style/MethodMissing
124
+
125
+ def to_h
126
+ @properties
127
+ end
120
128
 
121
129
  private
122
130
 
123
131
  def lvalue(key)
124
- key.to_s.chomp('=').to_sym
132
+ key.to_s.chomp("=").to_sym
125
133
  end
126
-
127
134
  end
128
-
129
135
  end
130
136
  end
131
137
  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,23 +9,28 @@ 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 }
13
- server.user = new_host.user if new_host.user
14
- server.port = new_host.port if new_host.port
15
- server.with(properties)
12
+ new_host.port = properties[:port] if properties.key?(:port)
13
+ # This matching logic must stay in sync with `Server#matches?`.
14
+ key = ServerKey.new(new_host.hostname, new_host.port)
15
+ existing = servers_by_key[key]
16
+ if existing
17
+ existing.user = new_host.user if new_host.user
18
+ existing.with(properties)
16
19
  else
17
- servers << new_host.with(properties)
20
+ servers_by_key[key] = new_host.with(properties)
18
21
  end
19
22
  end
20
23
 
24
+ # rubocop:disable Security/MarshalLoad
21
25
  def add_role(role, hosts, options={})
22
26
  options_deepcopy = Marshal.dump(options.merge(roles: role))
23
27
  Array(hosts).each { |host| add_host(host, Marshal.load(options_deepcopy)) }
24
28
  end
29
+ # rubocop:enable Security/MarshalLoad
25
30
 
26
31
  def roles_for(names)
27
32
  options = extract_options(names)
28
- s = Filter.new(:role, names).filter(servers)
33
+ s = Filter.new(:role, names).filter(servers_by_key.values)
29
34
  s.select { |server| server.select?(options) }
30
35
  end
31
36
 
@@ -38,12 +43,12 @@ module Capistrano
38
43
  if block_given?
39
44
  yield host, role, props
40
45
  else
41
- rps << (props || {}).merge( role: role, hostname: host.hostname )
46
+ rps << (props || {}).merge(role: role, hostname: host.hostname)
42
47
  end
43
48
  end
44
49
  end
45
50
  end
46
- block_given? ? nil: rps
51
+ block_given? ? nil : rps
47
52
  end
48
53
 
49
54
  def fetch_primary(role)
@@ -52,13 +57,15 @@ module Capistrano
52
57
  end
53
58
 
54
59
  def each
55
- servers.each { |server| yield server }
60
+ servers_by_key.values.each { |server| yield server }
56
61
  end
57
62
 
58
63
  private
59
64
 
60
- def servers
61
- @servers ||= []
65
+ ServerKey = Struct.new(:hostname, :port)
66
+
67
+ def servers_by_key
68
+ @servers_by_key ||= {}
62
69
  end
63
70
 
64
71
  def extract_options(array)
@@ -0,0 +1,110 @@
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 callable value or a block, validation is not
11
+ # performed immediately. Instead, the validation rules are invoked the first
12
+ # time `fetch` is used 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
+ assert_value_or_block_not_both(value, block)
35
+
36
+ # Skip validation behavior if no validators are registered for this key
37
+ return super unless validators.key?(key)
38
+
39
+ value_to_evaluate = block || value
40
+
41
+ if callable_without_parameters?(value_to_evaluate)
42
+ super(key, assert_valid_later(key, value_to_evaluate), &nil)
43
+ else
44
+ assert_valid_now(key, value_to_evaluate)
45
+ super
46
+ end
47
+ end
48
+
49
+ # Register a validation rule for the given key.
50
+ def validate(key, &validator)
51
+ vs = (validators[key] || [])
52
+ vs << validator
53
+ validators[key] = vs
54
+ end
55
+
56
+ private
57
+
58
+ attr_reader :validators
59
+
60
+ # Given a callable that provides a value, wrap the callable with another
61
+ # object that responds to `call`. This new object will perform validation
62
+ # and then return the original callable's value.
63
+ #
64
+ # If the callable is a `Question`, the object returned by this method will
65
+ # also be a `Question` (a `ValidatedQuestion`, to be precise). This
66
+ # ensures that `is_a?(Question)` remains true even after the validation
67
+ # wrapper is applied. This is needed so that `Configuration#is_question?`
68
+ # works as expected.
69
+ #
70
+ def assert_valid_later(key, callable)
71
+ validation_callback = lambda do
72
+ value = callable.call
73
+ assert_valid_now(key, value)
74
+ value
75
+ end
76
+
77
+ if callable.is_a?(Question)
78
+ ValidatedQuestion.new(validation_callback)
79
+ else
80
+ validation_callback
81
+ end
82
+ end
83
+
84
+ # Runs all validation rules registered for the given key against the
85
+ # user-supplied value for that variable. If no validator raises an
86
+ # exception, the value is assumed to be valid.
87
+ def assert_valid_now(key, value)
88
+ validators[key].each do |validator|
89
+ validator.call(key, value)
90
+ end
91
+ end
92
+
93
+ def assert_value_or_block_not_both(value, block)
94
+ return if value.nil? || block.nil?
95
+ raise Capistrano::ValidationError,
96
+ "Value and block both passed to Configuration#set"
97
+ end
98
+
99
+ class ValidatedQuestion < Question
100
+ def initialize(validator)
101
+ @validator = validator
102
+ end
103
+
104
+ def call
105
+ @validator.call
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,112 @@
1
+ require "capistrano/proc_helpers"
2
+
3
+ module Capistrano
4
+ class Configuration
5
+ # Holds the variables assigned at Capistrano runtime via `set` and retrieved
6
+ # with `fetch`. Does internal bookkeeping to help identify user mistakes
7
+ # like spelling errors or unused variables that may lead to unexpected
8
+ # behavior.
9
+ class Variables
10
+ CAPISTRANO_LOCATION = File.expand_path("../..", __FILE__).freeze
11
+ IGNORED_LOCATIONS = [
12
+ "#{CAPISTRANO_LOCATION}/configuration/variables.rb:",
13
+ "#{CAPISTRANO_LOCATION}/configuration.rb:",
14
+ "#{CAPISTRANO_LOCATION}/dsl/env.rb:",
15
+ "/dsl.rb:",
16
+ "/forwardable.rb:"
17
+ ].freeze
18
+ private_constant :CAPISTRANO_LOCATION, :IGNORED_LOCATIONS
19
+
20
+ include Capistrano::ProcHelpers
21
+
22
+ def initialize(values={})
23
+ @trusted_keys = []
24
+ @fetched_keys = []
25
+ @locations = {}
26
+ @values = values
27
+ @trusted = true
28
+ end
29
+
30
+ def untrusted!
31
+ @trusted = false
32
+ yield
33
+ ensure
34
+ @trusted = true
35
+ end
36
+
37
+ def set(key, value=nil, &block)
38
+ @trusted_keys << key if trusted? && !@trusted_keys.include?(key)
39
+ remember_location(key)
40
+ values[key] = block || value
41
+ trace_set(key)
42
+ values[key]
43
+ end
44
+
45
+ def fetch(key, default=nil, &block)
46
+ fetched_keys << key unless fetched_keys.include?(key)
47
+ peek(key, default, &block)
48
+ end
49
+
50
+ # Internal use only.
51
+ def peek(key, default=nil, &block)
52
+ value = fetch_for(key, default, &block)
53
+ while callable_without_parameters?(value)
54
+ value = (values[key] = value.call)
55
+ end
56
+ value
57
+ end
58
+
59
+ def fetch_for(key, default, &block)
60
+ block ? values.fetch(key, &block) : values.fetch(key, default)
61
+ end
62
+
63
+ def delete(key)
64
+ values.delete(key)
65
+ end
66
+
67
+ def trusted_keys
68
+ @trusted_keys.dup
69
+ end
70
+
71
+ def untrusted_keys
72
+ keys - @trusted_keys
73
+ end
74
+
75
+ def keys
76
+ values.keys
77
+ end
78
+
79
+ # Keys that have been set, but which have never been fetched.
80
+ def unused_keys
81
+ keys - fetched_keys
82
+ end
83
+
84
+ # Returns an array of source file location(s) where the given key was
85
+ # assigned (i.e. where `set` was called). If the key was never assigned,
86
+ # returns `nil`.
87
+ def source_locations(key)
88
+ locations[key]
89
+ end
90
+
91
+ private
92
+
93
+ attr_reader :locations, :values, :fetched_keys
94
+
95
+ def trusted?
96
+ @trusted
97
+ end
98
+
99
+ def remember_location(key)
100
+ location = caller.find do |line|
101
+ IGNORED_LOCATIONS.none? { |i| line.include?(i) }
102
+ end
103
+ (locations[key] ||= []) << location
104
+ end
105
+
106
+ def trace_set(key)
107
+ return unless fetch(:print_config_variables, false)
108
+ puts "Config variable set: #{key.inspect} => #{values[key].inspect}"
109
+ end
110
+ end
111
+ end
112
+ end
@@ -1,15 +1,15 @@
1
- require_relative 'configuration/filter'
2
- require_relative 'configuration/question'
3
- require_relative 'configuration/server'
4
- require_relative 'configuration/servers'
1
+ require_relative "configuration/filter"
2
+ require_relative "configuration/question"
3
+ require_relative "configuration/plugin_installer"
4
+ require_relative "configuration/server"
5
+ require_relative "configuration/servers"
6
+ require_relative "configuration/validated_variables"
7
+ require_relative "configuration/variables"
5
8
 
6
9
  module Capistrano
7
- class Configuration
8
-
9
- def initialize(config = nil)
10
- @config ||= config
11
- end
10
+ class ValidationError < RuntimeError; end
12
11
 
12
+ class Configuration
13
13
  def self.env
14
14
  @env ||= new
15
15
  end
@@ -18,38 +18,53 @@ module Capistrano
18
18
  @env = new
19
19
  end
20
20
 
21
+ extend Forwardable
22
+ attr_reader :variables
23
+ def_delegators :variables,
24
+ :set, :fetch, :fetch_for, :delete, :keys, :validate
25
+
26
+ def initialize(values={})
27
+ @variables = ValidatedVariables.new(Variables.new(values))
28
+ end
29
+
21
30
  def ask(key, default=nil, options={})
22
31
  question = Question.new(key, default, options)
23
32
  set(key, question)
24
33
  end
25
34
 
26
- def set(key, value)
27
- config[key] = value
35
+ def set_if_empty(key, value=nil, &block)
36
+ set(key, value, &block) unless keys.include?(key)
28
37
  end
29
38
 
30
- def set_if_empty(key, value)
31
- config[key] = value unless config.has_key? key
39
+ def append(key, *values)
40
+ set(key, Array(fetch(key)).concat(values))
32
41
  end
33
42
 
34
- def delete(key)
35
- config.delete(key)
43
+ def remove(key, *values)
44
+ set(key, Array(fetch(key)) - values)
36
45
  end
37
46
 
38
- def fetch(key, default=nil, &block)
39
- value = fetch_for(key, default, &block)
40
- while callable_without_parameters?(value)
41
- value = set(key, value.call)
47
+ def any?(key)
48
+ value = fetch(key)
49
+ if value && value.respond_to?(:any?)
50
+ begin
51
+ return value.any?
52
+ rescue ArgumentError # rubocop:disable Lint/HandleExceptions
53
+ # Gracefully ignore values whose `any?` method doesn't accept 0 args
54
+ end
42
55
  end
43
- return value
56
+
57
+ !value.nil?
44
58
  end
45
59
 
46
- def keys
47
- config.keys
60
+ def is_question?(key)
61
+ value = fetch_for(key, nil)
62
+ !value.nil? && value.is_a?(Question)
48
63
  end
49
64
 
50
65
  def role(name, hosts, options={})
51
66
  if name == :all
52
- raise ArgumentError.new("#{name} reserved name for role. Please choose another name")
67
+ raise ArgumentError, "#{name} reserved name for role. Please choose another name"
53
68
  end
54
69
 
55
70
  servers.add_role(name, hosts, options)
@@ -79,27 +94,50 @@ module Capistrano
79
94
 
80
95
  def configure_backend
81
96
  backend.configure do |sshkit|
82
- sshkit.format = fetch(:format)
97
+ configure_sshkit_output(sshkit)
83
98
  sshkit.output_verbosity = fetch(:log_level)
84
99
  sshkit.default_env = fetch(:default_env)
85
100
  sshkit.backend = fetch(:sshkit_backend, SSHKit::Backend::Netssh)
86
101
  sshkit.backend.configure do |backend|
87
102
  backend.pty = fetch(:pty)
88
103
  backend.connection_timeout = fetch(:connection_timeout)
89
- backend.ssh_options = (backend.ssh_options || {}).merge(fetch(:ssh_options,{}))
104
+ backend.ssh_options = (backend.ssh_options || {}).merge(fetch(:ssh_options, {}))
90
105
  end
91
106
  end
92
107
  end
93
108
 
109
+ def configure_scm
110
+ Capistrano::Configuration::SCMResolver.new.resolve
111
+ end
112
+
94
113
  def timestamp
95
114
  @timestamp ||= Time.now.utc
96
115
  end
97
116
 
117
+ def add_filter(filter=nil, &block)
118
+ if block
119
+ raise ArgumentError, "Both a block and an object were given" if filter
120
+
121
+ filter = Object.new
122
+ def filter.filter(servers)
123
+ block.call(servers)
124
+ end
125
+ elsif !filter.respond_to? :filter
126
+ raise TypeError, "Provided custom filter <#{filter.inspect}> does " \
127
+ "not have a public 'filter' method"
128
+ end
129
+ @custom_filters ||= []
130
+ @custom_filters << filter
131
+ end
132
+
98
133
  def setup_filters
99
- @filters = cmdline_filters.clone
100
- @filters << Filter.new(:role, ENV['ROLES']) if ENV['ROLES']
101
- @filters << Filter.new(:host, ENV['HOSTS']) if ENV['HOSTS']
102
- fh = fetch_for(:filter,{})
134
+ @filters = cmdline_filters
135
+ @filters += @custom_filters if @custom_filters
136
+ @filters << Filter.new(:role, ENV["ROLES"]) if ENV["ROLES"]
137
+ @filters << Filter.new(:host, ENV["HOSTS"]) if ENV["HOSTS"]
138
+ fh = fetch_for(:filter, {}) || {}
139
+ @filters << Filter.new(:host, fh[:hosts]) if fh[:hosts]
140
+ @filters << Filter.new(:role, fh[:roles]) if fh[:roles]
103
141
  @filters << Filter.new(:host, fh[:host]) if fh[:host]
104
142
  @filters << Filter.new(:role, fh[:role]) if fh[:role]
105
143
  end
@@ -108,35 +146,44 @@ module Capistrano
108
146
  cmdline_filters << Filter.new(type, values)
109
147
  end
110
148
 
111
- def filter list
149
+ def filter(list)
112
150
  setup_filters if @filters.nil?
113
- @filters.reduce(list) { |l,f| f.filter l }
151
+ @filters.reduce(list) { |l, f| f.filter l }
114
152
  end
115
153
 
116
- private
154
+ def dry_run?
155
+ fetch(:sshkit_backend) == SSHKit::Backend::Printer
156
+ end
117
157
 
118
- def cmdline_filters
119
- @cmdline_filters ||= []
158
+ def install_plugin(plugin, load_hooks: true, load_immediately: false)
159
+ installer.install(plugin,
160
+ load_hooks: load_hooks,
161
+ load_immediately: load_immediately)
162
+ end
163
+
164
+ def scm_plugin_installed?
165
+ installer.scm_installed?
120
166
  end
121
167
 
122
168
  def servers
123
169
  @servers ||= Servers.new
124
170
  end
125
171
 
126
- def config
127
- @config ||= Hash.new
172
+ private
173
+
174
+ def cmdline_filters
175
+ @cmdline_filters ||= []
128
176
  end
129
177
 
130
- def fetch_for(key, default, &block)
131
- if block_given?
132
- config.fetch(key, &block)
133
- else
134
- config.fetch(key, default)
135
- end
178
+ def installer
179
+ @installer ||= PluginInstaller.new
136
180
  end
137
181
 
138
- def callable_without_parameters?(x)
139
- x.respond_to?(:call) && ( !x.respond_to?(:arity) || x.arity == 0)
182
+ def configure_sshkit_output(sshkit)
183
+ format_args = [fetch(:format)]
184
+ format_args.push(fetch(:format_options)) if any?(:format_options)
185
+
186
+ sshkit.use_format(*format_args)
140
187
  end
141
188
  end
142
189
  end
@@ -1,14 +1,36 @@
1
- set_if_empty :scm, :git
2
- set_if_empty :branch, :master
1
+ validate :application do |_key, value|
2
+ changed_value = value.gsub(/[^A-Z0-9\.\-]/i, "_")
3
+ if value != changed_value
4
+ warn %Q(The :application value "#{value}" is invalid!)
5
+ warn "Use only letters, numbers, hyphens, dots, and underscores. For example:"
6
+ warn " set :application, '#{changed_value}'"
7
+ raise Capistrano::ValidationError
8
+ end
9
+ end
10
+
11
+ %i(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
+
21
+ # We use a special :_default_git value so that SCMResolver can tell whether the
22
+ # default has been replaced by the user via `set`.
23
+ set_if_empty :scm, Capistrano::Configuration::SCMResolver::DEFAULT_GIT
24
+ set_if_empty :branch, "master"
3
25
  set_if_empty :deploy_to, -> { "/var/www/#{fetch(:application)}" }
4
26
  set_if_empty :tmp_dir, "/tmp"
5
27
 
6
28
  set_if_empty :default_env, {}
7
29
  set_if_empty :keep_releases, 5
8
30
 
9
- set_if_empty :format, :pretty
31
+ set_if_empty :format, :airbrussh
10
32
  set_if_empty :log_level, :debug
11
33
 
12
34
  set_if_empty :pty, false
13
35
 
14
- set_if_empty :local_user, -> { Etc.getlogin }
36
+ set_if_empty :local_user, -> { ENV["USER"] || ENV["LOGNAME"] || ENV["USERNAME"] }
@@ -1,3 +1,3 @@
1
- require 'capistrano/framework'
1
+ require "capistrano/framework"
2
2
 
3
3
  load File.expand_path("../tasks/deploy.rake", __FILE__)