compat_resource 12.5.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.
@@ -0,0 +1,572 @@
1
+ require 'chef_compat/copied_from_chef'
2
+ module ChefCompat
3
+ module CopiedFromChef
4
+ #
5
+ # Author:: John Keiser <jkeiser@chef.io>
6
+ # Copyright:: Copyright (c) 2015 John Keiser.
7
+ # License:: Apache License, Version 2.0
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+
22
+ require 'chef_compat/copied_from_chef/chef/delayed_evaluator'
23
+
24
+ class Chef < (defined?(::Chef) ? ::Chef : Object)
25
+ #
26
+ # Type and validation information for a property on a resource.
27
+ #
28
+ # A property named "x" manipulates the "@x" instance variable on a
29
+ # resource. The *presence* of the variable (`instance_variable_defined?(@x)`)
30
+ # tells whether the variable is defined; it may have any actual value,
31
+ # constrained only by validation.
32
+ #
33
+ # Properties may have validation, defaults, and coercion, and have full
34
+ # support for lazy values.
35
+ #
36
+ # @see Chef::Resource.property
37
+ # @see Chef::DelayedEvaluator
38
+ #
39
+ class Property < (defined?(::Chef::Property) ? ::Chef::Property : Object)
40
+ #
41
+ # Create a reusable property type that can be used in multiple properties
42
+ # in different resources.
43
+ #
44
+ # @param options [Hash<Symbol,Object>] Validation options. See Chef::Resource.property for
45
+ # the list of options.
46
+ #
47
+ # @example
48
+ # Property.derive(default: 'hi')
49
+ #
50
+ def self.derive(**options)
51
+ new(**options)
52
+ end
53
+
54
+ #
55
+ # Create a new property.
56
+ #
57
+ # @param options [Hash<Symbol,Object>] Property options, including
58
+ # control options here, as well as validation options (see
59
+ # Chef::Mixin::ParamsValidate#validate for a description of validation
60
+ # options).
61
+ # @option options [Symbol] :name The name of this property.
62
+ # @option options [Class] :declared_in The class this property comes from.
63
+ # @option options [Symbol] :instance_variable_name The instance variable
64
+ # tied to this property. Must include a leading `@`. Defaults to `@<name>`.
65
+ # `nil` means the property is opaque and not tied to a specific instance
66
+ # variable.
67
+ # @option options [Boolean] :desired_state `true` if this property is part of desired
68
+ # state. Defaults to `true`.
69
+ # @option options [Boolean] :identity `true` if this property is part of object
70
+ # identity. Defaults to `false`.
71
+ # @option options [Boolean] :name_property `true` if this
72
+ # property defaults to the same value as `name`. Equivalent to
73
+ # `default: lazy { name }`, except that #property_is_set? will
74
+ # return `true` if the property is set *or* if `name` is set.
75
+ # @option options [Object] :default The value this property
76
+ # will return if the user does not set one. If this is `lazy`, it will
77
+ # be run in the context of the instance (and able to access other
78
+ # properties) and cached. If not, the value will be frozen with Object#freeze
79
+ # to prevent users from modifying it in an instance.
80
+ # @option options [Proc] :coerce A proc which will be called to
81
+ # transform the user input to canonical form. The value is passed in,
82
+ # and the transformed value returned as output. Lazy values will *not*
83
+ # be passed to this method until after they are evaluated. Called in the
84
+ # context of the resource (meaning you can access other properties).
85
+ # @option options [Boolean] :required `true` if this property
86
+ # must be present; `false` otherwise. This is checked after the resource
87
+ # is fully initialized.
88
+ #
89
+ def initialize(**options)
90
+ options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) }
91
+
92
+ # Replace name_attribute with name_property
93
+ if options.has_key?(:name_attribute)
94
+ # If we have both name_attribute and name_property and they differ, raise an error
95
+ if options.has_key?(:name_property)
96
+ raise ArgumentError, "Cannot specify both name_property and name_attribute together on property #{options[:name]}#{options[:declared_in] ? " of resource #{options[:declared_in].resource_name}" : ""}."
97
+ end
98
+ # replace name_property with name_attribute in place
99
+ options = Hash[options.map { |k,v| k == :name_attribute ? [ :name_property, v ] : [ k,v ] }]
100
+ end
101
+
102
+ # Only pick the first of :default, :name_property and :name_attribute if
103
+ # more than one is specified.
104
+ if options.has_key?(:default) && options[:name_property]
105
+ if options[:default].nil? || options.keys.index(:name_property) < options.keys.index(:default)
106
+ options.delete(:default)
107
+ preferred_default = :name_property
108
+ else
109
+ options.delete(:name_property)
110
+ preferred_default = :default
111
+ end
112
+ Chef.log_deprecation("Cannot specify both default and name_property together on property #{options[:name]}#{options[:declared_in] ? " of resource #{options[:declared_in].resource_name}" : ""}. Only one (#{preferred_default}) will be obeyed. In Chef 13, this will become an error.")
113
+ end
114
+
115
+ @options = options
116
+
117
+ options[:name] = options[:name].to_sym if options[:name]
118
+ options[:instance_variable_name] = options[:instance_variable_name].to_sym if options[:instance_variable_name]
119
+ end
120
+
121
+ #
122
+ # The name of this property.
123
+ #
124
+ # @return [String]
125
+ #
126
+ def name
127
+ options[:name]
128
+ end
129
+
130
+ #
131
+ # The class this property was defined in.
132
+ #
133
+ # @return [Class]
134
+ #
135
+ def declared_in
136
+ options[:declared_in]
137
+ end
138
+
139
+ #
140
+ # The instance variable associated with this property.
141
+ #
142
+ # Defaults to `@<name>`
143
+ #
144
+ # @return [Symbol]
145
+ #
146
+ def instance_variable_name
147
+ if options.has_key?(:instance_variable_name)
148
+ options[:instance_variable_name]
149
+ elsif name
150
+ :"@#{name}"
151
+ end
152
+ end
153
+
154
+ #
155
+ # The raw default value for this resource.
156
+ #
157
+ # Does not coerce or validate the default. Does not evaluate lazy values.
158
+ #
159
+ # Defaults to `lazy { name }` if name_property is true; otherwise defaults to
160
+ # `nil`
161
+ #
162
+ def default
163
+ return options[:default] if options.has_key?(:default)
164
+ return Chef::DelayedEvaluator.new { name } if name_property?
165
+ nil
166
+ end
167
+
168
+ #
169
+ # Whether this is part of the resource's natural identity or not.
170
+ #
171
+ # @return [Boolean]
172
+ #
173
+ def identity?
174
+ options[:identity]
175
+ end
176
+
177
+ #
178
+ # Whether this is part of desired state or not.
179
+ #
180
+ # Defaults to true.
181
+ #
182
+ # @return [Boolean]
183
+ #
184
+ def desired_state?
185
+ return true if !options.has_key?(:desired_state)
186
+ options[:desired_state]
187
+ end
188
+
189
+ #
190
+ # Whether this is name_property or not.
191
+ #
192
+ # @return [Boolean]
193
+ #
194
+ def name_property?
195
+ options[:name_property]
196
+ end
197
+
198
+ #
199
+ # Whether this property has a default value.
200
+ #
201
+ # @return [Boolean]
202
+ #
203
+ def has_default?
204
+ options.has_key?(:default) || name_property?
205
+ end
206
+
207
+ #
208
+ # Whether this property is required or not.
209
+ #
210
+ # @return [Boolean]
211
+ #
212
+ def required?
213
+ options[:required]
214
+ end
215
+
216
+ #
217
+ # Validation options. (See Chef::Mixin::ParamsValidate#validate.)
218
+ #
219
+ # @return [Hash<Symbol,Object>]
220
+ #
221
+ def validation_options
222
+ @validation_options ||= options.reject { |k,v|
223
+ [:declared_in,:name,:instance_variable_name,:desired_state,:identity,:default,:name_property,:coerce,:required].include?(k)
224
+ }
225
+ end
226
+
227
+ #
228
+ # Handle the property being called.
229
+ #
230
+ # The base implementation does the property get-or-set:
231
+ #
232
+ # ```ruby
233
+ # resource.myprop # get
234
+ # resource.myprop value # set
235
+ # ```
236
+ #
237
+ # Subclasses may implement this with any arguments they want, as long as
238
+ # the corresponding DSL calls it correctly.
239
+ #
240
+ # @param resource [Chef::Resource] The resource to get the property from.
241
+ # @param value The value to set (or NOT_PASSED if it is a get).
242
+ #
243
+ # @return The current value of the property. If it is a `set`, lazy values
244
+ # will be returned without running, validating or coercing. If it is a
245
+ # `get`, the non-lazy, coerced, validated value will always be returned.
246
+ #
247
+ def call(resource, value=NOT_PASSED)
248
+ if value == NOT_PASSED
249
+ return get(resource)
250
+ end
251
+
252
+ # myprop nil is sometimes a get (backcompat)
253
+ if value.nil? && !explicitly_accepts_nil?(resource)
254
+ # If you say "my_property nil" and the property explicitly accepts
255
+ # nil values, we consider this a get.
256
+ Chef.log_deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.")
257
+ return get(resource)
258
+ end
259
+
260
+ # Anything else (myprop value) is a set
261
+ set(resource, value)
262
+ end
263
+
264
+ #
265
+ # Get the property value from the resource, handling lazy values,
266
+ # defaults, and validation.
267
+ #
268
+ # - If the property's value is lazy, it is evaluated, coerced and validated.
269
+ # - If the property has no value, and is required, raises ValidationFailed.
270
+ # - If the property has no value, but has a lazy default, it is evaluated,
271
+ # coerced and validated. If the evaluated value is frozen, the resulting
272
+ # - If the property has no value, but has a default, the default value
273
+ # will be returned and frozen. If the default value is lazy, it will be
274
+ # evaluated, coerced and validated, and the result stored in the property.
275
+ # - If the property has no value, but is name_property, `resource.name`
276
+ # is retrieved, coerced, validated and stored in the property.
277
+ # - Otherwise, `nil` is returned.
278
+ #
279
+ # @param resource [Chef::Resource] The resource to get the property from.
280
+ #
281
+ # @return The value of the property.
282
+ #
283
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
284
+ # this property, or if the value is required and not set.
285
+ #
286
+ def get(resource)
287
+ if is_set?(resource)
288
+ value = get_value(resource)
289
+ if value.is_a?(DelayedEvaluator)
290
+ value = exec_in_resource(resource, value)
291
+ value = coerce(resource, value)
292
+ validate(resource, value)
293
+ end
294
+ value
295
+
296
+ else
297
+ if has_default?
298
+ value = default
299
+ if value.is_a?(DelayedEvaluator)
300
+ value = exec_in_resource(resource, value)
301
+ end
302
+
303
+ value = coerce(resource, value)
304
+
305
+ # We don't validate defaults
306
+
307
+ # If the value is mutable (non-frozen), we set it on the instance
308
+ # so that people can mutate it. (All constant default values are
309
+ # frozen.)
310
+ if !value.frozen? && !value.nil?
311
+ set_value(resource, value)
312
+ end
313
+
314
+ value
315
+
316
+ elsif required?
317
+ raise Chef::Exceptions::ValidationFailed, "#{name} is required"
318
+ end
319
+ end
320
+ end
321
+
322
+ #
323
+ # Set the value of this property in the given resource.
324
+ #
325
+ # Non-lazy values are coerced and validated before being set. Coercion
326
+ # and validation of lazy values is delayed until they are first retrieved.
327
+ #
328
+ # @param resource [Chef::Resource] The resource to set this property in.
329
+ # @param value The value to set.
330
+ #
331
+ # @return The value that was set, after coercion (if lazy, still returns
332
+ # the lazy value)
333
+ #
334
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
335
+ # this property.
336
+ #
337
+ def set(resource, value)
338
+ unless value.is_a?(DelayedEvaluator)
339
+ value = coerce(resource, value)
340
+ validate(resource, value)
341
+ end
342
+ set_value(resource, value)
343
+ end
344
+
345
+ #
346
+ # Find out whether this property has been set.
347
+ #
348
+ # This will be true if:
349
+ # - The user explicitly set the value
350
+ # - The property has a default, and the value was retrieved.
351
+ #
352
+ # From this point of view, it is worth looking at this as "what does the
353
+ # user think this value should be." In order words, if the user grabbed
354
+ # the value, even if it was a default, they probably based calculations on
355
+ # it. If they based calculations on it and the value changes, the rest of
356
+ # the world gets inconsistent.
357
+ #
358
+ # @param resource [Chef::Resource] The resource to get the property from.
359
+ #
360
+ # @return [Boolean]
361
+ #
362
+ def is_set?(resource)
363
+ value_is_set?(resource)
364
+ end
365
+
366
+ #
367
+ # Reset the value of this property so that is_set? will return false and the
368
+ # default will be returned in the future.
369
+ #
370
+ # @param resource [Chef::Resource] The resource to get the property from.
371
+ #
372
+ def reset(resource)
373
+ reset_value(resource)
374
+ end
375
+
376
+ #
377
+ # Coerce an input value into canonical form for the property.
378
+ #
379
+ # After coercion, the value is suitable for storage in the resource.
380
+ # You must validate values after coercion, however.
381
+ #
382
+ # Does no special handling for lazy values.
383
+ #
384
+ # @param resource [Chef::Resource] The resource we're coercing against
385
+ # (to provide context for the coerce).
386
+ # @param value The value to coerce.
387
+ #
388
+ # @return The coerced value.
389
+ #
390
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
391
+ # this property.
392
+ #
393
+ def coerce(resource, value)
394
+ if options.has_key?(:coerce)
395
+ value = exec_in_resource(resource, options[:coerce], value)
396
+ end
397
+ value
398
+ end
399
+
400
+ #
401
+ # Validate a value.
402
+ #
403
+ # Calls Chef::Mixin::ParamsValidate#validate with #validation_options as
404
+ # options.
405
+ #
406
+ # @param resource [Chef::Resource] The resource we're validating against
407
+ # (to provide context for the validate).
408
+ # @param value The value to validate.
409
+ #
410
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
411
+ # this property.
412
+ #
413
+ def validate(resource, value)
414
+ resource.validate({ name => value }, { name => validation_options })
415
+ end
416
+
417
+ #
418
+ # Derive a new Property that is just like this one, except with some added or
419
+ # changed options.
420
+ #
421
+ # @param options [Hash<Symbol,Object>] List of options that would be passed
422
+ # to #initialize.
423
+ #
424
+ # @return [Property] The new property type.
425
+ #
426
+ def derive(**modified_options)
427
+ # Since name_property, name_attribute and default override each other,
428
+ # if you specify one of them in modified_options it overrides anything in
429
+ # the original options.
430
+ options = self.options
431
+ if modified_options.has_key?(:name_property) ||
432
+ modified_options.has_key?(:name_attribute) ||
433
+ modified_options.has_key?(:default)
434
+ options = options.reject { |k,v| k == :name_attribute || k == :name_property || k == :default }
435
+ end
436
+ Property.new(options.merge(modified_options))
437
+ end
438
+
439
+ #
440
+ # Emit the DSL for this property into the resource class (`declared_in`).
441
+ #
442
+ # Creates a getter and setter for the property.
443
+ #
444
+ def emit_dsl
445
+ # We don't create the getter/setter if it's a custom property; we will
446
+ # be using the existing getter/setter to manipulate it instead.
447
+ return if !instance_variable_name
448
+
449
+ # We prefer this form because the property name won't show up in the
450
+ # stack trace if you use `define_method`.
451
+ declared_in.class_eval <<-EOM, __FILE__, __LINE__+1
452
+ def #{name}(value=NOT_PASSED)
453
+ self.class.properties[#{name.inspect}].call(self, value)
454
+ end
455
+ def #{name}=(value)
456
+ self.class.properties[#{name.inspect}].set(self, value)
457
+ end
458
+ EOM
459
+ rescue SyntaxError
460
+ # If the name is not a valid ruby name, we use define_method.
461
+ declared_in.define_method(name) do |value=NOT_PASSED|
462
+ self.class.properties[name].call(self, value)
463
+ end
464
+ declared_in.define_method("#{name}=") do |value|
465
+ self.class.properties[name].set(self, value)
466
+ end
467
+ end
468
+
469
+ protected
470
+
471
+ #
472
+ # The options this Property will use for get/set behavior and validation.
473
+ #
474
+ # @see #initialize for a list of valid options.
475
+ #
476
+ attr_reader :options
477
+
478
+ #
479
+ # Find out whether this type accepts nil explicitly.
480
+ #
481
+ # A type accepts nil explicitly if "is" allows nil, it validates as nil, *and* is not simply
482
+ # an empty type.
483
+ #
484
+ # These examples accept nil explicitly:
485
+ # ```ruby
486
+ # property :a, [ String, nil ]
487
+ # property :a, [ String, NilClass ]
488
+ # property :a, [ String, proc { |v| v.nil? } ]
489
+ # ```
490
+ #
491
+ # This does not (because the "is" doesn't exist or doesn't have nil):
492
+ #
493
+ # ```ruby
494
+ # property :x, String
495
+ # ```
496
+ #
497
+ # These do not, even though nil would validate fine (because they do not
498
+ # have "is"):
499
+ #
500
+ # ```ruby
501
+ # property :a
502
+ # property :a, equal_to: [ 1, 2, 3, nil ]
503
+ # property :a, kind_of: [ String, NilClass ]
504
+ # property :a, respond_to: [ ]
505
+ # property :a, callbacks: { "a" => proc { |v| v.nil? } }
506
+ # ```
507
+ #
508
+ # @param resource [Chef::Resource] The resource we're coercing against
509
+ # (to provide context for the coerce).
510
+ #
511
+ # @return [Boolean] Whether this value explicitly accepts nil.
512
+ #
513
+ # @api private
514
+ def explicitly_accepts_nil?(resource)
515
+ options.has_key?(:is) && resource.send(:_pv_is, { name => nil }, name, options[:is], raise_error: false)
516
+ end
517
+
518
+ def get_value(resource)
519
+ if instance_variable_name
520
+ resource.instance_variable_get(instance_variable_name)
521
+ else
522
+ resource.send(name)
523
+ end
524
+ end
525
+
526
+ def set_value(resource, value)
527
+ if instance_variable_name
528
+ resource.instance_variable_set(instance_variable_name, value)
529
+ else
530
+ resource.send(name, value)
531
+ end
532
+ end
533
+
534
+ def value_is_set?(resource)
535
+ if instance_variable_name
536
+ resource.instance_variable_defined?(instance_variable_name)
537
+ else
538
+ true
539
+ end
540
+ end
541
+
542
+ def reset_value(resource)
543
+ if instance_variable_name
544
+ if value_is_set?(resource)
545
+ resource.remove_instance_variable(instance_variable_name)
546
+ end
547
+ else
548
+ raise ArgumentError, "Property #{name} has no instance variable defined and cannot be reset"
549
+ end
550
+ end
551
+
552
+ def exec_in_resource(resource, proc, *args)
553
+ if resource
554
+ if proc.arity > args.size
555
+ value = proc.call(resource, *args)
556
+ else
557
+ value = resource.instance_exec(*args, &proc)
558
+ end
559
+ else
560
+ value = proc.call
561
+ end
562
+
563
+ if value.is_a?(DelayedEvaluator)
564
+ value = coerce(resource, value)
565
+ validate(resource, value)
566
+ end
567
+ value
568
+ end
569
+ end
570
+ end
571
+ end
572
+ end