test-kitchen 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +8 -7
  3. data/.github/ISSUE_TEMPLATE.md +56 -0
  4. data/.gitignore +28 -27
  5. data/.kitchen.ci.yml +23 -0
  6. data/.kitchen.proxy.yml +27 -0
  7. data/.rubocop.yml +3 -3
  8. data/.travis.yml +70 -53
  9. data/.yardopts +3 -3
  10. data/Berksfile +3 -0
  11. data/CHANGELOG.md +1083 -1051
  12. data/CONTRIBUTING.md +14 -14
  13. data/Gemfile +19 -14
  14. data/Gemfile.proxy_tests +4 -5
  15. data/Guardfile +42 -42
  16. data/LICENSE +15 -15
  17. data/MAINTAINERS.md +23 -24
  18. data/README.md +135 -135
  19. data/Rakefile +61 -76
  20. data/appveyor.yml +44 -34
  21. data/features/kitchen_action_commands.feature +164 -164
  22. data/features/kitchen_command.feature +16 -16
  23. data/features/kitchen_console_command.feature +34 -34
  24. data/features/kitchen_defaults.feature +38 -38
  25. data/features/kitchen_diagnose_command.feature +96 -96
  26. data/features/kitchen_driver_create_command.feature +64 -64
  27. data/features/kitchen_driver_discover_command.feature +25 -25
  28. data/features/kitchen_help_command.feature +16 -16
  29. data/features/kitchen_init_command.feature +274 -274
  30. data/features/kitchen_list_command.feature +104 -104
  31. data/features/kitchen_login_command.feature +62 -62
  32. data/features/kitchen_sink_command.feature +30 -30
  33. data/features/kitchen_test_command.feature +88 -88
  34. data/features/step_definitions/gem_steps.rb +36 -36
  35. data/features/step_definitions/git_steps.rb +5 -5
  36. data/features/step_definitions/output_steps.rb +5 -5
  37. data/features/support/env.rb +75 -75
  38. data/lib/kitchen.rb +150 -150
  39. data/lib/kitchen/base64_stream.rb +55 -55
  40. data/lib/kitchen/cli.rb +419 -419
  41. data/lib/kitchen/collection.rb +55 -55
  42. data/lib/kitchen/color.rb +65 -65
  43. data/lib/kitchen/command.rb +185 -185
  44. data/lib/kitchen/command/action.rb +45 -45
  45. data/lib/kitchen/command/console.rb +58 -58
  46. data/lib/kitchen/command/diagnose.rb +92 -92
  47. data/lib/kitchen/command/driver_discover.rb +105 -105
  48. data/lib/kitchen/command/exec.rb +41 -41
  49. data/lib/kitchen/command/list.rb +119 -119
  50. data/lib/kitchen/command/login.rb +43 -43
  51. data/lib/kitchen/command/sink.rb +54 -54
  52. data/lib/kitchen/command/test.rb +51 -51
  53. data/lib/kitchen/config.rb +322 -322
  54. data/lib/kitchen/configurable.rb +529 -529
  55. data/lib/kitchen/data_munger.rb +959 -960
  56. data/lib/kitchen/diagnostic.rb +141 -141
  57. data/lib/kitchen/driver.rb +56 -56
  58. data/lib/kitchen/driver/base.rb +134 -134
  59. data/lib/kitchen/driver/dummy.rb +108 -108
  60. data/lib/kitchen/driver/proxy.rb +72 -72
  61. data/lib/kitchen/driver/ssh_base.rb +357 -357
  62. data/lib/kitchen/errors.rb +229 -229
  63. data/lib/kitchen/generator/driver_create.rb +177 -177
  64. data/lib/kitchen/generator/init.rb +296 -296
  65. data/lib/kitchen/instance.rb +662 -662
  66. data/lib/kitchen/lazy_hash.rb +142 -142
  67. data/lib/kitchen/loader/yaml.rb +349 -349
  68. data/lib/kitchen/logger.rb +423 -423
  69. data/lib/kitchen/logging.rb +56 -56
  70. data/lib/kitchen/login_command.rb +52 -52
  71. data/lib/kitchen/metadata_chopper.rb +52 -52
  72. data/lib/kitchen/platform.rb +67 -67
  73. data/lib/kitchen/provisioner.rb +54 -54
  74. data/lib/kitchen/provisioner/base.rb +236 -236
  75. data/lib/kitchen/provisioner/chef/berkshelf.rb +114 -114
  76. data/lib/kitchen/provisioner/chef/common_sandbox.rb +322 -322
  77. data/lib/kitchen/provisioner/chef/librarian.rb +112 -112
  78. data/lib/kitchen/provisioner/chef_apply.rb +124 -125
  79. data/lib/kitchen/provisioner/chef_base.rb +341 -294
  80. data/lib/kitchen/provisioner/chef_solo.rb +88 -89
  81. data/lib/kitchen/provisioner/chef_zero.rb +245 -245
  82. data/lib/kitchen/provisioner/dummy.rb +79 -79
  83. data/lib/kitchen/provisioner/shell.rb +138 -138
  84. data/lib/kitchen/rake_tasks.rb +63 -63
  85. data/lib/kitchen/shell_out.rb +93 -93
  86. data/lib/kitchen/ssh.rb +276 -276
  87. data/lib/kitchen/state_file.rb +120 -120
  88. data/lib/kitchen/suite.rb +51 -51
  89. data/lib/kitchen/thor_tasks.rb +66 -66
  90. data/lib/kitchen/transport.rb +54 -54
  91. data/lib/kitchen/transport/base.rb +176 -176
  92. data/lib/kitchen/transport/dummy.rb +79 -79
  93. data/lib/kitchen/transport/ssh.rb +364 -364
  94. data/lib/kitchen/transport/winrm.rb +486 -486
  95. data/lib/kitchen/util.rb +147 -147
  96. data/lib/kitchen/verifier.rb +55 -55
  97. data/lib/kitchen/verifier/base.rb +235 -235
  98. data/lib/kitchen/verifier/busser.rb +277 -277
  99. data/lib/kitchen/verifier/dummy.rb +79 -79
  100. data/lib/kitchen/verifier/shell.rb +101 -101
  101. data/lib/kitchen/version.rb +21 -21
  102. data/lib/vendor/hash_recursive_merge.rb +82 -82
  103. data/spec/kitchen/base64_stream_spec.rb +77 -77
  104. data/spec/kitchen/cli_spec.rb +56 -56
  105. data/spec/kitchen/collection_spec.rb +80 -80
  106. data/spec/kitchen/color_spec.rb +54 -54
  107. data/spec/kitchen/config_spec.rb +408 -408
  108. data/spec/kitchen/configurable_spec.rb +1095 -1062
  109. data/spec/kitchen/data_munger_spec.rb +2694 -2383
  110. data/spec/kitchen/diagnostic_spec.rb +129 -129
  111. data/spec/kitchen/driver/base_spec.rb +121 -121
  112. data/spec/kitchen/driver/dummy_spec.rb +199 -199
  113. data/spec/kitchen/driver/proxy_spec.rb +138 -138
  114. data/spec/kitchen/driver/ssh_base_spec.rb +1115 -1115
  115. data/spec/kitchen/driver_spec.rb +112 -112
  116. data/spec/kitchen/errors_spec.rb +309 -309
  117. data/spec/kitchen/instance_spec.rb +1419 -1419
  118. data/spec/kitchen/lazy_hash_spec.rb +117 -117
  119. data/spec/kitchen/loader/yaml_spec.rb +774 -774
  120. data/spec/kitchen/logger_spec.rb +429 -429
  121. data/spec/kitchen/logging_spec.rb +59 -59
  122. data/spec/kitchen/login_command_spec.rb +68 -68
  123. data/spec/kitchen/metadata_chopper_spec.rb +82 -82
  124. data/spec/kitchen/platform_spec.rb +89 -89
  125. data/spec/kitchen/provisioner/base_spec.rb +386 -386
  126. data/spec/kitchen/provisioner/chef_apply_spec.rb +136 -136
  127. data/spec/kitchen/provisioner/chef_base_spec.rb +1161 -1067
  128. data/spec/kitchen/provisioner/chef_solo_spec.rb +557 -557
  129. data/spec/kitchen/provisioner/chef_zero_spec.rb +1001 -1001
  130. data/spec/kitchen/provisioner/dummy_spec.rb +99 -99
  131. data/spec/kitchen/provisioner/shell_spec.rb +566 -566
  132. data/spec/kitchen/provisioner_spec.rb +107 -107
  133. data/spec/kitchen/shell_out_spec.rb +150 -150
  134. data/spec/kitchen/ssh_spec.rb +693 -693
  135. data/spec/kitchen/state_file_spec.rb +129 -129
  136. data/spec/kitchen/suite_spec.rb +62 -62
  137. data/spec/kitchen/transport/base_spec.rb +89 -89
  138. data/spec/kitchen/transport/ssh_spec.rb +1255 -1255
  139. data/spec/kitchen/transport/winrm_spec.rb +1143 -1143
  140. data/spec/kitchen/transport_spec.rb +112 -112
  141. data/spec/kitchen/util_spec.rb +165 -165
  142. data/spec/kitchen/verifier/base_spec.rb +362 -362
  143. data/spec/kitchen/verifier/busser_spec.rb +610 -610
  144. data/spec/kitchen/verifier/dummy_spec.rb +99 -99
  145. data/spec/kitchen/verifier/shell_spec.rb +160 -158
  146. data/spec/kitchen/verifier_spec.rb +120 -120
  147. data/spec/kitchen_spec.rb +114 -114
  148. data/spec/spec_helper.rb +85 -85
  149. data/spec/support/powershell_max_size_spec.rb +40 -40
  150. data/support/busser_install_command.ps1 +14 -14
  151. data/support/busser_install_command.sh +14 -14
  152. data/support/chef-client-zero.rb +77 -77
  153. data/support/chef_base_init_command.ps1 +18 -18
  154. data/support/chef_base_init_command.sh +2 -2
  155. data/support/chef_base_install_command.ps1 +85 -85
  156. data/support/chef_base_install_command.sh +229 -229
  157. data/support/chef_zero_prepare_command_legacy.ps1 +9 -9
  158. data/support/chef_zero_prepare_command_legacy.sh +10 -10
  159. data/support/download_helpers.sh +109 -109
  160. data/support/dummy-validation.pem +27 -27
  161. data/templates/driver/CHANGELOG.md.erb +3 -3
  162. data/templates/driver/Gemfile.erb +3 -3
  163. data/templates/driver/README.md.erb +64 -64
  164. data/templates/driver/Rakefile.erb +21 -21
  165. data/templates/driver/driver.rb.erb +23 -23
  166. data/templates/driver/gemspec.erb +29 -29
  167. data/templates/driver/gitignore.erb +17 -17
  168. data/templates/driver/license_apachev2.erb +15 -15
  169. data/templates/driver/license_lgplv3.erb +16 -16
  170. data/templates/driver/license_mit.erb +22 -22
  171. data/templates/driver/license_reserved.erb +5 -5
  172. data/templates/driver/tailor.erb +4 -4
  173. data/templates/driver/travis.yml.erb +11 -11
  174. data/templates/driver/version.rb.erb +12 -12
  175. data/templates/init/chefignore.erb +1 -1
  176. data/templates/init/kitchen.yml.erb +18 -18
  177. data/test-kitchen.gemspec +62 -62
  178. data/test/integration/default/default_spec.rb +3 -0
  179. data/testing_windows.md +37 -37
  180. metadata +23 -11
