chef 12.4.3 → 12.5.0.alpha.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -2
  3. data/lib/chef.rb +1 -1
  4. data/lib/chef/application/solo.rb +1 -1
  5. data/lib/chef/application/windows_service_manager.rb +17 -12
  6. data/lib/chef/chef_class.rb +7 -0
  7. data/lib/chef/chef_fs/config.rb +22 -24
  8. data/lib/chef/chef_fs/file_pattern.rb +4 -15
  9. data/lib/chef/chef_fs/file_system/cookbook_dir.rb +1 -0
  10. data/lib/chef/chef_fs/knife.rb +35 -7
  11. data/lib/chef/chef_fs/path_utils.rb +65 -34
  12. data/lib/chef/constants.rb +27 -0
  13. data/lib/chef/delayed_evaluator.rb +21 -0
  14. data/lib/chef/dsl/recipe.rb +20 -2
  15. data/lib/chef/event_dispatch/base.rb +40 -16
  16. data/lib/chef/event_dispatch/dsl.rb +64 -0
  17. data/lib/chef/exceptions.rb +6 -1
  18. data/lib/chef/formatters/doc.rb +3 -1
  19. data/lib/chef/guard_interpreter/resource_guard_interpreter.rb +3 -1
  20. data/lib/chef/http/http_request.rb +1 -1
  21. data/lib/chef/knife/bootstrap/templates/chef-full.erb +1 -1
  22. data/lib/chef/knife/ssl_check.rb +3 -2
  23. data/lib/chef/knife/user_edit.rb +1 -2
  24. data/lib/chef/mixin/params_validate.rb +362 -135
  25. data/lib/chef/node.rb +19 -0
  26. data/lib/chef/platform/handler_map.rb +0 -5
  27. data/lib/chef/platform/rebooter.rb +1 -1
  28. data/lib/chef/property.rb +539 -0
  29. data/lib/chef/provider.rb +129 -12
  30. data/lib/chef/provider/deploy.rb +3 -5
  31. data/lib/chef/provider/lwrp_base.rb +1 -75
  32. data/lib/chef/provider/package.rb +1 -1
  33. data/lib/chef/provider/powershell_script.rb +32 -19
  34. data/lib/chef/provider/registry_key.rb +5 -5
  35. data/lib/chef/provider/service/macosx.rb +5 -1
  36. data/lib/chef/recipe.rb +1 -8
  37. data/lib/chef/resource.rb +499 -84
  38. data/lib/chef/resource/file/verification.rb +7 -1
  39. data/lib/chef/resource/lwrp_base.rb +1 -7
  40. data/lib/chef/run_context.rb +404 -83
  41. data/lib/chef/version.rb +1 -1
  42. data/lib/chef/win32/registry.rb +10 -2
  43. data/lib/chef/workstation_config_loader.rb +3 -158
  44. data/spec/data/run_context/cookbooks/include/recipes/default.rb +24 -0
  45. data/spec/data/run_context/cookbooks/include/recipes/includee.rb +3 -0
  46. data/spec/functional/rebooter_spec.rb +1 -1
  47. data/spec/functional/resource/{powershell_spec.rb → powershell_script_spec.rb} +3 -3
  48. data/spec/functional/win32/registry_helper_spec.rb +12 -0
  49. data/spec/functional/win32/service_manager_spec.rb +2 -2
  50. data/spec/integration/knife/chef_repo_path_spec.rb +13 -11
  51. data/spec/integration/recipes/recipe_dsl_spec.rb +0 -15
  52. data/spec/integration/recipes/resource_action_spec.rb +343 -0
  53. data/spec/spec_helper.rb +1 -0
  54. data/spec/support/shared/functional/win32_service.rb +2 -1
  55. data/spec/unit/application/solo_spec.rb +4 -3
  56. data/spec/unit/chef_class_spec.rb +23 -0
  57. data/spec/unit/chef_fs/path_util_spec.rb +108 -0
  58. data/spec/unit/event_dispatch/dsl_spec.rb +87 -0
  59. data/spec/unit/json_compat_spec.rb +4 -3
  60. data/spec/unit/knife/ssl_check_spec.rb +4 -0
  61. data/spec/unit/mixin/params_validate_spec.rb +4 -2
  62. data/spec/unit/node_spec.rb +7 -0
  63. data/spec/unit/property/state_spec.rb +506 -0
  64. data/spec/unit/property/validation_spec.rb +658 -0
  65. data/spec/unit/property_spec.rb +968 -0
  66. data/spec/unit/provider/{powershell_spec.rb → powershell_script_spec.rb} +0 -0
  67. data/spec/unit/provider/registry_key_spec.rb +12 -0
  68. data/spec/unit/provider/service/macosx_spec.rb +4 -4
  69. data/spec/unit/provider_spec.rb +1 -3
  70. data/spec/unit/recipe_spec.rb +0 -4
  71. data/spec/unit/registry_helper_spec.rb +15 -1
  72. data/spec/unit/resource/file/verification_spec.rb +33 -5
  73. data/spec/unit/resource/{powershell_spec.rb → powershell_script_spec.rb} +0 -0
  74. data/spec/unit/resource_spec.rb +2 -2
  75. data/spec/unit/run_context/child_run_context_spec.rb +133 -0
  76. data/spec/unit/run_context_spec.rb +7 -0
  77. metadata +25 -25
  78. data/spec/unit/workstation_config_loader_spec.rb +0 -283
