summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2019-04-08 19:04:38 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2019-04-08 19:04:38 -0700
commitfa282d1e39d07628549967f92a090739ba4bb065 (patch)
treeb84d12e4c435a9934914b37713198be047cad9e2
parent6837d6bda9fbfdcab0c2d26f3313ec106137e203 (diff)
downloadchef-lcg/copy-properties-from.tar.gz
Implement Chef::Resource#copy_properties_fromlcg/copy-properties-from
Kept in the properties mixin since it is tightly coupled. closes #7046 Signed-off-by: Lamont Granquist <lamont@scriptkiddie.org>
-rw-r--r--lib/chef/mixin/properties.rb35
-rw-r--r--spec/unit/property_spec.rb93
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