Class: Chef::Node::Attribute

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

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

Returns a new instance of Attribute.



39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/chef/node/attribute.rb', line 39

def initialize(attribute, default, override, state=[])
  @attribute = attribute
  @current_attribute = attribute
  @default = default
  @current_default = default
  @override = override
  @current_override = override
  @state = state
  @auto_vivifiy_on_read = false
  @set_unless_value_present = false
  @has_been_read = false
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/chef/node/attribute.rb', line 377

def method_missing(symbol, *args)
  by = symbol
  if self.attribute?(symbol)
    by = symbol
  elsif self.attribute?(symbol.to_s)
    by = symbol.to_s
  else
    if args.length != 0
      by = symbol
    else
      raise ArgumentError, "Attribute #{symbol.to_s} is not defined!" unless auto_vivifiy_on_read
    end
  end

  if args.length != 0
    if by.to_s =~ /^(.+)=$/
      by = $1
    end
    self[by] = args.length == 1 ? args[0] : args
  else
    self[by]
  end
end

Instance Attribute Details

#attributeObject

Returns the value of attribute attribute.



26
27
28
# File 'lib/chef/node/attribute.rb', line 26

def attribute
  @attribute
end

#auto_vivifiy_on_readObject

Returns the value of attribute auto_vivifiy_on_read.



26
27
28
# File 'lib/chef/node/attribute.rb', line 26

def auto_vivifiy_on_read
  @auto_vivifiy_on_read
end

#current_attributeObject

Returns the value of attribute current_attribute.



26
27
28
# File 'lib/chef/node/attribute.rb', line 26

def current_attribute
  @current_attribute
end

#current_defaultObject

Returns the value of attribute current_default.



26
27
28
# File 'lib/chef/node/attribute.rb', line 26

def current_default
  @current_default
end

#current_overrideObject

Returns the value of attribute current_override.



26
27
28
# File 'lib/chef/node/attribute.rb', line 26

def current_override
  @current_override
end

#defaultObject

Returns the value of attribute default.



26
27
28
# File 'lib/chef/node/attribute.rb', line 26

def default
  @default
end

#has_been_readObject

Returns the value of attribute has_been_read.



26
27
28
# File 'lib/chef/node/attribute.rb', line 26

def has_been_read
  @has_been_read
end

#overrideObject

Returns the value of attribute override.



26
27
28
# File 'lib/chef/node/attribute.rb', line 26

def override
  @override
end

#set_unless_value_presentObject

Returns the value of attribute set_unless_value_present.



26
27
28
# File 'lib/chef/node/attribute.rb', line 26

def set_unless_value_present
  @set_unless_value_present
end

#stateObject

Returns the value of attribute state.



26
27
28
# File 'lib/chef/node/attribute.rb', line 26

def state
  @state
end

Instance Method Details

#[](key) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/chef/node/attribute.rb', line 61

def [](key)
  @state << 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

  o_value = value_or_descend(current_override, key, auto_vivifiy_on_read)
  a_value = value_or_descend(current_attribute, key, auto_vivifiy_on_read)
  d_value = value_or_descend(current_default, key, auto_vivifiy_on_read)

  determine_value(o_value, a_value, d_value)
end

#[]=(key, value) ⇒ Object



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/chef/node/attribute.rb', line 281

