summaryrefslogtreecommitdiff
path: root/lib/chef/node/attribute.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/node/attribute.rb')
-rw-r--r--lib/chef/node/attribute.rb75
1 files changed, 71 insertions, 4 deletions
diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb
index 6130925aea..3c48f653eb 100644
--- a/lib/chef/node/attribute.rb
+++ b/lib/chef/node/attribute.rb
@@ -175,6 +175,19 @@ class Chef
# return the automatic level attribute component
attr_reader :automatic
+ # This is used to track the top level key as we descend through method chaining into
+ # a precedence level (e.g. node.default['foo']['bar']['baz']= 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.
+ attr_accessor :top_level_breadcrumb
+
+ # 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.
+ attr_accessor :deep_merge_cache
+
def initialize(normal, default, override, automatic)
@set_unless_present = false
@@ -195,6 +208,8 @@ class Chef
@merged_attributes = nil
@combined_override = nil
@combined_default = nil
+ @top_level_breadcrumb = nil
+ @deep_merge_cache = {}
end
# Debug what's going on with an attribute. +args+ is a path spec to the
@@ -230,51 +245,75 @@ class Chef
@set_unless_present = setting
end
+ # 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['foo']['bar']['baz']=
+ # 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.
+ def reset_cache(path = nil)
+ if path.nil?
+ @deep_merge_cache = {}
+ else
+ deep_merge_cache.delete(path)
+ end
+ end
+
+ alias :reset :reset_cache
+
# Set the cookbook level default attribute component to +new_data+.
def default=(new_data)
+ reset
@default = VividMash.new(self, new_data)
end
# Set the role level default attribute component to +new_data+
def role_default=(new_data)
+ reset
@role_default = VividMash.new(self, new_data)
end
# Set the environment level default attribute component to +new_data+
def env_default=(new_data)
+ reset
@env_default = VividMash.new(self, new_data)
end
# Set the force_default (+default!+) level attributes to +new_data+
def force_default=(new_data)
+ reset
@force_default = VividMash.new(self, new_data)
end
# Set the normal level attribute component to +new_data+
def normal=(new_data)
+ reset
@normal = VividMash.new(self, new_data)
end
# Set the cookbook level override attribute component to +new_data+
def override=(new_data)
+ reset
@override = VividMash.new(self, new_data)
end
# Set the role level override attribute component to +new_data+
def role_override=(new_data)
+ reset
@role_override = VividMash.new(self, new_data)
end
# Set the environment level override attribute component to +new_data+
def env_override=(new_data)
+ reset
@env_override = VividMash.new(self, new_data)
end
def force_override=(new_data)
+ reset
@force_override = VividMash.new(self, new_data)
end
def automatic=(new_data)
+ reset
@automatic = VividMash.new(self, new_data)
end
@@ -284,6 +323,7 @@ class Chef
# clears attributes from all precedence levels
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?(:[])
@@ -314,6 +354,7 @@ class Chef
#
# equivalent to: force_default!['foo']['bar'].delete('baz')
def rm_default(*args)
+ reset(args[0])
remove_from_precedence_level(force_default!(autovivify: false), *args)
end
@@ -321,6 +362,7 @@ class Chef
#
# equivalent to: normal!['foo']['bar'].delete('baz')
def rm_normal(*args)
+ reset(args[0])
remove_from_precedence_level(normal!(autovivify: false), *args)
end
@@ -328,6 +370,7 @@ class Chef
#
# equivalent to: force_override!['foo']['bar'].delete('baz')
def rm_override(*args)
+ reset(args[0])
remove_from_precedence_level(force_override!(autovivify: false), *args)
end
@@ -337,35 +380,51 @@ class Chef
# sets default attributes without merging
def default!(opts={})
+ # FIXME: do not flush whole cache
+ reset
MultiMash.new(self, @default, [], opts)
end
# sets normal attributes without merging
def normal!(opts={})
+ # FIXME: do not flush whole cache
+ reset
MultiMash.new(self, @normal, [], opts)
end
# sets override attributes without merging
def override!(opts={})
+ # FIXME: do not flush whole cache
+ reset
MultiMash.new(self, @override, [], opts)
end
# clears from all default precedence levels and then sets force_default
def force_default!(opts={})
+ # FIXME: do not flush whole cache
+ reset
MultiMash.new(self, @force_default, [@default, @env_default, @role_default], opts)
end
# clears from all override precedence levels and then sets force_override
def force_override!(opts={})
+ # FIXME: do not flush whole cache
+ reset
MultiMash.new(self, @force_override, [@override, @env_override, @role_override], opts)
end
#
- # Accessing merged attributes
+ # Accessing merged attributes.
+ #
+ # Note that merged_attributes('foo', 'bar', 'baz') can be called to compute only the
+ # deep merge of node['foo']['bar']['baz'], but in practice we currently always compute
+ # all of node['foo'] even if the user only requires node['foo']['bar']['baz'].
#
def merged_attributes(*path)
- immutablize(merge_all(path))
+ # immutablize(
+ merge_all(path)
+ # )
end
def combined_override(*path)
@@ -377,7 +436,13 @@ class Chef
end
def [](key)
- merged_attributes(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
def []=(key, value)
@@ -480,7 +545,9 @@ class Chef
safe_dup(component)
end
- components.inject(nil) do |merged, component|
+ return nil if components.compact.empty?
+
+ components.inject(ImmutableMash.new({})) do |merged, component|
Chef::Mixin::DeepMerge.hash_only_merge!(merged, component)
end
end