Class: Chef::Node::Attribute

Inherits:
Mash show all
Includes:
Immutablize, Enumerable
Defined in:
lib/chef/node/attribute.rb

Overview

Attribute

Attribute implements a nested key-value (Hash) and flat collection (Array) data structure supporting multiple levels of precedence, such that a given key may have multiple values internally, but will only return the highest precedence value when reading.

Constant Summary collapse

COMPONENTS =

List of the component attribute hashes, in order of precedence, low to high.

[
  :@default,
  :@env_default,
  :@role_default,
  :@force_default,
  :@normal,
  :@override,
  :@role_override,
  :@env_override,
  :@force_override,
  :@automatic
].freeze
DEFAULT_COMPONENTS =
[
  :@default,
  :@env_default,
  :@role_default,
  :@force_default
]
OVERRIDE_COMPONENTS =
[
  :@override,
  :@role_override,
  :@env_override,
  :@force_override
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Immutablize

#immutablize

Methods inherited from Mash

#delete, #except, #fetch, from_hash, #initialize_copy, #merge, #regular_update, #regular_writer, #stringify_keys!, #symbolize_keys, #to_hash, #update, #values_at

Constructor Details

#initialize(normal, default, override, automatic) ⇒ Attribute

Returns a new instance of Attribute.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/chef/node/attribute.rb', line 191

def initialize(normal, default, override, automatic)
  @set_unless_present = false

  @default = VividMash.new(self, default)
  @env_default = VividMash.new(self, {})
  @role_default = VividMash.new(self, {})
  @force_default = VividMash.new(self, {})

  @normal = VividMash.new(self, normal)

  @override = VividMash.new(self, override)
  @role_override = VividMash.new(self, {})
  @env_override = VividMash.new(self, {})
  @force_override = VividMash.new(self, {})

  @automatic = VividMash.new(self, automatic)

  @merged_attributes = nil
  @combined_override = nil
  @combined_default = nil
  @top_level_breadcrumb = nil
  @deep_merge_cache = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object



465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/chef/node/attribute.rb', line 465

def method_missing(symbol, *args)
  if args.empty?
    if key?(symbol)
      self[symbol]
    else
      raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'"
    end
  elsif symbol.to_s =~ /=$/
    key_to_set = symbol.to_s[/^(.+)=$/, 1]
    self[key_to_set] = (args.length == 1 ? args[0] : args)
  else
    raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'"
  end
end

Instance Attribute Details

#automaticObject

return the automatic level attribute component



176
177
178
# File 'lib/chef/node/attribute.rb', line 176

def automatic
  @automatic
end

#deep_merge_cacheObject

Cache of deep merged values by top-level key. This is a simple hash which has keys that are the top-level keys of the node object, and we save the computed deep-merge for that key here. There is no cache of subtrees.



189
190
191
# File 'lib/chef/node/attribute.rb', line 189

def deep_merge_cache
  @deep_merge_cache
end

#defaultObject

return the cookbook level default attribute component



149
150
151
# File 'lib/chef/node/attribute.rb', line 149

def default
  @default
end

#env_defaultObject

return the environment level default attribute component



155
156
157
# File 'lib/chef/node/attribute.rb', line 155

def env_default
  @env_default
end

#env_overrideObject

return the enviroment level override attribute component



170
171
172
# File 'lib/chef/node/attribute.rb', line 170

def env_override
  @env_override
end

#force_defaultObject

return the force_default level attribute component



158
159
160
# File 'lib/chef/node/attribute.rb', line 158

def force_default
  @force_default
end

#force_overrideObject

return the force override level attribute component



173
174
175
# File 'lib/chef/node/attribute.rb', line 173

def force_override
  @force_override
end

#normalObject

return the “normal” level attribute component



161
162
163
# File 'lib/chef/node/attribute.rb', line 161

def normal
  @normal
end

#overrideObject

return the cookbook level override attribute component



164
165
166
# File 'lib/chef/node/attribute.rb', line 164

def override
  @override
end

#role_defaultObject

return the role level default attribute component



152
153
154
# File 'lib/chef/node/attribute.rb', line 152

def role_default
  @role_default
end

#role_overrideObject

return the role level override attribute component



167
168
169
# File 'lib/chef/node/attribute.rb', line 167

def role_override
  @role_override
end

#top_level_breadcrumbObject

This is used to track the top level key as we descend through method chaining into a precedence level (e.g. node.default[‘bar’]= results in ‘foo’ here). We need this so that when we hit the end of a method chain which results in a mutator method that we can invalidate the whole top-level deep merge cache for the top-level key. It is the responsibility of the accessor on the Chef::Node object to reset this to nil, and then the first VividMash#[] call can ||= and set this to the first key we encounter.



184
185
186
# File 'lib/chef/node/attribute.rb', line 184

def top_level_breadcrumb
  @top_level_breadcrumb
end

Instance Method Details

#[](key) ⇒ Object



438
439
440
441
442
443
444
445
446
# File 'lib/chef/node/attribute.rb', line 438

def [](key)
  if deep_merge_cache.has_key?(key)
    # return the cache of the deep merged values by top-level key
    deep_merge_cache[key]
  else
    # save all the work of computing node[key]
    deep_merge_cache[key] = merged_attributes(key)
  end
end

#[]=(key, value) ⇒ Object



448
449
450
# File 'lib/chef/node/attribute.rb', line 448

def []=(key, value)
  raise Exceptions::ImmutableAttributeModification
end

#combined_default(*path) ⇒ Object



434
435
436
# File 'lib/chef/node/attribute.rb', line 434

def combined_default(*path)
  immutablize(merge_defaults(path))
end

#combined_override(*path) ⇒ Object



430
431
432
# File 'lib/chef/node/attribute.rb', line 430

def combined_override(*path)
  immutablize(merge_overrides(path))
end

#debug_value(*args) ⇒ Object

Debug what’s going on with an attribute. args is a path spec to the attribute you’re interested in. For example, to debug where the value of ‘node[:default_interface]` is coming from, use:

debug_value(:network, :default_interface).

The return value is an Array of Arrays. The first element is ‘[“set_unless_enabled?”, Boolean]`, which describes whether the attribute collection is in “set_unless” mode. The rest of the Arrays are pairs of `[“precedence_level”, value]`, where precedence level is the component, such as role default, normal, etc. and value is the attribute value set at that precedence level. If there is no value at that precedence level, value will be the symbol :not_present.



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/chef/node/attribute.rb', line 226

def debug_value(*args)
  components = COMPONENTS.map do |component|
    ivar = instance_variable_get(component)
    value = args.inject(ivar) do |so_far, key|
      if so_far == :not_present
        :not_present
      elsif so_far.has_key?(key)
        so_far[key]
      else
        :not_present
      end
    end
    [component.to_s.sub(/^@/,""), value]
  end
  [["set_unless_enabled?", @set_unless_present]] + components
end

#default!(opts = {}) ⇒ Object

sets default attributes without merging



382
383
384
385
386
# File 'lib/chef/node/attribute.rb', line 382

def default!(opts={})
  # FIXME: do not flush whole cache
  reset
  MultiMash.new(self, @default, [], opts)
end

#force_default!(opts = {}) ⇒ Object

clears from all default precedence levels and then sets force_default



403
404
405
406
407
# File 'lib/chef/node/attribute.rb', line 403

def force_default!(opts={})
  # FIXME: do not flush whole cache
  reset
  MultiMash.new(self, @force_default, [@default, @env_default, @role_default], opts)
end

#force_override!(opts = {}) ⇒ Object

clears from all override precedence levels and then sets force_override



410
411
412
413
414
# File 'lib/chef/node/attribute.rb', line 410

def force_override!(opts={})
  # FIXME: do not flush whole cache
  reset
  MultiMash.new(self, @force_override, [@override, @env_override, @role_override], opts)
end

#has_key?(key) ⇒ Boolean Also known as: attribute?, member?, include?, key?

Returns:

  • (Boolean)


452
453
454
455
456
# File 'lib/chef/node/attribute.rb', line 452

def has_key?(key)
  COMPONENTS.any? do |component_ivar|
    instance_variable_get(component_ivar).has_key?(key)
  end
end

#inspectObject



480
481
482
483
484
# File 'lib/chef/node/attribute.rb', line 480

def inspect
  "#<#{self.class} " << (COMPONENTS + [:@merged_attributes, :@properties]).map{|iv|
    "#{iv}=#{instance_variable_get(iv).inspect}"
  }.join(', ') << ">"
end

#merged_attributes(*path) ⇒ Object

Accessing merged attributes.

Note that merged_attributes(‘foo’, ‘bar’, ‘baz’) can be called to compute only the deep merge of node[‘bar’], but in practice we currently always compute all of node even if the user only requires node[‘bar’].



424
425
426
427
428
# File 'lib/chef/node/attribute.rb', line 424

def merged_attributes(*path)
 # immutablize(
    merge_all(path)
 # )
end

#normal!(opts = {}) ⇒ Object

sets normal attributes without merging



389
390
391
392
393
# File 'lib/chef/node/attribute.rb', line 389

def normal!(opts={})
  # FIXME: do not flush whole cache
  reset
  MultiMash.new(self, @normal, [], opts)
end

#override!(opts = {}) ⇒ Object

sets override attributes without merging



396
397
398
399
400
# File 'lib/chef/node/attribute.rb', line 396

def override!(opts={})
  # FIXME: do not flush whole cache
  reset
  MultiMash.new(self, @override, [], opts)
end

#reset_cache(path = nil) ⇒ Object Also known as: reset

Invalidate a key in the deep_merge_cache. If called with nil, or no arg, this will invalidate the entire deep_merge cache. In the case of the user doing node.default[‘bar’]= that eventually results in a call to reset_cache(‘foo’) here. A node.default=hash_thing call must invalidate the entire cache and re-deep-merge the entire node object.



252
253
254
255
256
257
258
# File 'lib/chef/node/attribute.rb', line 252

def reset_cache(path = nil)
  if path.nil?
    @deep_merge_cache = {}
  else
    deep_merge_cache.delete(path)
  end
end

#rm(*args) ⇒ Object

clears attributes from all precedence levels



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/chef/node/attribute.rb', line 325

def rm(*args)
  reset(args[0])
  # just easier to compute our retval, rather than collect+merge sub-retvals
  ret = args.inject(merged_attributes) do |attr, arg|
    if attr.nil? || !attr.respond_to?(:[])
      nil
    else
      begin
        attr[arg]
      rescue TypeError
        raise TypeError, "Wrong type in index of attribute (did you use a Hash index on an Array?)"
      end
    end
  end
  rm_default(*args)
  rm_normal(*args)
  rm_override(*args)
  ret
end

#rm_default(*args) ⇒ Object

clears attributes from all default precedence levels

equivalent to: force_default![‘bar’].delete(‘baz’)



356
357
358
359
# File 'lib/chef/node/attribute.rb', line 356

def rm_default(*args)
  reset(args[0])
  remove_from_precedence_level(force_default!(autovivify: false), *args)
end

#rm_normal(*args) ⇒ Object

clears attributes from normal precedence

equivalent to: normal![‘bar’].delete(‘baz’)



364
365
366
367
# File 'lib/chef/node/attribute.rb', line 364

def rm_normal(*args)
  reset(args[0])
  remove_from_precedence_level(normal!(autovivify: false), *args)
end

#rm_override(*args) ⇒ Object

clears attributes from all override precedence levels

equivalent to: force_override![‘bar’].delete(‘baz’)



372
373
374
375
# File 'lib/chef/node/attribute.rb', line 372

def rm_override(*args)
  reset(args[0])
  remove_from_precedence_level(force_override!(autovivify: false), *args)
end

#set_unless?Boolean

Returns:

  • (Boolean)


486
487
488
# File 'lib/chef/node/attribute.rb', line 486

def set_unless?
  @set_unless_present
end

#set_unless_value_present=(setting) ⇒ Object

Enables or disables ‘||=`-like attribute setting. See, e.g., Node#set_unless



244
245
246
# File 'lib/chef/node/attribute.rb', line 244

def set_unless_value_present=(setting)
  @set_unless_present = setting
end