diff options
author | John Keiser <john@johnkeiser.com> | 2015-10-15 12:56:41 -0700 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2015-12-09 10:26:08 -0800 |
commit | 90cddbbfcf5d7524485c41b6f1fccfe6756a7da4 (patch) | |
tree | d9bd4eaed6c329ab591c7698b7c56a5cf915bebf | |
parent | 2d915fee09e402bdba8928d28fdc12fa6c37f4c9 (diff) | |
download | chef-90cddbbfcf5d7524485c41b6f1fccfe6756a7da4.tar.gz |
Add immediately_before notification
-rw-r--r-- | lib/chef/resource.rb | 20 | ||||
-rw-r--r-- | lib/chef/run_context.rb | 43 | ||||
-rw-r--r-- | lib/chef/runner.rb | 23 | ||||
-rw-r--r-- | spec/functional/notifications_spec.rb | 78 |
4 files changed, 159 insertions, 5 deletions
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 43fc1f997d..634d00feef 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -209,6 +209,8 @@ class Chef # actions have been run. This is the default. # - `immediate`, `immediately`: Will run the action on the other resource # immediately (before any other action is run). + # - `immediately_before`: Will run the action on the other resource + # immediately *before* the action is actually run. # # @example Resource by string # file '/foo.txt' do @@ -251,9 +253,11 @@ class Chef notifies_delayed(action, resource) when 'immediate', 'immediately' notifies_immediately(action, resource) + when 'immediately_before' + notifies_immediately_before(action, resource) else raise ArgumentError, "invalid timing: #{timing} for notifies(#{action}, #{resources.inspect}, #{timing}) resource #{self} "\ - "Valid timings are: :delayed, :immediate, :immediately" + "Valid timings are: :delayed, :immediate, :immediately, :immediately_before" end end @@ -277,6 +281,8 @@ class Chef # actions have been run. This is the default. # - `immediate`, `immediately`: The action will run immediately following # the other resource being updated. + # - `immediately_before`: The action will run immediately before the + # other resource is updated. # # @example Resources by string # file '/foo.txt' do @@ -1238,6 +1244,9 @@ class Chef # resolve_resource_reference on each in turn, causing them to # resolve lazy/forward references. def resolve_notification_references + run_context.immediately_before_notifications(self).each { |n| + n.resolve_resource_reference(run_context.resource_collection) + } run_context.immediate_notifications(self).each { |n| n.resolve_resource_reference(run_context.resource_collection) } @@ -1247,6 +1256,11 @@ class Chef end # Helper for #notifies + def notifies_immediately_before(action, resource_spec) + run_context.notifies_immediately_before(Notification.new(resource_spec, action, self)) + end + + # Helper for #notifies def notifies_immediately(action, resource_spec) run_context.notifies_immediately(Notification.new(resource_spec, action, self)) end @@ -1341,6 +1355,10 @@ class Chef "#{declared_type}[#{@name}]" end + def immediately_before_notifications + run_context.immediately_before_notifications(self) + end + def immediate_notifications run_context.immediate_notifications(self) end diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index f7ab88f7e0..b725857bc5 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -104,6 +104,15 @@ class Chef # # + # A Hash containing the immediately_before notifications triggered by resources + # during the converge phase of the chef run. + # + # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from + # <notifying resource name> => <list of notifications it sent> + # + attr_reader :immediately_before_notification_collection + + # # A Hash containing the immediate notifications triggered by resources # during the converge phase of the chef run. # @@ -164,11 +173,26 @@ class Chef def initialize_child_state @audits = {} @resource_collection = Chef::ResourceCollection.new + @immediately_before_notification_collection = Hash.new {|h,k| h[k] = []} @immediate_notification_collection = Hash.new {|h,k| h[k] = []} @delayed_notification_collection = Hash.new {|h,k| h[k] = []} end # + # Adds an immediately_before notification to the +immediately_before_notification_collection+. + # + # @param [Chef::Resource::Notification] The notification to add. + # + def notifies_immediately_before(notification) + nr = notification.notifying_resource + if nr.instance_of?(Chef::Resource) + immediately_before_notification_collection[nr.name] << notification + else + immediately_before_notification_collection[nr.declared_key] << notification + end + end + + # # Adds an immediate notification to the +immediate_notification_collection+. # # @param [Chef::Resource::Notification] The notification to add. @@ -197,6 +221,22 @@ class Chef end # + # Get the list of immediately_before notifications sent by the given resource. + # + # TODO seriously, this is actually wrong. resource.name is not unique, + # you need the type as well. + # + # @return [Array[Notification]] + # + def immediately_before_notifications(resource) + if resource.instance_of?(Chef::Resource) + return immediately_before_notification_collection[resource.name] + else + return immediately_before_notification_collection[resource.declared_key] + end + end + + # # Get the list of immediate notifications sent by the given resource. # # TODO seriously, this is actually wrong. resource.name is not unique, @@ -608,10 +648,13 @@ ERROR_MESSAGE immediate_notification_collection immediate_notification_collection= immediate_notifications + immediately_before_notification_collection + immediately_before_notifications include_recipe initialize_child_state load_recipe load_recipe_file + notifies_immediately_before notifies_immediately notifies_delayed parent_run_context diff --git a/lib/chef/runner.rb b/lib/chef/runner.rb index 6125fe59e1..50554dcdbc 100644 --- a/lib/chef/runner.rb +++ b/lib/chef/runner.rb @@ -46,6 +46,29 @@ class Chef # Determine the appropriate provider for the given resource, then # execute it. def run_action(resource, action, notification_type=nil, notifying_resource=nil) + + # If there are any immediately_before notifications, why-run the resource + # and notify anyone who needs notifying + if !run_context.immediately_before_notifications(resource).empty? + whyrun_before = Chef::Config[:why_run] + begin + Chef::Config[:why_run] = true + Chef::Log.info("#{resource} running why-run #{action} action to support immediately_before action") + resource.run_action(action, notification_type, notifying_resource) + ensure + Chef::Config[:why_run] = whyrun_before + end + + if resource.updated_by_last_action? + run_context.immediately_before_notifications(resource).each do |notification| + Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (immediately_before)") + run_action(notification.resource, notification.action, :immediately_before, resource) + end + end + + end + + # Actually run the action for realsies resource.run_action(action, notification_type, notifying_resource) # Execute any immediate and queue up any delayed notifications diff --git a/spec/functional/notifications_spec.rb b/spec/functional/notifications_spec.rb index a02fdffe5e..42702a8bb2 100644 --- a/spec/functional/notifications_spec.rb +++ b/spec/functional/notifications_spec.rb @@ -74,6 +74,76 @@ describe "Notifications" do runner.converge end + it "should notify from one resource to another immediately_before" do + log_resource = recipe.declare_resource(:log, "log") do + message "This is a log message" + action :write + notifies :install, "package[vim]", :immediately_before + end + update_action(log_resource, 2) + + package_resource = recipe.declare_resource(:package, "vim") do + action :nothing + end + + actions = [] + [ log_resource, package_resource ].each do |resource| + allow(resource).to receive(:run_action).and_wrap_original do |m, action, notification_type, notifying_resource| + actions << { resource: resource.to_s, action: action } + actions[-1][:why_run] = Chef::Config[:why_run] if Chef::Config[:why_run] + actions[-1][:notification_type] = notification_type if notification_type + actions[-1][:notifying_resource] = notifying_resource.to_s if notifying_resource + m.call(action, notification_type, notifying_resource) + end + end + + runner.converge + + expect(actions).to eq [ + # First it runs why-run to check if the resource would update + { resource: log_resource.to_s, action: :write, why_run: true }, + # Then it runs the immediately_before action + { resource: package_resource.to_s, action: :install, notification_type: :immediately_before, notifying_resource: log_resource.to_s }, + # Then it runs the actual action + { resource: log_resource.to_s, action: :write }, + { resource: package_resource.to_s, action: :nothing } + ] + end + + it "should not notify from one resource to another immediately_before if the resource is not updated" do + log_resource = recipe.declare_resource(:log, "log") do + message "This is a log message" + action :write + notifies :install, "package[vim]", :immediately_before + end + + package_resource = recipe.declare_resource(:package, "vim") do + action :nothing + end + + actions = [] + [ log_resource, package_resource ].each do |resource| + allow(resource).to receive(:run_action).and_wrap_original do |m, action, notification_type, notifying_resource| + actions << { resource: resource.to_s, action: action } + actions[-1][:why_run] = Chef::Config[:why_run] if Chef::Config[:why_run] + actions[-1][:notification_type] = notification_type if notification_type + actions[-1][:notifying_resource] = notifying_resource.to_s if notifying_resource + m.call(action, notification_type, notifying_resource) + end + end + + runner.converge + + expect(actions).to eq [ + # First it runs why-run to check if the resource would update + { resource: log_resource.to_s, action: :write, why_run: true }, + # Then it does NOT run the immediately_before action + # Then it runs the actual action + { resource: log_resource.to_s, action: :write }, + { resource: package_resource.to_s, action: :nothing } + ] + end + it "should notify from one resource to another delayed" do log_resource = recipe.declare_resource(:log, "log") do message "This is a log message" @@ -94,7 +164,7 @@ describe "Notifications" do runner.converge end - + describe "when one resource is defined lazily" do it "subscribes to a resource defined in a ruby block" do @@ -158,10 +228,10 @@ describe "Notifications" do end # Mocks having the provider run successfully and update the resource - def update_action(resource) + def update_action(resource, times=1) p = Chef::Provider.new(resource, run_context) - expect(resource).to receive(:provider_for_action).and_return(p) - expect(p).to receive(:run_action) { + expect(resource).to receive(:provider_for_action).exactly(times).times.and_return(p) + expect(p).to receive(:run_action).exactly(times).times { resource.updated_by_last_action(true) } end |