Class: Chef::Node::Attribute

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/chef/node/attribute.rb

Constant Summary collapse

HIDDEN_ATTRIBUES =
[:@override, :@attribute, :@default, :@normal, :@automatic]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(normal, default, override, automatic, state = []) ⇒ Attribute



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/chef/node/attribute.rb', line 42

def initialize(normal, default, override, automatic, state=[])
  @normal = normal
  @current_normal = normal
  @default = default
  @current_default = default
  @override = override
  @current_override = override
  @automatic = automatic
  @current_automatic = automatic
  @current_nesting_level = state
  @auto_vivifiy_on_read = false
  @set_unless_value_present = false
  @set_type = nil
  @has_been_read = false
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object

Fetches or sets the value, depending on if any arguments are given.

Fetching

If no arguments are given, fetches the value:

node.network
=> {network data}

Getters will find either a string or symbol key.

Setting

If arguments are given, a value will be set. Both normal setter and DSL style setters are allowed:

node.foo = "bar"
node.foo("bar")

Both set node = “bar”



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/chef/node/attribute.rb', line 418

def method_missing(symbol, *args)
  if args.empty?
    if key?(symbol)
      self[symbol]
    elsif key?(symbol.to_s)
      self[symbol.to_s]
    elsif auto_vivifiy_on_read?
      self[symbol] = Mash.new
      self[symbol]
    else
      raise ArgumentError, "Attribute #{symbol} is not defined!" unless auto_vivifiy_on_read
    end
  else
    key_to_set = symbol.to_s[/^(.+)=$/, 1] || symbol
    self[key_to_set] = (args.length == 1 ? args[0] : args)
  end
end

Instance Attribute Details

#auto_vivifiy_on_readObject

Returns the value of attribute auto_vivifiy_on_read



28
29
30
# File 'lib/chef/node/attribute.rb', line 28

def auto_vivifiy_on_read
  @auto_vivifiy_on_read
end

#automaticObject

Returns the value of attribute automatic



28
29
30
# File 'lib/chef/node/attribute.rb', line 28

def automatic
  @automatic
end

#current_automaticObject

Returns the value of attribute current_automatic



28
29
30
# File 'lib/chef/node/attribute.rb', line 28

def current_automatic
  @current_automatic
end

#current_defaultObject

Returns the value of attribute current_default



28
29
30
# File 'lib/chef/node/attribute.rb', line 28

def current_default
  @current_default
end

#current_normalObject

Returns the value of attribute current_normal



28
29
30
# File 'lib/chef/node/attribute.rb', line 28

def current_normal
  @current_normal
end

#current_overrideObject

Returns the value of attribute current_override



28
29
30
# File 'lib/chef/node/attribute.rb', line 28

def current_override
  @current_override
end

#defaultObject

Returns the value of attribute default



28
29
30
# File 'lib/chef/node/attribute.rb', line 28

def default
  @default
end

#normalObject

Returns the value of attribute normal



28
29
30
# File 'lib/chef/node/attribute.rb', line 28

def normal
  @normal
end

#overrideObject

Returns the value of attribute override



28
29
30
# File 'lib/chef/node/attribute.rb', line 28

def override
  @override
end

#set_typeObject

Returns the value of attribute set_type



28
29
30
# File 'lib/chef/node/attribute.rb', line 28

def set_type
  @set_type
end

#set_unless_value_presentObject

Returns the value of attribute set_unless_value_present



28
29
30
# File 'lib/chef/node/attribute.rb', line 28

def set_unless_value_present
  @set_unless_value_present
end

Instance Method Details

#[](key) ⇒ Object



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
# File 'lib/chef/node/attribute.rb', line 89

def [](key)
  @current_nesting_level << key

  # We set this to so that we can cope with ||= as a setting.
  # See the comments in []= for more details.
  @has_been_read = true

  # If we have a set type, our destiny is to write
  if @set_type
    a_value = @set_type == :automatic ? value_or_descend(current_automatic, key, auto_vivifiy_on_read) : nil
    o_value = @set_type == :override ? value_or_descend(current_override, key, auto_vivifiy_on_read) : nil
    n_value = @set_type == :normal ? value_or_descend(current_normal, key, auto_vivifiy_on_read) : nil
    d_value = @set_type == :default ? value_or_descend(current_default, key, auto_vivifiy_on_read) : nil

    determine_value(a_value, o_value, n_value, d_value)
  # Our destiny is only to read, so we get the full list.
  else
    a_value = value_or_descend(current_automatic, key)
    o_value = value_or_descend(current_override, key)
    n_value = value_or_descend(current_normal, key)
    d_value = value_or_descend(current_default, key)

    determine_value(a_value, o_value, n_value, d_value)
  end
