Class: Chef::Property
- Inherits:
-
Object
- Object
- Chef::Property
- Defined in:
- lib/chef/property.rb
Overview
Type and validation information for a property on a resource.
A property named “x” manipulates the “@x” instance variable on a resource. The presence of the variable (‘instance_variable_defined?(@x)`) tells whether the variable is defined; it may have any actual value, constrained only by validation.
Properties may have validation, defaults, and coercion, and have full support for lazy values.
Direct Known Subclasses
Class Method Summary collapse
-
.derive(**options) ⇒ Object
Create a reusable property type that can be used in multiple properties in different resources.
Instance Method Summary collapse
-
#call(resource, value = NOT_PASSED) ⇒ Object
Handle the property being called.
-
#coerce(resource, value) ⇒ Object
Coerce an input value into canonical form for the property.
-
#declared_in ⇒ Class
The class this property was defined in.
-
#default ⇒ Object
The raw default value for this resource.
-
#derive(**modified_options) ⇒ Property
Derive a new Property that is just like this one, except with some added or changed options.
-
#desired_state? ⇒ Boolean
Whether this is part of desired state or not.
-
#emit_dsl ⇒ Object
Emit the DSL for this property into the resource class (‘declared_in`).
-
#get(resource) ⇒ Object
Get the property value from the resource, handling lazy values, defaults, and validation.
-
#has_default? ⇒ Boolean
Whether this property has a default value.
-
#identity? ⇒ Boolean
Whether this is part of the resource’s natural identity or not.
-
#initialize(**options) ⇒ Property
constructor
Create a new property.
-
#instance_variable_name ⇒ Symbol
The instance variable associated with this property.
-
#is_set?(resource) ⇒ Boolean
Find out whether this property has been set.
-
#name ⇒ String
The name of this property.
-
#name_property? ⇒ Boolean
Whether this is name_property or not.
-
#required? ⇒ Boolean
Whether this property is required or not.
-
#reset(resource) ⇒ Object
Reset the value of this property so that is_set? will return false and the default will be returned in the future.
-
#set(resource, value) ⇒ Object
Set the value of this property in the given resource.
- #to_s ⇒ Object
-
#validate(resource, value) ⇒ Object
Validate a value.
-
#validation_options ⇒ Hash<Symbol,Object>
Validation options.
Constructor Details
#initialize(**options) ⇒ Property
Create a new property.
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 |
# File 'lib/chef/property.rb', line 89 def initialize(**) .each { |k, v| [k.to_sym] = v; .delete(k) if k.is_a?(String) } = [:name] = [:name].to_sym if [:name] [:instance_variable_name] = [:instance_variable_name].to_sym if [:instance_variable_name] # Replace name_attribute with name_property if .has_key?(:name_attribute) # If we have both name_attribute and name_property and they differ, raise an error if .has_key?(:name_property) raise ArgumentError, "Cannot specify both name_property and name_attribute together on property #{self}." end # replace name_property with name_attribute in place = Hash[.map { |k, v| k == :name_attribute ? [ :name_property, v ] : [ k, v ] }] = end # Only pick the first of :default, :name_property and :name_attribute if # more than one is specified. if .has_key?(:default) && [:name_property] if [:default].nil? || .keys.index(:name_property) < .keys.index(:default) .delete(:default) preferred_default = :name_property else .delete(:name_property) preferred_default = :default end Chef.log_deprecation("Cannot specify both default and name_property together on property #{self}. Only one (#{preferred_default}) will be obeyed. In Chef 13, this will become an error. Please remove one or the other from the property.") end # Validate the default early, so the user gets a good error message, and # cache it so we don't do it again if so begin # If we can validate it all the way to output, do it. @stored_default = input_to_stored_value(nil, default, is_default: true) rescue Chef::Exceptions::CannotValidateStaticallyError # If the validation is not static (i.e. has procs), we will have to # coerce and validate the default each time we run end end |
Class Method Details
.derive(**options) ⇒ Object
Create a reusable property type that can be used in multiple properties in different resources.
50 51 52 |
# File 'lib/chef/property.rb', line 50 def self.derive(**) new(**) end |
Instance Method Details
#call(resource, value = NOT_PASSED) ⇒ Object
Handle the property being called.
The base implementation does the property get-or-set:
“‘ruby resource.myprop # get resource.myprop value # set “`
Subclasses may implement this with any arguments they want, as long as the corresponding DSL calls it correctly.
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 |
# File 'lib/chef/property.rb', line 260 def call(resource, value = NOT_PASSED) if value == NOT_PASSED return get(resource) end if value.nil? # In Chef 12, value(nil) does a *get* instead of a set, so we # warn if the value would have been changed. In Chef 13, it will be # equivalent to value = nil. result = get(resource) # Warn about this becoming a set in Chef 13. begin input_to_stored_value(resource, value) # If nil is valid, and it would change the value, warn that this will change to a set. if !result.nil? Chef.log_deprecation("An attempt was made to change #{name} from #{result.inspect} to nil by calling #{name}(nil). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil.") end rescue Chef::Exceptions::DeprecatedFeatureError raise rescue # If nil is invalid, warn that this will become an error. Chef.log_deprecation("nil is an invalid value for #{self}. In Chef 13, this warning will change to an error. Error: #{$!}") end result else # Anything else, such as myprop(value) is a set set(resource, value) end end |
#coerce(resource, value) ⇒ Object
Coerce an input value into canonical form for the property.
After coercion, the value is suitable for storage in the resource. You must validate values after coercion, however.
Does no special handling for lazy values.
439 440 441 442 443 444 445 446 447 |
# File 'lib/chef/property.rb', line 439 def coerce(resource, value) if .has_key?(:coerce) # If we have no default value, `nil` is never coerced or validated unless !has_default? && value.nil? value = exec_in_resource(resource, [:coerce], value) end end value end |
#declared_in ⇒ Class
The class this property was defined in.
148 149 150 |
# File 'lib/chef/property.rb', line 148 def declared_in [:declared_in] end |
#default ⇒ Object
The raw default value for this resource.
Does not coerce or validate the default. Does not evaluate lazy values.
Defaults to ‘lazy { name }` if name_property is true; otherwise defaults to `nil`
175 176 177 178 179 |
# File 'lib/chef/property.rb', line 175 def default return [:default] if .has_key?(:default) return Chef::DelayedEvaluator.new { name } if name_property? nil end |
#derive(**modified_options) ⇒ Property
Derive a new Property that is just like this one, except with some added or changed options.
483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'lib/chef/property.rb', line 483 def derive(**) # Since name_property, name_attribute and default override each other, # if you specify one of them in modified_options it overrides anything in # the original options. = self. if .has_key?(:name_property) || .has_key?(:name_attribute) || .has_key?(:default) = .reject { |k, v| k == :name_attribute || k == :name_property || k == :default } end self.class.new(.merge()) end |
#desired_state? ⇒ Boolean
Whether this is part of desired state or not.
Defaults to true.
197 198 199 200 |
# File 'lib/chef/property.rb', line 197 def desired_state? return true if !.has_key?(:desired_state) [:desired_state] end |
#emit_dsl ⇒ Object
Emit the DSL for this property into the resource class (‘declared_in`).
Creates a getter and setter for the property.
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 |
# File 'lib/chef/property.rb', line 501 def emit_dsl # We don't create the getter/setter if it's a custom property; we will # be using the existing getter/setter to manipulate it instead. return if !instance_variable_name # We prefer this form because the property name won't show up in the # stack trace if you use `define_method`. declared_in.class_eval " def \#{name}(value=NOT_PASSED)\n raise \"Property \#{name} of \\\#{self} cannot be passed a block! If you meant to create a resource named \#{name} instead, you'll need to first rename the property.\" if block_given?\n self.class.properties[\#{name.inspect}].call(self, value)\n end\n def \#{name}=(value)\n raise \"Property \#{name} of \\\#{self} cannot be passed a block! If you meant to create a resource named \#{name} instead, you'll need to first rename the property.\" if block_given?\n self.class.properties[\#{name.inspect}].set(self, value)\n end\n EOM\nrescue SyntaxError\n # If the name is not a valid ruby name, we use define_method.\n declared_in.define_method(name) do |value = NOT_PASSED, &block|\n raise \"Property \#{name} of \#{self} cannot be passed a block! If you meant to create a resource named \#{name} instead, you'll need to first rename the property.\" if block\n self.class.properties[name].call(self, value)\n end\n declared_in.define_method(\"\#{name}=\") do |value, &block|\n raise \"Property \#{name} of \#{self} cannot be passed a block! If you meant to create a resource named \#{name} instead, you'll need to first rename the property.\" if block\n self.class.properties[name].set(self, value)\n end\nend\n", __FILE__, __LINE__ + 1 |
#get(resource) ⇒ Object
Get the property value from the resource, handling lazy values, defaults, and validation.
-
If the property’s value is lazy, it is evaluated, coerced and validated.
-
If the property has no value, and is required, raises ValidationFailed.
-
If the property has no value, but has a lazy default, it is evaluated, coerced and validated. If the evaluated value is frozen, the resulting
-
If the property has no value, but has a default, the default value will be returned and frozen. If the default value is lazy, it will be evaluated, coerced and validated, and the result stored in the property.
-
If the property has no value, but is name_property, ‘resource.name` is retrieved, coerced, validated and stored in the property.
-
Otherwise, ‘nil` is returned.
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 |
# File 'lib/chef/property.rb', line 314 def get(resource) # If it's set, return it (and evaluate any lazy values) if is_set?(resource) value = get_value(resource) value = stored_value_to_output(resource, value) else # We are getting the default value. # If the user does something like this: # # ``` # class MyResource < Chef::Resource # property :content # action :create do # file '/x.txt' do # content content # end # end # end # ``` # # It won't do what they expect. This checks whether you try to *read* # `content` while we are compiling the resource. if resource.respond_to?(:resource_initializing) && resource.resource_initializing && resource.respond_to?(:enclosing_provider) && resource.enclosing_provider && resource.enclosing_provider.new_resource && resource.enclosing_provider.new_resource.respond_to?(name) Chef::Log.warn("#{Chef::Log.caller_location}: property #{name} is declared in both #{resource} and #{resource.enclosing_provider}. Use new_resource.#{name} instead. At #{Chef::Log.caller_location}") end if has_default? # If we were able to cache the stored_default, grab it. if defined?(@stored_default) value = @stored_default else # Otherwise, we have to validate it now. value = input_to_stored_value(resource, default, is_default: true) end value = stored_value_to_output(resource, value, is_default: true) # If the value is mutable (non-frozen), we set it on the instance # so that people can mutate it. (All constant default values are # frozen.) if !value.frozen? && !value.nil? set_value(resource, value) end value elsif required? raise Chef::Exceptions::ValidationFailed, "#{name} is required" end end end |
#has_default? ⇒ Boolean
Whether this property has a default value.
216 217 218 |
# File 'lib/chef/property.rb', line 216 def has_default? .has_key?(:default) || name_property? end |
#identity? ⇒ Boolean
Whether this is part of the resource’s natural identity or not.
186 187 188 |
# File 'lib/chef/property.rb', line 186 def identity? [:identity] end |
#instance_variable_name ⇒ Symbol
The instance variable associated with this property.
Defaults to ‘@<name>`
159 160 161 162 163 164 165 |
# File 'lib/chef/property.rb', line 159 def instance_variable_name if .has_key?(:instance_variable_name) [:instance_variable_name] elsif name :"@#{name}" end end |
#is_set?(resource) ⇒ Boolean
Find out whether this property has been set.
This will be true if:
-
The user explicitly set the value
-
The property has a default, and the value was retrieved.
From this point of view, it is worth looking at this as “what does the user think this value should be.” In order words, if the user grabbed the value, even if it was a default, they probably based calculations on it. If they based calculations on it and the value changes, the rest of the world gets inconsistent.
408 409 410 |
# File 'lib/chef/property.rb', line 408 def is_set?(resource) value_is_set?(resource) end |
#name ⇒ String
The name of this property.
139 140 141 |
# File 'lib/chef/property.rb', line 139 def name [:name] end |
#name_property? ⇒ Boolean
Whether this is name_property or not.
207 208 209 |
# File 'lib/chef/property.rb', line 207 def name_property? [:name_property] end |
#required? ⇒ Boolean
Whether this property is required or not.
225 226 227 |
# File 'lib/chef/property.rb', line 225 def required? [:required] end |
#reset(resource) ⇒ Object
Reset the value of this property so that is_set? will return false and the default will be returned in the future.
418 419 420 |
# File 'lib/chef/property.rb', line 418 def reset(resource) reset_value(resource) end |
#set(resource, value) ⇒ Object
Set the value of this property in the given resource.
Non-lazy values are coerced and validated before being set. Coercion and validation of lazy values is delayed until they are first retrieved.
387 388 389 |
# File 'lib/chef/property.rb', line 387 def set(resource, value) set_value(resource, input_to_stored_value(resource, value)) end |
#to_s ⇒ Object
130 131 132 |
# File 'lib/chef/property.rb', line 130 def to_s "#{name || "<property type>"}#{declared_in ? " of resource #{declared_in.resource_name}" : ""}" end |
#validate(resource, value) ⇒ Object
Validate a value.
Calls Chef::Mixin::ParamsValidate#validate with #validation_options as options.
462 463 464 465 466 467 468 469 470 471 472 |
# File 'lib/chef/property.rb', line 462 def validate(resource, value) # If we have no default value, `nil` is never coerced or validated unless value.nil? && !has_default? if resource resource.validate({ name => value }, { name => }) else name = self.name || :property_type Chef::Mixin::ParamsValidate.validate({ name => value }, { name => }) end end end |
#validation_options ⇒ Hash<Symbol,Object>
Validation options. (See Chef::Mixin::ParamsValidate#validate.)
234 235 236 237 238 |
# File 'lib/chef/property.rb', line 234 def ||= .reject { |k, v| [:declared_in, :name, :instance_variable_name, :desired_state, :identity, :default, :name_property, :coerce, :required].include?(k) } end |