summaryrefslogtreecommitdiff
path: root/lib/chef
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2019-06-17 21:07:08 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2019-08-12 11:29:16 -0700
commit35a63ddc192e50c45f6e94a3b270ed0e75c93668 (patch)
treefed93952978c3b75febae5d90f80e1f0cd5fcc8b /lib/chef
parent84da5f7a45d7ccba250d160626e6da8762a7f222 (diff)
downloadchef-35a63ddc192e50c45f6e94a3b270ed0e75c93668.tar.gz
Add unified_mode switch for resources
This is inspired by "use_inline_resources". Setting `unified_mode false` in a resource would be the existing behavior with separate compile/converge phases. Setting `unified_mode true` in a resource will eliminate the converge phase. Reverse notifications and delayed notifications will still fire. The resource action will behave like all resources are executing at compile time. As a aside, notifications have never worked for resources firing at compile time. This implementation gets that behavior correct so that notifications will work. Of course forward immediate notifications to resources not yet declared will not be possible. Setting `resource_unified_mode_default true` in `Chef::Config` would turn off the split compile/converge mode for every custom resource. NOTE: This does not affect recipe mode at all. Signed-off-by: Lamont Granquist <lamont@scriptkiddie.org>
Diffstat (limited to 'lib/chef')
-rw-r--r--lib/chef/exceptions.rb12
-rw-r--r--lib/chef/provider.rb6
-rw-r--r--lib/chef/resource.rb48
-rw-r--r--lib/chef/resource/resource_notification.rb30
-rw-r--r--lib/chef/resource_collection.rb6
-rw-r--r--lib/chef/run_context.rb78
-rw-r--r--lib/chef/runner.rb63
7 files changed, 172 insertions, 71 deletions
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index 7687e7d89c..40d20cc3ac 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -509,5 +509,17 @@ class Chef
super "Conflicting requirements for gem '#{gem_name}': Both #{value1.inspect} and #{value2.inspect} given for option #{option.inspect}"
end
end
+
+ class UnifiedModeImmediateSubscriptionEarlierResource < RuntimeError
+ def initialize(notification)
+ super "immediate subscription from #{notification.resource} resource cannot be setup to #{notification.notifying_resource} resource, which has already fired while in unified mode"
+ end
+ end
+
+ class UnifiedModeBeforeSubscriptionEarlierResource < RuntimeError
+ def initialize(notification)
+ super "before subscription from #{notification.resource} resource cannot be setup to #{notification.notifying_resource} resource, which has already fired while in unified mode"
+ end
+ end
end
end
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 6d1985bbbb..fb5697fd0c 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2008-2016, 2009-2018, Chef Software Inc.
+# Copyright:: Copyright 2008-2016, 2009-2019, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -238,8 +238,10 @@ class Chef
def compile_and_converge_action(&block)
old_run_context = run_context
@run_context = run_context.create_child
+ @run_context.resource_collection.unified_mode = new_resource.class.unified_mode
+ runner = Chef::Runner.new(@run_context)
return_value = instance_eval(&block)
- Chef::Runner.new(run_context).converge
+ runner.converge
return_value
ensure
if run_context.resource_collection.any?(&:updated?)
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 7f0895d6c9..a413e3e8d4 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -182,7 +182,7 @@ class Chef
{ action: { kind_of: Symbol, equal_to: allowed_actions } }
)
# the resource effectively sends a delayed notification to itself
- run_context.add_delayed_action(Notification.new(self, action, self))
+ run_context.add_delayed_action(Notification.new(self, action, self, run_context.unified_mode))
end
end
@@ -453,7 +453,6 @@ class Chef
#
attr_reader :elapsed_time
- #
# @return [Boolean] If the resource was executed by the runner
#
attr_accessor :executed_by_runner
@@ -985,6 +984,16 @@ class Chef
resource_name automatic_name
end
+ # If the resource's action should run in separated compile/converge mode.
+ #
+ # @param flag [Boolean] value to set unified_mode to
+ # @return [Boolean] unified_mode value
+ def self.unified_mode(flag = nil)
+ @unified_mode = Chef::Config[:resource_unified_mode_default] if @unified_mode.nil?
+ @unified_mode = flag unless flag.nil?
+ !!@unified_mode
+ end
+
#
# The list of allowed actions for the resource.
#
@@ -1038,7 +1047,6 @@ class Chef
default_action action_name
end
- #
# Define an action on this resource.
#
# The action is defined as a *recipe* block that will be compiled and then
@@ -1076,7 +1084,6 @@ class Chef
default_action action if Array(default_action) == [:nothing]
end
- #
# Define a method to load up this resource's properties with the current
# actual values.
#
@@ -1087,7 +1094,6 @@ class Chef
define_method(:load_current_value!, &load_block)
end
- #
# Call this in `load_current_value` to indicate that the value does not
# exist and that `current_resource` should therefore be `nil`.
#
@@ -1097,7 +1103,6 @@ class Chef
raise Chef::Exceptions::CurrentValueDoesNotExist
end
- #
# Get the current actual value of this resource.
#
# This does not cache--a new value will be returned each time.
@@ -1154,7 +1159,6 @@ class Chef
end
end
- #
# Ensure the action class actually gets created. This is called
# when the user does `action :x do ... end`.
#
@@ -1211,22 +1215,27 @@ class Chef
# @return [Chef::RunContext] The run context for this Resource. This is
# where the context for the current Chef run is stored, including the node
# and the resource collection.
+ #
attr_accessor :run_context
# @return [Mixlib::Log::Child] The logger for this resources. This is a child
# of the run context's logger, if one exists.
+ #
attr_reader :logger
# @return [String] The cookbook this resource was declared in.
+ #
attr_accessor :cookbook_name
# @return [String] The recipe this resource was declared in.
+ #
attr_accessor :recipe_name
# @return [Chef::Provider] The provider this resource was declared in (if
# it was declared in an LWRP). When you call methods that do not exist
# on this Resource, Chef will try to call the method on the provider
# as well before giving up.
+ #
attr_accessor :enclosing_provider
# @return [String] The source line where this resource was declared.
@@ -1234,6 +1243,7 @@ class Chef
# of these formats:
# /some/path/to/file.rb:80:in `wombat_tears'
# C:/some/path/to/file.rb:80 in 1`wombat_tears'
+ #
attr_accessor :source_line
# @return [String] The actual name that was used to create this resource.
@@ -1242,37 +1252,40 @@ class Chef
# user will expect to see the thing they wrote, not the type that was
# returned. May be `nil`, in which case callers should read #resource_name.
# See #declared_key.
+ #
attr_accessor :declared_type
- #
# Iterates over all immediate and delayed notifications, calling
# resolve_resource_reference on each in turn, causing them to
# resolve lazy/forward references.
- def resolve_notification_references
+ #
+ def resolve_notification_references(always_raise = false)
run_context.before_notifications(self).each do |n|
- n.resolve_resource_reference(run_context.resource_collection)
+ n.resolve_resource_reference(run_context.resource_collection, true)
end
+
run_context.immediate_notifications(self).each do |n|
- n.resolve_resource_reference(run_context.resource_collection)
+ n.resolve_resource_reference(run_context.resource_collection, always_raise)
end
+
run_context.delayed_notifications(self).each do |n|
- n.resolve_resource_reference(run_context.resource_collection)
+ n.resolve_resource_reference(run_context.resource_collection, always_raise)
end
end
# Helper for #notifies
def notifies_before(action, resource_spec)
- run_context.notifies_before(Notification.new(resource_spec, action, self))
+ run_context.notifies_before(Notification.new(resource_spec, action, self, run_context.unified_mode))
end
# Helper for #notifies
def notifies_immediately(action, resource_spec)
- run_context.notifies_immediately(Notification.new(resource_spec, action, self))
+ run_context.notifies_immediately(Notification.new(resource_spec, action, self, run_context.unified_mode))
end
# Helper for #notifies
def notifies_delayed(action, resource_spec)
- run_context.notifies_delayed(Notification.new(resource_spec, action, self))
+ run_context.notifies_delayed(Notification.new(resource_spec, action, self, run_context.unified_mode))
end
class << self
@@ -1321,7 +1334,6 @@ class Chef
end
end
- #
# This API can be used for backcompat to do:
#
# chef_version_for_provides "< 14.0" if defined?(:chef_version_for_provides)
@@ -1343,7 +1355,6 @@ class Chef
@chef_version_for_provides = constraint
end
- #
# Mark this resource as providing particular DSL.
#
# Resources have an automatic DSL based on their resource_name, equivalent to
@@ -1472,7 +1483,6 @@ class Chef
@default_description
end
- #
# The cookbook in which this Resource was defined (if any).
#
# @return Chef::CookbookVersion The cookbook in which this Resource was defined.
@@ -1498,7 +1508,6 @@ class Chef
provider
end
- #
# Preface an exception message with generic Resource information.
#
# @param e [StandardError] An exception with `e.message`
@@ -1554,7 +1563,6 @@ class Chef
klass
end
- #
# Returns the class with the given resource_name.
#
# NOTE: Chef::Resource.resource_matching_short_name(:package) returns
diff --git a/lib/chef/resource/resource_notification.rb b/lib/chef/resource/resource_notification.rb
index 7e93fff433..d3b9856332 100644
--- a/lib/chef/resource/resource_notification.rb
+++ b/lib/chef/resource/resource_notification.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright 2014-2019, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,12 +26,13 @@ class Chef
# @attr [Resource] notifying_resource the Chef resource performing the notification
class Notification
- attr_accessor :resource, :action, :notifying_resource
+ attr_accessor :resource, :action, :notifying_resource, :unified_mode
- def initialize(resource, action, notifying_resource)
+ def initialize(resource, action, notifying_resource, unified_mode = false)
@resource = resource
@action = action&.to_sym
@notifying_resource = notifying_resource
+ @unified_mode = unified_mode
end
# Is the current notification a duplicate of another notification
@@ -52,11 +53,11 @@ class Chef
# @param [ResourceCollection] resource_collection
#
# @return [void]
- def resolve_resource_reference(resource_collection)
+ def resolve_resource_reference(resource_collection, always_raise = false)
return resource if resource.is_a?(Chef::Resource) && notifying_resource.is_a?(Chef::Resource)
unless resource.is_a?(Chef::Resource)
- fix_resource_reference(resource_collection)
+ fix_resource_reference(resource_collection, always_raise)
end
unless notifying_resource.is_a?(Chef::Resource)
@@ -69,7 +70,7 @@ class Chef
# @param [ResourceCollection] resource_collection
#
# @return [void]
- def fix_resource_reference(resource_collection)
+ def fix_resource_reference(resource_collection, always_raise = false)
matching_resource = resource_collection.find(resource)
if Array(matching_resource).size > 1
msg = "Notification #{self} from #{notifying_resource} was created with a reference to multiple resources, "\
@@ -79,13 +80,16 @@ class Chef
self.resource = matching_resource
rescue Chef::Exceptions::ResourceNotFound => e
- err = Chef::Exceptions::ResourceNotFound.new(<<~FAIL)
- resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \
- but #{resource} cannot be found in the resource collection. #{notifying_resource} is defined in \
- #{notifying_resource.source_line}
- FAIL
- err.set_backtrace(e.backtrace)
- raise err
+ # in unified mode we allow lazy notifications to resources not yet declared
+ if !unified_mode || always_raise
+ err = Chef::Exceptions::ResourceNotFound.new(<<~FAIL)
+ resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \
+ but #{resource} cannot be found in the resource collection. #{notifying_resource} is defined in \
+ #{notifying_resource.source_line}
+ FAIL
+ err.set_backtrace(e.backtrace)
+ raise err
+ end
rescue Chef::Exceptions::InvalidResourceSpecification => e
err = Chef::Exceptions::InvalidResourceSpecification.new(<<~F)
Resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \
diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb
index 3b6ff4297e..da675fc4de 100644
--- a/lib/chef/resource_collection.rb
+++ b/lib/chef/resource_collection.rb
@@ -32,6 +32,8 @@ class Chef
include ResourceCollectionSerialization
extend Forwardable
+ attr_accessor :unified_mode
+
attr_reader :resource_set, :resource_list
attr_accessor :run_context
@@ -41,6 +43,7 @@ class Chef
@run_context = run_context
@resource_set = ResourceSet.new
@resource_list = ResourceList.new
+ @unified_mode = false
end
# @param resource [Chef::Resource] The resource to insert
@@ -57,6 +60,9 @@ class Chef
else
resource_set.insert_as(resource)
end
+ if unified_mode
+ run_context.runner.run_all_actions(resource)
+ end
end
def delete(key)
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index eb211dc5a5..7a5924a3c7 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -26,12 +26,16 @@ require_relative "recipe"
require_relative "run_context/cookbook_compiler"
require_relative "event_dispatch/events_output_stream"
require_relative "train_transport"
+require_relative "exceptions"
require "forwardable" unless defined?(Forwardable)
+require "set" unless defined?(Set)
class Chef
# Value object that loads and tracks the context of a Chef run
class RunContext
+ extend Forwardable
+
#
# Global state
#
@@ -72,14 +76,12 @@ class Chef
#
attr_reader :definitions
- #
# Event dispatcher for this run.
#
# @return [Chef::EventDispatch::Dispatcher]
#
attr_accessor :events
- #
# Hash of factoids for a reboot request.
#
# @return [Hash]
@@ -90,7 +92,6 @@ class Chef
# Scoped state
#
- #
# The parent run context.
#
# @return [Chef::RunContext] The parent run context, or `nil` if this is the
@@ -98,7 +99,6 @@ class Chef
#
attr_reader :parent_run_context
- #
# The root run context.
#
# @return [Chef::RunContext] The root run context.
@@ -109,7 +109,6 @@ class Chef
rc
end
- #
# The collection of resources intended to be converged (and able to be
# notified).
#
@@ -119,8 +118,12 @@ class Chef
#
attr_reader :resource_collection
- attr_accessor :action_collection
+ # Handle to the global action_collection of executed actions for reporting / data_collector /etc
+ #
+ # @return [Chef::ActionCollection
#
+ attr_accessor :action_collection
+
# Pointer back to the Chef::Runner that created this
#
attr_accessor :runner
@@ -129,7 +132,6 @@ class Chef
# Notification handling
#
- #
# A Hash containing the before notifications triggered by resources
# during the converge phase of the chef run.
#
@@ -138,7 +140,6 @@ class Chef
#
attr_reader :before_notification_collection
- #
# A Hash containing the immediate notifications triggered by resources
# during the converge phase of the chef run.
#
@@ -147,7 +148,6 @@ class Chef
#
attr_reader :immediate_notification_collection
- #
# A Hash containing the delayed (end of run) notifications triggered by
# resources during the converge phase of the chef run.
#
@@ -156,7 +156,6 @@ class Chef
#
attr_reader :delayed_notification_collection
- #
# An Array containing the delayed (end of run) notifications triggered by
# resources during the converge phase of the chef run.
#
@@ -164,7 +163,16 @@ class Chef
#
attr_reader :delayed_actions
+ # A Set keyed by the string name, of all the resources that are updated. We do not
+ # track actions or individual resource objects, since this matches the behavior of
+ # the notification collections which are keyed by Strings.
+ #
+ attr_reader :updated_resources
+
+ # @return [Boolean] If the resource_collection is in unified_mode (no separate converge phase)
#
+ def_delegator :resource_collection, :unified_mode
+
# A child of the root Chef::Log logging object.
#
# @return Mixlib::Log::Child A child logger
@@ -190,7 +198,6 @@ class Chef
@loaded_attributes_hash = {}
@reboot_info = {}
@cookbook_compiler = nil
- @delayed_actions = []
initialize_child_state
end
@@ -221,6 +228,7 @@ class Chef
@immediate_notification_collection = Hash.new { |h, k| h[k] = [] }
@delayed_notification_collection = Hash.new { |h, k| h[k] = [] }
@delayed_actions = []
+ @updated_resources = Set.new
end
#
@@ -232,6 +240,10 @@ class Chef
# Note for the future, notification.notifying_resource may be an instance
# of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes}
# with a string value.
+ if unified_mode && updated_resources.include?(notification.notifying_resource.declared_key)
+ raise Chef::Exceptions::UnifiedModeBeforeSubscriptionEarlierResource.new(notification)
+ end
+
before_notification_collection[notification.notifying_resource.declared_key] << notification
end
@@ -256,11 +268,13 @@ class Chef
# Note for the future, notification.notifying_resource may be an instance
# of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes}
# with a string value.
+ if unified_mode && updated_resources.include?(notification.notifying_resource.declared_key)
+ add_delayed_action(notification)
+ end
delayed_notification_collection[notification.notifying_resource.declared_key] << notification
end
- #
- # Adds a delayed action to the +delayed_actions+.
+ # Adds a delayed action to the delayed_actions collection
#
def add_delayed_action(notification)
if delayed_actions.any? { |existing_notification| existing_notification.duplicates?(notification) }
@@ -271,32 +285,45 @@ class Chef
end
end
- #
# Get the list of before notifications sent by the given resource.
#
# @return [Array[Notification]]
#
def before_notifications(resource)
- before_notification_collection[resource.declared_key]
+ key = resource.is_a?(String) ? resource : resource.declared_key
+ before_notification_collection[key]
end
- #
# Get the list of immediate notifications sent by the given resource.
#
# @return [Array[Notification]]
#
def immediate_notifications(resource)
- immediate_notification_collection[resource.declared_key]
+ key = resource.is_a?(String) ? resource : resource.declared_key
+ immediate_notification_collection[key]
end
+ # Get the list of immeidate notifications pending to the given resource
+ #
+ # @return [Array[Notification]]
#
+ def reverse_immediate_notifications(resource)
+ immediate_notification_collection.map do |k, v|
+ v.select do |n|
+ (n.resource.is_a?(String) && n.resource == resource.declared_key) ||
+ n.resource == resource
+ end
+ end.flatten
+ end
+
# Get the list of delayed (end of run) notifications sent by the given
# resource.
#
# @return [Array[Notification]]
#
def delayed_notifications(resource)
- delayed_notification_collection[resource.declared_key]
+ key = resource.is_a?(String) ? resource : resource.declared_key
+ delayed_notification_collection[key]
end
#
@@ -666,9 +693,9 @@ class Chef
rest=
rest_clean
rest_clean=
- unreachable_cookbook?
transport
transport_connection
+ unreachable_cookbook?
}
def initialize(parent_run_context)
@@ -681,8 +708,10 @@ class Chef
end
CHILD_STATE = %w{
- create_child
add_delayed_action
+ before_notification_collection
+ before_notifications
+ create_child
delayed_actions
delayed_notification_collection
delayed_notification_collection=
@@ -690,21 +719,22 @@ class Chef
immediate_notification_collection
immediate_notification_collection=
immediate_notifications
- before_notification_collection
- before_notifications
include_recipe
initialize_child_state
load_recipe
load_recipe_file
notifies_before
- notifies_immediately
notifies_delayed
+ notifies_immediately
parent_run_context
- root_run_context
resource_collection
resource_collection=
+ reverse_immediate_notifications
+ root_run_context
runner
runner=
+ unified_mode
+ updated_resources
}.map(&:to_sym)
# Verify that we didn't miss any methods
diff --git a/lib/chef/runner.rb b/lib/chef/runner.rb
index a0ae61fe4c..1ece2f97c3 100644
--- a/lib/chef/runner.rb
+++ b/lib/chef/runner.rb
@@ -34,7 +34,7 @@ class Chef
def initialize(run_context)
@run_context = run_context
- run_context.runner = self
+ @run_context.runner = self
end
def delayed_actions
@@ -45,6 +45,10 @@ class Chef
@run_context.events
end
+ def updated_resources
+ @run_context.updated_resources
+ end
+
# Determine the appropriate provider for the given resource, then
# execute it.
def run_action(resource, action, notification_type = nil, notifying_resource = nil)
@@ -73,33 +77,67 @@ class Chef
# associated with the resource, but only if it was updated *this time*
# we ran an action on it.
if resource.updated_by_last_action?
+ updated_resources.add(resource.declared_key) # track updated resources for unified_mode
run_context.immediate_notifications(resource).each do |notification|
- Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (immediate)")
- run_action(notification.resource, notification.action, :immediate, resource)
+ if notification.resource.is_a?(String) && run_context.unified_mode
+ Chef::Log.debug("immediate notification from #{resource} to #{notification.resource} is delayed until declaration due to unified_mode")
+ else
+ Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (immediate)")
+ run_action(notification.resource, notification.action, :immediate, resource)
+ end
end
run_context.delayed_notifications(resource).each do |notification|
- # send the notification to the run_context of the receiving resource
- notification.resource.run_context.add_delayed_action(notification)
+ if notification.resource.is_a?(String)
+ # for string resources that have not be declared yet in unified mode we only support notifying the current run_context
+ run_context.add_delayed_action(notification)
+ else
+ # send the notification to the run_context of the receiving resource
+ notification.resource.run_context.add_delayed_action(notification)
+ end
+ end
+ end
+ end
+
+ # Runs all of the actions on a given resource. This fires notifications and marks
+ # the resource as having been executed by the runner.
+ #
+ # @param resource [Chef::Resource] the resource to run
+ #
+ def run_all_actions(resource)
+ Array(resource.action).each { |action| run_action(resource, action) }
+ if run_context.unified_mode
+ run_context.reverse_immediate_notifications(resource).each do |n|
+ if updated_resources.include?(n.notifying_resource.declared_key)
+ n.resolve_resource_reference(run_context.resource_collection)
+ Chef::Log.info("#{resource} sent #{n.action} action to #{n.resource} (immediate at declaration time)")
+ run_action(n.resource, n.action, :immediate, n.notifying_resource)
+ end
end
end
+ ensure
+ resource.executed_by_runner = true
end
- # Iterates over the +resource_collection+ in the +run_context+ calling
- # +run_action+ for each resource in turn.
+ # Iterates over the resource_collection in the run_context calling
+ # run_action for each resource in turn.
+ #
def converge
# Resolve all lazy/forward references in notifications
run_context.resource_collection.each(&:resolve_notification_references)
# Execute each resource.
run_context.resource_collection.execute_each_resource do |resource|
- begin
- Array(resource.action).each { |action| run_action(resource, action) }
- ensure
- resource.executed_by_runner = true
+ unless run_context.resource_collection.unified_mode
+ run_all_actions(resource)
end
end
+ if run_context.resource_collection.unified_mode
+ puts "HERE WE ARE!"
+ run_context.resource_collection.each { |r| r.resolve_notification_references(true) }
+ end
+
rescue Exception => e
Chef::Log.info "Running queued delayed notifications before re-raising exception"
run_delayed_notifications(e)
@@ -126,7 +164,8 @@ class Chef
def run_delayed_notification(notification)
Chef::Log.info( "#{notification.notifying_resource} sending #{notification.action}"\
" action to #{notification.resource} (delayed)")
- # Struct of resource/action to call
+ # notifications may have lazy strings in them to resolve
+ notification.resolve_resource_reference(run_context.resource_collection)
run_action(notification.resource, notification.action, :delayed)
true
rescue Exception => e