test-kitchen-rsync 3.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +21 -0
  3. data/LICENSE +15 -0
  4. data/Rakefile +53 -0
  5. data/bin/zl-kitchen +11 -0
  6. data/lib/kitchen/base64_stream.rb +48 -0
  7. data/lib/kitchen/chef_utils_wiring.rb +40 -0
  8. data/lib/kitchen/cli.rb +413 -0
  9. data/lib/kitchen/collection.rb +52 -0
  10. data/lib/kitchen/color.rb +63 -0
  11. data/lib/kitchen/command/action.rb +41 -0
  12. data/lib/kitchen/command/console.rb +54 -0
  13. data/lib/kitchen/command/diagnose.rb +84 -0
  14. data/lib/kitchen/command/doctor.rb +39 -0
  15. data/lib/kitchen/command/exec.rb +37 -0
  16. data/lib/kitchen/command/list.rb +148 -0
  17. data/lib/kitchen/command/login.rb +39 -0
  18. data/lib/kitchen/command/package.rb +32 -0
  19. data/lib/kitchen/command/sink.rb +50 -0
  20. data/lib/kitchen/command/test.rb +47 -0
  21. data/lib/kitchen/command.rb +207 -0
  22. data/lib/kitchen/config.rb +344 -0
  23. data/lib/kitchen/configurable.rb +616 -0
  24. data/lib/kitchen/data_munger.rb +1024 -0
  25. data/lib/kitchen/diagnostic.rb +138 -0
  26. data/lib/kitchen/driver/base.rb +133 -0
  27. data/lib/kitchen/driver/dummy.rb +105 -0
  28. data/lib/kitchen/driver/exec.rb +70 -0
  29. data/lib/kitchen/driver/proxy.rb +70 -0
  30. data/lib/kitchen/driver/ssh_base.rb +351 -0
  31. data/lib/kitchen/driver.rb +40 -0
  32. data/lib/kitchen/errors.rb +243 -0
  33. data/lib/kitchen/generator/init.rb +254 -0
  34. data/lib/kitchen/instance.rb +726 -0
  35. data/lib/kitchen/lazy_hash.rb +148 -0
  36. data/lib/kitchen/lifecycle_hook/base.rb +78 -0
  37. data/lib/kitchen/lifecycle_hook/local.rb +53 -0
  38. data/lib/kitchen/lifecycle_hook/remote.rb +39 -0
  39. data/lib/kitchen/lifecycle_hooks.rb +92 -0
  40. data/lib/kitchen/loader/yaml.rb +377 -0
  41. data/lib/kitchen/logger.rb +422 -0
  42. data/lib/kitchen/logging.rb +52 -0
  43. data/lib/kitchen/login_command.rb +49 -0
  44. data/lib/kitchen/metadata_chopper.rb +49 -0
  45. data/lib/kitchen/platform.rb +64 -0
  46. data/lib/kitchen/plugin.rb +76 -0
  47. data/lib/kitchen/plugin_base.rb +60 -0
  48. data/lib/kitchen/provisioner/base.rb +269 -0
  49. data/lib/kitchen/provisioner/chef/berkshelf.rb +116 -0
  50. data/lib/kitchen/provisioner/chef/common_sandbox.rb +350 -0
  51. data/lib/kitchen/provisioner/chef/policyfile.rb +163 -0
  52. data/lib/kitchen/provisioner/chef_apply.rb +121 -0
  53. data/lib/kitchen/provisioner/chef_base.rb +705 -0
  54. data/lib/kitchen/provisioner/chef_infra.rb +167 -0
  55. data/lib/kitchen/provisioner/chef_solo.rb +82 -0
  56. data/lib/kitchen/provisioner/chef_zero.rb +12 -0
  57. data/lib/kitchen/provisioner/dummy.rb +75 -0
  58. data/lib/kitchen/provisioner/shell.rb +157 -0
  59. data/lib/kitchen/provisioner.rb +42 -0
  60. data/lib/kitchen/rake_tasks.rb +80 -0
  61. data/lib/kitchen/shell_out.rb +90 -0
  62. data/lib/kitchen/ssh.rb +289 -0
  63. data/lib/kitchen/state_file.rb +112 -0
  64. data/lib/kitchen/suite.rb +48 -0
  65. data/lib/kitchen/thor_tasks.rb +63 -0
  66. data/lib/kitchen/transport/base.rb +236 -0
  67. data/lib/kitchen/transport/dummy.rb +78 -0
  68. data/lib/kitchen/transport/exec.rb +145 -0
  69. data/lib/kitchen/transport/ssh.rb +579 -0
  70. data/lib/kitchen/transport/winrm.rb +546 -0
  71. data/lib/kitchen/transport.rb +40 -0
  72. data/lib/kitchen/util.rb +229 -0
  73. data/lib/kitchen/verifier/base.rb +243 -0
  74. data/lib/kitchen/verifier/busser.rb +275 -0
  75. data/lib/kitchen/verifier/dummy.rb +75 -0
  76. data/lib/kitchen/verifier/shell.rb +99 -0
  77. data/lib/kitchen/verifier.rb +39 -0
  78. data/lib/kitchen/version.rb +20 -0
  79. data/lib/kitchen/which.rb +26 -0
  80. data/lib/kitchen.rb +152 -0
  81. data/lib/vendor/hash_recursive_merge.rb +79 -0
  82. data/support/busser_install_command.ps1 +14 -0
  83. data/support/busser_install_command.sh +21 -0
  84. data/support/chef-client-fail-if-update-handler.rb +15 -0
  85. data/support/chef_base_init_command.ps1 +18 -0
  86. data/support/chef_base_init_command.sh +1 -0
  87. data/support/chef_base_install_command.ps1 +85 -0
  88. data/support/chef_base_install_command.sh +229 -0
  89. data/support/download_helpers.sh +109 -0
  90. data/support/dummy-validation.pem +27 -0
  91. data/templates/driver/CHANGELOG.md.erb +3 -0
  92. data/templates/driver/Gemfile.erb +3 -0
  93. data/templates/driver/README.md.erb +64 -0
  94. data/templates/driver/Rakefile.erb +21 -0
  95. data/templates/driver/driver.rb.erb +23 -0
  96. data/templates/driver/gemspec.erb +29 -0
  97. data/templates/driver/gitignore.erb +17 -0
  98. data/templates/driver/license_apachev2.erb +15 -0
  99. data/templates/driver/license_lgplv3.erb +16 -0
  100. data/templates/driver/license_mit.erb +22 -0
  101. data/templates/driver/license_reserved.erb +5 -0
  102. data/templates/driver/tailor.erb +4 -0
  103. data/templates/driver/travis.yml.erb +11 -0
  104. data/templates/driver/version.rb.erb +12 -0
  105. data/templates/init/chefignore.erb +2 -0
  106. data/templates/init/kitchen.yml.erb +18 -0
  107. data/test-kitchen.gemspec +52 -0
  108. metadata +528 -0
