summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2014-11-08 11:40:59 -0800
committerSerdar Sutay <serdar@opscode.com>2014-11-08 14:05:24 -0800
commitc7c51e3ab60871837caa81c3c4c39fd3febe83cf (patch)
tree2b394f236762c4263ba72164ac822a7407a762a9
parent5be5e42ba2359e0cdadc345fad10b8d398731675 (diff)
downloadchef-c7c51e3ab60871837caa81c3c4c39fd3febe83cf.tar.gz
Merge pull request #2097 from opscode/lcg/chef-12-attr
Lcg/chef 12 attr Conflicts: spec/unit/node_spec.rb
-rw-r--r--CHANGELOG.md1
-rw-r--r--RELEASE_NOTES.md8
-rw-r--r--lib/chef/node.rb16
-rw-r--r--lib/chef/node/attribute.rb107
-rw-r--r--lib/chef/node/attribute_collections.rb112
-rw-r--r--spec/unit/node/attribute_spec.rb6
-rw-r--r--spec/unit/node_spec.rb334
7 files changed, 540 insertions, 44 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a388733423..aed9f16163 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -146,6 +146,7 @@
### Chef Contributions
+* Added RFC-023 Chef 12 Attribute Changes (https://github.com/opscode/chef-rfc/blob/master/rfc023-chef-12-attributes-changes.md)
* Added os/platform_family options to provides syntax on the Chef::Resource DSL
* Added provides methods to the Chef::Provider DSL
* Added supported?(resource, action) class method to all Providers for late-evaluation if a provider can handle a
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 3541460625..924f189543 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -21,6 +21,14 @@ block to the `on_create` method; it is now configured by calling either
# End-User Changes
+## Chef 12 Attribute Changes
+
+The Chef 12 Attribute RFC 23 (https://github.com/opscode/chef-rfc/blob/master/rfc023-chef-12-attributes-changes.md) has been merged into
+Chef. This adds the ability to remove precedence levels (or all levels) of attributes in recipes code, or to
+force setting an attribute precedence level. The major backwards incompatible change to call out in this RFC is that
+`node.force_default!` and `node.force_override!` have changed from accessors to setters, and any cookbook code that used these functions
+(extremely uncommon) simply needs to drop the exclamation point off of the method in order to use the accessor.
+
## Knife Prefers `config.rb` to `knife.rb`.
Knife will now look for `config.rb` in preference to `knife.rb` for its
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index 5f788af4d4..dbb7852586 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -42,6 +42,8 @@ class Chef
extend Forwardable
def_delegators :attributes, :keys, :each_key, :each_value, :key?, :has_key?
+ def_delegators :attributes, :rm, :rm_default, :rm_normal, :rm_override
+ def_delegators :attributes, :default!, :normal!, :override!, :force_default!, :force_override!
attr_accessor :recipe_list, :run_state, :override_runlist
@@ -146,13 +148,6 @@ class Chef
attributes.default
end
- # Set a force default attribute. Intermediate mashes will be created by
- # auto-vivify if necessary.
- def default!
- attributes.set_unless_value_present = false
- attributes.default!
- end
-
# Set a default attribute of this node, auto-vivifying any mashes that are
# missing, but if the final value already exists, don't set it
def default_unless
@@ -167,13 +162,6 @@ class Chef
attributes.override
end
- # Set a force override attribute. Intermediate mashes will be created by
- # auto-vivify if needed.
- def override!
- attributes.set_unless_value_present = false
- attributes.override!
- end
-
# Set an override attribute of this node, auto-vivifying any mashes that
# are missing, but if the final value already exists, don't set it
def override_unless
diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb
index 66569cf0e1..3eb6449046 100644
--- a/lib/chef/node/attribute.rb
+++ b/lib/chef/node/attribute.rb
@@ -58,7 +58,6 @@ class Chef
:@force_default
]
-
OVERRIDE_COMPONENTS = [
:@override,
:@role_override,
@@ -146,7 +145,6 @@ class Chef
METHOD_DEFN
end
-
# return the cookbook level default attribute component
attr_reader :default
@@ -159,11 +157,6 @@ class Chef
# return the force_default level attribute component
attr_reader :force_default
- # default! is the "advertised" method for force_default, but is
- # implemented as an alias because instance variables can't (easily) have
- # +!+ characters.
- alias :default! :force_default
-
# return the "normal" level attribute component
attr_reader :normal
@@ -179,11 +172,6 @@ class Chef
# return the force override level attribute component
attr_reader :force_override
- # +override!+ is the "advertised" method for +force_override+ but is
- # implemented as an alias because instance variables can't easily have
- # +!+ characters.
- alias :override! :force_override
-
# return the automatic level attribute component
attr_reader :automatic
@@ -311,6 +299,100 @@ class Chef
@automatic = VividMash.new(self, new_data)
end
+ #
+ # Deleting attributes
+ #
+
+ # clears attributes from all precedence levels
+ def rm(*args)
+ # 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?(:[])
+ nil
+ else
+ begin
+ attr[arg]
+ rescue TypeError
+ raise TypeError, "Wrong type in index of attribute (did you use a Hash index on an Array?)"
+ end
+ end
+ end
+ rm_default(*args)
+ rm_normal(*args)
+ rm_override(*args)
+ ret
+ end
+
+ # does <level>['foo']['bar'].delete('baz')
+ def remove_from_precedence_level(level, *args, key)
+ multimash = level.element(*args)
+ multimash.nil? ? nil : multimash.delete(key)
+ end
+
+ private :remove_from_precedence_level
+
+ # clears attributes from all default precedence levels
+ #
+ # equivalent to: force_default!['foo']['bar'].delete('baz')
+ def rm_default(*args)
+ reset
+ remove_from_precedence_level(force_default!(autovivify: false), *args)
+ end
+
+ # clears attributes from normal precedence
+ #
+ # equivalent to: normal!['foo']['bar'].delete('baz')
+ def rm_normal(*args)
+ reset
+ remove_from_precedence_level(normal!(autovivify: false), *args)
+ end
+
+ # clears attributes from all override precedence levels
+ #
+ # equivalent to: force_override!['foo']['bar'].delete('baz')
+ def rm_override(*args)
+ reset
+ remove_from_precedence_level(force_override!(autovivify: false), *args)
+ end
+
+ #
+ # Replacing attributes without merging
+ #
+
+ # sets default attributes without merging
+ def default!(opts={})
+ reset
+ MultiMash.new(self, @default, [], opts)
+ end
+
+ # sets normal attributes without merging
+ def normal!(opts={})
+ reset
+ MultiMash.new(self, @normal, [], opts)
+ end
+
+ # sets override attributes without merging
+ def override!(opts={})
+ reset
+ MultiMash.new(self, @override, [], opts)
+ end
+
+ # clears from all default precedence levels and then sets force_default
+ def force_default!(opts={})
+ 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={})
+ reset
+ MultiMash.new(self, @force_override, [@override, @env_override, @role_override], opts)
+ end
+
+ #
+ # Accessing merged attributes
+ #
+
def merged_attributes
@merged_attributes ||= begin
components = [merge_defaults, @normal, merge_overrides, @automatic]
@@ -391,7 +473,6 @@ class Chef
end
end
-
end
end
diff --git a/lib/chef/node/attribute_collections.rb b/lib/chef/node/attribute_collections.rb
index f09b02b106..c8bc618762 100644
--- a/lib/chef/node/attribute_collections.rb
+++ b/lib/chef/node/attribute_collections.rb
@@ -209,5 +209,117 @@ class Chef
end
+ # == MultiMash
+ # This is a Hash-like object that contains multiple VividMashes in it. Its
+ # purpose is so that the user can descend into the mash and delete a subtree
+ # from all of the Mash objects (used to delete all values in a subtree from
+ # default, force_default, role_default and env_default at the same time). The
+ # assignment operator strictly does assignment (does no merging) and works
+ # by deleting the subtree and then assigning to the last mash which passed in
+ # the initializer.
+ #
+ # A lot of the complexity of this class comes from the fact that at any key
+ # value some or all of the mashes may walk off their ends and become nil or
+ # true or something. The schema may change so that one precidence leve may
+ # be 'true' object and another may be a VividMash. It is also possible that
+ # one or many of them may transition from VividMashes to Hashes or Arrays.
+ #
+ # It also supports the case where you may be deleting a key using node.rm
+ # in which case if intermediate keys all walk off into nil then you don't want
+ # to be autovivifying keys as you go. On the other hand you may be using
+ # node.force_default! in which case you'll wind up with a []= operator at the
+ # end and you want autovivification, so we conditionally have to support either
+ # operation.
+ #
+ # @todo: can we have an autovivify class that decorates a class that doesn't
+ # autovivify or something so that the code is less awful?
+ #
+ class MultiMash
+ attr_reader :root
+ attr_reader :mashes
+ attr_reader :opts
+ attr_reader :primary_mash
+
+ # Initialize with an array of mashes. For the delete return value to work
+ # properly the mashes must come from the same attribute level (i.e. all
+ # override or all default, but not a mix of both).
+ def initialize(root, primary_mash, mashes, opts={})
+ @root = root
+ @primary_mash = primary_mash
+ @mashes = mashes
+ @opts = opts
+ @opts[:autovivify] = true if @opts[:autovivify].nil?
+ end
+
+ def [](key)
+ # handle the secondary mashes
+ new_mashes = []
+ mashes.each do |mash|
+ new_mash = safe_evalute_key(mash, key)
+ # secondary mashes never autovivify so once they fall into nil, we just stop tracking them
+ new_mashes.push(new_mash) unless new_mash.nil?
+ end
+
+ new_primary_mash = safe_evalute_key(primary_mash, key)
+
+ if new_primary_mash.nil? && @opts[:autovivify]
+ primary_mash[key] = VividMash.new(root)
+ new_primary_mash = primary_mash[key]
+ end
+
+ MultiMash.new(root, new_primary_mash, new_mashes, opts)
+ end
+
+ def []=(key, value)
+ if primary_mash.nil?
+ # This theoretically should never happen since node#force_default! setter methods will autovivify and
+ # node#rm methods do not end in #[]= operators.
+ raise TypeError, "No autovivification was specified initially on a method chain ending in assignment"
+ end
+ ret = delete(key)
+ primary_mash[key] = value
+ ret
+ end
+
+ # mash.element('foo', 'bar') is the same as mash['foo']['bar']
+ def element(key = nil, *subkeys)
+ return self if key.nil?
+ submash = self[key]
+ subkeys.empty? ? submash : submash.element(*subkeys)
+ end
+
+ def delete(key)
+ # the return value is a deep merge which is correct semantics when
+ # merging between attributes on the same level (this would be incorrect
+ # if passed both override and default attributes which would need hash_only
+ # merging).
+ ret = mashes.inject(Mash.new) do |merged, mash|
+ Chef::Mixin::DeepMerge.merge(merged, mash)
+ end
+ ret = Chef::Mixin::DeepMerge.merge(ret, primary_mash)
+ mashes.each do |mash|
+ mash.delete(key) if mash.respond_to?(:delete)
+ end
+ primary_mash.delete(key) if primary_mash.respond_to?(:delete)
+ ret[key]
+ end
+
+ private
+
+ def safe_evalute_key(mash, key)
+ if mash.respond_to?(:[])
+ if mash.respond_to?(:has_key?)
+ if mash.has_key?(key)
+ return mash[key] if mash[key].respond_to?(:[])
+ end
+ elsif !mash[key].nil?
+ return mash[key] if mash[key].respond_to?(:[])
+ end
+ end
+ return nil
+ end
+
+ end
+
end
end
diff --git a/spec/unit/node/attribute_spec.rb b/spec/unit/node/attribute_spec.rb
index 5325117d6c..3b3a81f3a6 100644
--- a/spec/unit/node/attribute_spec.rb
+++ b/spec/unit/node/attribute_spec.rb
@@ -285,7 +285,7 @@ describe Chef::Node::Attribute do
end
it "prefers 'forced default' over any other default" do
- @attributes.default!["default"] = "force default"
+ @attributes.force_default["default"] = "force default"
@attributes.role_default["default"] = "role default"
@attributes.env_default["default"] = "environment default"
@attributes["default"].should == "force default"
@@ -307,7 +307,7 @@ describe Chef::Node::Attribute do
end
it "prefers 'forced overrides' over role or cookbook overrides" do
- @attributes.override!["override"] = "force override"
+ @attributes.force_override["override"] = "force override"
@attributes.env_override["override"] = "environment override"
@attributes.role_override["override"] = "role override"
@attributes["override"].should == "force override"
@@ -939,7 +939,6 @@ describe Chef::Node::Attribute do
end
-
describe "values" do
before do
@attributes = Chef::Node::Attribute.new(
@@ -1185,4 +1184,3 @@ describe Chef::Node::Attribute do
end
end
-
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
index da7a67aec1..695da8d671 100644
--- a/spec/unit/node_spec.rb
+++ b/spec/unit/node_spec.rb
@@ -247,13 +247,6 @@ describe Chef::Node do
node.default.fuu.bahrr.baz = "qux"
node.fuu.bahrr.baz.should == "qux"
end
-
- it "accesses force defaults via default!" do
- node.default![:foo] = "wet bar"
- node.default[:foo] = "bar"
- node[:foo].should == "wet bar"
- end
-
end
describe "override attributes" do
@@ -292,13 +285,330 @@ describe Chef::Node do
node.override.fuu.bahrr.baz = "qux"
node.fuu.bahrr.baz.should == "qux"
end
+ end
+
+ describe "globally deleting attributes" do
+ context "with hash values" do
+ before do
+ node.role_default["mysql"]["server"]["port"] = 1234
+ node.normal["mysql"]["server"]["port"] = 2345
+ node.override["mysql"]["server"]["port"] = 3456
+ end
+
+ it "deletes all the values and returns the value with the highest precidence" do
+ expect( node.rm("mysql", "server", "port") ).to eql(3456)
+ expect( node["mysql"]["server"]["port"] ).to be_nil
+ expect( node["mysql"]["server"] ).to eql({})
+ end
+
+ it "deletes nested things correctly" do
+ node.default["mysql"]["client"]["client_setting"] = "foo"
+ expect( node.rm("mysql", "server") ).to eql( {"port" => 3456} )
+ expect( node["mysql"] ).to eql( { "client" => { "client_setting" => "foo" } } )
+ end
+
+ it "returns nil if the node attribute does not exist" do
+ expect( node.rm("no", "such", "thing") ).to be_nil
+ end
- it "sets force_overrides via override!" do
- node.override![:foo] = "wet bar"
- node.override[:foo] = "bar"
- node[:foo].should == "wet bar"
+ it "can delete the entire tree" do
+ expect( node.rm("mysql") ).to eql({"server"=>{"port"=>3456}})
+ end
end
+ context "when trying to delete through a thing that isn't an array-like or hash-like object" do
+ before do
+ node.default["mysql"] = true
+ end
+
+ it "returns nil when you're two levels deeper" do
+ expect( node.rm("mysql", "server", "port") ).to eql(nil)
+ end
+
+ it "returns nil when you're one level deeper" do
+ expect( node.rm("mysql", "server") ).to eql(nil)
+ end
+
+ it "correctly deletes at the top level" do
+ expect( node.rm("mysql") ).to eql(true)
+ end
+ end
+
+ context "with array indexes" do
+ before do
+ node.role_default["mysql"]["server"][0]["port"] = 1234
+ node.normal["mysql"]["server"][0]["port"] = 2345
+ node.override["mysql"]["server"][0]["port"] = 3456
+ node.override["mysql"]["server"][1]["port"] = 3456
+ end
+
+ it "deletes the array element" do
+ expect( node.rm("mysql", "server", 0, "port") ).to eql(3456)
+ expect( node["mysql"]["server"][0]["port"] ).to be_nil
+ expect( node["mysql"]["server"][1]["port"] ).to eql(3456)
+ end
+ end
+
+ context "with real arrays" do
+ before do
+ node.role_default["mysql"]["server"] = [ {
+ "port" => 1234,
+ } ]
+ node.normal["mysql"]["server"] = [ {
+ "port" => 2345,
+ } ]
+ node.override["mysql"]["server"] = [ {
+ "port" => 3456,
+ } ]
+ end
+
+ it "deletes the array element" do
+ expect( node.rm("mysql", "server", 0, "port") ).to eql(3456)
+ expect( node["mysql"]["server"][0]["port"] ).to be_nil
+ end
+
+ it "does not have a horrible error message when mistaking arrays for hashes" do
+ expect { node.rm("mysql", "server", "port") }.to raise_error(TypeError, "Wrong type in index of attribute (did you use a Hash index on an Array?)")
+ end
+ end
+ end
+
+ describe "granular deleting attributes" do
+ context "when only defaults exist" do
+ before do
+ node.role_default["mysql"]["server"]["port"] = 1234
+ node.default["mysql"]["server"]["port"] = 2345
+ node.force_default["mysql"]["server"]["port"] = 3456
+ end
+
+ it "returns the deleted values" do
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
+ end
+
+ it "returns nil for the combined attribues" do
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
+ expect( node["mysql"]["server"]["port"] ).to eql(nil)
+ end
+
+ it "returns an empty hash for the default attrs" do
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
+ # this auto-vivifies, should it?
+ expect( node.default_attrs["mysql"]["server"]["port"] ).to eql({})
+ end
+
+ it "returns an empty hash after the last key is deleted" do
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
+ expect( node["mysql"]["server"] ).to eql({})
+ end
+ end
+
+ context "when trying to delete through a thing that isn't an array-like or hash-like object" do
+ before do
+ node.default["mysql"] = true
+ end
+
+ it "returns nil when you're two levels deeper" do
+ expect( node.rm_default("mysql", "server", "port") ).to eql(nil)
+ end
+
+ it "returns nil when you're one level deeper" do
+ expect( node.rm_default("mysql", "server") ).to eql(nil)
+ end
+
+ it "correctly deletes at the top level" do
+ expect( node.rm_default("mysql") ).to eql(true)
+ end
+ end
+
+ context "when a higher precedence exists" do
+ before do
+ node.role_default["mysql"]["server"]["port"] = 1234
+ node.default["mysql"]["server"]["port"] = 2345
+ node.force_default["mysql"]["server"]["port"] = 3456
+
+ node.override["mysql"]["server"]["port"] = 9999
+ end
+
+ it "returns the deleted values" do
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
+ end
+
+ it "returns the higher precedence values after the delete" do
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
+ expect( node["mysql"]["server"]["port"] ).to eql(9999)
+ end
+
+ it "returns an empty has for the default attrs" do
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
+ # this auto-vivifies, should it?
+ expect( node.default_attrs["mysql"]["server"]["port"] ).to eql({})
+ end
+ end
+
+ context "when a lower precedence exists" do
+ before do
+ node.default["mysql"]["server"]["port"] = 2345
+ node.override["mysql"]["server"]["port"] = 9999
+ node.role_override["mysql"]["server"]["port"] = 9876
+ node.force_override["mysql"]["server"]["port"] = 6669
+ end
+
+ it "returns the deleted values" do
+ expect( node.rm_override("mysql", "server", "port") ).to eql(6669)
+ end
+
+ it "returns the lower precedence levels after the delete" do
+ expect( node.rm_override("mysql", "server", "port") ).to eql(6669)
+ expect( node["mysql"]["server"]["port"] ).to eql(2345)
+ end
+
+ it "returns an empty has for the override attrs" do
+ expect( node.rm_override("mysql", "server", "port") ).to eql(6669)
+ # this auto-vivifies, should it?
+ expect( node.override_attrs["mysql"]["server"]["port"] ).to eql({})
+ end
+ end
+
+ it "rm_default returns nil on deleting non-existent values" do
+ expect( node.rm_default("no", "such", "thing") ).to be_nil
+ end
+
+ it "rm_normal returns nil on deleting non-existent values" do
+ expect( node.rm_normal("no", "such", "thing") ).to be_nil
+ end
+
+ it "rm_override returns nil on deleting non-existent values" do
+ expect( node.rm_override("no", "such", "thing") ).to be_nil
+ end
+ end
+
+ describe "granular replacing attributes" do
+ it "removes everything at the level of the last key" do
+ node.default["mysql"]["server"]["port"] = 2345
+
+ node.default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql" }
+
+ expect( node["mysql"]["server"] ).to eql({ "data_dir" => "/my_raid_volume/lib/mysql" })
+ end
+
+ it "replaces a value at the cookbook sub-level of the atributes only" do
+ node.default["mysql"]["server"]["port"] = 2345
+ node.default["mysql"]["server"]["service_name"] = "fancypants-sql"
+ node.role_default["mysql"]["server"]["port"] = 1234
+ node.force_default["mysql"]["server"]["port"] = 3456
+
+ node.default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql" }
+
+ expect( node["mysql"]["server"]["port"] ).to eql(3456)
+ expect( node["mysql"]["server"]["service_name"] ).to be_nil
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
+ expect( node["mysql"]["server"] ).to eql({ "port" => 3456, "data_dir" => "/my_raid_volume/lib/mysql" })
+ end
+
+ it "higher precedence values aren't removed" do
+ node.role_default["mysql"]["server"]["port"] = 1234
+ node.default["mysql"]["server"]["port"] = 2345
+ node.force_default["mysql"]["server"]["port"] = 3456
+ node.override["mysql"]["server"]["service_name"] = "fancypants-sql"
+
+ node.default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql" }
+
+ expect( node["mysql"]["server"]["port"] ).to eql(3456)
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
+ expect( node["mysql"]["server"] ).to eql({ "service_name" => "fancypants-sql", "port" => 3456, "data_dir" => "/my_raid_volume/lib/mysql" })
+ end
+ end
+
+ describe "granular force replacing attributes" do
+ it "removes everything at the level of the last key" do
+ node.force_default["mysql"]["server"]["port"] = 2345
+
+ node.force_default!["mysql"]["server"] = {
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ }
+
+ expect( node["mysql"]["server"] ).to eql({
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ })
+ end
+
+ it "removes all values from the precedence level when setting" do
+ node.role_default["mysql"]["server"]["port"] = 1234
+ node.default["mysql"]["server"]["port"] = 2345
+ node.force_default["mysql"]["server"]["port"] = 3456
+
+ node.force_default!["mysql"]["server"] = {
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ }
+
+ expect( node["mysql"]["server"]["port"] ).to be_nil
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
+ expect( node["mysql"]["server"] ).to eql({
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ })
+ end
+
+ it "higher precedence levels are not removed" do
+ node.role_default["mysql"]["server"]["port"] = 1234
+ node.default["mysql"]["server"]["port"] = 2345
+ node.force_default["mysql"]["server"]["port"] = 3456
+ node.override["mysql"]["server"]["service_name"] = "fancypants-sql"
+
+ node.force_default!["mysql"]["server"] = {
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ }
+
+ expect( node["mysql"]["server"]["port"] ).to be_nil
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
+ expect( node["mysql"]["server"] ).to eql({
+ "service_name" => "fancypants-sql",
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ })
+ end
+
+ it "will autovivify" do
+ node.force_default!["mysql"]["server"] = {
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ }
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
+ end
+
+ it "lower precedence levels aren't removed" do
+ node.role_override["mysql"]["server"]["port"] = 1234
+ node.override["mysql"]["server"]["port"] = 2345
+ node.force_override["mysql"]["server"]["port"] = 3456
+ node.default["mysql"]["server"]["service_name"] = "fancypants-sql"
+
+ node.force_override!["mysql"]["server"] = {
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ }
+
+ expect( node["mysql"]["server"]["port"] ).to be_nil
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
+ expect( node["mysql"]["server"] ).to eql({
+ "service_name" => "fancypants-sql",
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ })
+ end
+
+ it "when overwriting a non-hash/array" do
+ node.override["mysql"] = false
+ node.force_override["mysql"] = true
+ node.force_override!["mysql"]["server"] = {
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ }
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
+ end
+
+ it "when overwriting an array with a hash" do
+ node.force_override["mysql"][0] = true
+ node.force_override!["mysql"]["server"] = {
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ }
+ expect( node["mysql"]["server"] ).to eql({
+ "data_dir" => "/my_raid_volume/lib/mysql",
+ })
+ end
end
it "should raise an ArgumentError if you ask for an attribute that doesn't exist via method_missing" do
@@ -536,7 +846,6 @@ describe Chef::Node do
@expansion.default_attrs.replace({:default => "from role", :d_role => "role only"})
@expansion.override_attrs.replace({:override => "from role", :o_role => "role only"})
-
@environment = Chef::Environment.new
@environment.default_attributes = {:default => "from env", :d_env => "env only" }
@environment.override_attributes = {:override => "from env", :o_env => "env only"}
@@ -753,7 +1062,6 @@ describe Chef::Node do
node_for_json["default"]["env default"].should == "env default"
end
-
it "should deserialize itself from json", :json => true do
node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA))
json = Chef::JSONCompat.to_json(node)