@@ -0,0 +1,27 @@
1
+ #
2
+ # Author:: John Keiser <jkeiser@chef.io>
3
+ # Copyright:: Copyright (c) 2015 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
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
+ class Chef
19
+ NOT_PASSED = Object.new
20
+ def NOT_PASSED.to_s
21
+ "NOT_PASSED"
22
+ end
23
+ def NOT_PASSED.inspect
24
+ to_s
25
+ end
26
+ NOT_PASSED.freeze
27
+ end
@@ -0,0 +1,21 @@
1
+ #
2
+ # Author:: John Keiser <jkeiser@chef.io>
3
+ # Copyright:: Copyright (c) 2015 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
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
+ class Chef
19
+ class DelayedEvaluator < Proc
20
+ end
21
+ end
@@ -122,9 +122,9 @@ class Chef
122
122
 
123
123
  def describe_self_for_error
124
124
  if respond_to?(:name)
125
- %Q[`#{self.class.name} "#{name}"']
125
+ %Q[`#{self.class} "#{name}"']
126
126
  elsif respond_to?(:recipe_name)
127
- %Q[`#{self.class.name} "#{recipe_name}"']
127
+ %Q[`#{self.class} "#{recipe_name}"']
128
128
  else
129
129
  to_s
130
130
  end
@@ -176,6 +176,24 @@ class Chef
176
176
  raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
177
177
  end
178
178
  end
179
+
180
+ module FullDSL
181
+ require 'chef/dsl/data_query'
182
+ require 'chef/dsl/platform_introspection'
183
+ require 'chef/dsl/include_recipe'
184
+ require 'chef/dsl/registry_helper'
185
+ require 'chef/dsl/reboot_pending'
186
+ require 'chef/dsl/audit'
187
+ require 'chef/dsl/powershell'
188
+ include Chef::DSL::DataQuery
189
+ include Chef::DSL::PlatformIntrospection
190
+ include Chef::DSL::IncludeRecipe
191
+ include Chef::DSL::Recipe
192
+ include Chef::DSL::RegistryHelper
193
+ include Chef::DSL::RebootPending
194
+ include Chef::DSL::Audit
195
+ include Chef::DSL::Powershell
196
+ end
179
197
  end
180
198
  end
181
199
  end
@@ -269,26 +269,37 @@ class Chef
269
269
  # def notifications_resolved
270
270
  # end
271
271
 
272
+ #
273
+ # Resource events and ordering:
274
+ #
275
+ # 1. Start the action
276
+ # - resource_action_start
277
+ # 2. Check the guard
278
+ # - resource_skipped: (goto 7) if only_if/not_if say to skip
279
+ # 3. Load the current resource
280
+ # - resource_current_state_loaded
281
+ # - resource_current_state_load_bypassed (if not why-run safe)
282
+ # 4. Check if why-run safe
283
+ # - resource_bypassed: (goto 7) if not why-run safe
284
+ # 5. During processing:
285
+ # - resource_update_applied: For each actual change (many per action)
286
+ # 6. Processing complete status:
287
+ # - resource_failed if the resource threw an exception while running
288
+ # - resource_failed_retriable: (goto 3) if resource failed and will be retried
289
+ # - resource_updated if the resource was updated (resource_update_applied will have been called)
290
+ # - resource_up_to_date if the resource was up to date (no resource_update_applied)
291
+ # 7. Processing complete:
292
+ # - resource_completed
293
+ #
294
+
272
295
  # Called before action is executed on a resource.
273
296
  def resource_action_start(resource, action, notification_type=nil, notifier=nil)
274
297
  end
275
298
 
276
- # Called when a resource fails, but will retry.
277
- def resource_failed_retriable(resource, action, retry_count, exception)
278
- end
279
-
280
- # Called when a resource fails and will not be retried.
281
- def resource_failed(resource, action, exception)
282
- end
283
-
284
299
  # Called when a resource action has been skipped b/c of a conditional
285
300
  def resource_skipped(resource, action, conditional)
286
301
  end
287
302
 
288
- # Called when a resource action has been completed
289
- def resource_completed(resource)
290
- end
291
-
292
303
  # Called after #load_current_resource has run.
293
304
  def resource_current_state_loaded(resource, action, current_resource)
294
305
  end
@@ -302,21 +313,34 @@ class Chef
302
313
  def resource_bypassed(resource, action, current_resource)
303
314
  end
304
315
 
305
- # Called when a resource has no converge actions, e.g., it was already correct.
306
- def resource_up_to_date(resource, action)
307
- end
308
-
309
316
  # Called when a change has been made to a resource. May be called multiple
310
317
  # times per resource, e.g., a file may have its content updated, and then
311
318
  # its permissions updated.
312
319
  def resource_update_applied(resource, action, update)
313
320
  end
314
321
 
322
+ # Called when a resource fails, but will retry.
323
+ def resource_failed_retriable(resource, action, retry_count, exception)
324
+ end
325
+
326
+ # Called when a resource fails and will not be retried.
327
+ def resource_failed(resource, action, exception)
328
+ end
329
+
315
330
  # Called after a resource has been completely converged, but only if
316
331
  # modifications were made.
317
332
  def resource_updated(resource, action)
318
333
  end
319
334
 
335
+ # Called when a resource has no converge actions, e.g., it was already correct.
336
+ def resource_up_to_date(resource, action)
337
+ end
338
+
339
+ # Called when a resource action has been completed
340
+ def resource_completed(resource)
341
+ end
342
+
343
+
320
344
  # A stream has opened.
321
345
  def stream_opened(stream, options = {})
322
346
  end
@@ -0,0 +1,64 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@linux.com>)
3
+ # Copyright:: Copyright (c) 2015 Ranjib Dey
4
+ # License:: Apache License, Version 2.0
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 'chef/event_dispatch/base'
19
+ require 'chef/exceptions'
20
+ require 'chef/config'
21
+
22
+ class Chef
23
+ module EventDispatch
24
+ class DSL
25
+ attr_reader :handler
26
+
27
+ def initialize(name)
28
+ klass = Class.new(Chef::EventDispatch::Base) do
29
+ attr_reader :name
30
+ end
31
+ @handler = klass.new
32
+ @handler.instance_variable_set(:@name, name)
33
+
34
+ # Use event.register API to add anonymous handler if Chef.run_context
35
+ # and associated event dispatcher is set, else fallback to
36
+ # Chef::Config[:hanlder]
37
+ if Chef.run_context && Chef.run_context.events
38
+ Chef::Log.debug("Registering handler '#{name}' using events api")
39
+ Chef.run_context.events.register(handler)
40
+ else
41
+ Chef::Log.debug("Registering handler '#{name}' using global config")
42
+ Chef::Config[:event_handlers] << handler
43
+ end
44
+ end
45
+
46
+ # Adds a new event handler derived from base handler
47
+ # with user defined block against a chef event
48
+ #
49
+ # @return [Chef::EventDispatch::Base] a base handler object
50
+ def on(event_type, &block)
51
+ validate!(event_type)
52
+ handler.define_singleton_method(event_type) do |*args|
53
+ instance_exec(*args, &block)
54
+ end
55
+ end
56
+
57
+ private
58
+ def validate!(event_type)
59
+ all_event_types = (Chef::EventDispatch::Base.instance_methods - Object.instance_methods)
60
+ raise Chef::Exceptions::InvalidEventType, "Invalid event type: #{event_type}" unless all_event_types.include?(event_type)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -17,12 +17,16 @@
17
17
  # See the License for the specific language governing permissions and
18
18
  # limitations under the License.
19
19
 
20
+ require 'chef-config/exceptions'
21
+
20
22
  class Chef
21
23
  # == Chef::Exceptions
22
24
  # Chef's custom exceptions are all contained within the Chef::Exceptions
23
25
  # namespace.
24
26
  class Exceptions
25
27
 
28
+ ConfigurationError = ChefConfig::ConfigurationError
29
+
26
30
  # Backcompat with Chef::ShellOut code:
27
31
  require 'mixlib/shellout/exceptions'
28
32
 
@@ -68,7 +72,6 @@ class Chef
68
72
  class DuplicateRole < RuntimeError; end
69
73
  class ValidationFailed < ArgumentError; end
70
74
  class InvalidPrivateKey < ArgumentError; end
71
- class ConfigurationError < ArgumentError; end
72
75
  class MissingKeyAttribute < ArgumentError; end
73
76
  class KeyCommandInputError < ArgumentError; end
74
77
  class InvalidKeyArgument < ArgumentError; end
@@ -100,6 +103,8 @@ class Chef
100
103
  class ProviderNotFound < RuntimeError; end
101
104
  NoProviderAvailable = ProviderNotFound
102
105
  class VerificationNotFound < RuntimeError; end
106
+ class InvalidEventType < ArgumentError; end
107
+ class MultipleIdentityError < RuntimeError; end
103
108
 
104
109
  # Can't find a Resource of this type that is valid on this platform.
105
110
  class NoSuchResourceType < NameError
@@ -22,6 +22,7 @@ class Chef
22
22
  @failed_audits = 0
23
23
  @start_time = Time.now
24
24
  @end_time = @start_time
25
+ @skipped_resources = 0
25
26
  end
26
27
 
27
28
  def elapsed_time
@@ -33,7 +34,7 @@ class Chef
33
34
  end
34
35
 
35
36
  def total_resources
36
- @up_to_date_resources + @updated_resources
37
+ @up_to_date_resources + @updated_resources + @skipped_resources
37
38
  end
38
39
 
39
40
  def total_audits
@@ -236,6 +237,7 @@ class Chef
236
237
 
237
238
  # Called when a resource action has been skipped b/c of a conditional
238
239
  def resource_skipped(resource, action, conditional)
240
+ @skipped_resources += 1
239
241
  # TODO: more info about conditional
240
242
  puts " (skipped due to #{conditional.short_description})", :stream => resource
241
243
  unindent
@@ -68,7 +68,9 @@ class Chef
68
68
  run_action = action || @resource.action
69
69
 
70
70
  begin
71
- # Coerce to an array to be safe.
71
+ # Coerce to an array to be safe. This could happen with a legacy
72
+ # resource or something overriding the default_action code in a
73
+ # subclass.
72
74
  Array(run_action).each {|action_to_run| @resource.run_action(action_to_run) }
73
75
  resource_updated = @resource.updated
74
76
  rescue Mixlib::ShellOut::ShellCommandFailed
@@ -40,7 +40,7 @@ class Chef
40
40
 
41
41
  engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
42
42
 
43
- UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +http://opscode.com)"
43
+ UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +https://chef.io)"
44
44
  DEFAULT_UA = "Chef Client" << UA_COMMON
45
45
 
46
46
  USER_AGENT = "User-Agent".freeze
@@ -226,6 +226,6 @@ cat > /etc/chef/first-boot.json <<EOP
226
226
  <%= Chef::JSONCompat.to_json(first_boot) %>
227
227
  EOP
228
228
 
229
- echo "Starting first Chef Client run..."
229
+ echo "Starting the first Chef Client run..."
230
230
 
231
231
  <%= start_chef %>'
@@ -73,11 +73,12 @@ class Chef
73
73
  exit 1
74
74
  end
75
75
 
76
-
77
76
  def verify_peer_socket
78
77
  @verify_peer_socket ||= begin
79
78
  tcp_connection = TCPSocket.new(host, port)
80
- OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
79
+ ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
80
+ ssl_client.hostname = host
81
+ ssl_client
81
82
  end
82
83
  end
83
84
 
@@ -57,7 +57,6 @@ EOF
57
57
  end
58
58
 
59
59
  original_user = Chef::UserV1.load(@user_name).to_hash
60
-
61
60
  # DEPRECATION NOTE
62
61
  # Remove this if statement and corrosponding code post OSC 11 support.
63
62
  #
@@ -73,7 +72,7 @@ EOF
73
72
  user.update
74
73
  ui.msg("Saved #{user}.")
75
74
  else
76
- ui.msg("User unchaged, not saving.")
75
+ ui.msg("User unchanged, not saving.")
77
76
  end
78
77
  end
79
78
 
@@ -15,9 +15,11 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
+ require 'chef/constants'
19
+ require 'chef/property'
20
+ require 'chef/delayed_evaluator'
21
+
18
22
  class Chef
19
- class DelayedEvaluator < Proc
20
- end
21
23
  module Mixin
22
24
  module ParamsValidate
23
25
 
@@ -32,20 +34,55 @@ class Chef
32
34
  # Would raise an exception if the value of :one above is not a kind_of? string. Valid
33
35
  # map options are:
34
36
  #
35
- # :default:: Sets the default value for this parameter.
36
- # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid.
37
- # The key will be inserted into the error message if the Proc does not return true:
38
- # "Option #{key}'s value #{value} #{message}!"
39
- # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure
40
- # that the value is one of those types.
41
- # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of
42
- # method names.
43
- # :required:: Raise an exception if this parameter is missing. Valid values are true or false,
44
- # by default, options are not required.
45
- # :regex:: Match the value of the parameter against a regular expression.
46
- # :equal_to:: Match the value of the parameter with ==. An array means it can be equal to any
47
- # of the values.
37
+ # @param opts [Hash<Symbol,Object>] Validation opts.
38
+ # @option opts [Object,Array] :is An object, or list of
39
+ # objects, that must match the value using Ruby's `===` operator
40
+ # (`opts[:is].any? { |v| v === value }`). (See #_pv_is.)
41
+ # @option opts [Object,Array] :equal_to An object, or list
42
+ # of objects, that must be equal to the value using Ruby's `==`
43
+ # operator (`opts[:is].any? { |v| v == value }`) (See #_pv_equal_to.)
44
+ # @option opts [Regexp,Array<Regexp>] :regex An object, or
45
+ # list of objects, that must match the value with `regex.match(value)`.
46
+ # (See #_pv_regex)
47
+ # @option opts [Class,Array<Class>] :kind_of A class, or
48
+ # list of classes, that the value must be an instance of. (See
49
+ # #_pv_kind_of.)
50
+ # @option opts [Hash<String,Proc>] :callbacks A hash of
51
+ # messages -> procs, all of which match the value. The proc must
52
+ # return a truthy or falsey value (true means it matches). (See
53
+ # #_pv_callbacks.)
54
+ # @option opts [Symbol,Array<Symbol>] :respond_to A method
55
+ # name, or list of method names, the value must respond to. (See
56
+ # #_pv_respond_to.)
57
+ # @option opts [Symbol,Array<Symbol>] :cannot_be A property,
58
+ # or a list of properties, that the value cannot have (such as `:nil` or
59
+ # `:empty`). The method with a questionmark at the end is called on the
60
+ # value (e.g. `value.empty?`). If the value does not have this method,
61
+ # it is considered valid (i.e. if you don't respond to `empty?` we
62
+ # assume you are not empty). (See #_pv_cannot_be.)
63
+ # @option opts [Proc] :coerce A proc which will be called to
64
+ # transform the user input to canonical form. The value is passed in,
65
+ # and the transformed value returned as output. Lazy values will *not*
66
+ # be passed to this method until after they are evaluated. Called in the
67
+ # context of the resource (meaning you can access other properties).
68
+ # (See #_pv_coerce.) (See #_pv_coerce.)
69
+ # @option opts [Boolean] :required `true` if this property
70
+ # must be present and not `nil`; `false` otherwise. This is checked
71
+ # after the resource is fully initialized. (See #_pv_required.)
72
+ # @option opts [Boolean] :name_property `true` if this
73
+ # property defaults to the same value as `name`. Equivalent to
74
+ # `default: lazy { name }`, except that #property_is_set? will
75
+ # return `true` if the property is set *or* if `name` is set. (See
76
+ # #_pv_name_property.)
77
+ # @option opts [Boolean] :name_attribute Same as `name_property`.
78
+ # @option opts [Object] :default The value this property
79
+ # will return if the user does not set one. If this is `lazy`, it will
80
+ # be run in the context of the instance (and able to access other
81
+ # properties). (See #_pv_default.)
82
+ #
48
83
  def validate(opts, map)
84
+ map = map.validation_options if map.is_a?(Property)
85
+
49
86
  #--
50
87
  # validate works by taking the keys in the validation map, assuming it's a hash, and
51
88
  # looking for _pv_:symbol as methods. Assuming it find them, it calls the right
@@ -65,7 +102,7 @@ class Chef
65
102
  true
66
103
  when Hash
67
104
  validation.each do |check, carg|
68
- check_method = "_pv_#{check.to_s}"
105
+ check_method = "_pv_#{check}"
69
106
  if self.respond_to?(check_method, true)
70
107
  self.send(check_method, opts, key, carg)
71
108
  else
@@ -81,162 +118,352 @@ class Chef
81
118
  DelayedEvaluator.new(&block)
82
119
  end
83
120
 
84
- def set_or_return(symbol, arg, validation)
85
- iv_symbol = "@#{symbol.to_s}".to_sym
86
- if arg == nil && self.instance_variable_defined?(iv_symbol) == true
87
- ivar = self.instance_variable_get(iv_symbol)
88
- if(ivar.is_a?(DelayedEvaluator))
89
- validate({ symbol => ivar.call }, { symbol => validation })[symbol]
90
- else
91
- ivar
92
- end
93
- else
94
- if(arg.is_a?(DelayedEvaluator))
95
- val = arg
96
- else
97
- val = validate({ symbol => arg }, { symbol => validation })[symbol]
121
+ def set_or_return(symbol, value, validation)
122
+ property = SetOrReturnProperty.new(name: symbol, **validation)
123
+ property.call(self, value)
124
+ end
98
125
 
99
- # Handle the case where the "default" was a DelayedEvaluator. In
100
- # this case, the block yields an optional parameter of +self+,
101
- # which is the equivalent of "new_resource"
102
- if val.is_a?(DelayedEvaluator)
103
- val = val.call(self)
104
- end
105
- end
106
- self.instance_variable_set(iv_symbol, val)
126
+ private
127
+
128
+ def explicitly_allows_nil?(key, validation)
129
+ validation.has_key?(:is) && _pv_is({ key => nil }, key, validation[:is], raise_error: false)
130
+ end
131
+
132
+ # Return the value of a parameter, or nil if it doesn't exist.
133
+ def _pv_opts_lookup(opts, key)
134
+ if opts.has_key?(key.to_s)
135
+ opts[key.to_s]
136
+ elsif opts.has_key?(key.to_sym)
137
+ opts[key.to_sym]
138
+ else
139
+ nil
107
140
  end
108
141
  end
109
142
 
110
- private
143
+ # Raise an exception if the parameter is not found.
144
+ def _pv_required(opts, key, is_required=true, explicitly_allows_nil=false)
145
+ if is_required
146
+ return true if opts.has_key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?)
147
+ return true if opts.has_key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?)
148
+ raise Exceptions::ValidationFailed, "Required argument #{key.inspect} is missing!"
149
+ end
150
+ true
151
+ end
111
152
 
