diff options
Diffstat (limited to 'lib/chef/node/attribute.rb')
-rw-r--r-- | lib/chef/node/attribute.rb | 75 |
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 |