diff options
author | Lamont Granquist <lamont@scriptkiddie.org> | 2016-11-01 07:34:27 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-11-01 07:34:27 -0700 |
commit | 46d5d1e731a754d0639c4d7c20c88beb02d9a025 (patch) | |
tree | a80fdd46784d56b808d1fec482da4d98a303a489 | |
parent | 492f61885abe228a14f29c6f9806c0043f03028f (diff) | |
parent | edede76c46eb388b74292a20b4701a1b9b88c535 (diff) | |
download | chef-46d5d1e731a754d0639c4d7c20c88beb02d9a025.tar.gz |
Merge pull request #5495 from chef/lcg/attribute-setting-event
Core: add attribute_changed hook to event handlers
-rw-r--r-- | lib/chef/event_dispatch/base.rb | 3 | ||||
-rw-r--r-- | lib/chef/node.rb | 2 | ||||
-rw-r--r-- | lib/chef/node/attribute.rb | 48 | ||||
-rw-r--r-- | lib/chef/node/attribute_collections.rb | 18 | ||||
-rw-r--r-- | lib/chef/node/immutable_collections.rb | 4 | ||||
-rw-r--r-- | lib/chef/node/mixin/state_tracking.rb | 54 | ||||
-rw-r--r-- | spec/unit/node/attribute_spec.rb | 47 |
7 files changed, 126 insertions, 50 deletions
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb index 04c960c7af..926bbe24b5 100644 --- a/lib/chef/event_dispatch/base.rb +++ b/lib/chef/event_dispatch/base.rb @@ -421,6 +421,9 @@ class Chef def msg(message) end + # Called when an attribute is changed by simple assignment + def attribute_changed(precedence, keys, value) + end end end end diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 34a92d325b..7351a7bfa5 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -78,7 +78,7 @@ class Chef @policy_name = nil @policy_group = nil - @attributes = Chef::Node::Attribute.new({}, {}, {}, {}) + @attributes = Chef::Node::Attribute.new({}, {}, {}, {}, self) @run_state = {} end diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb index 2d6aff0b21..d5b0ee5d72 100644 --- a/lib/chef/node/attribute.rb +++ b/lib/chef/node/attribute.rb @@ -187,21 +187,22 @@ class Chef # return the automatic level attribute component attr_reader :automatic - def initialize(normal, default, override, automatic) - @default = VividMash.new(default, self) - @env_default = VividMash.new({}, self) - @role_default = VividMash.new({}, self) - @force_default = VividMash.new({}, self) + def initialize(normal, default, override, automatic, node = nil) + @default = VividMash.new(default, self, node, :default) + @env_default = VividMash.new({}, self, node, :env_default) + @role_default = VividMash.new({}, self, node, :role_default) + @force_default = VividMash.new({}, self, node, :force_default) - @normal = VividMash.new(normal, self) + @normal = VividMash.new(normal, self, node, :normal) - @override = VividMash.new(override, self) - @role_override = VividMash.new({}, self) - @env_override = VividMash.new({}, self) - @force_override = VividMash.new({}, self) + @override = VividMash.new(override, self, node, :override) + @role_override = VividMash.new({}, self, node, :role_override) + @env_override = VividMash.new({}, self, node, :env_override) + @force_override = VividMash.new({}, self, node, :force_override) - @automatic = VividMash.new(automatic, self) - super() + @automatic = VividMash.new(automatic, self, node, :automatic) + + super(nil, self, node, :merged) end # Debug what's going on with an attribute. +args+ is a path spec to the @@ -232,59 +233,59 @@ class Chef # Set the cookbook level default attribute component to +new_data+. def default=(new_data) reset - @default = VividMash.new(new_data, self) + @default = VividMash.new(new_data, self, __node__, :default) end # Set the role level default attribute component to +new_data+ def role_default=(new_data) reset - @role_default = VividMash.new(new_data, self) + @role_default = VividMash.new(new_data, self, __node__, :role_default) end # Set the environment level default attribute component to +new_data+ def env_default=(new_data) reset - @env_default = VividMash.new(new_data, self) + @env_default = VividMash.new(new_data, self, __node__, :env_default) end # Set the force_default (+default!+) level attributes to +new_data+ def force_default=(new_data) reset - @force_default = VividMash.new(new_data, self) + @force_default = VividMash.new(new_data, self, __node__, :force_default) end # Set the normal level attribute component to +new_data+ def normal=(new_data) reset - @normal = VividMash.new(new_data, self) + @normal = VividMash.new(new_data, self, __node__, :normal) end # Set the cookbook level override attribute component to +new_data+ def override=(new_data) reset - @override = VividMash.new(new_data, self) + @override = VividMash.new(new_data, self, __node__, :override) end # Set the role level override attribute component to +new_data+ def role_override=(new_data) reset - @role_override = VividMash.new(new_data, self) + @role_override = VividMash.new(new_data, self, __node__, :role_override) end # Set the environment level override attribute component to +new_data+ def env_override=(new_data) reset - @env_override = VividMash.new(new_data, self) + @env_override = VividMash.new(new_data, self, __node__, :env_override) end def force_override=(new_data) reset - @force_override = VividMash.new(new_data, self) + @force_override = VividMash.new(new_data, self, __node__, :force_override) end def automatic=(new_data) reset - @automatic = VividMash.new(new_data, self) + @automatic = VividMash.new(new_data, self, __node__, :automatic) end # @@ -480,6 +481,7 @@ class Chef if symbol == :to_ary merged_attributes.send(symbol, *args) elsif args.empty? + puts symbol 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] @@ -565,7 +567,7 @@ class Chef return nil if components.compact.empty? - components.inject(ImmutableMash.new({}, self)) do |merged, component| + components.inject(ImmutableMash.new({}, self, __node__, :merged)) do |merged, component| Chef::Mixin::DeepMerge.hash_only_merge!(merged, component) end end diff --git a/lib/chef/node/attribute_collections.rb b/lib/chef/node/attribute_collections.rb index b01b447978..694b5fbc3a 100644 --- a/lib/chef/node/attribute_collections.rb +++ b/lib/chef/node/attribute_collections.rb @@ -34,7 +34,6 @@ class Chef :compact!, :default=, :default_proc=, - :delete, :delete_at, :delete_if, :fill, @@ -69,6 +68,11 @@ class Chef end end + def delete(key, &block) + send_reset_cache(__path__, key) + super + end + def initialize(data = []) super(data) map! { |e| convert_value(e) } @@ -94,9 +98,9 @@ class Chef when AttrArray value when Hash - VividMash.new(value, __root__) + VividMash.new(value, __root__, __node__, __precedence__) when Array - AttrArray.new(value, __root__) + AttrArray.new(value, __root__, __node__, __precedence__) else value end @@ -143,7 +147,7 @@ class Chef # object. def delete(key, &block) - send_reset_cache(__path__ + [ key ]) + send_reset_cache(__path__, key) super end @@ -170,7 +174,7 @@ class Chef def []=(key, value) ret = super - send_reset_cache(__path__ + [ key ]) + send_reset_cache(__path__, key) ret end @@ -209,9 +213,9 @@ class Chef when AttrArray value when Hash - VividMash.new(value, __root__) + VividMash.new(value, __root__, __node__, __precedence__) when Array - AttrArray.new(value, __root__) + AttrArray.new(value, __root__, __node__, __precedence__) else value end diff --git a/lib/chef/node/immutable_collections.rb b/lib/chef/node/immutable_collections.rb index 938135cbee..dad712e078 100644 --- a/lib/chef/node/immutable_collections.rb +++ b/lib/chef/node/immutable_collections.rb @@ -27,9 +27,9 @@ class Chef def immutablize(value) case value when Hash - ImmutableMash.new(value, __root__) + ImmutableMash.new(value, __root__, __node__, __precedence__) when Array - ImmutableArray.new(value, __root__) + ImmutableArray.new(value, __root__, __node__, __precedence__) else value end diff --git a/lib/chef/node/mixin/state_tracking.rb b/lib/chef/node/mixin/state_tracking.rb index 9be102eeeb..469d4d300f 100644 --- a/lib/chef/node/mixin/state_tracking.rb +++ b/lib/chef/node/mixin/state_tracking.rb @@ -21,33 +21,30 @@ class Chef module StateTracking attr_reader :__path__ attr_reader :__root__ + attr_reader :__node__ + attr_reader :__precedence__ - NULL = Object.new - - def initialize(data = NULL, root = self) + def initialize(data = nil, root = self, node = nil, precedence = nil) # __path__ and __root__ must be nil when we call super so it knows # to avoid resetting the cache on construction - data == NULL ? super() : super(data) + data.nil? ? super() : super(data) @__path__ = [] @__root__ = root + @__node__ = node + @__precedence__ = precedence end def [](key) ret = super - if ret.is_a?(StateTracking) - ret.__path__ = __path__ + [ convert_key(key) ] - ret.__root__ = __root__ - end - ret + next_path = [ __path__, convert_key(key) ].flatten.compact + copy_state_to(ret, next_path) end def []=(key, value) ret = super - if ret.is_a?(StateTracking) - ret.__path__ = __path__ + [ convert_key(key) ] - ret.__root__ = __root__ - end - ret + next_path = [ __path__, convert_key(key) ].flatten.compact + send_attribute_changed_event(next_path, value) + copy_state_to(ret, next_path) end protected @@ -60,10 +57,35 @@ class Chef @__root__ = root end + def __precedence__=(precedence) + @__precedence__ = precedence + end + + def __node__=(node) + @__node__ = node + end + private - def send_reset_cache(path = __path__) - __root__.reset_cache(path.first) if !__root__.nil? && __root__.respond_to?(:reset_cache) && !path.nil? + def send_attribute_changed_event(next_path, value) + if __node__ && __node__.run_context && __node__.run_context.events + __node__.run_context.events.attribute_changed(__precedence__, next_path, value) + end + end + + def send_reset_cache(path = nil, key = nil) + next_path = [ path, key ].flatten.compact + __root__.reset_cache(next_path.first) if !__root__.nil? && __root__.respond_to?(:reset_cache) && !next_path.nil? + end + + def copy_state_to(ret, next_path) + if ret.is_a?(StateTracking) + ret.__path__ = next_path + ret.__root__ = __root__ + ret.__node__ = __node__ + ret.__precedence__ = __precedence__ + end + ret end end end diff --git a/spec/unit/node/attribute_spec.rb b/spec/unit/node/attribute_spec.rb index 00081a9fd9..a3e62ff939 100644 --- a/spec/unit/node/attribute_spec.rb +++ b/spec/unit/node/attribute_spec.rb @@ -21,7 +21,11 @@ require "spec_helper" require "chef/node/attribute" describe Chef::Node::Attribute do + let(:events) { instance_double(Chef::EventDispatch::Dispatcher) } + let(:run_context) { instance_double(Chef::RunContext, :events => events) } + let(:node) { instance_double(Chef::Node, :run_context => run_context) } before(:each) do + allow(events).to receive(:attribute_changed) @attribute_hash = { "dmi" => {}, "command" => { "ps" => "ps -ef" }, @@ -166,7 +170,7 @@ describe Chef::Node::Attribute do }, } @automatic_hash = { "week" => "friday" } - @attributes = Chef::Node::Attribute.new(@attribute_hash, @default_hash, @override_hash, @automatic_hash) + @attributes = Chef::Node::Attribute.new(@attribute_hash, @default_hash, @override_hash, @automatic_hash, node) end describe "initialize" do @@ -1196,4 +1200,45 @@ describe Chef::Node::Attribute do expect(@attributes["foo"]["baz"]["bar"]).to be true end end + + describe "node state" do + it "sets __root__ correctly" do + @attributes.default["foo"]["bar"]["baz"] = "quux" + expect(@attributes["foo"].__root__).to eql(@attributes) + expect(@attributes["foo"]["bar"].__root__).to eql(@attributes) + expect(@attributes.default["foo"].__root__).to eql(@attributes) + expect(@attributes.default["foo"]["bar"].__root__).to eql(@attributes) + end + + it "sets __node__ correctly" do + @attributes.default["foo"]["bar"]["baz"] = "quux" + expect(@attributes["foo"].__node__).to eql(node) + expect(@attributes["foo"]["bar"].__node__).to eql(node) + expect(@attributes.default["foo"].__node__).to eql(node) + expect(@attributes.default["foo"]["bar"].__node__).to eql(node) + end + + it "sets __path__ correctly" do + @attributes.default["foo"]["bar"]["baz"] = "quux" + expect(@attributes["foo"].__path__).to eql(["foo"]) + expect(@attributes["foo"]["bar"].__path__).to eql(%w{foo bar}) + expect(@attributes.default["foo"].__path__).to eql(["foo"]) + expect(@attributes.default["foo"]["bar"].__path__).to eql(%w{foo bar}) + end + + it "sets __precedence__ correctly" do + @attributes.default["foo"]["bar"]["baz"] = "quux" + expect(@attributes["foo"].__precedence__).to eql(:merged) + expect(@attributes["foo"]["bar"].__precedence__).to eql(:merged) + expect(@attributes.default["foo"].__precedence__).to eql(:default) + expect(@attributes.default["foo"]["bar"].__precedence__).to eql(:default) + end + + it "notifies on attribute changes" do + expect(events).to receive(:attribute_changed).with(:default, ["foo"], {}) + expect(events).to receive(:attribute_changed).with(:default, %w{foo bar}, {}) + expect(events).to receive(:attribute_changed).with(:default, %w{foo bar baz}, "quux") + @attributes.default["foo"]["bar"]["baz"] = "quux" + end + end end |