112
- # Return the value of a parameter, or nil if it doesn't exist.
113
- def _pv_opts_lookup(opts, key)
114
- if opts.has_key?(key.to_s)
115
- opts[key.to_s]
116
- elsif opts.has_key?(key.to_sym)
117
- opts[key.to_sym]
118
- else
119
- nil
153
+ #
154
+ # List of things values must be equal to.
155
+ #
156
+ # Uses Ruby's `==` to evaluate (equal_to == value). At least one must
157
+ # match for the value to be valid.
158
+ #
159
+ # `nil` passes this validation automatically.
160
+ #
161
+ # @return [Array,nil] List of things values must be equal to, or nil if
162
+ # equal_to is unspecified.
163
+ #
164
+ def _pv_equal_to(opts, key, to_be)
165
+ value = _pv_opts_lookup(opts, key)
166
+ unless value.nil?
167
+ to_be = Array(to_be)
168
+ to_be.each do |tb|
169
+ return true if value == tb
120
170
  end
171
+ raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
121
172
  end
173
+ end
122
174
 
123
- # Raise an exception if the parameter is not found.
124
- def _pv_required(opts, key, is_required=true)
125
- if is_required
126
- if (opts.has_key?(key.to_s) && !opts[key.to_s].nil?) ||
127
- (opts.has_key?(key.to_sym) && !opts[key.to_sym].nil?)
128
- true
129
- else
130
- raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
131
- end
175
+ #
176
+ # List of things values must be instances of.
177
+ #
178
+ # Uses value.kind_of?(kind_of) to evaluate. At least one must match for
179
+ # the value to be valid.
180
+ #
181
+ # `nil` automatically passes this validation.
182
+ #
183
+ def _pv_kind_of(opts, key, to_be)
184
+ value = _pv_opts_lookup(opts, key)
185
+ unless value.nil?
186
+ to_be = Array(to_be)
187
+ to_be.each do |tb|
188
+ return true if value.kind_of?(tb)
132
189
  end
