File: property.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (611 lines) | stat: -rw-r--r-- 26,984 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
# The virtual base class for properties, which are the self-contained building
# blocks for actually doing work on the system.

require_relative '../puppet'
require_relative '../puppet/parameter'

# The Property class is the implementation of a resource's attributes of _property_ kind.
# A Property is a specialized Resource Type Parameter that has both an 'is' (current) state, and
# a 'should' (wanted state). However, even if this is conceptually true, the current _is_ value is
# obtained by asking the associated provider for the value, and hence it is not actually part of a
# property's state, and only available when a provider has been selected and can obtain the value (i.e. when
# running on an agent).
#
# A Property (also in contrast to a parameter) is intended to describe a managed attribute of
# some system entity, such as the name or mode of a file.
#
# The current value _(is)_ is read and written with the methods {#retrieve} and {#set}, and the wanted
# value _(should)_ is read and written with the methods {#value} and {#value=} which delegate to
# {#should} and {#should=}, i.e. when a property is used like any other parameter, it is the _should_ value
# that is operated on.
#
# All resource type properties in the puppet system are derived from this class.
#
# The intention is that new parameters are created by using the DSL method {Puppet::Type.newproperty}.
#
# @abstract
# @note Properties of Types are expressed using subclasses of this class. Such a class describes one
#   named property of a particular Type (as opposed to describing a type of property in general). This
#   limits the use of one (concrete) property class instance to occur only once for a given type's inheritance
#   chain. An instance of a Property class is the value holder of one instance of the resource type (e.g. the
#   mode of a file resource instance).
#   A Property class may server as the superclass _(parent)_ of another; e.g. a Size property that describes
#   handling of measurements such as kb, mb, gb. If a type requires two different size measurements it requires
#   one concrete class per such measure; e.g. MinSize (:parent => Size), and MaxSize (:parent => Size).
#
# @see Puppet::Type
# @see Puppet::Parameter
#
# @api public
#
class Puppet::Property < Puppet::Parameter
  require_relative 'property/ensure'

  # Returns the original wanted value(s) _(should)_ unprocessed by munging/unmunging.
  # The original values are set by {#value=} or {#should=}.
  # @return (see #should)
  #
  attr_reader :shouldorig

  # The noop mode for this property.
  # By setting a property's noop mode to `true`, any management of this property is inhibited. Calculation
  # and reporting still takes place, but if a change of the underlying managed entity's state
  # should take place it will not be carried out. This noop
  # setting overrides the overall `Puppet[:noop]` mode as well as the noop mode in the _associated resource_
  #
  attr_writer :noop

  class << self
    # @todo Figure out what this is used for. Can not find any logic in the puppet code base that
    #   reads or writes this attribute.
    # ??? Probably Unused
    attr_accessor :unmanaged

    # @return [Symbol] The name of the property as given when the property was created.
    #
    attr_reader :name

    # @!attribute [rw] array_matching
    # @comment note that $#46; is a period - char code require to not terminate sentence.
    # The `is` vs&#46; `should` array matching mode; `:first`, or `:all`.
    #
    # @comment there are two blank chars after the symbols to cause a break - do not remove these.
    # * `:first`
    #   This is primarily used for single value properties. When matched against an array of values
    #   a match is true if the `is` value matches any of the values in the `should` array. When the `is` value
    #   is also an array, the matching is performed against the entire array as the `is` value.
    # * `:all`
    #   : This is primarily used for multi-valued properties. When matched against an array of
    #     `should` values, the size of `is` and `should` must be the same, and all values in `is` must match
    #     a value in `should`.
    #
    # @note The semantics of these modes are implemented by the method {#insync?}. That method is the default
    #   implementation and it has a backwards compatible behavior that imposes additional constraints
    #   on what constitutes a positive match. A derived property may override that method.
    # @return [Symbol] (:first) the mode in which matching is performed
    # @see #insync?
    # @dsl type
    # @api public
    #
    def array_matching
      @array_matching ||= :first
    end

    # @comment This is documented as an attribute - see the {array_matching} method.
    #
    def array_matching=(value)
      value = value.intern if value.is_a?(String)
      #TRANSLATORS 'Property#array_matching', 'first', and 'all' should not be translated
      raise ArgumentError, _("Supported values for Property#array_matching are 'first' and 'all'") unless [:first, :all].include?(value)
      @array_matching = value
    end

    # Used to mark a type property as having or lacking idempotency (on purpose
    # generally). This is used to avoid marking the property as a
    # corrective_change when there is known idempotency issues with the property
    # rendering a corrective_change flag as useless.
    # @return [Boolean] true if the property is marked as idempotent
    def idempotent
      @idempotent.nil? ? @idempotent = true : @idempotent
    end

    # Attribute setter for the idempotent attribute.
    # @param [bool] value boolean indicating if the property is idempotent.
    # @see idempotent
    def idempotent=(value)
      @idempotent = value
    end
  end

  # Looks up a value's name among valid values, to enable option lookup with result as a key.
  # @param name [Object] the parameter value to match against valid values (names).
  # @return {Symbol, Regexp} a value matching predicate
  # @api private
  #
  def self.value_name(name)
    value = value_collection.match?(name)
    value.name if value
  end

  # Returns the value of the given option (set when a valid value with the given "name" was defined).
  # @param name [Symbol, Regexp] the valid value predicate as returned by {value_name}
  # @param option [Symbol] the name of the wanted option
  # @return [Object] value of the option
  # @raise [NoMethodError] if the option is not supported
  # @todo Guessing on result of passing a non supported option (it performs send(option)).
  # @api private
  #
  def self.value_option(name, option)
    value = value_collection.value(name)
    value.send(option) if value
  end

  # Defines a new valid value for this property.
  # A valid value is specified as a literal (typically a Symbol), but can also be
  # specified with a Regexp.
  #
  # @param name [Symbol, Regexp] a valid literal value, or a regexp that matches a value
  # @param options [Hash] a hash with options
  # @option options [Symbol] :event The event that should be emitted when this value is set.
  # @todo Option :event original comment says "event should be returned...", is "returned" the correct word
  #   to use?
  # @option options [Symbol] :invalidate_refreshes Indicates a change on this property should invalidate and
  #   remove any scheduled refreshes (from notify or subscribe) targeted at the same resource. For example, if
  #   a change in this property takes into account any changes that a scheduled refresh would have performed,
  #   then the scheduled refresh would be deleted.
  # @option options [Object] any Any other option is treated as a call to a setter having the given
  #   option name (e.g. `:required_features` calls `required_features=` with the option's value as an
  #   argument).
  #
  # @dsl type
  # @api public
  def self.newvalue(name, options = {}, &block)
    value = value_collection.newvalue(name, options, &block)

    unless value.method.nil?
      method = value.method.to_sym
      if value.block
        if instance_methods(false).include?(method)
          raise ArgumentError, _("Attempt to redefine method %{method} with block") % { method: method }
        end
        define_method(method, &value.block)
      else
        # Let the method be an alias for calling the providers setter unless we already have this method
        alias_method(method, :call_provider) unless method_defined?(method)
      end
    end
    value
  end

  # Calls the provider setter method for this property with the given value as argument.
  # @return [Object] what the provider returns when calling a setter for this property's name
  # @raise [Puppet::Error] when the provider can not handle this property.
  # @see #set
  # @api private
  #
  def call_provider(value)
    # We have no idea how to handle this unless our parent have a provider
    self.fail "#{self.class.name} cannot handle values of type #{value.inspect}" unless @resource.provider
    method = self.class.name.to_s + "="
    unless provider.respond_to? method
      self.fail "The #{provider.class.name} provider can not handle attribute #{self.class.name}"
    end
    provider.send(method, value)
  end

  # Formats a message for a property change from the given `current_value` to the given `newvalue`.
  # @return [String] a message describing the property change.
  # @note If called with equal values, this is reported as a change.
  # @raise [Puppet::DevError] if there were issues formatting the message
  #
  def change_to_s(current_value, newvalue)
    begin
      if current_value == :absent
        return "defined '#{name}' as #{should_to_s(newvalue)}"
      elsif newvalue == :absent or newvalue == [:absent]
        return "undefined '#{name}' from #{is_to_s(current_value)}"
      else
        return "#{name} changed #{is_to_s(current_value)} to #{should_to_s(newvalue)}"
      end
    rescue Puppet::Error
      raise
    rescue => detail
      message = _("Could not convert change '%{name}' to string: %{detail}") % { name: name, detail: detail }
      Puppet.log_exception(detail, message)
      raise Puppet::DevError, message, detail.backtrace
    end
  end

  # Produces the name of the event to use to describe a change of this property's value.
  # The produced event name is either the event name configured for this property, or a generic
  # event based on the name of the property with suffix `_changed`, or if the property is
  # `:ensure`, the name of the resource type and one of the suffixes `_created`, `_removed`, or `_changed`.
  # @return [String] the name of the event that describes the change
  #
  def event_name
    value = self.should

    event_name = self.class.value_option(value, :event) and return event_name

    name == :ensure or return (name.to_s + "_changed").to_sym

    return (resource.type.to_s + case value
    when :present; "_created"
    when :absent; "_removed"
    else
      "_changed"
    end).to_sym
  end

  # Produces an event describing a change of this property.
  # In addition to the event attributes set by the resource type, this method adds:
  #
  # * `:name` - the event_name
  # * `:desired_value` - a.k.a _should_ or _wanted value_
  # * `:property` - reference to this property
  # * `:source_description` - The containment path of this property, indicating what resource this
  #                           property is associated with and in what stage and class that resource
  #                           was declared, e.g. "/Stage[main]/Myclass/File[/tmp/example]/ensure"
  # * `:invalidate_refreshes` - if scheduled refreshes should be invalidated
  # * `:redacted` - if the event will be redacted (due to this property being sensitive)
  #
  # @return [Puppet::Transaction::Event] the created event
  # @see Puppet::Type#event
  def event(options = {})
    attrs = { :name => event_name, :desired_value => should, :property => self, :source_description => path }.merge(options)
    value = self.class.value_collection.match?(should) if should
    
    attrs[:invalidate_refreshes] = true if value && value.invalidate_refreshes 
    attrs[:redacted] = @sensitive
    resource.event attrs
  end

  # Determines whether the property is in-sync or not in a way that is protected against missing value.
  # @note If the wanted value _(should)_ is not defined or is set to a non-true value then this is
  #   a state that can not be fixed and the property is reported to be in sync.
  # @return [Boolean] the protected result of `true` or the result of calling {#insync?}.
  #
  # @api private
  # @note Do not override this method.
  #
  def safe_insync?(is)
    # If there is no @should value, consider the property to be in sync.
    return true unless @should

    # Otherwise delegate to the (possibly derived) insync? method.
    insync?(is)
  end

  # Protects against override of the {#safe_insync?} method.
  # @raise [RuntimeError] if the added method is `:safe_insync?`
  # @api private
  #
  def self.method_added(sym)
    raise "Puppet::Property#safe_insync? shouldn't be overridden; please override insync? instead" if sym == :safe_insync?
  end

  # Checks if the current _(is)_ value is in sync with the wanted _(should)_ value.
  # The check if the two values are in sync is controlled by the result of {#match_all?} which
  # specifies a match of `:first` or `:all`). The matching of the _is_ value against the entire _should_ value
  # or each of the _should_ values (as controlled by {#match_all?} is performed by {#property_matches?}.
  #
  # A derived property typically only needs to override the {#property_matches?} method, but may also
  # override this method if there is a need to have more control over the array matching logic.
  #
  # @note The array matching logic in this method contains backwards compatible logic that performs the
  #   comparison in `:all` mode by checking equality and equality of _is_ against _should_ converted to array of String,
  #   and that the lengths are equal, and in `:first` mode by checking if one of the _should_ values
  #   is included in the _is_ values. This means that the _is_ value needs to be carefully arranged to
  #   match the _should_.
  # @todo The implementation should really do return is.zip(@should).all? {|a, b| property_matches?(a, b) }
  #   instead of using equality check and then check against an array with converted strings.
  # @param is [Object] The current _(is)_ value to check if it is in sync with the wanted _(should)_ value(s)
  # @return [Boolean] whether the values are in sync or not.
  # @raise [Puppet::DevError] if wanted value _(should)_ is not an array.
  # @api public
  #
  def insync?(is)
    self.devfail "#{self.class.name}'s should is not array" unless @should.is_a?(Array)

    # an empty array is analogous to no should values
    return true if @should.empty?

    # Look for a matching value, either for all the @should values, or any of
    # them, depending on the configuration of this property.
    if match_all? then
      # Emulate Array#== using our own comparison function.
      # A non-array was not equal to an array, which @should always is.
      return false unless is.is_a? Array

      # If they were different lengths, they are not equal.
      return false unless is.length == @should.length

      # Finally, are all the elements equal?  In order to preserve the
      # behaviour of previous 2.7.x releases, we need to impose some fun rules
      # on "equality" here.
      #
      # Specifically, we need to implement *this* comparison: the two arrays
      # are identical if the is values are == the should values, or if the is
      # values are == the should values, stringified.
      #
      # This does mean that property equality is not commutative, and will not
      # work unless the `is` value is carefully arranged to match the should.
      return (is == @should or is == @should.map(&:to_s))

      # When we stop being idiots about this, and actually have meaningful
      # semantics, this version is the thing we actually want to do.
      #
      # return is.zip(@should).all? {|a, b| property_matches?(a, b) }
    else
      return @should.any? {|want| property_matches?(is, want) }
    end
  end

  # This method tests if two values are insync? outside of the properties current
  # should value. This works around the requirement for corrective_change analysis
  # that requires two older values to be compared with the properties potentially
  # custom insync? code.
  #
  # @param [Object] should the value it should be
  # @param [Object] is the value it is
  # @return [Boolean] whether or not the values are in sync or not
  # @api private
  def insync_values?(should, is)
    # Here be dragons. We're setting the should value of a property purely just to
    # call its insync? method, as it lacks a way to pass in a should.
    # Unfortunately there isn't an API compatible way of avoiding this, as both should
    # an insync? behaviours are part of the public API. Future API work should factor
    # this kind of arbitrary comparisons into the API to remove this complexity. -ken

    # Backup old should, set it to the new value, then call insync? on the property.
    old_should = @should

    begin
      @should = should
      insync?(is)
    rescue
      # Certain operations may fail, but we don't want to fail the transaction if we can
      # avoid it
      #TRANSLATORS 'insync_values?' should not be translated
      msg = _("Unknown failure using insync_values? on type: %{type} / property: %{name} to compare values %{should} and %{is}") %
          { type: self.resource.ref, name: self.name, should: should, is: is }
      Puppet.info(msg)

      # Return nil, ie. unknown
      nil
    ensure
      # Always restore old should
      @should = old_should
    end
  end

  # Checks if the given current and desired values are equal.
  # This default implementation performs this check in a backwards compatible way where
  # the equality of the two values is checked, and then the equality of current with desired
  # converted to a string.
  #
  # A derived implementation may override this method to perform a property specific equality check.
  #
  # The intent of this method is to provide an equality check suitable for checking if the property
  # value is in sync or not. It is typically called from {#insync?}.
  #
  def property_matches?(current, desired)
    # This preserves the older Puppet behaviour of doing raw and string
    # equality comparisons for all equality.  I am not clear this is globally
    # desirable, but at least it is not a breaking change. --daniel 2011-11-11
    current == desired or current == desired.to_s
  end

  # Produces a pretty printing string for the given value.
  # This default implementation calls {#format_value_for_display} on the class. A derived
  # implementation may perform property specific pretty printing when the _is_ values
  # are not already in suitable form.
  # @param value [Object] the value to format as a string
  # @return [String] a pretty printing string
  def is_to_s(value)
    self.class.format_value_for_display(value)
  end

  # Emits a log message at the log level specified for the associated resource.
  # The log entry is associated with this property.
  # @param msg [String] the message to log
  # @return [void]
  #
  def log(msg)
    Puppet::Util::Log.create(
      :level   => resource[:loglevel],
      :message => msg,
      :source  => self
    )
  end

  # @return [Boolean] whether the {array_matching} mode is set to `:all` or not
  def match_all?
    self.class.array_matching == :all
  end

  # @return [Boolean] whether the property is marked as idempotent for the purposes
  #   of calculating corrective change.
  def idempotent?
    self.class.idempotent
  end

  # @return [Symbol] the name of the property as stated when the property was created.
  # @note A property class (just like a parameter class) describes one specific property and
  #   can only be used once within one type's inheritance chain.
  def name
    self.class.name
  end

  # @return [Boolean] whether this property is in noop mode or not.
  # Returns whether this property is in noop mode or not; if a difference between the
  # _is_ and _should_ values should be acted on or not.
  # The noop mode is a transitive setting. The mode is checked in this property, then in
  # the _associated resource_ and finally in Puppet[:noop].
  # @todo This logic is different than Parameter#noop in that the resource noop mode overrides
  #   the property's mode - in parameter it is the other way around. Bug or feature?
  #
  def noop
    # This is only here to make testing easier.
    if @resource.respond_to?(:noop?)
      @resource.noop?
    else
      if defined?(@noop)
        @noop
      else
        Puppet[:noop]
      end
    end
  end

  # Retrieves the current value _(is)_ of this property from the provider.
  # This implementation performs this operation by calling a provider method with the
  # same name as this property (i.e. if the property name is 'gid', a call to the
  # 'provider.gid' is expected to return the current value.
  # @return [Object] what the provider returns as the current value of the property
  #
  def retrieve
    provider.send(self.class.name)
  end

  # Sets the current _(is)_ value of this property.
  # The _name_ associated with the value is first obtained by calling {value_name}. A dynamically created setter
  # method associated with this _name_ is called if it exists, otherwise the value is set using using the provider's
  # setter method for this property by calling ({#call_provider}).
  #
  # @param value [Object] the value to set
  # @return [Object] returns the result of calling the setter method or {#call_provider}
  # @raise [Puppet::Error] if there were problems setting the value using the setter method or when the provider
  #  setter should be used but there is no provider in the associated resource_
  # @raise [Puppet::ResourceError] if there was a problem setting the value and it was not raised
  #   as a Puppet::Error. The original exception is wrapped and logged.
  # @api public
  #
  def set(value)
    # Set a name for looking up associated options like the event.
    name = self.class.value_name(value)
    method = self.class.value_option(name, :method)
    if method && self.respond_to?(method)
      begin
        self.send(method)
      rescue Puppet::Error
        raise
      rescue => detail
        error = Puppet::ResourceError.new(_("Could not set '%{value}' on %{class_name}: %{detail}") %
                                              { value: value, class_name: self.class.name, detail: detail }, @resource.file, @resource.line, detail)
        error.set_backtrace detail.backtrace
        Puppet.log_exception(detail, error.message)
        raise error
      end
    else
      block = self.class.value_option(name, :block)
      if block
        # FIXME It'd be better here to define a method, so that
        # the blocks could return values.
        self.instance_eval(&block)
      else
        call_provider(value)
      end
    end
  end

  # Returns the wanted _(should)_ value of this property.
  # If the _array matching mode_ {#match_all?} is true, an array of the wanted values in unmunged format
  # is returned, else the first value in the array of wanted values in unmunged format is returned.
  # @return [Array<Object>, Object, nil] Array of values if {#match_all?} else a single value, or nil if there are no
  #   wanted values.
  # @raise [Puppet::DevError] if the wanted value is non nil and not an array
  #
  # @note This method will potentially return different values than the original values as they are
  #   converted via munging/unmunging. If the original values are wanted, call {#shouldorig}.
  #
  # @see #shouldorig
  # @api public
  #
  def should
    return nil unless defined?(@should)

    self.devfail "should for #{self.class.name} on #{resource.name} is not an array" unless @should.is_a?(Array)

    if match_all?
      return @should.collect { |val| self.unmunge(val) }
    else
      return self.unmunge(@should[0])
    end
  end

  # Sets the wanted _(should)_ value of this property.
  # If the given value is not already an Array, it will be wrapped in one before being set.
  # This method also sets the cached original _should_ values returned by {#shouldorig}.
  #
  # @param values [Array<Object>, Object] the value(s) to set as the wanted value(s)
  # @raise [StandardError] when validation of a value fails (see {#validate}).
  # @api public
  #
  def should=(values)
    values = [values] unless values.is_a?(Array)

    @shouldorig = values

    values.each { |val| validate(val) }
    @should = values.collect { |val| self.munge(val) }
  end

  # Produces a pretty printing string for the given value.
  # This default implementation calls {#format_value_for_display} on the class. A derived
  # implementation may perform property specific pretty printing when the _should_ values
  # are not already in suitable form.
  # @param value [Object] the value to format as a string
  # @return [String] a pretty printing string
  def should_to_s(value)
    self.class.format_value_for_display(value)
  end

  # Synchronizes the current value _(is)_ and the wanted value _(should)_ by calling {#set}.
  # @raise [Puppet::DevError] if {#should} is nil
  # @todo The implementation of this method is somewhat inefficient as it computes the should
  #  array twice.
  def sync
    devfail "Got a nil value for should" unless should
    set(should)
  end

  # Asserts that the given value is valid.
  # If the developer uses a 'validate' hook, this method will get overridden.
  # @raise [Exception] if the value is invalid, or value can not be handled.
  # @return [void]
  # @api private
  #
  def unsafe_validate(value)
    super
    validate_features_per_value(value)
  end

  # Asserts that all required provider features are present for the given property value.
  # @raise [ArgumentError] if a required feature is not present
  # @return [void]
  # @api private
  #
  def validate_features_per_value(value)
    features = self.class.value_option(self.class.value_name(value), :required_features)
    if features
      features = Array(features)
      needed_features = features.collect { |f| f.to_s }.join(", ")
      unless provider.satisfies?(features)
        #TRANSLATORS 'Provider' refers to a Puppet provider class
        raise ArgumentError, _("Provider %{provider} must have features '%{needed_features}' to set '%{property}' to '%{value}'") %
            { provider: provider.class.name, needed_features: needed_features, property: self.class.name, value: value }
      end
    end
  end

  # @return [Object, nil] Returns the wanted _(should)_ value of this property.
  def value
    self.should
  end

  # (see #should=)
  def value=(values)
    self.should = values
  end
end