end

#[]=(key, value) ⇒ Object



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

def []=(key, value)
  # If we don't have one, then we'll pretend we're normal
  @set_type ||= :normal

  if set_unless_value_present
    if get_value(set_type_hash, key) != nil
      Chef::Log.debug("Not setting #{@current_nesting_level.join("/")}/#{key} to #{value.inspect} because it has a #{@set_type} value already")
      return false
    end
  end

  # If we have been read, and the key we are writing is the same
  # as our parent, we have most like been ||='ed.  So we need to
  # just rewind a bit.
  #
  # In practice, these objects are single use - this is just
  # supporting one more single-use style.
  @current_nesting_level.pop if @has_been_read && @current_nesting_level.last == key

  set_value(set_type_hash, key, value)
  value
end

#attributeObject



58
59
60
# File 'lib/chef/node/attribute.rb', line 58

def attribute
  normal
end

#attribute=(value) ⇒ Object



62
63
64
# File 'lib/chef/node/attribute.rb', line 62

def attribute=(value)
  normal = value
end

#auto_vivifiy(data_hash, key) ⇒ Object



371
372
373
374
375
376
377
378
379
380
# File 'lib/chef/node/attribute.rb', line 371

def auto_vivifiy(data_hash, key)
  if data_hash.has_key?(key)
    unless data_hash[key].respond_to?(:has_key?)
      raise ArgumentError, "You tried to set a nested key, where the parent is not a hash-like object: #{@current_nesting_level.join("/")}/#{key} " unless auto_vivifiy_on_read
    end
  else
    data_hash[key] = Mash.new
  end
  data_hash
end

#auto_vivifiy_on_read?Boolean



367
368
369
# File 'lib/chef/node/attribute.rb', line 367

def auto_vivifiy_on_read?
  auto_vivifiy_on_read
end

#component_has_key?(component_attrs, key) ⇒ Boolean



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

def component_has_key?(component_attrs,key)
  # get the Hash-like object at the current nesting level:
  nested_attrs = value_at_current_nesting(component_attrs, key)
  nested_attrs.respond_to?(:key?) && nested_attrs.key?(key)
end

#delete(key) ⇒ Object



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

def delete(key)
  [@automatic, @override, @normal, @default].inject(nil) do |return_value, attrs|
    deleted_value = delete_from_component(attrs, key)
    return_value || deleted_value
  end
end

#delete_from_component(component_attrs, key) ⇒ Object



460
461
462
463
464
465
466
467
468
469
# File 'lib/chef/node/attribute.rb', line 460

def delete_from_component(component_attrs, key)
  # get the Hash-like object at the current nesting level:
  nested_attrs = value_at_current_nesting(component_attrs, key)

  if nested_attrs.respond_to?(:delete)
    nested_attrs.delete(key)
  else
    nil
  end
end

#determine_value(a_value, o_value, n_value, d_value) ⇒ Object



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/chef/node/attribute.rb', line 289

def determine_value(a_value, o_value, n_value, d_value)
  if hash_and_not_cna?(a_value)
    value = {}
    value = Chef::Mixin::DeepMerge.merge(value, d_value) if hash_and_not_cna?(d_value)
    value = Chef::Mixin::DeepMerge.merge(value, n_value) if hash_and_not_cna?(n_value)
    value = Chef::Mixin::DeepMerge.merge(value, o_value) if hash_and_not_cna?(o_value)
    value = Chef::Mixin::DeepMerge.merge(value, a_value)
    value
  elsif hash_and_not_cna?(o_value)
    value = {}
    value = Chef::Mixin::DeepMerge.merge(value, d_value) if hash_and_not_cna?(d_value)
    value = Chef::Mixin::DeepMerge.merge(value, n_value) if hash_and_not_cna?(n_value)
    value = Chef::Mixin::DeepMerge.merge(value, o_value)
    value
  elsif hash_and_not_cna?(n_value)
    value = {}
    value = Chef::Mixin::DeepMerge.merge(value, d_value) if hash_and_not_cna?(d_value)
    value = Chef::Mixin::DeepMerge.merge(value, n_value)
    value
  elsif hash_and_not_cna?(d_value)
    d_value
  else
    return a_value if ! a_value.nil?
    return o_value if ! o_value.nil?
    return n_value if ! n_value.nil?
    return d_value if ! d_value.nil?
    return nil
  end