190
+ raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
133
191
  end
192
+ end
134
193
 
135
- def _pv_equal_to(opts, key, to_be)
136
- value = _pv_opts_lookup(opts, key)
137
- unless value.nil?
138
- passes = false
139
- Array(to_be).each do |tb|
140
- passes = true if value == tb
141
- end
142
- unless passes
143
- raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
194
+ #
195
+ # List of method names values must respond to.
196
+ #
197
+ # Uses value.respond_to?(respond_to) to evaluate. At least one must match
198
+ # for the value to be valid.
199
+ #
200
+ def _pv_respond_to(opts, key, method_name_list)
201
+ value = _pv_opts_lookup(opts, key)
202
+ unless value.nil?
203
+ Array(method_name_list).each do |method_name|
204
+ unless value.respond_to?(method_name)
205
+ raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
144
206
  end
145
207
  end
146
208
  end
209
+ end
147
210
 
148
- # Raise an exception if the parameter is not a kind_of?(to_be)
149
- def _pv_kind_of(opts, key, to_be)
150
- value = _pv_opts_lookup(opts, key)
151
- unless value.nil?
152
- passes = false
153
- Array(to_be).each do |tb|
154
- passes = true if value.kind_of?(tb)
155
- end
156
- unless passes
157
- raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
211
+ #
212
+ # List of things that must not be true about the value.
213
+ #
214
+ # Calls `value.<thing>?` All responses must be false for the value to be
215
+ # valid.
216
+ # Values which do not respond to <thing>? are considered valid (because if
217
+ # a value doesn't respond to `:readable?`, then it probably isn't
218
+ # readable.)
219
+ #
220
+ # @example
221
+ # ```ruby
222
+ # property :x, cannot_be: [ :nil, :empty ]
223
+ # x [ 1, 2 ] #=> valid
224
+ # x 1 #=> valid
225
+ # x [] #=> invalid
226
+ # x nil #=> invalid
227
+ # ```
228
+ #
229
+ def _pv_cannot_be(opts, key, predicate_method_base_name)
230
+ value = _pv_opts_lookup(opts, key)
231
+ if !value.nil?
232
+ Array(predicate_method_base_name).each do |method_name|
233
+ predicate_method = :"#{method_name}?"
234
+
235
+ if value.respond_to?(predicate_method)
236
+ if value.send(predicate_method)
237
+ raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
238
+ end
158
239
  end
