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