class Puppet::Property
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
Attributes
@return [Symbol] The name of the property as given when the property was created.
@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
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` mode as well as the noop mode in the _associated resource_
Returns the original wanted value(s) _(should)_ unprocessed by munging/unmunging. The original values are set by {#value=} or {#should=}. @return (see should
)
Public Class Methods
@!attribute [rw] array_matching
@comment note that $#46; is a period - char code require to not terminate sentence. The `is` vs. `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
# File lib/puppet/property.rb 90 def array_matching 91 @array_matching ||= :first 92 end
@comment This is documented as an attribute - see the {array_matching} method.
# File lib/puppet/property.rb 96 def array_matching=(value) 97 value = value.intern if value.is_a?(String) 98 #TRANSLATORS 'Property#array_matching', 'first', and 'all' should not be translated 99 raise ArgumentError, _("Supported values for Property#array_matching are 'first' and 'all'") unless [:first, :all].include?(value) 100 @array_matching = value 101 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
# File lib/puppet/property.rb 108 def idempotent 109 @idempotent.nil? ? @idempotent = true : @idempotent 110 end
Attribute setter for the idempotent attribute. @param [bool] value boolean indicating if the property is idempotent. @see idempotent
# File lib/puppet/property.rb 115 def idempotent=(value) 116 @idempotent = value 117 end
Protects against override of the {#safe_insync?} method. @raise [RuntimeError] if the added method is `:safe_insync?` @api private
# File lib/puppet/property.rb 283 def self.method_added(sym) 284 raise "Puppet::Property#safe_insync? shouldn't be overridden; please override insync? instead" if sym == :safe_insync? 285 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
# File lib/puppet/property.rb 162 def self.newvalue(name, options = {}, &block) 163 value = value_collection.newvalue(name, options, &block) 164 165 unless value.method.nil? 166 method = value.method.to_sym 167 if value.block 168 if instance_methods(false).include?(method) 169 raise ArgumentError, _("Attempt to redefine method %{method} with block") % { method: method } 170 end 171 define_method(method, &value.block) 172 else 173 # Let the method be an alias for calling the providers setter unless we already have this method 174 alias_method(method, :call_provider) unless method_defined?(method) 175 end 176 end 177 value 178 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
# File lib/puppet/property.rb 125 def self.value_name(name) 126 value = value_collection.match?(name) 127 value.name if value 128 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
# File lib/puppet/property.rb 138 def self.value_option(name, option) 139 value = value_collection.value(name) 140 value.send(option) if value 141 end
Public Instance Methods
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
# File lib/puppet/property.rb 186 def call_provider(value) 187 # We have no idea how to handle this unless our parent have a provider 188 self.fail "#{self.class.name} cannot handle values of type #{value.inspect}" unless @resource.provider 189 method = self.class.name.to_s + "=" 190 unless provider.respond_to? method 191 self.fail "The #{provider.class.name} provider can not handle attribute #{self.class.name}" 192 end 193 provider.send(method, value) 194 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
# File lib/puppet/property.rb 201 def change_to_s(current_value, newvalue) 202 begin 203 if current_value == :absent 204 return "defined '#{name}' as #{should_to_s(newvalue)}" 205 elsif newvalue == :absent or newvalue == [:absent] 206 return "undefined '#{name}' from #{is_to_s(current_value)}" 207 else 208 return "#{name} changed #{is_to_s(current_value)} to #{should_to_s(newvalue)}" 209 end 210 rescue Puppet::Error 211 raise 212 rescue => detail 213 message = _("Could not convert change '%{name}' to string: %{detail}") % { name: name, detail: detail } 214 Puppet.log_exception(detail, message) 215 raise Puppet::DevError, message, detail.backtrace 216 end 217 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
# File lib/puppet/property.rb 254 def event(options = {}) 255 attrs = { :name => event_name, :desired_value => should, :property => self, :source_description => path }.merge(options) 256 value = self.class.value_collection.match?(should) if should 257 258 attrs[:invalidate_refreshes] = true if value && value.invalidate_refreshes 259 attrs[:redacted] = @sensitive 260 resource.event attrs 261 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
# File lib/puppet/property.rb 225 def event_name 226 value = self.should 227 228 event_name = self.class.value_option(value, :event) and return event_name 229 230 name == :ensure or return (name.to_s + "_changed").to_sym 231 232 return (resource.type.to_s + case value 233 when :present; "_created" 234 when :absent; "_removed" 235 else 236 "_changed" 237 end).to_sym 238 end
@return [Boolean] whether the property is marked as idempotent for the purposes
of calculating corrective change.
# File lib/puppet/property.rb 429 def idempotent? 430 self.class.idempotent 431 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
# File lib/puppet/property.rb 307 def insync?(is) 308 self.devfail "#{self.class.name}'s should is not array" unless @should.is_a?(Array) 309 310 # an empty array is analogous to no should values 311 return true if @should.empty? 312 313 # Look for a matching value, either for all the @should values, or any of 314 # them, depending on the configuration of this property. 315 if match_all? then 316 # Emulate Array#== using our own comparison function. 317 # A non-array was not equal to an array, which @should always is. 318 return false unless is.is_a? Array 319 320 # If they were different lengths, they are not equal. 321 return false unless is.length == @should.length 322 323 # Finally, are all the elements equal? In order to preserve the 324 # behaviour of previous 2.7.x releases, we need to impose some fun rules 325 # on "equality" here. 326 # 327 # Specifically, we need to implement *this* comparison: the two arrays 328 # are identical if the is values are == the should values, or if the is 329 # values are == the should values, stringified. 330 # 331 # This does mean that property equality is not commutative, and will not 332 # work unless the `is` value is carefully arranged to match the should. 333 return (is == @should or is == @should.map(&:to_s)) 334 335 # When we stop being idiots about this, and actually have meaningful 336 # semantics, this version is the thing we actually want to do. 337 # 338 # return is.zip(@should).all? {|a, b| property_matches?(a, b) } 339 else 340 return @should.any? {|want| property_matches?(is, want) } 341 end 342 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
# File lib/puppet/property.rb 353 def insync_values?(should, is) 354 # Here be dragons. We're setting the should value of a property purely just to 355 # call its insync? method, as it lacks a way to pass in a should. 356 # Unfortunately there isn't an API compatible way of avoiding this, as both should 357 # an insync? behaviours are part of the public API. Future API work should factor 358 # this kind of arbitrary comparisons into the API to remove this complexity. -ken 359 360 # Backup old should, set it to the new value, then call insync? on the property. 361 old_should = @should 362 363 begin 364 @should = should 365 insync?(is) 366 rescue 367 # Certain operations may fail, but we don't want to fail the transaction if we can 368 # avoid it 369 #TRANSLATORS 'insync_values?' should not be translated 370 msg = _("Unknown failure using insync_values? on type: %{type} / property: %{name} to compare values %{should} and %{is}") % 371 { type: self.resource.ref, name: self.name, should: should, is: is } 372 Puppet.info(msg) 373 374 # Return nil, ie. unknown 375 nil 376 ensure 377 # Always restore old should 378 @should = old_should 379 end 380 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
# File lib/puppet/property.rb 405 def is_to_s(value) 406 self.class.format_value_for_display(value) 407 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]
# File lib/puppet/property.rb 414 def log(msg) 415 Puppet::Util::Log.create( 416 :level => resource[:loglevel], 417 :message => msg, 418 :source => self 419 ) 420 end
@return [Boolean] whether the {array_matching} mode is set to `:all` or not
# File lib/puppet/property.rb 423 def match_all? 424 self.class.array_matching == :all 425 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.
# File lib/puppet/property.rb 436 def name 437 self.class.name 438 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. @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?
# File lib/puppet/property.rb 448 def noop 449 # This is only here to make testing easier. 450 if @resource.respond_to?(:noop?) 451 @resource.noop? 452 else 453 if defined?(@noop) 454 @noop 455 else 456 Puppet[:noop] 457 end 458 end 459 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?}.
# File lib/puppet/property.rb 392 def property_matches?(current, desired) 393 # This preserves the older Puppet behaviour of doing raw and string 394 # equality comparisons for all equality. I am not clear this is globally 395 # desirable, but at least it is not a breaking change. --daniel 2011-11-11 396 current == desired or current == desired.to_s 397 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
# File lib/puppet/property.rb 467 def retrieve 468 provider.send(self.class.name) 469 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.
# File lib/puppet/property.rb 271 def safe_insync?(is) 272 # If there is no @should value, consider the property to be in sync. 273 return true unless @should 274 275 # Otherwise delegate to the (possibly derived) insync? method. 276 insync?(is) 277 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
# File lib/puppet/property.rb 484 def set(value) 485 # Set a name for looking up associated options like the event. 486 name = self.class.value_name(value) 487 method = self.class.value_option(name, :method) 488 if method && self.respond_to?(method) 489 begin 490 self.send(method) 491 rescue Puppet::Error 492 raise 493 rescue => detail 494 error = Puppet::ResourceError.new(_("Could not set '%{value}' on %{class_name}: %{detail}") % 495 { value: value, class_name: self.class.name, detail: detail }, @resource.file, @resource.line, detail) 496 error.set_backtrace detail.backtrace 497 Puppet.log_exception(detail, error.message) 498 raise error 499 end 500 else 501 block = self.class.value_option(name, :block) 502 if block 503 # FIXME It'd be better here to define a method, so that 504 # the blocks could return values. 505 self.instance_eval(&block) 506 else 507 call_provider(value) 508 end 509 end 510 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
# File lib/puppet/property.rb 525 def should 526 return nil unless defined?(@should) 527 528 self.devfail "should for #{self.class.name} on #{resource.name} is not an array" unless @should.is_a?(Array) 529 530 if match_all? 531 return @should.collect { |val| self.unmunge(val) } 532 else 533 return self.unmunge(@should[0]) 534 end 535 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
# File lib/puppet/property.rb 545 def should=(values) 546 values = [values] unless values.is_a?(Array) 547 548 @shouldorig = values 549 550 values.each { |val| validate(val) } 551 @should = values.collect { |val| self.munge(val) } 552 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
# File lib/puppet/property.rb 560 def should_to_s(value) 561 self.class.format_value_for_display(value) 562 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.
# File lib/puppet/property.rb 568 def sync 569 devfail "Got a nil value for should" unless should 570 set(should) 571 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
Puppet::Parameter#unsafe_validate
# File lib/puppet/property.rb 579 def unsafe_validate(value) 580 super 581 validate_features_per_value(value) 582 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
# File lib/puppet/property.rb 589 def validate_features_per_value(value) 590 features = self.class.value_option(self.class.value_name(value), :required_features) 591 if features 592 features = Array(features) 593 needed_features = features.collect { |f| f.to_s }.join(", ") 594 unless provider.satisfies?(features) 595 #TRANSLATORS 'Provider' refers to a Puppet provider class 596 raise ArgumentError, _("Provider %{provider} must have features '%{needed_features}' to set '%{property}' to '%{value}'") % 597 { provider: provider.class.name, needed_features: needed_features, property: self.class.name, value: value } 598 end 599 end 600 end
@return [Object, nil] Returns the wanted _(should)_ value of this property.
# File lib/puppet/property.rb 603 def value 604 self.should 605 end
(see should=
)
# File lib/puppet/property.rb 608 def value=(values) 609 self.should = values 610 end