159
240
  end
160
241
  end
242
+ end
161
243
 
162
- # Raise an exception if the parameter does not respond to a given set of methods.
163
- def _pv_respond_to(opts, key, method_name_list)
164
- value = _pv_opts_lookup(opts, key)
165
- unless value.nil?
166
- Array(method_name_list).each do |method_name|
167
- unless value.respond_to?(method_name)
168
- raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
169
- end
170
- end
244
+ #
245
+ # The default value for a property.
246
+ #
247
+ # When the property is not assigned, this will be used.
248
+ #
249
+ # If this is a lazy value, it will either be passed the resource as a value,
250
+ # or if the lazy proc does not take parameters, it will be run in the
251
+ # context of the instance with instance_eval.
252
+ #
253
+ # @example
254
+ # ```ruby
255
+ # property :x, default: 10
256
+ # ```
257
+ #
258
+ # @example
259
+ # ```ruby
260
+ # property :x
261
+ # property :y, default: lazy { x+2 }
262
+ # ```
263
+ #
264
+ # @example
265
+ # ```ruby
266
+ # property :x
267
+ # property :y, default: lazy { |r| r.x+2 }
268
+ # ```
269
+ #
270
+ def _pv_default(opts, key, default_value)
271
+ value = _pv_opts_lookup(opts, key)
272
+ if value.nil?
273
+ default_value = default_value.freeze if !default_value.is_a?(DelayedEvaluator)
274
+ opts[key] = default_value
275
+ end
276
+ end
277
+
278
+ #
279
+ # List of regexes values that must match.
280
+ #
281
+ # Uses regex.match() to evaluate. At least one must match for the value to
282
+ # be valid.
283
+ #
284
+ # `nil` passes regex validation automatically.
285
+ #
286
+ # @example
287
+ # ```ruby
288
+ # property :x, regex: [ /abc/, /xyz/ ]
289
+ # ```
290
+ #
291
+ def _pv_regex(opts, key, regex)
292
+ value = _pv_opts_lookup(opts, key)
293
+ if !value.nil?
294
+ Array(regex).each do |r|
295
+ return true if r.match(value.to_s)
171
296
  end