@@ -0,0 +1,616 @@
1
+ #
2
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
+ #
4
+ # Copyright (C) 2014, Fletcher Nichol
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require "thor/util"
19
+
20
+ require_relative "lazy_hash"
21
+
22
+ module Kitchen
23
+ # A mixin for providing configuration-related behavior such as default
24
+ # config (static, computed, inherited), required config, local path
25
+ # expansion, etc.
26
+ #
27
+ # @author Fletcher Nichol <fnichol@nichol.ca>
28
+ module Configurable
29
+ def self.included(base)
30
+ base.extend(ClassMethods)
31
+ end
32
+
33
+ # @return [Kitchen::Instance] the associated instance
34
+ attr_reader :instance
35
+
36
+ # A lifecycle method that should be invoked when the object is about ready
37
+ # to be used. A reference to an Instance is required as configuration
38
+ # dependant data may be access through an Instance. This also acts as a
39
+ # hook point where the object may wish to perform other last minute
40
+ # checks, validations, or configuration expansions.
41
+ #
42
+ # @param instance [Instance] an associated instance
43
+ # @return [self] itself, for use in chaining
44
+ # @raise [ClientError] if instance parameter is nil
45
+ def finalize_config!(instance)
46
+ raise ClientError, "Instance must be provided to #{self}" if instance.nil?
47
+
48
+ @instance = instance
49
+ expand_paths!
50
+ deprecate_config!
51
+ validate_config!
52
+ load_needed_dependencies!
53
+
54
+ self
55
+ end
56
+
57
+ # Provides hash-like access to configuration keys.
58
+ #
59
+ # @param attr [Object] configuration key
60
+ # @return [Object] value at configuration key
61
+ def [](attr)
62
+ config[attr]
63
+ end
64
+
65
+ # @return [TrueClass,FalseClass] true if `:shell_type` is `"bourne"` (or
66
+ # unset, for backwards compatability)
67
+ def bourne_shell?
68
+ ["bourne", nil].include?(instance.platform.shell_type)
69
+ end
70
+
71
+ # Find an appropriate path to a file or directory, based on graceful
72
+ # fallback rules or returns nil if path cannot be determined.
73
+ #
74
+ # Given an instance with suite named `"server"`, a `test_base_path` of
75
+ # `"/a/b"`, and a path segement of `"roles"` then following will be tried
76
+ # in order (first match that exists wins):
77
+ #
78
+ # 1. /a/b/server/roles
79
+ # 2. /a/b/roles
80
+ # 3. $PWD/roles
81
+ #
82
+ # @param path [String] the base path segment to search for
83
+ # @param opts [Hash] options
84
+ # @option opts [Symbol] :type either `:file` or `:directory` (default)
85
+ # @option opts [Symbol] :base_path a custom base path to search under,
86
+ # default uses value from `config[:test_base_path]`
87
+ # @return [String] path to the existing file or directory, or nil if file
88
+ # or directory was not found
89
+ # @raise [UserError] if `config[:test_base_path]` is used and is not set
90
+ def calculate_path(path, opts = {})
91
+ type = opts.fetch(:type, :directory)
92
+ base = opts.fetch(:base_path) do
93
+ config.fetch(:test_base_path) do |key|
94
+ raise UserError, "#{key} is not found in #{self}"
95
+ end
96
+ end
97
+
98
+ [
99
+ File.join(base, instance.suite.name, path),
100
+ File.join(base, path),
101
+ File.join(Dir.pwd, path),
102
+ ].find do |candidate|
103
+ type == :directory ? File.directory?(candidate) : File.file?(candidate)
104
+ end
105
+ end
106
+
107
+ # Returns an array of configuration keys.
108
+ #
109
+ # @return [Array] array of configuration keys
110
+ def config_keys
111
+ config.keys
112
+ end
113
+
114
+ # Returns a Hash of configuration and other useful diagnostic information.
115
+ #
116
+ # @return [Hash] a diagnostic hash
117
+ def diagnose
118
+ result = {}
119
+ config_keys.sort.each { |k| result[k] = config[k] }
120
+ result
121
+ end
122
+
123
+ # Returns a Hash of configuration and other useful diagnostic information
124
+ # associated with the plugin itself (such as loaded version, class name,
125
+ # etc.).
126
+ #
127
+ # @return [Hash] a diagnostic hash
128
+ def diagnose_plugin
129
+ result = {}
130
+ result[:name] = name
131
+ result.merge!(self.class.diagnose)
132
+ result
133
+ end
134
+
135
+ # Returns the name of this plugin, suitable for display in a CLI.
136
+ #
137
+ # @return [String] name of this plugin
138
+ def name
139
+ self.class.name.split("::").last
140
+ end
141
+
142
+ # @return [TrueClass,FalseClass] true if `:shell_type` is `"powershell"`
143
+ def powershell_shell?
144
+ ["powershell"].include?(instance.platform.shell_type)
145
+ end
146
+
147
+ # Builds a file path based on the `:os_type` (`"windows"` or `"unix"`).
148
+ #
149
+ # @return [String] joined path for instance's os_type
150
+ def remote_path_join(*parts)
151
+ path = File.join(*parts)
152
+ windows_os? ? path.tr("/", "\\") : path.tr("\\", "/")
153
+ end
154
+
155
+ # @return [TrueClass,FalseClass] true if `:os_type` is `"unix"` (or
156
+ # unset, for backwards compatibility)
157
+ def unix_os?
158
+ ["unix", nil].include?(instance.platform.os_type)
159
+ end
160
+
161
+ # Performs whatever tests that may be required to ensure that this plugin
162
+ # will be able to function in the current environment. This may involve
163
+ # checking for the presence of certain directories, software installed,
164
+ # etc.
165
+ #
166
+ # @raise [UserError] if the plugin will not be able to perform or if a
167
+ # documented dependency is missing from the system
168
+ def verify_dependencies
169
+ # this method may be left unimplemented if that is applicable
170
+ end
171
+
172
+ # @return [TrueClass,FalseClass] true if `:os_type` is `"windows"`
173
+ def windows_os?
174
+ ["windows"].include?(instance.platform.os_type)
175
+ end
176
+
177
+ private
178
+
179
+ # @return [LzayHash] a configuration hash
180
+ # @api private
181
+ attr_reader :config
182
+
183
+ # @return [Hash] a hash of the detected deprecated config attributes
184
+ # @api private
185
+ attr_reader :deprecated_config
186
+
187
+ # @return [Hash] user provided configuration hash
188
+ # @api private
189
+ attr_reader :provided_config
190
+
191
+ # Initializes an internal configuration hash. The hash may contain
192
+ # callable blocks as values that are meant to be called lazily. This
193
+ # method is intended to be included in an object's .initialize method.
194
+ #
195
+ # @param config [Hash] initial provided configuration
196
+ # @api private
197
+ def init_config(config)
198
+ @config = LazyHash.new(config, self)
199
+ @provided_config = config.dup
200
+ self.class.defaults.each do |attr, value|
201
+ @config[attr] = value unless @config.key?(attr)
202
+ end
203
+ end
204
+
205
+ # Expands file paths for certain configuration values. A configuration
206
+ # value is marked for file expansion with a expand_path_for declaration
207
+ # in the included class.
208
+ #
209
+ # @api private
210
+ def expand_paths!
211
+ root_path = config[:kitchen_root] || Dir.pwd
212
+ expanded_paths = LazyHash.new(self.class.expanded_paths, self).to_hash
213
+
214
+ expanded_paths.each do |key, should_expand|
215
+ next if !should_expand || config[key].nil? || config[key] == false
216
+
217
+ config[key] = if config[key].is_a?(Array)
218
+ config[key].map { |path| File.expand_path(path, root_path) }
219
+ else
220
+ File.expand_path(config[key], root_path)
221
+ end
222
+ end
223
+ end
224
+
225
+ # Initialize detected deprecated configuration hash.
226
+ # Display warning if deprecations have been detected.
227
+ #
228
+ # @api private
229
+ def deprecate_config!
230
+ # We only want to display the deprecation list of config values once per execution on the default config.
231
+ # This prevents the output of deprecations from being printed for each permutation of test suites.
232
+ return if defined? @@has_been_warned_of_deprecations
233
+
234
+ deprecated_attributes = LazyHash.new(self.class.deprecated_attributes, self)
235
+ # Remove items from hash when not provided in the loaded config or when the rendered message is nil
236
+ @deprecated_config = deprecated_attributes.delete_if { |attr, obj| !provided_config.key?(attr) || obj.nil? }
237
+
238
+ unless deprecated_config.empty?
239
+ warning = Util.outdent!(<<-MSG)
240
+ Deprecated configuration detected:
241
+ #{deprecated_config.keys.join("\n")}
242
+ Run 'kitchen doctor' for details.
243
+ MSG
244
+ warn(warning)
245
+
246
+ # Set global var that the deprecation message has been printed
247
+ @@has_been_warned_of_deprecations = true
248
+ end
249
+ end
250
+
251
+ # Loads any required third party Ruby libraries or runs any shell out
252
+ # commands to prepare the plugin. This method will be called in the
253
+ # context of the main thread of execution and so does not necessarily
254
+ # have to be thread safe.
255
+ #
256
+ # **Note:** any subclasses overriding this method would be well advised
257
+ # to call super when overriding this method, for example:
258
+ #
259
+ # @example overriding `#load_needed_dependencies!`
260
+ #
261
+ # class MyProvisioner < Kitchen::Provisioner::Base
262
+ # def load_needed_dependencies!
263
+ # super
264
+ # # any further work
265
+ # end
266
+ # end
267
+ #
268
+ # @raise [ClientError] if any library loading fails or any of the
269
+ # dependency requirements cannot be satisfied
270
+ # @api private
271
+ def load_needed_dependencies!
272
+ # this method may be left unimplemented if that is applicable
273
+ end
274
+
275
+ # @return [Logger] the instance's logger or Test Kitchen's common logger
276
+ # otherwise
277
+ # @api private
278
+ def logger
279
+ instance ? instance.logger : Kitchen.logger
280
+ end
281
+
282
+ # @return [String] a powershell command to reload the `PATH` environment
283
+ # variable, only to be used to support old Omnibus Chef packages that
284
+ # require `PATH` to find the `ruby.exe` binary
285
+ # @api private
286
+ def reload_ps1_path
287
+ [
288
+ "$env:PATH = try {",
289
+ "[System.Environment]::GetEnvironmentVariable('PATH','Machine')",
290
+ "} catch { $env:PATH }\n\n",
291
+ ].join("\n")
292
+ end
293
+
294
+ # Builds a shell environment variable assignment string for the
295
+ # required shell type.
296
+ #
297
+ # @param name [String] variable name
298
+ # @param value [String] variable value
299
+ # @return [String] shell variable assignment
300
+ # @api private
301
+ def shell_env_var(name, value)
302
+ if powershell_shell?
303
+ shell_var("env:#{name}", value)
304
+ else
305
+ "#{shell_var(name, value)}; export #{name}"
306
+ end
307
+ end
308
+
309
+ # Builds a shell variable assignment string for the required shell type.
310
+ #
311
+ # @param name [String] variable name
312
+ # @param value [String] variable value
313
+ # @return [String] shell variable assignment
314
+ # @api private
315
+ def shell_var(name, value)
316
+ if powershell_shell?
317
+ %{$#{name} = "#{value}"}
318
+ else
319
+ %{#{name}="#{value}"}
320
+ end
321
+ end
322
+
323
+ # Runs all validations set up for the included class. Each validation is
324
+ # for a specific configuration attribute and has an associated callable
325
+ # block. Each validation block is called with the attribute, its value,
326
+ # and the included object for context.
327
+ #
328
+ # @api private
329
+ def validate_config!
330
+ self.class.validations.each do |attr, block|
331
+ block.call(attr, config[attr], self)
332
+ end
333
+ end
334
+
335
+ # Wraps a body of shell code with common context appropriate for the type
336
+ # of shell.
337
+ #
338
+ # @param code [String] the shell code to be wrapped
339
+ # @return [String] wrapped shell code
340
+ # @api private
341
+ def wrap_shell_code(code)
342
+ return env_wrapped(code) if powershell_shell?
343
+
344
+ Util.wrap_command((env_wrapped code))
345
+ end
346
+
347
+ def env_wrapped(code)
348
+ code_parts = resolve_proxy_settings_from_config
349
+ code_parts << shell_env_var("TEST_KITCHEN", 1)
350
+ code_parts << shell_env_var("CI", ENV["CI"]) if ENV["CI"]
351
+ code_parts << shell_env_var("CHEF_LICENSE", ENV["CHEF_LICENSE"]) if ENV["CHEF_LICENSE"]
352
+ ENV.select { |key, value| key.start_with?("TKENV_") }.each do |key, value|
353
+ env_var_name = "#{key}".sub!("TKENV_", "")
354
+ code_parts << shell_env_var(env_var_name, value)
355
+ end
356
+
357
+ code_parts << code
358
+ code_parts.join("\n")
359
+ end
360
+
361
+ def proxy_setting_keys
362
+ %i{http_proxy https_proxy ftp_proxy no_proxy}
363
+ end
364
+
365
+ def resolve_proxy_settings_from_config
366
+ proxy_setting_keys.each_with_object([]) do |protocol, set_env|
367
+ if !config.key?(protocol) || config[protocol].nil?
368
+ export_proxy(set_env, protocol)
369
+ elsif proxy_config_setting_present?(protocol)
370
+ set_env << shell_env_var(protocol.downcase.to_s, config[protocol])
371
+ set_env << shell_env_var(protocol.upcase.to_s, config[protocol])
372
+ end
373
+ end
374
+ end
375
+
376
+ def proxy_from_config?
377
+ proxy_setting_keys.any? do |protocol|
378
+ !config[protocol].nil?
379
+ end
380
+ end
381
+
382
+ def proxy_from_environment?
383
+ proxy_setting_keys.any? do |protocol|
384
+ !ENV[protocol.downcase.to_s].nil? || !ENV[protocol.upcase.to_s].nil?
385
+ end
386
+ end
387
+
388
+ def proxy_config_setting_present?(protocol)
389
+ config.key?(protocol) && !config[protocol].nil? && !config[protocol].empty?
390
+ end
391
+
392
+ # Helper method to export
393
+ #
394
+ # @param env [Array] the environment to modify
395
+ # @param code [String] the type of proxy to export, one of 'http', 'https' or 'ftp'
396
+ # @api private
397
+ def export_proxy(env, type)
398
+ env << shell_env_var(type.to_s, ENV[type.to_s]) if ENV[type.to_s]
399
+ env << shell_env_var(type.upcase.to_s, ENV[type.upcase.to_s]) if
400
+ ENV[type.upcase.to_s]
401
+ end
402
+
403
+ # Class methods which will be mixed in on inclusion of Configurable module.
404
+ module ClassMethods
405
+ # Sets the loaded version of this plugin, usually corresponding to the
406
+ # RubyGems version of the plugin's library. If the plugin does not set
407
+ # this value, then `nil` will be used and reported.
408
+ #
409
+ # @example setting a version used by RubyGems
410
+ #
411
+ # require "kitchen/driver/vagrant_version"
412
+ #
413
+ # module Kitchen
414
+ # module Driver
415
+ # class Vagrant < Kitchen::Driver::Base
416
+ #
417
+ # plugin_version Kitchen::Driver::VAGRANT_VERSION
418
+ #
419
+ # end
420
+ # end
421
+ # end
422
+ #
423
+ # @param version [String] a version string
424
+ def plugin_version(version) # rubocop:disable Style/TrivialAccessors
425
+ @plugin_version = version
426
+ end
427
+
428
+ # Returns a Hash of configuration and other useful diagnostic
429
+ # information.
430
+ #
431
+ # @return [Hash] a diagnostic hash
432
+ def diagnose
433
+ {
434
+ class: name,
435
+ version: @plugin_version ||= nil,
436
+ api_version: @api_version ||= nil,
437
+ }
438
+ end
439
+
440
+ # Sets a sane default value for a configuration attribute. These values
441
+ # can be overridden by provided configuration or in a subclass with
442
+ # another default_config declaration.
443
+ #
444
+ # @example a nil default value
445
+ #
446
+ # default_config :i_am_nil
447
+ #
448
+ # @example a primitive default value
449
+ #
450
+ # default_config :use_sudo, true
451
+ #
452
+ # @example a block to compute a default value
453
+ #
454
+ # default_config :box_name do |subject|
455
+ # subject.instance.platform.name
456
+ # end
457
+ #
458
+ # @param attr [String] configuration attribute name
459
+ # @param value [Object, nil] static default value for attribute
460
+ # @yieldparam object [Object] a reference to the instantiated object
461
+ # @yieldreturn [Object, nil] dynamically computed value for the attribute
462
+ def default_config(attr, value = nil, &block)
463
+ defaults[attr] = block_given? ? block : value
464
+ end
465
+
466
+ # Ensures that an attribute which is a path will be fully expanded at
467
+ # the right time. This helps make the configuration unambiguous and much
468
+ # easier to debug and diagnose.
469
+ #
470
+ # Note that the file path expansion is only intended for paths on the
471
+ # local workstation invking the Test Kitchen code.
472
+ #
473
+ # @example the default usage
474
+ #
475
+ # expand_path_for :data_path
476
+ #
477
+ # @example disabling path expansion with a falsey value
478
+ #
479
+ # expand_path_for :relative_path, false
480
+ #
481
+ # @example using a block to determine whether or not to expand
482
+ #
483
+ # expand_path_for :relative_or_not_path do |subject|
484
+ # subject.instance.name =~ /default/
485
+ # end
486
+ #
487
+ # @param attr [String] configuration attribute name
488
+ # @param value [Object, nil] whether or not to exand the file path
489
+ # @yieldparam object [Object] a reference to the instantiated object
490
+ # @yieldreturn [Object, nil] dynamically compute whether or not to
491
+ # perform the file expansion
492
+ def expand_path_for(attr, value = true, &block)
493
+ expanded_paths[attr] = block_given? ? block : value
494
+ end
495
+
496
+ # Set the appropriate deprecation message for a given attribute name
497
+ #
498
+ # @example the default usage
499
+ #
500
+ # deprecate_config_for :attribute_name, "Detailed deprecation message."
501
+ #
502
+ # @example using a block
503
+ #
504
+ # deprecate_config_for :attribute_name do |subject|
505
+ # "Detailed deprecation message." if subject == true
506
+ # end
507
+ #
508
+ # @param attr [String] configuration attribute name
509
+ # @param value [Object, nil] static default value for attribute
510
+ # @yieldparam object [Object] a reference to the instantiated object
511
+ # @yieldreturn [Object, nil] dynamically computed value for the attribute
512
+ def deprecate_config_for(attr, value = nil, &block)
513
+ deprecated_attributes[attr] = block_given? ? block : value
514
+ end
515
+
516
+ # Ensures that an attribute must have a non-nil, non-empty String value.
517
+ # The default behavior will be to raise a user error and thereby halting
518
+ # further configuration processing. Good use cases for require_config
519
+ # might be cloud provider credential keys and other similar data.
520
+ #
521
+ # @example a value that must not be nil or an empty String
522
+ #
523
+ # required_config :cloud_api_token
524
+ #
525
+ # @example using a block to use custom validation logic
526
+ #
527
+ # required_config :email do |attr, value, subject|
528
+ # raise UserError, "Must be an email address" unless value =~ /@/
529
+ # end
530
+ #
531
+ # @param attr [String] configuration attribute name
532
+ # @yieldparam attr [Symbol] the attribute name
533
+ # @yieldparam value [Object] the current value of the attribute
534
+ # @yieldparam object [Object] a reference to the instantiated object
535
+ def required_config(attr, &block)
536
+ unless block_given?
537
+ klass = self
538
+ block = lambda do |_, value, thing|
539
+ if value.nil? || value.to_s.empty?
540
+ attribute = "#{klass}#{thing.instance.to_str}#config[:#{attr}]"
541
+ raise UserError, "#{attribute} cannot be blank"
542
+ end
543
+ end
544
+ end
545
+ validations[attr] = block
546
+ end
547
+
548
+ # @return [Hash] a hash of attribute keys and default values which has
549
+ # been merged with any superclass defaults
550
+ # @api private
551
+ def defaults
552
+ @defaults ||= {}.merge(super_defaults)
553
+ end
554
+
555
+ # @return [Hash] a hash of defaults from the included class' superclass
556
+ # if defined in the superclass, or an empty hash otherwise
557
+ # @api private
558
+ def super_defaults
559
+ if superclass.respond_to?(:defaults)
560
+ superclass.defaults
561
+ else
562
+ {}
563
+ end
564
+ end
565
+
566
+ # @return [Hash] a hash of attribute keys and truthy/falsey values to
567
+ # determine if said attribute needs to be fully file path expanded,
568
+ # which has been merged with any superclass expanded paths
569
+ # @api private
570
+ def expanded_paths
571
+ @expanded_paths ||= {}.merge(super_expanded_paths)
572
+ end
573
+
574
+ # @return [Hash] a hash of expanded paths from the included class'
575
+ # superclass if defined in the superclass, or an empty hash otherwise
576
+ # @api private
577
+ def super_expanded_paths
578
+ if superclass.respond_to?(:expanded_paths)
579
+ superclass.expanded_paths
580
+ else
581
+ {}
582
+ end
583
+ end
584
+
585
+ def deprecated_attributes
586
+ @deprecated_attributes ||= {}.merge(super_deprecated_attributes)
587
+ end
588
+
589
+ def super_deprecated_attributes
590
+ if superclass.respond_to?(:deprecated_attributes)
591
+ superclass.deprecated_attributes
592
+ else
593
+ {}
594
+ end
595
+ end
596
+
597
+ # @return [Hash] a hash of attribute keys and valudation callable blocks
598
+ # which has been merged with any superclass valudations
599
+ # @api private
600
+ def validations
601
+ @validations ||= {}.merge(super_validations)
602
+ end
603
+
604
+ # @return [Hash] a hash of validations from the included class'
605
+ # superclass if defined in the superclass, or an empty hash otherwise
606
+ # @api private
607
+ def super_validations
608
+ if superclass.respond_to?(:validations)
609
+ superclass.validations
610
+ else
611
+ {}
612
+ end
613
+ end
614
+ end
615
+ end
616
+ end