summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <john@johnkeiser.com>2015-07-21 11:37:28 -0600
committerJohn Keiser <john@johnkeiser.com>2015-07-27 09:38:25 -0600
commit5e1567fdeb2b0693000fde7d702c106dc51b335c (patch)
treec0281be36dfc3474fddcfbba0c096633e9a35b48
parentca8a8bdb000c7cb82588cc56ddfb32464babf3c3 (diff)
downloadchef-5e1567fdeb2b0693000fde7d702c106dc51b335c.tar.gz
Add Resource.load_current_value
-rw-r--r--lib/chef/resource.rb50
-rw-r--r--spec/integration/recipes/resource_load_spec.rb178
2 files changed, 227 insertions, 1 deletions
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index dca0033409..cdb351f643 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -1375,6 +1375,34 @@ class Chef
end
#
+ # Define a method to load up this resource's properties with the current
+ # actual values.
+ #
+ # @param load_block The block to load. Will be run in the context of a newly
+ # created resource with its identity values filled in.
+ #
+ def self.load_current_value(&load_block)
+ define_method(:load_current_value!, &load_block)
+ end
+
+ #
+ # Get the current actual value of this resource.
+ #
+ # This does not cache--a new value will be returned each time.
+ #
+ # @return A new copy of the resource, with values filled in from the actual
+ # current value.
+ #
+ def current_resource
+ provider = provider_for_action(Array(action).first)
+ if provider.whyrun_mode? && !provider.whyrun_supported?
+ raise "Cannot retrieve #{self.class.current_resource} in why-run mode: #{provider} does not support why-run"
+ end
+ provider.load_current_resource
+ provider.current_resource
+ end
+
+ #
# The action provider class is an automatic `Provider` created to handle
# actions declared by `action :x do ... end`.
#
@@ -1415,7 +1443,27 @@ class Chef
include_resource_dsl true
define_singleton_method(:to_s) { "#{resource_class} action provider" }
define_singleton_method(:inspect) { to_s }
- define_method(:load_current_resource) {}
+ define_method(:load_current_resource) do
+ if new_resource.respond_to?(:load_current_value!)
+ current_resource = new_resource.dup
+
+ # We clear desired state in the copy, because it is supposed to be actual state.
+ # We keep identity properties and non-desired-state, which are assumed to be
+ # "control" values like `recurse: true`
+ current_resource.class.properties.each do |name,property|
+ if property.desired_state? && !property.identity? && !property.name_property?
+ property.reset(current_resource)
+ end
+ end
+
+ if current_resource.method(:load_current_value!).arity > 0
+ current_resource.load_current_value!(new_resource)
+ else
+ current_resource.load_current_value!
+ end
+ end
+ @current_resource = current_resource
+ end
end
end
diff --git a/spec/integration/recipes/resource_load_spec.rb b/spec/integration/recipes/resource_load_spec.rb
new file mode 100644
index 0000000000..f91039abb4
--- /dev/null
+++ b/spec/integration/recipes/resource_load_spec.rb
@@ -0,0 +1,178 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Resource.load_current_value" do
+ include IntegrationSupport
+
+ module Namer
+ extend self
+ attr_accessor :current_index
+ def incrementing_value
+ @incrementing_value += 1
+ @incrementing_value
+ end
+ attr_writer :incrementing_value
+ end
+
+ before(:all) { Namer.current_index = 1 }
+ before { Namer.current_index += 1 }
+ before { Namer.incrementing_value = 0 }
+
+ let(:resource_name) { :"load_current_value_dsl#{Namer.current_index}" }
+ let(:resource_class) {
+ result = Class.new(Chef::Resource) do
+ def self.to_s; resource_name; end
+ def self.inspect; resource_name.inspect; end
+ property :x, default: lazy { "default #{Namer.incrementing_value}" }
+ action :create do
+ end
+ default_action :nothing
+ end
+ result.resource_name resource_name
+ result
+ }
+
+ # Pull on resource_class to initialize it
+ before { resource_class }
+
+ context "with a resource with load_current_value" do
+ before :each do
+ resource_class.load_current_value do
+ x "loaded #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name,p| name }.
+ select { |name,p| p.is_set?(self) }.
+ map { |name,p| "#{name}=#{p.get(self)}" }.
+ join(", ") })"
+ end
+ end
+
+ let(:resource) do
+ e = self
+ r = nil
+ converge {
+ r = public_send(e.resource_name, 'blah') do
+ x 'desired'
+ end
+ }
+ r
+ end
+
+ it "current_resource is passed name but not x" do
+ expect(resource.current_resource.x).to eq 'loaded 1 (name=blah)'
+ end
+
+ it "resource.current_resource returns a different resource" do
+ expect(resource.current_resource.x).to eq 'loaded 1 (name=blah)'
+ expect(resource.x).to eq 'desired'
+ end
+
+ it "resource.current_resource constructs the resource anew each time" do
+ expect(resource.current_resource.x).to eq 'loaded 1 (name=blah)'
+ expect(resource.current_resource.x).to eq 'loaded 2 (name=blah)'
+ end
+
+ context "and identity: :i and :d with desired_state: false" do
+ before {
+ resource_class.class_eval do
+ property :i, identity: true
+ property :d, desired_state: false
+ end
+ }
+
+ before {
+ resource.i 'desired_i'
+ resource.d 'desired_d'
+ }
+
+ it "i, name and d are passed to load_current_value, but not x" do
+ expect(resource.current_resource.x).to eq 'loaded 1 (d=desired_d, i=desired_i, name=blah)'
+ end
+ end
+
+ context "and name_property: :i and :d with desired_state: false" do
+ before {
+ resource_class.class_eval do
+ property :i, name_property: true
+ property :d, desired_state: false
+ end
+ }
+
+ before {
+ resource.i 'desired_i'
+ resource.d 'desired_d'
+ }
+
+ it "i, name and d are passed to load_current_value, but not x" do
+ expect(resource.current_resource.x).to eq 'loaded 1 (d=desired_d, i=desired_i, name=blah)'
+ end
+ end
+
+ let (:subresource_name) {
+ :"load_current_value_subresource_dsl#{Namer.current_index}"
+ }
+ let (:subresource_class) {
+ r = Class.new(resource_class) do
+ property :y, default: lazy { "default_y #{Namer.incrementing_value}" }
+ end
+ r.resource_name subresource_name
+ r
+ }
+
+ # Pull on subresource_class to initialize it
+ before { subresource_class }
+
+ let(:subresource) do
+ e = self
+ r = nil
+ converge {
+ r = public_send(e.subresource_name, 'blah') do
+ x 'desired'
+ end
+ }
+ r
+ end
+
+ context "and a child resource class with no load_current_value" do
+ it "the parent load_current_value is used" do
+ expect(subresource.current_resource.x).to eq 'loaded 1 (name=blah)'
+ end
+ it "load_current_value yields a copy of the child class" do
+ expect(subresource.current_resource).to be_kind_of(subresource_class)
+ end
+ end
+
+ context "And a child resource class with load_current_value" do
+ before {
+ subresource_class.load_current_value do
+ y "loaded_y #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name,p| name }.
+ select { |name,p| p.is_set?(self) }.
+ map { |name,p| "#{name}=#{p.get(self)}" }.
+ join(", ") })"
+ end
+ }
+
+ it "the overridden load_current_value is used" do
+ current_resource = subresource.current_resource
+ expect(current_resource.x).to eq 'default 2'
+ expect(current_resource.y).to eq 'loaded_y 1 (name=blah)'
+ end
+ end
+
+ context "and a child resource class with load_current_value calling super()" do
+ before {
+ subresource_class.load_current_value do
+ super()
+ y "loaded_y #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name,p| name }.
+ select { |name,p| p.is_set?(self) }.
+ map { |name,p| "#{name}=#{p.get(self)}" }.
+ join(", ") })"
+ end
+ }
+
+ it "the original load_current_value is called as well as the child one" do
+ current_resource = subresource.current_resource
+ expect(current_resource.x).to eq 'loaded 1 (name=blah)'
+ expect(current_resource.y).to eq 'loaded_y 2 (name=blah, x=loaded 1 (name=blah))'
+ end
+ end
+ end
+
+end