summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <john@johnkeiser.com>2015-10-15 12:56:41 -0700
committerJohn Keiser <john@johnkeiser.com>2015-12-09 10:26:08 -0800
commit90cddbbfcf5d7524485c41b6f1fccfe6756a7da4 (patch)
treed9bd4eaed6c329ab591c7698b7c56a5cf915bebf
parent2d915fee09e402bdba8928d28fdc12fa6c37f4c9 (diff)
downloadchef-90cddbbfcf5d7524485c41b6f1fccfe6756a7da4.tar.gz
Add immediately_before notification
-rw-r--r--lib/chef/resource.rb20
-rw-r--r--lib/chef/run_context.rb43
-rw-r--r--lib/chef/runner.rb23
-rw-r--r--spec/functional/notifications_spec.rb78
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