297
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
172
298
  end
299
+ end
173
300
 
174
- # Assert that parameter returns false when passed a predicate method.
175
- # For example, :cannot_be => :blank will raise a Exceptions::ValidationFailed
176
- # error value.blank? returns a 'truthy' (not nil or false) value.
177
- #
178
- # Note, this will *PASS* if the object doesn't respond to the method.
179
- # So, to make sure a value is not nil and not blank, you need to do
180
- # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true)
181
- def _pv_cannot_be(opts, key, predicate_method_base_name)
182
- value = _pv_opts_lookup(opts, key)
183
- predicate_method = (predicate_method_base_name.to_s + "?").to_sym
184
-
185
- if value.respond_to?(predicate_method)
186
- if value.send(predicate_method)
187
- raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
301
+ #
302
+ # List of procs we pass the value to.
303
+ #
304
+ # All procs must return true for the value to be valid. If any procs do
305
+ # not return true, the key will be used for the message: `"Property x's
306
+ # value :y <message>"`.
307
+ #
308
+ # @example
309
+ # ```ruby
310
+ # property :x, callbacks: { "is bigger than 10" => proc { |v| v <= 10 }, "is not awesome" => proc { |v| !v.awesome }}
311
+ # ```
312
+ #
313
+ def _pv_callbacks(opts, key, callbacks)
314
+ raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
315
+ value = _pv_opts_lookup(opts, key)
316
+ if !value.nil?
317
+ callbacks.each do |message, zeproc|
318
+ if zeproc.call(value) != true
319
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
188
320
  end