def []=(key, value)
  if set_unless_value_present
    if get_value(@default, key) != nil
      Chef::Log.debug("Not setting #{state.join("/")}/#{key} to #{value.inspect} because it has a default value already")
      return false
    end
    if get_value(@attribute, key) != nil
      Chef::Log.debug("Not setting #{state.join("/")}/#{key} to #{value.inspect} because it has a node attribute value already")
      return false
    end
    if get_value(@override, key) != nil
      Chef::Log.debug("Not setting #{state.join("/")}/#{key} to #{value.inspect} because it has an override 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.
  @state.pop if @has_been_read && @state.last == key

  set_value(@attribute, key, value)
  set_value(@override, key, value)
  value
end

#attribute?(key) ⇒ Boolean

Returns:

  • (Boolean)


75
76
77
78
79
80
# File 'lib/chef/node/attribute.rb', line 75

def attribute?(key)
  return true if get_value(override, key)
  return true if get_value(attribute, key)
  return true if get_value(default, key)
  false
end

#auto_vivifiy(data_hash, key) ⇒ Object



335
336
337
338
339
340
341
342
343
344
# File 'lib/chef/node/attribute.rb', line 335

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: #{@state.join("/")}/#{key} " unless auto_vivifiy_on_read
    end
  else
    data_hash[key] = Mash.new
  end
  data_hash
end

#determine_value(o_value, a_value, d_value) ⇒ Object



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/chef/node/attribute.rb', line 252

def determine_value(o_value, a_value, d_value)
  # If all three have hash values, merge them
  if hash_and_not_cna?(o_value) && hash_and_not_cna?(a_value) && hash_and_not_cna?(d_value)
    value = Chef::Mixin::DeepMerge.merge(d_value, a_value)
    value = Chef::Mixin::DeepMerge.merge(value, o_value)
    value
  # If only the override and attributes have values, merge them
  elsif hash_and_not_cna?(o_value) && hash_and_not_cna?(a_value)
    Chef::Mixin::DeepMerge.merge(a_value, o_value)
  # If only the override and default attributes have values, merge them
  elsif hash_and_not_cna?(o_value) && hash_and_not_cna?(d_value)
    Chef::Mixin::DeepMerge.merge(d_value, o_value)
  # If only the override attribute has a value (any value) use it
  elsif ! o_value.nil?
    o_value
  # If the attributes is a hash, and the default is a hash, merge them
  elsif hash_and_not_cna?(a_value) && hash_and_not_cna?(d_value)
    Chef::Mixin::DeepMerge.merge(d_value, a_value)
  # If we have an attribute value, use it
  elsif ! a_value.nil?
    a_value
  # If we have a default value, use it
  elsif ! d_value.nil?
    d_value
  else
    nil
  end
end

#each(&block) ⇒ Object



90
91
92
93
94
95
96
97
98
99
# File 'lib/chef/node/attribute.rb', line 90

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

#each_attribute(&block) ⇒ Object



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

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

#each_key(&block) ⇒ Object



123
124
125
126
127
# File 'lib/chef/node/attribute.rb', line 123

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

#each_pair(&block) ⇒ Object



101
102
103
104
105
106
107
108
109
110
# File 'lib/chef/node/attribute.rb', line 101

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

#each_value(&block) ⇒ Object



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

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

#empty?Boolean

Returns:

  • (Boolean)


140
141
142
# File 'lib/chef/node/attribute.rb', line 140

def empty?
  get_keys.empty?
end

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



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/chef/node/attribute.rb', line 144

def fetch(key, default_value=nil, &block)
  if get_keys.include? key
    determine_value(
      get_value(override, key),
      get_value(attribute, 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



199
200
201
# File 'lib/chef/node/attribute.rb', line 199

def get_keys
  keys
end

#get_value(data_hash, key) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/chef/node/attribute.rb', line 221

def get_value(data_hash, key)
  last = nil

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

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

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

Returns:

  • (Boolean)


82
83
84
# File 'lib/chef/node/attribute.rb', line 82

def has_key?(key)
  attribute?(key)
end

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

Returns:

  • (Boolean)


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

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

#hash_and_not_cna?(to_check) ⇒ Boolean

Returns:

  • (Boolean)


248
249
250
# File 'lib/chef/node/attribute.rb', line 248

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

#index(value) ⇒ Object



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

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

#keysObject



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/chef/node/attribute.rb', line 203

def keys
  tkeys = []
  if current_override
    tkeys = current_override.keys
  end
  if current_attribute
    current_attribute.keys.each do |key|
      tkeys << key unless tkeys.include?(key)
    end
  end
  if current_default
    current_default.keys.each do |key|
      tkeys << key unless tkeys.include?(key)
    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. :)

Returns:

  • (Boolean)


166
167
168
169
170
171
172
# File 'lib/chef/node/attribute.rb', line 166

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

#resetObject

Reset our internal state to the top of every tree



53
54
55
56
57
58
59
# File 'lib/chef/node/attribute.rb', line 53

def reset
  @current_attribute = @attribute
  @current_default = @default
  @current_override = @override
  @has_been_read = false
  @state = []
end

#set_value(data_hash, key, value) ⇒ Object



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/chef/node/attribute.rb', line 310

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

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

  # Walk all the previous places we have been
  0.upto(state.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, state[i])
    # If we are one past the last state, we are adding a key to that hash with a value
    elsif i == state.length
      last[state[i - 1]][key] = value
    # Otherwise, we're auto-vivifiy-ing an interim mash
    else
      last = auto_vivifiy(last[state[i - 1]], state[i])
    end
  end
  data_hash
end

#sizeObject Also known as: length



193
194
195
# File 'lib/chef/node/attribute.rb', line 193

def size
  self.collect{}.length
end

#to_hashObject



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

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

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



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
371
372
373
374
375
# File 'lib/chef/node/attribute.rb', line 346

def value_or_descend(data_hash, key, auto_vivifiy=false)

  if auto_vivifiy
    data_hash = auto_vivifiy(data_hash, key)
    unless current_attribute.has_key?(key)
      current_attribute[key] = data_hash[key]
    end
    unless current_default.has_key?(key)
      current_default[key] = data_hash[key]
    end
    unless current_override.has_key?(key)
      current_override[key] = data_hash[key]
    end
  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(@attribute, @default, @override, @state)
    cna.current_attribute = current_attribute.nil? ? Mash.new : current_attribute[key]
    cna.current_default   = current_default.nil? ? Mash.new : current_default[key]
    cna.current_override  = current_override.nil? ? Mash.new : current_override[key]
    cna.auto_vivifiy_on_read = auto_vivifiy_on_read
    cna.set_unless_value_present = set_unless_value_present
    cna
  else
    data_hash[key]
  end
end

#valuesObject



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

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