chef 12.4.3 → 12.5.0.alpha.1

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