189
321
  end
190
322
  end
323
+ end
191
324
 
192
- # Assign a default value to a parameter.
193
- def _pv_default(opts, key, default_value)
194
- value = _pv_opts_lookup(opts, key)
195
- if value == nil
196
- opts[key] = default_value
325
+ #
326
+ # Allows a parameter to default to the value of the resource name.
327
+ #
328
+ # @example
329
+ # ```ruby
330
+ # property :x, name_property: true
331
+ # ```
332
+ #
333
+ def _pv_name_property(opts, key, is_name_property=true)
334
+ if is_name_property
335
+ if opts[key].nil?
336
+ opts[key] = self.instance_variable_get(:"@name")
197
337
  end
198
338
  end
339
+ end
340
+ alias :_pv_name_attribute :_pv_name_property
199
341
 
200
- # Check a parameter against a regular expression.
201
- def _pv_regex(opts, key, regex)
202
- value = _pv_opts_lookup(opts, key)
203
- if value != nil
204
- passes = false
205
- [ regex ].flatten.each do |r|
206
- if value != nil
207
- if r.match(value.to_s)
208
- passes = true
209
- end
210
- end
211
- end
212
- unless passes
213
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
214
- end
342
+ #
343
+ # List of valid things values can be.
344
+ #
345
+ # Uses Ruby's `===` to evaluate (is === value). At least one must match
346
+ # for the value to be valid.
347
+ #
348
+ # If a proc is passed, it is instance_eval'd in the resource, passed the
349
+ # value, and must return a truthy or falsey value.
350
+ #
351
+ # @example Class
352
+ # ```ruby
353
+ # property :x, String
354
+ # x 'valid' #=> valid
355
+ # x 1 #=> invalid
356
+ # x nil #=> invalid
357
+ #
358
+ # @example Value
359
+ # ```ruby
360
+ # property :x, [ :a, :b, :c, nil ]
361
+ # x :a #=> valid
362
+ # x nil #=> valid
363
+ # ```
364
+ #
365
+ # @example Regex
366
+ # ```ruby
367
+ # property :x, /bar/
368
+ # x 'foobar' #=> valid
369
+ # x 'foo' #=> invalid
370
+ # x nil #=> invalid
371
+ # ```
372
+ #
373
+ # @example Proc
374
+ # ```ruby
375
+ # property :x, proc { |x| x > y }
376
+ # property :y, default: 2
377
+ # x 3 #=> valid
378
+ # x 1 #=> invalid
379
+ # ```
380
+ #
381
+ # @example Property
382
+ # ```ruby
383
+ # type = Property.new(is: String)
384
+ # property :x, type
385
+ # x 'foo' #=> valid
386
+ # x 1 #=> invalid
387
+ # x nil #=> invalid
388
+ # ```
389
+ #
390
+ # @example RSpec Matcher
391
+ # ```ruby
392
+ # include RSpec::Matchers
393
+ # property :x, a_string_matching /bar/
394
+ # x 'foobar' #=> valid
395
+ # x 'foo' #=> invalid
396
+ # x nil #=> invalid
397
+ # ```
398
+ #
399
+ def _pv_is(opts, key, to_be, raise_error: true)
400
+ return true if !opts.has_key?(key.to_s) && !opts.has_key?(key.to_sym)
401
+ value = _pv_opts_lookup(opts, key)
402
+ to_be = [ to_be ].flatten(1)
403
+ to_be.each do |tb|
404
+ case tb
405
+ when Proc
406
+ return true if instance_exec(value, &tb)
407
+ when Property
408
+ validate(opts, { key => tb.validation_options })
409
+ return true
410
+ else
411
+ return true if tb === value
215
412
  end
