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.



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 191

def initialize(normal, default, override, automatic)
  @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



516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
# File 'lib/chef/node/attribute.rb', line 516

def method_missing(symbol, *args)
  if symbol == :to_ary
    merged_attributes.send(symbol, *args)
  elsif args.empty?
    Chef.log_deprecation %q{method access to node attributes (node.foo.bar) is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]["bar"])}
    if key?(symbol)
      self[symbol]
    else
      raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'"
    end
  elsif symbol.to_s =~ /=$/
    Chef.log_deprecation %q{method setting of node attributes (node.foo="bar") is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]="bar")}
    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



489
490
491
492
493
494
495
496
497
# File 'lib/chef/node/attribute.rb', line 489

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



499
500
501
# File 'lib/chef/node/attribute.rb', line 499

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

#combined_default(*path) ⇒ Object



464
465
466
# File 'lib/chef/node/attribute.rb', line 464

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

#combined_override(*path) ⇒ Object



460
461
462
# File 'lib/chef/node/attribute.rb', line 460

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



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

def debug_value(*args)
  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
end

#default!(*args) ⇒ Object

sets default attributes without merging

  • this API autovivifies (and cannot trainwreck)



377
378
379
380
# File 'lib/chef/node/attribute.rb', line 377

def default!(*args)
  return Decorator::Unchain.new(self, :default!) unless args.length > 0
  write(:default, *args)
end

#default_unless(*args) ⇒ Object



473
474
475
476
# File 'lib/chef/node/attribute.rb', line 473

def default_unless(*args)
  return Decorator::Unchain.new(self, :default_unless) unless args.length > 0
  write(:default, *args) if read(*args[0...-1]).nil?
end

#exist?(*path) ⇒ Boolean

Returns:

  • (Boolean)


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

def exist?(*path)
  merged_attributes.exist?(*path)
end

#force_default!(*args) ⇒ Object

clears from all default precedence levels and then sets force_default

  • this API autovivifies (and cannot trainwreck)



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

def force_default!(*args)
  return Decorator::Unchain.new(self, :force_default!) unless args.length > 0
  value = args.pop
  rm_default(*args)
  write(:force_default, *args, value)
end

#force_override!(*args) ⇒ Object

clears from all override precedence levels and then sets force_override



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

def force_override!(*args)
  return Decorator::Unchain.new(self, :force_override!) unless args.length > 0
  value = args.pop
  rm_override(*args)
  write(:force_override, *args, value)
end

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

Returns:

  • (Boolean)


503
504
505
506
507
# File 'lib/chef/node/attribute.rb', line 503

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

#inspectObject



539
540
541
542
543
# File 'lib/chef/node/attribute.rb', line 539

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



454
455
456
457
458
# File 'lib/chef/node/attribute.rb', line 454

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

#normal!(*args) ⇒ Object

sets normal attributes without merging

  • this API autovivifies (and cannot trainwreck)



385
386
387
388
# File 'lib/chef/node/attribute.rb', line 385

def normal!(*args)
  return Decorator::Unchain.new(self, :normal!) unless args.length > 0
  write(:normal, *args)
end

#normal_unless(*args) ⇒ Object



468
469
470
471
# File 'lib/chef/node/attribute.rb', line 468

def normal_unless(*args)
  return Decorator::Unchain.new(self, :normal_unless) unless args.length > 0
  write(:normal, *args) if read(*args[0...-1]).nil?
end

#override!(*args) ⇒ Object

sets override attributes without merging

  • this API autovivifies (and cannot trainwreck)



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

def override!(*args)
  return Decorator::Unchain.new(self, :override!) unless args.length > 0
  write(:override, *args)
end

#override_unless(*args) ⇒ Object



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

def override_unless(*args)
  return Decorator::Unchain.new(self, :override_unless) unless args.length > 0
  write(:override, *args) if read(*args[0...-1]).nil?
end

#read(*path) ⇒ Object

method-style access to attributes



418
419
420
# File 'lib/chef/node/attribute.rb', line 418

def read(*path)
  merged_attributes.read(*path)
end

#read!(*path) ⇒ Object



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

def read!(*path)
  merged_attributes.read!(*path)
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.



242
243
244
245
246
247
248
# File 'lib/chef/node/attribute.rb', line 242

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



315
316
317
318
319
320
321
# File 'lib/chef/node/attribute.rb', line 315

def rm(*args)
  with_deep_merged_return_value(self, *args) do
    rm_default(*args)
    rm_normal(*args)
    rm_override(*args)
  end
end

#rm_default(*args) ⇒ Object

clears attributes from all default precedence levels

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

  • does not autovivify

  • does not trainwreck if interior keys do not exist



328
329
330
331
332
333
334
335
# File 'lib/chef/node/attribute.rb', line 328

def rm_default(*args)
  with_deep_merged_return_value(combined_default, *args) do
    default.unlink(*args)
    role_default.unlink(*args)
    env_default.unlink(*args)
    force_default.unlink(*args)
  end
end

#rm_normal(*args) ⇒ Object

clears attributes from normal precedence

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

  • does not autovivify

  • does not trainwreck if interior keys do not exist



342
343
344
# File 'lib/chef/node/attribute.rb', line 342

def rm_normal(*args)
  normal.unlink(*args)
end

#rm_override(*args) ⇒ Object

clears attributes from all override precedence levels

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

  • does not autovivify

  • does not trainwreck if interior keys do not exist



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

def rm_override(*args)
  with_deep_merged_return_value(combined_override, *args) do
    override.unlink(*args)
    role_override.unlink(*args)
    env_override.unlink(*args)
    force_override.unlink(*args)
  end
end

#set_unless(*args) ⇒ Object



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

def set_unless(*args)
  Chef.log_deprecation("node.set_unless is deprecated and will be removed in Chef 14, please use node.default_unless/node.override_unless (or node.normal_unless if you really need persistence)")
  return Decorator::Unchain.new(self, :default_unless) unless args.length > 0
  write(:normal, *args) if read(*args[0...-1]).nil?
end

#to_sObject



535
536
537
# File 'lib/chef/node/attribute.rb', line 535

def to_s
  merged_attributes.to_s
end


438
439
440
# File 'lib/chef/node/attribute.rb', line 438

def unlink(level, *path)
  self.send(level).unlink(*path)
end

#unlink!(level, *path) ⇒ Object



442
443
444
# File 'lib/chef/node/attribute.rb', line 442

def unlink!(level, *path)
  self.send(level).unlink!(*path)
end

#write(level, *args, &block) ⇒ Object



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

def write(level, *args, &block)
  self.send(level).write(*args, &block)
end

#write!(level, *args, &block) ⇒ Object



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

def write!(level, *args, &block)
  self.send(level).write!(*args, &block)
end