Class: Chef::Node::Attribute

Inherits:
Mash
  • Object
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.



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

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



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

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



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

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.



187
188
189
# File 'lib/chef/node/attribute.rb', line 187

def deep_merge_cache
  @deep_merge_cache
end

#defaultObject

return the cookbook level default attribute component



147
148
149
# File 'lib/chef/node/attribute.rb', line 147

def default
  @default
end

#env_defaultObject

return the environment level default attribute component



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

def env_default
  @env_default
end

#env_overrideObject

return the enviroment level override attribute component



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

def env_override
  @env_override
end

#force_defaultObject

return the force_default level attribute component



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

def force_default
  @force_default
end

#force_overrideObject

return the force override level attribute component



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

def force_override
  @force_override
end

#normalObject

return the “normal” level attribute component



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

def normal
  @normal
end

#overrideObject

return the cookbook level override attribute component



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

def override
  @override
end

#role_defaultObject

return the role level default attribute component



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

def role_default
  @role_default
end

#role_overrideObject

return the role level override attribute component



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

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.



182
183
184
# File 'lib/chef/node/attribute.rb', line 182

def top_level_breadcrumb
  @top_level_breadcrumb
end

Instance Method Details

#[](key) ⇒ Object



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

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

#[]=(key, value) ⇒ Object



446
447
448
# File 'lib/chef/node/attribute.rb', line 446

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

#combined_default(*path) ⇒ Object



432
433
434
# File 'lib/chef/node/attribute.rb', line 432

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

#combined_override(*path) ⇒ Object



428
429
430
# File 'lib/chef/node/attribute.rb', line 428

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.



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

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



380
381
382
383
384
# File 'lib/chef/node/attribute.rb', line 380

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



401
402
403
404
405
# File 'lib/chef/node/attribute.rb', line 401

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



408
409
410
411
412
# File 'lib/chef/node/attribute.rb', line 408

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)


450
451
452
453
454
# File 'lib/chef/node/attribute.rb', line 450

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

#inspectObject



482
483
484
485
486
# File 'lib/chef/node/attribute.rb', line 482

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’].



422
423
424
425
426
# File 'lib/chef/node/attribute.rb', line 422

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

#normal!(opts = {}) ⇒ Object

sets normal attributes without merging



387
388
389
390
391
# File 'lib/chef/node/attribute.rb', line 387

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

#override!(opts = {}) ⇒ Object

sets override attributes without merging



394
395
396
397
398
# File 'lib/chef/node/attribute.rb', line 394

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.



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

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

#rm(*args) ⇒ Object

clears attributes from all precedence levels



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

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’)



354
355
356
357
# File 'lib/chef/node/attribute.rb', line 354

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’)



362
363
364
365
# File 'lib/chef/node/attribute.rb', line 362

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’)



370
371
372
373
# File 'lib/chef/node/attribute.rb', line 370

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

#set_unless?Boolean

Returns:

  • (Boolean)


488
489
490
# File 'lib/chef/node/attribute.rb', line 488

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



242
243
244
# File 'lib/chef/node/attribute.rb', line 242

def set_unless_value_present=(setting)
  @set_unless_present = setting
end

#to_sObject



478
479
480
# File 'lib/chef/node/attribute.rb', line 478

def to_s
  merged_attributes.to_s
end