diff options
-rw-r--r-- | lib/chef/mixin/properties.rb | 35 | ||||
-rw-r--r-- | spec/unit/property_spec.rb | 93 |
2 files changed, 128 insertions, 0 deletions
diff --git a/lib/chef/mixin/properties.rb b/lib/chef/mixin/properties.rb index e0b32d2b9d..3c61c88f55 100644 --- a/lib/chef/mixin/properties.rb +++ b/lib/chef/mixin/properties.rb @@ -315,6 +315,41 @@ class Chef raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property property.description end + + # Copy properties from another property object (resource) + # + # By default this copies all properties other than the name property (that is required to create the + # destination object so it has already been done in advance and this way we do not clobber the name + # that was set in that constructor). By default it copies everything, optional arguments can be use + # to only select a subset. Or specific excludes can be set (and the default exclude on the name property + # can also be overridden). Exclude has priority over include, although the caller is likely better + # off doing the set arithmetic themselves for explicitness. + # + # action :doit do + # # use it inside a block + # file "/etc/whatever.xyz" do + # copy_properties_from new_resource + # end + # + # # or directly call it + # r = declare_resource(:file, "etc/whatever.xyz") + # r.copy_properties_from(new_resource, :owner, :group, :mode) + # end + # + # @param other [Object] the other object (Chef::Resource) which implements the properties API + # @param includes [Array<Symbol>] splat-args list of symbols of the properties to copy. + # @param exclude [Array<Symbol>] list of symbosl of the properties to exclude. + # @return the self object the properties were copied to for method chaining + # + def copy_properties_from(other, *includes, exclude: [ :name ]) + includes = other.class.properties.keys if includes.empty? + includes -= exclude + includes.each do |p| + send(p, other.send(p)) if other.property_is_set?(p) + end + self + end + end end end diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 793bb3248f..56e44fd1d1 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -1228,4 +1228,97 @@ describe "Chef::Resource.property" do end end end + + context "#copy_properties_from" do + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:node) { Chef::Node.new } + let(:run_context) { Chef::RunContext.new(node, {}, events) } + + let(:thing_one_class) do + Class.new(Chef::Resource) do + resource_name :thing_one + provides :thing_two + property :foo, String + property :bar, String + end + end + + let(:thing_two_class) do + Class.new(Chef::Resource) do + resource_name :thing_two + provides :thing_two + property :foo, String + property :bar, String + end + end + + let(:thing_three_class) do + Class.new(Chef::Resource) do + resource_name :thing_three + provides :thing_three + property :foo, String + property :bar, String + property :baz, String + end + end + + let(:thing_one_resource) do + thing_one_class.new("name_one", run_context) + end + + let(:thing_two_resource) do + thing_two_class.new("name_two", run_context) + end + + let(:thing_three_resource) do + thing_three_class.new("name_three", run_context) + end + + it "copies foo and bar" do + thing_one_resource.foo "foo" + thing_one_resource.bar "bar" + thing_two_resource.copy_properties_from thing_one_resource + expect(thing_two_resource.name).to eql("name_two") + expect(thing_two_resource.foo).to eql("foo") + expect(thing_two_resource.bar).to eql("bar") + end + + it "copies only foo when it is only included" do + thing_one_resource.foo "foo" + thing_one_resource.bar "bar" + thing_two_resource.copy_properties_from(thing_one_resource, :foo) + expect(thing_two_resource.name).to eql("name_two") + expect(thing_two_resource.foo).to eql("foo") + expect(thing_two_resource.bar).to eql(nil) + end + + it "copies foo and name when bar is excluded" do + thing_one_resource.foo "foo" + thing_one_resource.bar "bar" + thing_two_resource.copy_properties_from(thing_one_resource, exclude: [ :bar ]) + expect(thing_two_resource.name).to eql("name_one") + expect(thing_two_resource.foo).to eql("foo") + expect(thing_two_resource.bar).to eql(nil) + end + + it "copies only foo when bar and name are excluded" do + thing_one_resource.foo "foo" + thing_one_resource.bar "bar" + thing_two_resource.copy_properties_from(thing_one_resource, exclude: [ :name, :bar ]) + expect(thing_two_resource.name).to eql("name_two") + expect(thing_two_resource.foo).to eql("foo") + expect(thing_two_resource.bar).to eql(nil) + end + + it "blows up if the target resource does not implement a set property" do + thing_three_resource.baz "baz" + expect { thing_two_resource.copy_properties_from(thing_three_resource) }.to raise_error(NoMethodError) + end + + it "does not blow up if blows up if the target resource does not implement a set propery" do + thing_three_resource.foo "foo" + thing_three_resource.bar "bar" + thing_two_resource.copy_properties_from(thing_three_resource) + end + end end |