@@ -1,529 +1,529 @@
1
- # -*- encoding: utf-8 -*-
2
- #
3
- # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
- #
5
- # Copyright (C) 2014, Fletcher Nichol
6
- #
7
- # Licensed under the Apache License, Version 2.0 (the "License");
8
- # you may not use this file except in compliance with the License.
9
- # You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing, software
14
- # distributed under the License is distributed on an "AS IS" BASIS,
15
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- # See the License for the specific language governing permissions and
17
- # limitations under the License.
18
-
19
- require "thor/util"
20
-
21
- require "kitchen/lazy_hash"
22
-
23
- module Kitchen
24
-
25
- # A mixin for providing configuration-related behavior such as default
26
- # config (static, computed, inherited), required config, local path
27
- # expansion, etc.
28
- #
29
- # @author Fletcher Nichol <fnichol@nichol.ca>
30
- module Configurable
31
-
32
- def self.included(base)
33
- base.extend(ClassMethods)
34
- end
35
-
36
- # @return [Kitchen::Instance] the associated instance
37
- attr_reader :instance
38
-
39
- # A lifecycle method that should be invoked when the object is about ready
40
- # to be used. A reference to an Instance is required as configuration
41
- # dependant data may be access through an Instance. This also acts as a
42
- # hook point where the object may wish to perform other last minute
43
- # checks, validations, or configuration expansions.
44
- #
45
- # @param instance [Instance] an associated instance
46
- # @return [self] itself, for use in chaining
47
- # @raise [ClientError] if instance parameter is nil
48
- def finalize_config!(instance)
49
- if instance.nil?
50
- raise ClientError, "Instance must be provided to #{self}"
51
- end
52
-
53
- @instance = instance
54
- expand_paths!
55
- validate_config!
56
- load_needed_dependencies!
57
-
58
- self
59
- end
60
-
61
- # Provides hash-like access to configuration keys.
62
- #
63
- # @param attr [Object] configuration key
64
- # @return [Object] value at configuration key
65
- def [](attr)
66
- config[attr]
67
- end
68
-
69
- # @return [TrueClass,FalseClass] true if `:shell_type` is `"bourne"` (or
70
- # unset, for backwards compatability)
71
- def bourne_shell?
72
- ["bourne", nil].include?(instance.platform.shell_type)
73
- end
74
-
75
- # Find an appropriate path to a file or directory, based on graceful
76
- # fallback rules or returns nil if path cannot be determined.
77
- #
78
- # Given an instance with suite named `"server"`, a `test_base_path` of
79
- # `"/a/b"`, and a path segement of `"roles"` then following will be tried
80
- # in order (first match that exists wins):
81
- #
82
- # 1. /a/b/server/roles
83
- # 2. /a/b/roles
84
- # 3. $PWD/roles
85
- #
86
- # @param path [String] the base path segment to search for
87
- # @param opts [Hash] options
88
- # @option opts [Symbol] :type either `:file` or `:directory` (default)
89
- # @option opts [Symbol] :base_path a custom base path to search under,
90
- # default uses value from `config[:test_base_path]`
91
- # @return [String] path to the existing file or directory, or nil if file
92
- # or directory was not found
93
- # @raise [UserError] if `config[:test_base_path]` is used and is not set
94
- def calculate_path(path, opts = {})
95
- type = opts.fetch(:type, :directory)
96
- base = opts.fetch(:base_path) do
97
- config.fetch(:test_base_path) do |key|
98
- raise UserError, "#{key} is not found in #{self}"
99
- end
100
- end
101
-
102
- [
103
- File.join(base, instance.suite.name, path),
104
- File.join(base, path),
105
- File.join(Dir.pwd, path)
106
- ].find do |candidate|
107
- type == :directory ? File.directory?(candidate) : File.file?(candidate)
108
- end
109
- end
110
-
111
- # Returns an array of configuration keys.
112
- #
113
- # @return [Array] array of configuration keys
114
- def config_keys
115
- config.keys
116
- end
117
-
118
- # Returns a Hash of configuration and other useful diagnostic information.
119
- #
120
- # @return [Hash] a diagnostic hash
121
- def diagnose
122
- result = Hash.new
123
- config_keys.sort.each { |k| result[k] = config[k] }
124
- result
125
- end
126
-
127
- # Returns a Hash of configuration and other useful diagnostic information
128
- # associated with the plugin itself (such as loaded version, class name,
129
- # etc.).
130
- #
131
- # @return [Hash] a diagnostic hash
132
- def diagnose_plugin
133
- result = Hash.new
134
- result[:name] = name
135
- result.merge!(self.class.diagnose)
136
- result
137
- end
138
-
139
- # Returns the name of this plugin, suitable for display in a CLI.
140
- #
141
- # @return [String] name of this plugin
142
- def name
143
- self.class.name.split("::").last
144
- end
145
-
146
- # @return [TrueClass,FalseClass] true if `:shell_type` is `"powershell"`
147
- def powershell_shell?
148
- ["powershell"].include?(instance.platform.shell_type)
149
- end
150
-
151
- # Builds a file path based on the `:os_type` (`"windows"` or `"unix"`).
152
- #
153
- # @return [String] joined path for instance's os_type
154
- def remote_path_join(*parts)
155
- path = File.join(*parts)
156
- windows_os? ? path.gsub("/", "\\") : path.gsub("\\", "/")
157
- end
158
-
159
- # @return [TrueClass,FalseClass] true if `:os_type` is `"unix"` (or
160
- # unset, for backwards compatibility)
161
- def unix_os?
162
- ["unix", nil].include?(instance.platform.os_type)
163
- end
164
-
165
- # Performs whatever tests that may be required to ensure that this plugin
166
- # will be able to function in the current environment. This may involve
167
- # checking for the presence of certain directories, software installed,
168
- # etc.
169
- #
170
- # @raise [UserError] if the plugin will not be able to perform or if a
171
- # documented dependency is missing from the system
172
- def verify_dependencies
173
- # this method may be left unimplemented if that is applicable
174
- end
175
-
176
- # @return [TrueClass,FalseClass] true if `:os_type` is `"windows"`
177
- def windows_os?
178
- ["windows"].include?(instance.platform.os_type)
179
- end
180
-
181
- private
182
-
183
- # @return [LzayHash] a configuration hash
184
- # @api private
185
- attr_reader :config
186
-
187
- # Initializes an internal configuration hash. The hash may contain
188
- # callable blocks as values that are meant to be called lazily. This
189
- # method is intended to be included in an object's .initialize method.
190
- #
191
- # @param config [Hash] initial provided configuration
192
- # @api private
193
- def init_config(config)
194
- @config = LazyHash.new(config, self)
195
- self.class.defaults.each do |attr, value|
196
- @config[attr] = value unless @config.key?(attr)
197
- end
198
- end
199
-
200
- # Expands file paths for certain configuration values. A configuration
201
- # value is marked for file expansion with a expand_path_for declaration
202
- # in the included class.
203
- #
204
- # @api private
205
- def expand_paths!
206
- root_path = config[:kitchen_root] || Dir.pwd
207
- expanded_paths = LazyHash.new(self.class.expanded_paths, self).to_hash
208
-
209
- expanded_paths.each do |key, should_expand|
210
- next if !should_expand || config[key].nil? || config[key] == false
211
-
212
- config[key] = if config[key].is_a?(Array)
213
- config[key].map { |path| File.expand_path(path, root_path) }
214
- else
215
- File.expand_path(config[key], root_path)
216
- end
217
- end
218
- end
219
-
220
- # Loads any required third party Ruby libraries or runs any shell out
221
- # commands to prepare the plugin. This method will be called in the
222
- # context of the main thread of execution and so does not necessarily
223
- # have to be thread safe.
224
- #
225
- # **Note:** any subclasses overriding this method would be well advised
226
- # to call super when overriding this method, for example:
227
- #
228
- # @example overriding `#load_needed_dependencies!`
229
- #
230
- # class MyProvisioner < Kitchen::Provisioner::Base
231
- # def load_needed_dependencies!
232
- # super
233
- # # any further work
234
- # end
235
- # end
236
- #
237
- # @raise [ClientError] if any library loading fails or any of the
238
- # dependency requirements cannot be satisfied
239
- # @api private
240
- def load_needed_dependencies!
241
- # this method may be left unimplemented if that is applicable
242
- end
243
-
244
- # @return [Logger] the instance's logger or Test Kitchen's common logger
245
- # otherwise
246
- # @api private
247
- def logger
248
- instance ? instance.logger : Kitchen.logger
249
- end
250
-
251
- # Builds a shell environment variable assignment string for the
252
- # required shell type.
253
- #
254
- # @param name [String] variable name
255
- # @param value [String] variable value
256
- # @return [String] shell variable assignment
257
- # @api private
258
- def shell_env_var(name, value)
259
- if powershell_shell?
260
- shell_var("env:#{name}", value)
261
- else
262
- "#{shell_var(name, value)}; export #{name}"
263
- end
264
- end
265
-
266
- # Builds a shell variable assignment string for the required shell type.
267
- #
268
- # @param name [String] variable name
269
- # @param value [String] variable value
270
- # @return [String] shell variable assignment
271
- # @api private
272
- def shell_var(name, value)
273
- if powershell_shell?
274
- %{$#{name} = "#{value}"}
275
- else
276
- %{#{name}="#{value}"}
277
- end
278
- end
279
-
280
- # Runs all validations set up for the included class. Each validation is
281
- # for a specific configuration attribute and has an associated callable
282
- # block. Each validation block is called with the attribute, its value,
283
- # and the included object for context.
284
- #
285
- # @api private
286
- def validate_config!
287
- self.class.validations.each do |attr, block|
288
- block.call(attr, config[attr], self)
289
- end
290
- end
291
-
292
- # Wraps a body of shell code with common context appropriate for the type
293
- # of shell.
294
- #
295
- # @param code [String] the shell code to be wrapped
296
- # @return [String] wrapped shell code
297
- # @api private
298
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
299
- # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
300
- def wrap_shell_code(code)
301
- env = []
302
- if config[:http_proxy]
303
- env << shell_env_var("http_proxy", config[:http_proxy])
304
- env << shell_env_var("HTTP_PROXY", config[:http_proxy])
305
- else
306
- export_proxy(env, "http")
307
- end
308
- if config[:https_proxy]
309
- env << shell_env_var("https_proxy", config[:https_proxy])
310
- env << shell_env_var("HTTPS_PROXY", config[:https_proxy])
311
- else
312
- export_proxy(env, "https")
313
- end
314
- if config[:ftp_proxy]
315
- env << shell_env_var("ftp_proxy", config[:ftp_proxy])
316
- env << shell_env_var("FTP_PROXY", config[:ftp_proxy])
317
- else
318
- export_proxy(env, "ftp")
319
- end
320
- # if http_proxy was set from environment variable or https_proxy was set
321
- # from environment variable, or ftp_proxy was set from environment
322
- # variable, include no_proxy environment variable, if set.
323
- if (!config[:http_proxy] && (ENV["http_proxy"] || ENV["HTTP_PROXY"])) ||
324
- (!config[:https_proxy] && (ENV["https_proxy"] || ENV["HTTPS_PROXY"])) ||
325
- (!config[:ftp_proxy] && (ENV["ftp_proxy"] || ENV["FTP_PROXY"]))
326
- env << shell_env_var("no_proxy", ENV["no_proxy"]) if ENV["no_proxy"]
327
- env << shell_env_var("NO_PROXY", ENV["NO_PROXY"]) if ENV["NO_PROXY"]
328
- end
329
- if powershell_shell?
330
- env.join("\n").concat("\n").concat(code)
331
- else
332
- Util.wrap_command(env.join("\n").concat("\n").concat(code))
333
- end
334
- end
335
-
336
- # Helper method to export
337
- #
338
- # @param env [Array] the environment to modify
339
- # @param code [String] the type of proxy to export, one of 'http', 'https' or 'ftp'
340
- # @api private
341
- def export_proxy(env, type)
342
- env << shell_env_var("#{type}_proxy", ENV["#{type}_proxy"]) if ENV["#{type}_proxy"]
343
- env << shell_env_var("#{type.upcase}_PROXY", ENV["#{type.upcase}_PROXY"]) if
344
- ENV["#{type.upcase}_PROXY"]
345
- end
346
-
347
- # Class methods which will be mixed in on inclusion of Configurable module.
348
- module ClassMethods
349
-
350
- # Sets the loaded version of this plugin, usually corresponding to the
351
- # RubyGems version of the plugin's library. If the plugin does not set
352
- # this value, then `nil` will be used and reported.
353
- #
354
- # @example setting a version used by RubyGems
355
- #
356
- # require "kitchen/driver/vagrant_version"
357
- #
358
- # module Kitchen
359
- # module Driver
360
- # class Vagrant < Kitchen::Driver::Base
361
- #
362
- # plugin_version Kitchen::Driver::VAGRANT_VERSION
363
- #
364
- # end
365
- # end
366
- # end
367
- #
368
- # @param version [String] a version string
369
- def plugin_version(version) # rubocop:disable Style/TrivialAccessors
370
- @plugin_version = version
371
- end
372
-
373
- # Returns a Hash of configuration and other useful diagnostic
374
- # information.
375
- #
376
- # @return [Hash] a diagnostic hash
377
- def diagnose
378
- {
379
- :class => name,
380
- :version => @plugin_version,
381
- :api_version => @api_version
382
- }
383
- end
384
-
385
- # Sets a sane default value for a configuration attribute. These values
386
- # can be overridden by provided configuration or in a subclass with
387
- # another default_config declaration.
388
- #
389
- # @example a nil default value
390
- #
391
- # default_config :i_am_nil
392
- #
393
- # @example a primitive default value
394
- #
395
- # default_config :use_sudo, true
396
- #
397
- # @example a block to compute a default value
398
- #
399
- # default_config :box_name do |subject|
400
- # subject.instance.platform.name
401
- # end
402
- #
403
- # @param attr [String] configuration attribute name
404
- # @param value [Object, nil] static default value for attribute
405
- # @yieldparam object [Object] a reference to the instantiated object
406
- # @yieldreturn [Object, nil] dynamically computed value for the attribute
407
- def default_config(attr, value = nil, &block)
408
- defaults[attr] = block_given? ? block : value
409
- end
410
-
411
- # Ensures that an attribute which is a path will be fully expanded at
412
- # the right time. This helps make the configuration unambiguous and much
413
- # easier to debug and diagnose.
414
- #
415
- # Note that the file path expansion is only intended for paths on the
416
- # local workstation invking the Test Kitchen code.
417
- #
418
- # @example the default usage
419
- #
420
- # expand_path_for :data_path
421
- #
422
- # @example disabling path expansion with a falsey value
423
- #
424
- # expand_path_for :relative_path, false
425
- #
426
- # @example using a block to determine whether or not to expand
427
- #
428
- # expand_path_for :relative_or_not_path do |subject|
429
- # subject.instance.name =~ /default/
430
- # end
431
- #
432
- # @param attr [String] configuration attribute name
433
- # @param value [Object, nil] whether or not to exand the file path
434
- # @yieldparam object [Object] a reference to the instantiated object
435
- # @yieldreturn [Object, nil] dynamically compute whether or not to
436
- # perform the file expansion
437
- def expand_path_for(attr, value = true, &block)
438
- expanded_paths[attr] = block_given? ? block : value
439
- end
440
-
441
- # Ensures that an attribute must have a non-nil, non-empty String value.
442
- # The default behavior will be to raise a user error and thereby halting
443
- # further configuration processing. Good use cases for require_config
444
- # might be cloud provider credential keys and other similar data.
445
- #
446
- # @example a value that must not be nil or an empty String
447
- #
448
- # required_config :cloud_api_token
449
- #
450
- # @example using a block to use custom validation logic
451
- #
452
- # required_config :email do |attr, value, subject|
453
- # raise UserError, "Must be an email address" unless value =~ /@/
454
- # end
455
- #
456
- # @param attr [String] configuration attribute name
457
- # @yieldparam attr [Symbol] the attribute name
458
- # @yieldparam value [Object] the current value of the attribute
459
- # @yieldparam object [Object] a reference to the instantiated object
460
- def required_config(attr, &block)
461
- if !block_given?
462
- klass = self
463
- block = lambda do |_, value, thing|
464
- if value.nil? || value.to_s.empty?
465
- attribute = "#{klass}#{thing.instance.to_str}#config[:#{attr}]"
466
- raise UserError, "#{attribute} cannot be blank"
467
- end
468
- end
469
- end
470
- validations[attr] = block
471
- end
472
-
473
- # @return [Hash] a hash of attribute keys and default values which has
474
- # been merged with any superclass defaults
475
- # @api private
476
- def defaults
477
- @defaults ||= Hash.new.merge(super_defaults)
478
- end
479
-
480
- # @return [Hash] a hash of defaults from the included class' superclass
481
- # if defined in the superclass, or an empty hash otherwise
482
- # @api private
483
- def super_defaults
484
- if superclass.respond_to?(:defaults)
485
- superclass.defaults
486
- else
487
- Hash.new
488
- end
489
- end
490
-
491
- # @return [Hash] a hash of attribute keys and truthy/falsey values to
492
- # determine if said attribute needs to be fully file path expanded,
493
- # which has been merged with any superclass expanded paths
494
- # @api private
495
- def expanded_paths
496
- @expanded_paths ||= Hash.new.merge(super_expanded_paths)
497
- end
498
-
499
- # @return [Hash] a hash of expanded paths from the included class'
500
- # superclass if defined in the superclass, or an empty hash otherwise
501
- # @api private
502
- def super_expanded_paths
503
- if superclass.respond_to?(:expanded_paths)
504
- superclass.expanded_paths
505
- else
506
- Hash.new
507
- end
508
- end
509
-
510
- # @return [Hash] a hash of attribute keys and valudation callable blocks
511
- # which has been merged with any superclass valudations
512
- # @api private
513
- def validations
514
- @validations ||= Hash.new.merge(super_validations)
515
- end
516
-
517
- # @return [Hash] a hash of validations from the included class'
518
- # superclass if defined in the superclass, or an empty hash otherwise
519
- # @api private
520
- def super_validations
521
- if superclass.respond_to?(:validations)
522
- superclass.validations
523
- else
524
- Hash.new
525
- end
526
- end
527
- end
528
- end
529
- end
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2014, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require "thor/util"
20
+
21
+ require "kitchen/lazy_hash"
22
+
23
+ module Kitchen
24
+
25
+ # A mixin for providing configuration-related behavior such as default
26
+ # config (static, computed, inherited), required config, local path
27
+ # expansion, etc.
28
+ #
29
+ # @author Fletcher Nichol <fnichol@nichol.ca>
30
+ module Configurable
31
+
32
+ def self.included(base)
33
+ base.extend(ClassMethods)
34
+ end
35
+
36
+ # @return [Kitchen::Instance] the associated instance
37
+ attr_reader :instance
38
+
39
+ # A lifecycle method that should be invoked when the object is about ready
40
+ # to be used. A reference to an Instance is required as configuration
41
+ # dependant data may be access through an Instance. This also acts as a
42
+ # hook point where the object may wish to perform other last minute
43
+ # checks, validations, or configuration expansions.
44
+ #
45
+ # @param instance [Instance] an associated instance
46
+ # @return [self] itself, for use in chaining
47
+ # @raise [ClientError] if instance parameter is nil
48
+ def finalize_config!(instance)
49
+ if instance.nil?
50
+ raise ClientError, "Instance must be provided to #{self}"
51
+ end
52
+
53
+ @instance = instance
54
+ expand_paths!
55
+ validate_config!
56
+ load_needed_dependencies!
57
+
58
+ self
59
+ end
60
+
61
+ # Provides hash-like access to configuration keys.
62
+ #
63
+ # @param attr [Object] configuration key
64
+ # @return [Object] value at configuration key
65
+ def [](attr)
66
+ config[attr]
67
+ end
68
+
69
+ # @return [TrueClass,FalseClass] true if `:shell_type` is `"bourne"` (or
70
+ # unset, for backwards compatability)
71
+ def bourne_shell?
72
+ ["bourne", nil].include?(instance.platform.shell_type)
73
+ end
74
+
75
+ # Find an appropriate path to a file or directory, based on graceful
76
+ # fallback rules or returns nil if path cannot be determined.
77
+ #
78
+ # Given an instance with suite named `"server"`, a `test_base_path` of
79
+ # `"/a/b"`, and a path segement of `"roles"` then following will be tried
80
+ # in order (first match that exists wins):
81
+ #
82
+ # 1. /a/b/server/roles
83
+ # 2. /a/b/roles
84
+ # 3. $PWD/roles
85
+ #
86
+ # @param path [String] the base path segment to search for
87
+ # @param opts [Hash] options
88
+ # @option opts [Symbol] :type either `:file` or `:directory` (default)
89
+ # @option opts [Symbol] :base_path a custom base path to search under,
90
+ # default uses value from `config[:test_base_path]`
91
+ # @return [String] path to the existing file or directory, or nil if file
92
+ # or directory was not found
93
+ # @raise [UserError] if `config[:test_base_path]` is used and is not set
94
+ def calculate_path(path, opts = {})
95
+ type = opts.fetch(:type, :directory)
96
+ base = opts.fetch(:base_path) do
97
+ config.fetch(:test_base_path) do |key|
98
+ raise UserError, "#{key} is not found in #{self}"
99
+ end
100
+ end
101
+
102
+ [
103
+ File.join(base, instance.suite.name, path),
104
+ File.join(base, path),
105
+ File.join(Dir.pwd, path)
106
+ ].find do |candidate|
107
+ type == :directory ? File.directory?(candidate) : File.file?(candidate)
108
+ end
109
+ end
110
+
111
+ # Returns an array of configuration keys.
112
+ #
113
+ # @return [Array] array of configuration keys
114
+ def config_keys
115
+ config.keys
116
+ end
117
+
118
+ # Returns a Hash of configuration and other useful diagnostic information.
119
+ #
120
+ # @return [Hash] a diagnostic hash
121
+ def diagnose
122
+ result = Hash.new
123
+ config_keys.sort.each { |k| result[k] = config[k] }
124
+ result
125
+ end
126
+
127
+ # Returns a Hash of configuration and other useful diagnostic information
128
+ # associated with the plugin itself (such as loaded version, class name,
129
+ # etc.).
130
+ #
131
+ # @return [Hash] a diagnostic hash
132
+ def diagnose_plugin
133
+ result = Hash.new
134
+ result[:name] = name
135
+ result.merge!(self.class.diagnose)
136
+ result
137
+ end
138
+
139
+ # Returns the name of this plugin, suitable for display in a CLI.
140
+ #
141
+ # @return [String] name of this plugin
142
+ def name
143
+ self.class.name.split("::").last
144
+ end
145
+
146
+ # @return [TrueClass,FalseClass] true if `:shell_type` is `"powershell"`
147
+ def powershell_shell?
148
+ ["powershell"].include?(instance.platform.shell_type)
149
+ end
150
+
151
+ # Builds a file path based on the `:os_type` (`"windows"` or `"unix"`).
152
+ #
153
+ # @return [String] joined path for instance's os_type
154
+ def remote_path_join(*parts)
155
+ path = File.join(*parts)
156
+ windows_os? ? path.gsub("/", "\\") : path.gsub("\\", "/")
157
+ end
158
+
159
+ # @return [TrueClass,FalseClass] true if `:os_type` is `"unix"` (or
160
+ # unset, for backwards compatibility)
161
+ def unix_os?
162
+ ["unix", nil].include?(instance.platform.os_type)
163
+ end
164
+
165
+ # Performs whatever tests that may be required to ensure that this plugin
166
+ # will be able to function in the current environment. This may involve
167
+ # checking for the presence of certain directories, software installed,
168
+ # etc.
169
+ #
170
+ # @raise [UserError] if the plugin will not be able to perform or if a
171
+ # documented dependency is missing from the system
172
+ def verify_dependencies
173
+ # this method may be left unimplemented if that is applicable
174
+ end
175
+
176
+ # @return [TrueClass,FalseClass] true if `:os_type` is `"windows"`
177
+ def windows_os?
178
+ ["windows"].include?(instance.platform.os_type)
179
+ end
180
+
181
+ private
182
+
183
+ # @return [LzayHash] a configuration hash
184
+ # @api private
185
+ attr_reader :config
186
+
187
+ # Initializes an internal configuration hash. The hash may contain
188
+ # callable blocks as values that are meant to be called lazily. This
189
+ # method is intended to be included in an object's .initialize method.
190
+ #
191
+ # @param config [Hash] initial provided configuration
192
+ # @api private
193
+ def init_config(config)
194
+ @config = LazyHash.new(config, self)
195
+ self.class.defaults.each do |attr, value|
196
+ @config[attr] = value unless @config.key?(attr)
197
+ end
198
+ end
199
+
200
+ # Expands file paths for certain configuration values. A configuration
201
+ # value is marked for file expansion with a expand_path_for declaration
202
+ # in the included class.
203
+ #
204
+ # @api private
205
+ def expand_paths!
206
+ root_path = config[:kitchen_root] || Dir.pwd
207
+ expanded_paths = LazyHash.new(self.class.expanded_paths, self).to_hash
208
+
209
+ expanded_paths.each do |key, should_expand|
210
+ next if !should_expand || config[key].nil? || config[key] == false
211
+
212
+ config[key] = if config[key].is_a?(Array)
213
+ config[key].map { |path| File.expand_path(path, root_path) }
214
+ else
215
+ File.expand_path(config[key], root_path)
216
+ end
217
+ end
218
+ end
219
+
220
+ # Loads any required third party Ruby libraries or runs any shell out
221
+ # commands to prepare the plugin. This method will be called in the
222
+ # context of the main thread of execution and so does not necessarily
223
+ # have to be thread safe.
224
+ #
225
+ # **Note:** any subclasses overriding this method would be well advised
226
+ # to call super when overriding this method, for example:
227
+ #
228
+ # @example overriding `#load_needed_dependencies!`
229
+ #
230
+ # class MyProvisioner < Kitchen::Provisioner::Base
231
+ # def load_needed_dependencies!
232
+ # super
233
+ # # any further work
234
+ # end
235
+ # end
236
+ #
237
+ # @raise [ClientError] if any library loading fails or any of the
238
+ # dependency requirements cannot be satisfied
239
+ # @api private
240
+ def load_needed_dependencies!
241
+ # this method may be left unimplemented if that is applicable
242
+ end
243
+
244
+ # @return [Logger] the instance's logger or Test Kitchen's common logger
245
+ # otherwise
246
+ # @api private
247
+ def logger
248
+ instance ? instance.logger : Kitchen.logger
249
+ end
250
+
251
+ # Builds a shell environment variable assignment string for the
252
+ # required shell type.
253
+ #
254
+ # @param name [String] variable name
255
+ # @param value [String] variable value
256
+ # @return [String] shell variable assignment
257
+ # @api private
258
+ def shell_env_var(name, value)
259
+ if powershell_shell?
260
+ shell_var("env:#{name}", value)
261
+ else
262
+ "#{shell_var(name, value)}; export #{name}"
263
+ end
264
+ end
265
+
266
+ # Builds a shell variable assignment string for the required shell type.
267
+ #
268
+ # @param name [String] variable name
269
+ # @param value [String] variable value
270
+ # @return [String] shell variable assignment
271
+ # @api private
272
+ def shell_var(name, value)
273
+ if powershell_shell?
274
+ %{$#{name} = "#{value}"}
275
+ else
276
+ %{#{name}="#{value}"}
277
+ end
278
+ end
279
+
280
+ # Runs all validations set up for the included class. Each validation is
281
+ # for a specific configuration attribute and has an associated callable
282
+ # block. Each validation block is called with the attribute, its value,
283
+ # and the included object for context.
284
+ #
285
+ # @api private
286
+ def validate_config!
287
+ self.class.validations.each do |attr, block|
288
+ block.call(attr, config[attr], self)
289
+ end
290
+ end
291
+
292
+ # Wraps a body of shell code with common context appropriate for the type
293
+ # of shell.
294
+ #
295
+ # @param code [String] the shell code to be wrapped
296
+ # @return [String] wrapped shell code
297
+ # @api private
298
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
299
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
300
+ def wrap_shell_code(code)
301
+ env = []
302
+ if config[:http_proxy] && !config[:http_proxy].empty?
303
+ env << shell_env_var("http_proxy", config[:http_proxy])
304
+ env << shell_env_var("HTTP_PROXY", config[:http_proxy])
305
+ else
306
+ export_proxy(env, "http")
307
+ end
308
+ if config[:https_proxy] && !config[:https_proxy].empty?
309
+ env << shell_env_var("https_proxy", config[:https_proxy])
310
+ env << shell_env_var("HTTPS_PROXY", config[:https_proxy])
311
+ else
312
+ export_proxy(env, "https")
313
+ end
314
+ if config[:ftp_proxy] && !config[:ftp_proxy].empty?
315
+ env << shell_env_var("ftp_proxy", config[:ftp_proxy])
316
+ env << shell_env_var("FTP_PROXY", config[:ftp_proxy])
317
+ else
318
+ export_proxy(env, "ftp")
319
+ end
320
+ # if http_proxy was set from environment variable or https_proxy was set
321
+ # from environment variable, or ftp_proxy was set from environment
322
+ # variable, include no_proxy environment variable, if set.
323
+ if (!config[:http_proxy] && (ENV["http_proxy"] || ENV["HTTP_PROXY"])) ||
324
+ (!config[:https_proxy] && (ENV["https_proxy"] || ENV["HTTPS_PROXY"])) ||
325
+ (!config[:ftp_proxy] && (ENV["ftp_proxy"] || ENV["FTP_PROXY"]))
326
+ env << shell_env_var("no_proxy", ENV["no_proxy"]) if ENV["no_proxy"]
327
+ env << shell_env_var("NO_PROXY", ENV["NO_PROXY"]) if ENV["NO_PROXY"]
328
+ end
329
+ if powershell_shell?
330
+ env.join("\n").concat("\n").concat(code)
331
+ else
332
+ Util.wrap_command(env.join("\n").concat("\n").concat(code))
333
+ end
334
+ end
335
+
336
+ # Helper method to export
337
+ #
338
+ # @param env [Array] the environment to modify
339
+ # @param code [String] the type of proxy to export, one of 'http', 'https' or 'ftp'
340
+ # @api private
341
+ def export_proxy(env, type)
342
+ env << shell_env_var("#{type}_proxy", ENV["#{type}_proxy"]) if ENV["#{type}_proxy"]
343
+ env << shell_env_var("#{type.upcase}_PROXY", ENV["#{type.upcase}_PROXY"]) if
344
+ ENV["#{type.upcase}_PROXY"]
345
+ end
346
+
347
+ # Class methods which will be mixed in on inclusion of Configurable module.
348
+ module ClassMethods
349
+
350
+ # Sets the loaded version of this plugin, usually corresponding to the
351
+ # RubyGems version of the plugin's library. If the plugin does not set
352
+ # this value, then `nil` will be used and reported.
353
+ #
354
+ # @example setting a version used by RubyGems
355
+ #
356
+ # require "kitchen/driver/vagrant_version"
357
+ #
358
+ # module Kitchen
359
+ # module Driver
360
+ # class Vagrant < Kitchen::Driver::Base
361
+ #
362
+ # plugin_version Kitchen::Driver::VAGRANT_VERSION
363
+ #
364
+ # end
365
+ # end
366
+ # end
367
+ #
368
+ # @param version [String] a version string
369
+ def plugin_version(version) # rubocop:disable Style/TrivialAccessors
370
+ @plugin_version = version
371
+ end
372
+
373
+ # Returns a Hash of configuration and other useful diagnostic
374
+ # information.
375
+ #
376
+ # @return [Hash] a diagnostic hash
377
+ def diagnose
378
+ {
379
+ :class => name,
380
+ :version => @plugin_version,
381
+ :api_version => @api_version
382
+ }
383
+ end
384
+
385
+ # Sets a sane default value for a configuration attribute. These values
386
+ # can be overridden by provided configuration or in a subclass with
387
+ # another default_config declaration.
388
+ #
389
+ # @example a nil default value
390
+ #
391
+ # default_config :i_am_nil
392
+ #
393
+ # @example a primitive default value
394
+ #
395
+ # default_config :use_sudo, true
396
+ #
397
+ # @example a block to compute a default value
398
+ #
399
+ # default_config :box_name do |subject|
400
+ # subject.instance.platform.name
401
+ # end
402
+ #
403
+ # @param attr [String] configuration attribute name
404
+ # @param value [Object, nil] static default value for attribute
405
+ # @yieldparam object [Object] a reference to the instantiated object
406
+ # @yieldreturn [Object, nil] dynamically computed value for the attribute
407
+ def default_config(attr, value = nil, &block)
408
+ defaults[attr] = block_given? ? block : value
409
+ end
410
+
411
+ # Ensures that an attribute which is a path will be fully expanded at
412
+ # the right time. This helps make the configuration unambiguous and much
413
+ # easier to debug and diagnose.
414
+ #
415
+ # Note that the file path expansion is only intended for paths on the
416
+ # local workstation invking the Test Kitchen code.
417
+ #
418
+ # @example the default usage
419
+ #
420
+ # expand_path_for :data_path
421
+ #
422
+ # @example disabling path expansion with a falsey value
423
+ #
424
+ # expand_path_for :relative_path, false
425
+ #
426
+ # @example using a block to determine whether or not to expand
427
+ #
428
+ # expand_path_for :relative_or_not_path do |subject|
429
+ # subject.instance.name =~ /default/
430
+ # end
431
+ #
432
+ # @param attr [String] configuration attribute name
433
+ # @param value [Object, nil] whether or not to exand the file path
434
+ # @yieldparam object [Object] a reference to the instantiated object
435
+ # @yieldreturn [Object, nil] dynamically compute whether or not to
436
+ # perform the file expansion
437
+ def expand_path_for(attr, value = true, &block)
438
+ expanded_paths[attr] = block_given? ? block : value
439
+ end
440
+
441
+ # Ensures that an attribute must have a non-nil, non-empty String value.
442
+ # The default behavior will be to raise a user error and thereby halting
443
+ # further configuration processing. Good use cases for require_config
444
+ # might be cloud provider credential keys and other similar data.
445
+ #
446
+ # @example a value that must not be nil or an empty String
447
+ #
448
+ # required_config :cloud_api_token
449
+ #
450
+ # @example using a block to use custom validation logic
451
+ #
452
+ # required_config :email do |attr, value, subject|
453
+ # raise UserError, "Must be an email address" unless value =~ /@/
454
+ # end
455
+ #
456
+ # @param attr [String] configuration attribute name
457
+ # @yieldparam attr [Symbol] the attribute name
458
+ # @yieldparam value [Object] the current value of the attribute
459
+ # @yieldparam object [Object] a reference to the instantiated object
460
+ def required_config(attr, &block)
461
+ if !block_given?
462
+ klass = self
463
+ block = lambda do |_, value, thing|
464
+ if value.nil? || value.to_s.empty?
465
+ attribute = "#{klass}#{thing.instance.to_str}#config[:#{attr}]"
466
+ raise UserError, "#{attribute} cannot be blank"
467
+ end
468
+ end
469
+ end
470
+ validations[attr] = block
471
+ end
472
+
473
+ # @return [Hash] a hash of attribute keys and default values which has
474
+ # been merged with any superclass defaults
475
+ # @api private
476
+ def defaults
477
+ @defaults ||= Hash.new.merge(super_defaults)
478
+ end
479
+
480
+ # @return [Hash] a hash of defaults from the included class' superclass
481
+ # if defined in the superclass, or an empty hash otherwise
482
+ # @api private
483
+ def super_defaults
484
+ if superclass.respond_to?(:defaults)
485
+ superclass.defaults
486
+ else
487
+ Hash.new
488
+ end
489
+ end
490
+
491
+ # @return [Hash] a hash of attribute keys and truthy/falsey values to
492
+ # determine if said attribute needs to be fully file path expanded,
493
+ # which has been merged with any superclass expanded paths
494
+ # @api private
495
+ def expanded_paths
496
+ @expanded_paths ||= Hash.new.merge(super_expanded_paths)
497
+ end
498
+
499
+ # @return [Hash] a hash of expanded paths from the included class'
500
+ # superclass if defined in the superclass, or an empty hash otherwise
501
+ # @api private
502
+ def super_expanded_paths
503
+ if superclass.respond_to?(:expanded_paths)
504
+ superclass.expanded_paths
505
+ else
506
+ Hash.new
507
+ end
508
+ end
509
+
510
+ # @return [Hash] a hash of attribute keys and valudation callable blocks
511
+ # which has been merged with any superclass valudations
512
+ # @api private
513
+ def validations
514
+ @validations ||= Hash.new.merge(super_validations)
515
+ end
516
+
517
+ # @return [Hash] a hash of validations from the included class'
518
+ # superclass if defined in the superclass, or an empty hash otherwise
519
+ # @api private
520
+ def super_validations
521
+ if superclass.respond_to?(:validations)
522
+ superclass.validations
523
+ else
524
+ Hash.new
525
+ end
526
+ end
527
+ end
528
+ end
529
+ end