end

#each(&block) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/chef/node/attribute.rb', line 128

def each(&block)
  get_keys.each do |key|
    value = determine_value(
      get_value(automatic, key),
      get_value(override, key),
      get_value(normal, key),
      get_value(default, key)
    )
    block.call([key, value])
  end
end

#each_attribute(&block) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
# File 'lib/chef/node/attribute.rb', line 152

def each_attribute(&block)
  get_keys.each do |key|
    value = determine_value(
      get_value(automatic, key),
      get_value(override, key),
      get_value(normal, key),
      get_value(default, key)
    )
    block.call(key, value)
  end
end

#each_key(&block) ⇒ Object



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

def each_key(&block)
  get_keys.each do |key|
    block.call(key)
  end
end

#each_pair(&block) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
# File 'lib/chef/node/attribute.rb', line 140

def each_pair(&block)
  get_keys.each do |key|
    value = determine_value(
      get_value(automatic, key),
      get_value(override, key),
      get_value(normal, key),
      get_value(default, key)
    )
    block.call(key, value)
  end
end

#each_value(&block) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
# File 'lib/chef/node/attribute.rb', line 170

def each_value(&block)
  get_keys.each do |key|
    value = determine_value(
      get_value(automatic, key),
      get_value(override, key),
      get_value(normal, key),
      get_value(default, key)
    )
    block.call(value)
  end
end

#empty?Boolean



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

def empty?
  get_keys.empty?
end

#fetch(key, default_value = nil, &block) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/chef/node/attribute.rb', line 186

def fetch(key, default_value=nil, &block)
  if get_keys.include? key
    determine_value(
      get_value(automatic, key),
      get_value(override, key),
      get_value(normal, key),
      get_value(default, key)
    )
  elsif default_value
    default_value
  elsif block_given?
    block.call(key)
  else
    raise IndexError, "Key #{key} does not exist"
  end
end

#get_keysObject



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

def get_keys
  keys
end

#get_value(data_hash, key) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/chef/node/attribute.rb', line 258

def get_value(data_hash, key)
  last = nil

  if @current_nesting_level.length == 0
    if data_hash.has_key?(key) && ! data_hash[key].nil?
      return data_hash[key]
    else
      return nil
    end
  end

  0.upto(@current_nesting_level.length) do |i|
    if i == 0
      last = auto_vivifiy(data_hash, @current_nesting_level[i])
    elsif i == @current_nesting_level.length
      fk = last[@current_nesting_level[i - 1]]
      if fk.has_key?(key) && ! fk[key].nil?
        return fk[key]
      else
        return nil
      end
    else
      last = auto_vivifiy(last[@current_nesting_level[i - 1]], @current_nesting_level[i])
    end
  end
end

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



115
116
117
118
119
120
121
# File 'lib/chef/node/attribute.rb', line 115

def has_key?(key)
  return true if component_has_key?(@default,key)
  return true if component_has_key?(@automatic,key)
  return true if component_has_key?(@normal,key)
  return true if component_has_key?(@override,key)
  false
end

#has_value?(value) ⇒ Boolean Also known as: value?



217
218
219
220
221
# File 'lib/chef/node/attribute.rb', line 217

def has_value?(value)
  self.any? do |k,v|
    value == v
  end
end

#hash_and_not_cna?(to_check) ⇒ Boolean



285
286
287
# File 'lib/chef/node/attribute.rb', line 285

def hash_and_not_cna?(to_check)
  (! to_check.kind_of?(Chef::Node::Attribute)) && to_check.respond_to?(:has_key?)
end

#index(value) ⇒ Object



225
226
227
228
229
230
# File 'lib/chef/node/attribute.rb', line 225

def index(value)
  index = self.find do |h|
    value == h[1]
  end
  index.first if index.is_a? Array || nil
end

#inspectObject



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

def inspect
  determine_value(current_automatic, current_override, current_normal, current_default)

  "#<#{self.class} " << instance_variables.map{|iv|
    iv.to_s + '=' + (HIDDEN_ATTRIBUES.include?(iv.to_sym) ? "{...}" : instance_variable_get(iv).inspect)
  }.join(', ') << ">"
end

#keysObject



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