216
413
  end
217
414
 
218
- # Check a parameter against a hash of proc's.
219
- def _pv_callbacks(opts, key, callbacks)
220
- raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
221
- value = _pv_opts_lookup(opts, key)
222
- if value != nil
223
- callbacks.each do |message, zeproc|
224
- if zeproc.call(value) != true
225
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
226
- end
227
- end
415
+ if raise_error
416
+ raise Exceptions::ValidationFailed, "Option #{key} must be one of: #{to_be.join(", ")}! You passed #{value.inspect}."
417
+ else
418
+ false
419
+ end
420
+ end
421
+
422
+ #
423
+ # Method to mess with a value before it is validated and stored.
424
+ #
425
+ # Allows you to transform values into a canonical form that is easy to
426
+ # work with.
427
+ #
428
+ # This is passed the value to transform, and is run in the context of the
429
+ # instance (so it has access to other resource properties). It must return
430
+ # the value that will be stored in the instance.
431
+ #
432
+ # @example
433
+ # ```ruby
434
+ # property :x, Integer, coerce: { |v| v.to_i }
435
+ # ```
436
+ #
437
+ def _pv_coerce(opts, key, coercer)
438
+ if opts.has_key?(key.to_s)
439
+ opts[key.to_s] = instance_exec(opts[key], &coercer)
440
+ elsif opts.has_key?(key.to_sym)
441
+ opts[key.to_sym] = instance_exec(opts[key], &coercer)
442
+ end
443
+ end
444
+
445
+ # Used by #set_or_return to avoid emitting a deprecation warning for
446
+ # "value nil" and to keep default stickiness working exactly the same
447
+ # @api private
448
+ class SetOrReturnProperty < Chef::Property
449
+ def get(resource)
450
+ value = super
451
+ # All values are sticky, frozen or not
452
+ if !is_set?(resource)
453
+ set_value(resource, value)
228
454
  end
455
+ value
229
456
  end
230
457
 
231
- # Allow a parameter to default to @name
232
- def _pv_name_attribute(opts, key, is_name_attribute=true)
233
- if is_name_attribute
234
- if opts[key] == nil
235
- opts[key] = self.instance_variable_get("@name")
236
- end
458
+ def call(resource, value=NOT_PASSED)
459
+ # setting to nil does a get
460
+ if value.nil? && !explicitly_accepts_nil?(resource)
461
+ get(resource)
462
+ else
463
+ super
237
464
  end
238
465
  end
466
+ end
239
467
  end
240
468
  end
241
469
  end
242
-