def keys
  tkeys = current_automatic ? current_automatic.keys : []
  [ current_override, current_normal, current_default ].each do |attr_hash|
    if attr_hash
      attr_hash.keys.each do |key|
        tkeys << key unless tkeys.include?(key)
      end
    end
  end
  tkeys
end

#kind_of?(klass) ⇒ Boolean

Writing this method hurts me a little bit.

TODO: Refactor all this stuff so this kind of horror is no longer needed

We have invented a new kind of duck-typing, we call it Madoff typing. We just lie and hope we die before you recognize our scheme. :)



209
210
211
212
213
214
215
# File 'lib/chef/node/attribute.rb', line 209

def kind_of?(klass)
  if klass == Hash || klass == Mash || klass == Chef::Node::Attribute
    true
  else
    false
  end
end

#resetObject

Reset our internal current_nesting_level to the top of every tree



80
81
82
83
84
85
86
87
# File 'lib/chef/node/attribute.rb', line 80

def reset
  @current_normal = @normal
  @current_default = @default
  @current_override = @override
  @current_automatic = @automatic
  @has_been_read = false
  @current_nesting_level = []
end

#set_type_hashObject



66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/chef/node/attribute.rb', line 66

def set_type_hash
  case @set_type
  when :normal
    @normal
  when :override
    @override
  when :default
    @default
  when :automatic
    @automatic
  end
end

#set_value(data_hash, key, value) ⇒ Object



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/chef/node/attribute.rb', line 342

def set_value(data_hash, key, value)
  last = nil

  # If there is no current_nesting_level, just set the value
  if @current_nesting_level.length == 0
    data_hash[key] = value
    return data_hash
  end

  # Walk all the previous places we have been
  0.upto(@current_nesting_level.length) do |i|
    # If we are the first, we are top level, and should vivifiy the data_hash
    if i == 0
      last = auto_vivifiy(data_hash, @current_nesting_level[i])
    # If we are one past the last current_nesting_level, we are adding a key to that hash with a value
    elsif i == @current_nesting_level.length
      last[@current_nesting_level[i - 1]][key] = value
    # Otherwise, we're auto-vivifiy-ing an interim mash
    else
      last = auto_vivifiy(last[@current_nesting_level[i - 1]], @current_nesting_level[i])
    end
  end
  data_hash
end

#sizeObject Also known as: length



236
237
238
# File 'lib/chef/node/attribute.rb', line 236

def size
  self.collect{}.length
end

#to_hashObject



444
445
446
447
448
449
450
451
# File 'lib/chef/node/attribute.rb', line 444

def to_hash
  result = determine_value(current_automatic, current_override, current_normal, current_default)
  if result.class == Hash
    result
  else
    result.to_hash
  end
end

#value_at_current_nesting(component_attrs, key) ⇒ Object



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

def value_at_current_nesting(component_attrs, key)
  @current_nesting_level.inject(component_attrs) do |subtree, intermediate_key|
    # if the intermediate value isn't a hash or doesn't have the intermediate key,
    # it can't have the bottom-level key we're looking for.
    (subtree.respond_to?(:key?) && subtree[intermediate_key]) or (return false)
  end
end

#value_or_descend(data_hash, key, auto_vivifiy = false) ⇒ Object



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/chef/node/attribute.rb', line 382

def value_or_descend(data_hash, key, auto_vivifiy=false)
  if auto_vivifiy
    hash_to_vivifiy = auto_vivifiy(data_hash, key)
    data_hash[key] = hash_to_vivifiy[key]
  else
    return nil if data_hash == nil
    return nil unless data_hash.has_key?(key)
  end

  if data_hash[key].respond_to?(:has_key?)
    cna = Chef::Node::Attribute.new(@normal, @default, @override, @automatic, @current_nesting_level)
    cna.current_normal = current_normal.nil? ? Mash.new : current_normal[key]
    cna.current_default   = current_default.nil? ? Mash.new : current_default[key]
    cna.current_override  = current_override.nil? ? Mash.new : current_override[key]
    cna.current_automatic  = current_automatic.nil? ? Mash.new : current_automatic[key]
    cna.auto_vivifiy_on_read = auto_vivifiy_on_read
    cna.set_unless_value_present = set_unless_value_present
    cna.set_type = set_type
    cna
  else
    data_hash[key]
  end
end

#valuesObject



232
233
234
# File 'lib/chef/node/attribute.rb', line 232

def values
  self.collect { |h| h[1] }
end