summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <john@johnkeiser.com>2015-05-15 23:06:51 -0700
committerJohn Keiser <john@johnkeiser.com>2015-06-23 14:42:27 -0700
commite8a80c674bfb67b6839eb8498a73da61d0e9cdc0 (patch)
tree010453ba5d9d7f787a7c5e92afac0deee6cca0cd
parent87a8b49efbccb6934ff2bacb8f8df53d1caf5e46 (diff)
downloadchef-e8a80c674bfb67b6839eb8498a73da61d0e9cdc0.tar.gz
Create the `action :name do ... end` syntax for Resource
-rw-r--r--lib/chef/dsl/recipe.rb22
-rw-r--r--lib/chef/event_dispatch/base.rb56
-rw-r--r--lib/chef/provider.rb122
-rw-r--r--lib/chef/provider/deploy.rb6
-rw-r--r--lib/chef/provider/lwrp_base.rb69
-rw-r--r--lib/chef/resource.rb119
-rw-r--r--lib/chef/run_context.rb114
-rw-r--r--spec/integration/recipes/resource_action_spec.rb338
-rw-r--r--spec/unit/provider_spec.rb4
9 files changed, 648 insertions, 202 deletions
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index d69f0a8f11..b238f45d9c 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -122,9 +122,9 @@ class Chef
def describe_self_for_error
if respond_to?(:name)
- %Q[`#{self.class.name} "#{name}"']
+ %Q[`#{self.class} "#{name}"']
elsif respond_to?(:recipe_name)
- %Q[`#{self.class.name} "#{recipe_name}"']
+ %Q[`#{self.class} "#{recipe_name}"']
else
to_s
end
@@ -176,6 +176,24 @@ class Chef
raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
end
end
+
+ module Everything
+ require 'chef/dsl/data_query'
+ require 'chef/dsl/platform_introspection'
+ require 'chef/dsl/include_recipe'
+ require 'chef/dsl/registry_helper'
+ require 'chef/dsl/reboot_pending'
+ require 'chef/dsl/audit'
+ require 'chef/dsl/powershell'
+ include Chef::DSL::DataQuery
+ include Chef::DSL::PlatformIntrospection
+ include Chef::DSL::IncludeRecipe
+ include Chef::DSL::Recipe
+ include Chef::DSL::RegistryHelper
+ include Chef::DSL::RebootPending
+ include Chef::DSL::Audit
+ include Chef::DSL::Powershell
+ end
end
end
end
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index 73fe25ec13..b64c143421 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -269,26 +269,37 @@ class Chef
# def notifications_resolved
# end
+ #
+ # Resource events and ordering:
+ #
+ # 1. Start the action
+ # - resource_action_start
+ # 2. Check the guard
+ # - resource_skipped: (goto 7) if only_if/not_if say to skip
+ # 3. Load the current resource
+ # - resource_current_state_loaded
+ # - resource_current_state_load_bypassed (if not why-run safe)
+ # 4. Check if why-run safe
+ # - resource_bypassed: (goto 7) if not why-run safe
+ # 5. During processing:
+ # - resource_update_applied: For each actual change
+ # 6. Processing complete status:
+ # - resource_failed if the resource threw an exception while running
+ # - resource_failed_retriable: (goto 3) if resource failed and will be retried
+ # - resource_updated if the resource was updated (resource_update_applied will have been called)
+ # - resource_up_to_date if the resource was up to date (no resource_update_applied)
+ # 7. Processing complete:
+ # - resource_completed
+ #
+
# Called before action is executed on a resource.
def resource_action_start(resource, action, notification_type=nil, notifier=nil)
end
- # Called when a resource fails, but will retry.
- def resource_failed_retriable(resource, action, retry_count, exception)
- end
-
- # Called when a resource fails and will not be retried.
- def resource_failed(resource, action, exception)
- end
-
# Called when a resource action has been skipped b/c of a conditional
def resource_skipped(resource, action, conditional)
end
- # Called when a resource action has been completed
- def resource_completed(resource)
- end
-
# Called after #load_current_resource has run.
def resource_current_state_loaded(resource, action, current_resource)
end
@@ -302,21 +313,34 @@ class Chef
def resource_bypassed(resource, action, current_resource)
end
- # Called when a resource has no converge actions, e.g., it was already correct.
- def resource_up_to_date(resource, action)
- end
-
# Called when a change has been made to a resource. May be called multiple
# times per resource, e.g., a file may have its content updated, and then
# its permissions updated.
def resource_update_applied(resource, action, update)
end
+ # Called when a resource fails, but will retry.
+ def resource_failed_retriable(resource, action, retry_count, exception)
+ end
+
+ # Called when a resource fails and will not be retried.
+ def resource_failed(resource, action, exception)
+ end
+
# Called after a resource has been completely converged, but only if
# modifications were made.
def resource_updated(resource, action)
end
+ # Called when a resource has no converge actions, e.g., it was already correct.
+ def resource_up_to_date(resource, action)
+ end
+
+ # Called when a resource action has been completed
+ def resource_completed(resource)
+ end
+
+
# A stream has opened.
def stream_opened(stream, options = {})
end
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 12f11d26af..f3bc67d12f 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -26,6 +26,7 @@ require 'chef/mixin/powershell_out'
require 'chef/mixin/provides'
require 'chef/platform/service_helpers'
require 'chef/node_map'
+require 'forwardable'
class Chef
class Provider
@@ -65,6 +66,7 @@ class Chef
@recipe_name = nil
@cookbook_name = nil
+ self.class.include_resource_dsl_module(new_resource)
end
def whyrun_mode?
@@ -119,11 +121,11 @@ class Chef
check_resource_semantics!
# user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode
- if !whyrun_mode? || whyrun_supported?
+ if whyrun_mode? && !whyrun_supported?
+ events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
+ else
load_current_resource
events.resource_current_state_loaded(@new_resource, @action, @current_resource)
- elsif whyrun_mode? && !whyrun_supported?
- events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
end
define_resource_requirements
@@ -136,9 +138,7 @@ class Chef
# we can't execute the action.
# in non-whyrun mode, this will still cause the action to be
# executed normally.
- if whyrun_supported? && !requirements.action_blocked?(@action)
- send("action_#{@action}")
- elsif whyrun_mode?
+ if whyrun_mode? && (!whyrun_supported? || requirements.action_blocked?(@action))
events.resource_bypassed(@new_resource, @action, self)
else
send("action_#{@action}")
@@ -183,6 +183,116 @@ class Chef
Chef::ProviderResolver.new(node, resource, :nothing).provided_by?(self)
end
+ #
+ # Include attributes, public and protected methods from this Resource in
+ # the provider. Will delegate to
+ #
+ # The actual include does not happen until the first time the Provider
+ # is instantiated (so that we don't have to worry about load order issues).
+ #
+ # @param include_resource_dsl [Boolean] Whether to include resource DSL or
+ # not (defaults to `false`).
+ def self.include_resource_dsl(include_resource_dsl)
+ @include_resource_dsl = include_resource_dsl
+ end
+
+ # Create the resource DSL module that forwards resource methods to new_resource
+ #
+ # @api private
+ def self.include_resource_dsl_module(resource)
+ if @include_resource_dsl && !defined?(@included_resource_dsl_module)
+ provider_class = self
+ @included_resource_dsl_module = Module.new do
+ extend Forwardable
+ define_singleton_method(:to_s) { "#{resource_class} forwarder module" }
+ define_singleton_method(:inspect) { to_s }
+ dsl_methods =
+ resource.class.public_instance_methods +
+ resource.class.protected_instance_methods -
+ provider_class.instance_methods
+ def_delegators(:new_resource, *dsl_methods)
+ end
+ include @included_resource_dsl_module
+ end
+ end
+
+ # Enables inline evaluation of resources in provider actions.
+ #
+ # Without this option, any resources declared inside the Provider are added
+ # to the resource collection after the current position at the time the
+ # action is executed. Because they are added to the primary resource
+ # collection for the chef run, they can notify other resources outside
+ # the Provider, and potentially be notified by resources outside the Provider
+ # (but this is complicated by the fact that they don't exist until the
+ # provider executes). In this mode, it is impossible to correctly set the
+ # updated_by_last_action flag on the parent Provider resource, since it
+ # executes and returns before its component resources are run.
+ #
+ # With this option enabled, each action creates a temporary run_context
+ # with its own resource collection, evaluates the action's code in that
+ # context, and then converges the resources created. If any resources
+ # were updated, then this provider's new_resource will be marked updated.
+ #
+ # In this mode, resources created within the Provider cannot interact with
+ # external resources via notifies, though notifications to other
+ # resources within the Provider will work. Delayed notifications are executed
+ # at the conclusion of the provider's action, *not* at the end of the
+ # main chef run.
+ #
+ # This mode of evaluation is experimental, but is believed to be a better
+ # set of tradeoffs than the append-after mode, so it will likely become
+ # the default in a future major release of Chef.
+ #
+ def self.use_inline_resources
+ extend InlineResources::ClassMethods
+ include InlineResources
+ end
+
+ # Chef::Provider::InlineResources
+ # Implementation of inline resource convergence for providers. See
+ # Provider.use_inline_resources for a longer explanation.
+ #
+ # This code is restricted to a module so that it can be selectively
+ # applied to providers on an opt-in basis.
+ #
+ # @api private
+ module InlineResources
+
+ # Our run context is a child of the main run context; that gives us a
+ # whole new resource collection and notification set.
+ def initialize(resource, run_context)
+ super(resource, run_context.create_child)
+ end
+
+ # Class methods for InlineResources. Overrides the `action` DSL method
+ # with one that enables inline resource convergence.
+ #
+ # @api private
+ module ClassMethods
+ # Defines an action method on the provider, running the block to
+ # compile the resources, converging them, and then checking if any
+ # were updated (and updating new-resource if so)
+ def action(name, &block)
+ class_eval <<-EOM, __FILE__, __LINE__+1
+ def action_#{name}
+ return_value = compile_action_#{name}
+ Chef::Runner.new(run_context).converge
+ return_value
+ ensure
+ if run_context.resource_collection.any? {|r| r.updated? }
+ new_resource.updated_by_last_action(true)
+ end
+ end
+ EOM
+ # We put the action in its own method so that super() works.
+ define_method("compile_action_#{name}", &block)
+ end
+ end
+
+ require 'chef/dsl/recipe'
+ include Chef::DSL::Recipe::Everything
+ end
+
protected
def converge_actions
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
index 387441699e..6d9b7f4397 100644
--- a/lib/chef/provider/deploy.rb
+++ b/lib/chef/provider/deploy.rb
@@ -373,9 +373,9 @@ class Chef
end
def gem_resource_collection_runner
- gems_collection = Chef::ResourceCollection.new
- gem_packages.each { |rbgem| gems_collection.insert(rbgem) }
- Chef::Runner.new(run_context.create_child)
+ child_context = run_context.create_child
+ gem_packages.each { |rbgem| child_context.resource_collection.insert(rbgem) }
+ Chef::Runner.new(child_context)
end
def gem_packages
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index 94c79918b7..27b2042f2b 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -28,43 +28,6 @@ class Chef
# Base class from which LWRP providers inherit.
class LWRPBase < Provider
- # Chef::Provider::LWRPBase::InlineResources
- # Implementation of inline resource convergence for LWRP providers. See
- # Provider::LWRPBase.use_inline_resources for a longer explanation.
- #
- # This code is restricted to a module so that it can be selectively
- # applied to providers on an opt-in basis.
- module InlineResources
-
- # Class methods for InlineResources. Overrides the `action` DSL method
- # with one that enables inline resource convergence.
- module ClassMethods
- # Defines an action method on the provider, running the block to
- # compile the resources, converging them, and then checking if any
- # were updated (and updating new-resource if so)
- def action(name, &block)
- define_method("action_#{name}") do
- begin
- return_value = instance_eval(&block)
- Chef::Runner.new(run_context).converge
- return_value
- ensure
- if run_context.resource_collection.any? {|r| r.updated? }
- new_resource.updated_by_last_action(true)
- end
- end
- end
- end
- end
-
- # Our run context is a child of the main run context; that gives us a
- # whole new resource collection and notification set.
- def initialize(*args, &block)
- super
- @run_context = @run_context.create_child
- end
- end
-
include Chef::DSL::Recipe
# These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
@@ -117,38 +80,6 @@ class Chef
provider_class
end
- # Enables inline evaluation of resources in provider actions.
- #
- # Without this option, any resources declared inside the LWRP are added
- # to the resource collection after the current position at the time the
- # action is executed. Because they are added to the primary resource
- # collection for the chef run, they can notify other resources outside
- # the LWRP, and potentially be notified by resources outside the LWRP
- # (but this is complicated by the fact that they don't exist until the
- # provider executes). In this mode, it is impossible to correctly set the
- # updated_by_last_action flag on the parent LWRP resource, since it
- # executes and returns before its component resources are run.
- #
- # With this option enabled, each action creates a temporary run_context
- # with its own resource collection, evaluates the action's code in that
- # context, and then converges the resources created. If any resources
- # were updated, then this provider's new_resource will be marked updated.
- #
- # In this mode, resources created within the LWRP cannot interact with
- # external resources via notifies, though notifications to other
- # resources within the LWRP will work. Delayed notifications are executed
- # at the conclusion of the provider's action, *not* at the end of the
- # main chef run.
- #
- # This mode of evaluation is experimental, but is believed to be a better
- # set of tradeoffs than the append-after mode, so it will likely become
- # the default in a future major release of Chef.
- #
- def use_inline_resources
- extend InlineResources::ClassMethods
- include InlineResources
- end
-
# DSL for defining a provider's actions.
def action(name, &block)
define_method("action_#{name}") do
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 4ec0a03f4f..f70137a7b6 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -61,42 +61,6 @@ class Chef
NULL_ARG = Object.new
#
- # Define an action on this resource.
- #
- # The action is defined as a *recipe* block that will be compiled and then
- # converged when the action is taken (when Resource is converged). The recipe
- # has access to the resource's attributes and methods, as well as the Chef
- # recipe DSL.
- #
- # Resources in the action recipe may notify and subscribe to other resources
- # within the action recipe, but cannot notify or subscribe to resources
- # in the main Chef run.
- #
- # Resource actions are *inheritable*: if resource A defines `action :create`
- # and B is a subclass of A, B gets all of A's actions. Additionally,
- # resource B can define `action :create` and call `super()` to invoke A's
- # action code.
- #
- # @param name [Symbol] The action name to define.
- # @param recipe_block The recipe to run when the action is taken. This block
- # takes no parameters, and will be evaluated in a new context containing:
- #
- # - The resource's public and protected methods (including attributes)
- # - The Chef Recipe DSL (file, etc.)
- # - super() referring to the parent version of the action (if any)
- #
- # @return The Action class implementing the action
- #
- def self.action(action, &recipe_block)
- action = action.to_sym
- self.action_classes[action] ||= Class.new(ActionRecipe) do
- resource_class self
- action action
- recipe_block recipe_block
- end
- end
-
- #
# The node the current Chef run is using.
#
# Corresponds to `run_context.node`.
@@ -223,8 +187,7 @@ class Chef
end
self.action = arg
else
- # Pull the action from the class if it's not set
- @action || self.class.default_action
+ @action
end
end
@@ -567,9 +530,7 @@ class Chef
#
# Equivalent to #ignore_failure.
#
- def epic_fail(arg=nil)
- ignore_failure(arg)
- end
+ alias :epic_fail :ignore_failure
#
# Make this resource into an exact (shallow) copy of the other resource.
@@ -738,7 +699,8 @@ class Chef
else
arg
end
- set_or_return(:provider, klass, kind_of: [ Class ])
+ set_or_return(:provider, klass, kind_of: [ Class ]) ||
+ self.class.action_provider_class
end
def provider=(arg)
provider(arg)
@@ -1042,6 +1004,75 @@ class Chef
else
:nothing
end
+
+ #
+ # Define an action on this resource.
+ #
+ # The action is defined as a *recipe* block that will be compiled and then
+ # converged when the action is taken (when Resource is converged). The recipe
+ # has access to the resource's attributes and methods, as well as the Chef
+ # recipe DSL.
+ #
+ # Resources in the action recipe may notify and subscribe to other resources
+ # within the action recipe, but cannot notify or subscribe to resources
+ # in the main Chef run.
+ #
+ # Resource actions are *inheritable*: if resource A defines `action :create`
+ # and B is a subclass of A, B gets all of A's actions. Additionally,
+ # resource B can define `action :create` and call `super()` to invoke A's
+ # action code.
+ #
+ # @param name [Symbol] The action name to define.
+ # @param recipe_block The recipe to run when the action is taken. This block
+ # takes no parameters, and will be evaluated in a new context containing:
+ #
+ # - The resource's public and protected methods (including attributes)
+ # - The Chef Recipe DSL (file, etc.)
+ # - super() referring to the parent version of the action (if any)
+ #
+ # @return The Action class implementing the action
+ #
+ def action(action, &recipe_block)
+ action = action.to_sym
+ create_action_provider_class.action(action, &recipe_block)
+ self.allowed_actions += [ action ]
+ default_action action if default_action == :nothing
+ end
+
+ #
+ # The created action provider class for this resource, or nil if it has
+ # not been / does not need to be created.
+ #
+ # @api private
+ def action_provider_class
+ @action_provider_class ||
+ # If the superclass needed one, then we need one as well.
+ if superclass.respond_to?(:action_provider_class) && superclass.action_provider_class
+ create_action_provider_class
+ end
+ end
+
+ #
+ # Create the action provider class
+ #
+ # @api private
+ def create_action_provider_class
+ return @action_provider_class if @action_provider_class
+
+ if superclass.respond_to?(:action_provider_class)
+ base_provider = superclass.action_provider_class
+ end
+ base_provider ||= Chef::Provider
+
+ resource_class = self
+ @action_provider_class = Class.new(base_provider) do
+ use_inline_resources
+ include_resource_dsl true
+ define_singleton_method(:to_s) { "#{resource_class} action provider" }
+ define_singleton_method(:inspect) { to_s }
+ define_method(:load_current_resource) {}
+ end
+ end
end
def self.default_action=(action_name)
default_action(action_name)
@@ -1244,7 +1275,9 @@ class Chef
end
def provider_for_action(action)
- provider = Chef::ProviderResolver.new(node, self, action).resolve.new(self, run_context)
+ provider_class = self.class.action_provider_class
+ provider_class ||= Chef::ProviderResolver.new(node, self, action).resolve
+ provider = provider_class.new(self, run_context)
provider.action = action
provider
end
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index ed5d7aaa79..15deae324f 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -70,7 +70,7 @@ class Chef
#
# @return [Hash]
#
- attr_reader :reboot_info
+ attr_accessor :reboot_info
#
# Scoped state
@@ -121,22 +121,53 @@ class Chef
#
attr_reader :delayed_notification_collection
+ # Creates a new Chef::RunContext object and populates its fields. This object gets
+ # used by the Chef Server to generate a fully compiled recipe list for a node.
#
- # Create non-shared state.
+ # @param node [Chef::Node] The node to run against.
+ # @param cookbook_collection [Chef::CookbookCollection] The cookbooks
+ # involved in this run.
+ # @param events [EventDispatch::Dispatcher] The event dispatcher for this
+ # run.
#
- def initialize
- # This is all non-shared state.
+ def initialize(node, cookbook_collection, events)
+ @node = node
+ @cookbook_collection = cookbook_collection
+ @events = events
+
+ node.run_context = self
+ node.set_cookbook_attribute
+
+ @definitions = Hash.new
+ @loaded_recipes_hash = {}
+ @loaded_attributes_hash = {}
+ @reboot_info = {}
+ @cookbook_compiler = nil
+
+ initialize_non_shared_state
+ end
+
+ #
+ # Triggers the compile phase of the chef run.
+ #
+ # @param run_list_expansion [Chef::RunList::RunListExpansion] The run list.
+ # @see Chef::RunContext::CookbookCompiler
+ #
+ def load(run_list_expansion)
+ @cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
+ cookbook_compiler.compile
+ end
+
+ #
+ # Initialize state that applies to both Chef::RunContext and Chef::ChildRunContext
+ #
+ def initialize_non_shared_state
@audits = {}
@resource_collection = Chef::ResourceCollection.new
@immediate_notification_collection = Hash.new {|h,k| h[k] = []}
@delayed_notification_collection = Hash.new {|h,k| h[k] = []}
end
- def self.new(node, cookbook_collection, events)
- Chef::Log.deprecation("RunContext.new will be removed in a future Chef version. Use RootRunContext instead.")
- RootRunContext.new(node, cookbook_collection, events)
- end
-
#
# Adds an immediate notification to the +immediate_notification_collection+.
#
@@ -373,7 +404,7 @@ ERROR_MESSAGE
# @param recipe [String] Recipe name.
#
def loaded_recipe(cookbook, recipe)
- loaded_recipes["#{cookbook}::#{recipe}"] = true
+ loaded_recipes_hash["#{cookbook}::#{recipe}"] = true
end
#
@@ -385,7 +416,7 @@ ERROR_MESSAGE
# @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
#
def loaded_fully_qualified_attribute?(cookbook, attribute_file)
- loaded_attributes.has_key?("#{cookbook}::#{attribute_file}")
+ loaded_attributes_hash.has_key?("#{cookbook}::#{attribute_file}")
end
#
@@ -477,12 +508,12 @@ ERROR_MESSAGE
# 6. ?
def request_reboot(reboot_info)
Chef::Log::info "Changing reboot status from #{self.reboot_info.inspect} to #{reboot_info.inspect}"
- self.reboot_info = reboot_info
+ @reboot_info = reboot_info
end
def cancel_reboot
Chef::Log::info "Changing reboot status from #{reboot_info.inspect} to {}"
- reboot_info = {}
+ @reboot_info = {}
end
def reboot_requested?
@@ -492,21 +523,21 @@ ERROR_MESSAGE
#
# Create a child RunContext.
#
- def push
+ def create_child
ChildRunContext.new(self)
end
private
+ attr_reader :cookbook_compiler
+ attr_reader :loaded_attributes_hash
+ attr_reader :loaded_recipes_hash
+
module Deprecated
###
# These need to be settable so deploy can run a resource_collection
# independent of any cookbooks via +recipe_eval+
- def resource_collection=(value)
- Chef::Log.deprecation("Setting run_context.resource_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
- end
-
def audits=(value)
Chef::Log.deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
end
@@ -529,52 +560,15 @@ ERROR_MESSAGE
#
class ChildRunContext < RunContext
extend Forwardable
- def_delegator :parent_run_context, :node, :cookbook_collection, :definitions, :events, :reboot_info
+ def_delegators :parent_run_context, :node, :cookbook_collection, :definitions, :events, :reboot_info, :reboot_info=, :cookbook_compiler
def initialize(parent_run_context)
+ # We don't call super, because we don't bother initializing stuff we're
+ # going to delegate to the parent anyway. Just initialize things that
+ # every instance needs.
+ initialize_non_shared_state
@parent_run_context = parent_run_context
end
end
-
- #
- # The root run context. Contains all top-level state for the run.
- #
- # @api private
- #
- class RootRunContext < RunContext
- # Creates a new Chef::RunContext object and populates its fields. This object gets
- # used by the Chef Server to generate a fully compiled recipe list for a node.
- #
- # @param node [Chef::Node] The node to run against.
- # @param cookbook_collection [Chef::CookbookCollection] The cookbooks
- # involved in this run.
- # @param events [EventDispatch::Dispatcher] The event dispatcher for this
- # run.
- #
- def initialize(node, cookbook_collection, events)
- node.run_context = self
- @node = node
- @cookbook_collection = cookbook_collection
- @definitions = Hash.new
- @loaded_recipes_hash = {}
- @loaded_attributes_hash = {}
- @events = events
- @reboot_info = {}
- @cookbook_compiler = nil
-
- @node.set_cookbook_attribute
- end
-
- #
- # Triggers the compile phase of the chef run.
- #
- # @param run_list_expansion [Chef::RunList::RunListExpansion] The run list.
- # @see Chef::RunContext::CookbookCompiler
- #
- def load(run_list_expansion)
- @cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
- cookbook_compiler.compile
- end
- end
end
end
diff --git a/spec/integration/recipes/resource_action_spec.rb b/spec/integration/recipes/resource_action_spec.rb
new file mode 100644
index 0000000000..8e8071abbc
--- /dev/null
+++ b/spec/integration/recipes/resource_action_spec.rb
@@ -0,0 +1,338 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Resource.action" do
+ include IntegrationSupport
+
+ def converge(str=nil, file=nil, line=nil, &block)
+ if block
+ super(&block)
+ else
+ super() do
+ eval(str, nil, file, line)
+ end
+ end
+ end
+
+ shared_context "ActionJackson" do
+ it "The default action is the first declared action" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+ expect(ActionJackson.succeeded).to eq true
+ end
+
+ it "The action can access recipe DSL" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_recipe_dsl
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+ expect(ActionJackson.succeeded).to eq true
+ end
+
+ it "The action can access attributes" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_attribute
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_attribute
+ expect(ActionJackson.succeeded).to eq 'foo!'
+ end
+
+ it "The action can access public methods" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_method
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_method
+ expect(ActionJackson.succeeded).to eq 'foo_public!'
+ end
+
+ it "The action can access protected methods" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_protected_method
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_protected_method
+ expect(ActionJackson.succeeded).to eq 'foo_protected!'
+ end
+
+ it "The action cannot access private methods" do
+ expect {
+ converge(<<-EOM, __FILE__, __LINE__+1)
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_private_method
+ end
+ EOM
+ }.to raise_error(NameError)
+ expect(ActionJackson.ran_action).to eq :access_private_method
+ end
+
+ it "The action cannot access resource instance variables" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_instance_variable
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_instance_variable
+ expect(ActionJackson.succeeded).to be_nil
+ end
+
+ it "The action does not compile until the prior resource has converged" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ ruby_block 'wow' do
+ block do
+ ActionJackson.ruby_block_converged = 'ruby_block_converged!'
+ end
+ end
+
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_class_method
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_class_method
+ expect(ActionJackson.succeeded).to eq 'ruby_block_converged!'
+ end
+
+ it "The action's resources converge before the next resource converges" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_attribute
+ end
+
+ ruby_block 'wow' do
+ block do
+ ActionJackson.ruby_block_converged = ActionJackson.succeeded
+ end
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_attribute
+ expect(ActionJackson.succeeded).to eq 'foo!'
+ expect(ActionJackson.ruby_block_converged).to eq 'foo!'
+ end
+ end
+
+ context "With resource 'action_jackson'" do
+ before(:context) {
+ class ActionJackson < Chef::Resource
+ provides :action_jackson
+ def foo(value=nil)
+ @foo = value if value
+ @foo
+ end
+ def blarghle(value=nil)
+ @blarghle = value if value
+ @blarghle
+ end
+
+ class <<self
+ attr_accessor :ran_action
+ attr_accessor :succeeded
+ attr_accessor :ruby_block_converged
+ end
+
+ public def foo_public
+ 'foo_public!'
+ end
+ protected def foo_protected
+ 'foo_protected!'
+ end
+ private def foo_private
+ 'foo_private!'
+ end
+
+ action :access_recipe_dsl do
+ ActionJackson.ran_action = :access_recipe_dsl
+ ruby_block 'hi there' do
+ block do
+ ActionJackson.succeeded = true
+ end
+ end
+ end
+ action :access_attribute do
+ ActionJackson.ran_action = :access_attribute
+ ActionJackson.succeeded = foo
+ ActionJackson.succeeded += " #{blarghle}" if blarghle
+ ActionJackson.succeeded += " #{bar}" if respond_to?(:bar)
+ end
+ action :access_attribute2 do
+ ActionJackson.ran_action = :access_attribute2
+ ActionJackson.succeeded = foo
+ ActionJackson.succeeded += " #{blarghle}" if blarghle
+ ActionJackson.succeeded += " #{bar}" if respond_to?(:bar)
+ end
+ action :access_method do
+ ActionJackson.ran_action = :access_method
+ ActionJackson.succeeded = foo_public
+ end
+ action :access_protected_method do
+ ActionJackson.ran_action = :access_protected_method
+ ActionJackson.succeeded = foo_protected
+ end
+ action :access_private_method do
+ ActionJackson.ran_action = :access_private_method
+ ActionJackson.succeeded = foo_private
+ end
+ action :access_instance_variable do
+ ActionJackson.ran_action = :access_instance_variable
+ ActionJackson.succeeded = @foo
+ end
+ action :access_class_method do
+ ActionJackson.ran_action = :access_class_method
+ ActionJackson.succeeded = ActionJackson.ruby_block_converged
+ end
+ end
+ }
+ before(:each) {
+ ActionJackson.ran_action = :error
+ ActionJackson.succeeded = :error
+ ActionJackson.ruby_block_converged = :error
+ }
+
+ it_behaves_like "ActionJackson" do
+ let(:resource_dsl) { :action_jackson }
+ end
+
+ context "And 'action_jackgrandson' inheriting from ActionJackson and changing nothing" do
+ before(:context) {
+ class ActionJackgrandson < ActionJackson
+ provides :action_jackgrandson
+ end
+ }
+
+ it_behaves_like "ActionJackson" do
+ let(:resource_dsl) { :action_jackgrandson }
+ end
+ end
+
+ context "And 'action_jackalope' inheriting from ActionJackson with an extra attribute and action" do
+ before(:context) {
+ class ActionJackalope < ActionJackson
+ provides :action_jackalope
+
+ def foo(value=nil)
+ @foo = "#{value}alope" if value
+ @foo
+ end
+ def bar(value=nil)
+ @bar = "#{value}alope" if value
+ @bar
+ end
+ class <<self
+ attr_accessor :jackalope_ran
+ end
+ action :access_jackalope do
+ ActionJackalope.jackalope_ran = :access_jackalope
+ ActionJackalope.succeeded = "#{foo} #{blarghle} #{bar}"
+ end
+ action :access_attribute do
+ super()
+ ActionJackalope.jackalope_ran = :access_attribute
+ ActionJackalope.succeeded = ActionJackson.succeeded
+ end
+ end
+ }
+ before do
+ ActionJackalope.jackalope_ran = nil
+ end
+
+ context "action_jackson still behaves the same" do
+ it_behaves_like "ActionJackson" do
+ let(:resource_dsl) { :action_jackson }
+ end
+ end
+
+ it "The default action remains the same even though new actions were specified first" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ end
+ }
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+ expect(ActionJackson.succeeded).to eq true
+ end
+
+ it "new actions run, and can access overridden, new, and overridden attributes" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ blarghle 'blarghle!'
+ action :access_jackalope
+ end
+ }
+ expect(ActionJackalope.jackalope_ran).to eq :access_jackalope
+ expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope"
+ end
+
+ it "overridden actions run, call super, and can access overridden, new, and overridden attributes" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ blarghle 'blarghle!'
+ action :access_attribute
+ end
+ }
+ expect(ActionJackson.ran_action).to eq :access_attribute
+ expect(ActionJackson.succeeded).to eq "foo!alope blarghle! bar!alope"
+ expect(ActionJackalope.jackalope_ran).to eq :access_attribute
+ expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope"
+ end
+
+ it "non-overridden actions run and can access overridden and non-overridden variables (but not necessarily new ones)" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ blarghle 'blarghle!'
+ action :access_attribute2
+ end
+ }
+ expect(ActionJackson.ran_action).to eq :access_attribute2
+ expect(ActionJackson.succeeded).to eq("foo!alope blarghle! bar!alope").or(eq("foo!alope blarghle!"))
+ end
+ end
+ end
+
+ context "With a resource with no actions" do
+ before(:context) {
+ class NoActionJackson < Chef::Resource
+ provides :no_action_jackson
+ def foo(value=nil)
+ @foo = value if value
+ @foo
+ end
+
+ class <<self
+ attr_accessor :action_was
+ end
+ end
+ }
+ it "The default action is :nothing" do
+ converge {
+ no_action_jackson 'hi' do
+ foo 'foo!'
+ NoActionJackson.action_was = action
+ end
+ }
+ expect(NoActionJackson.action_was).to eq :nothing
+ end
+ end
+end
diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb
index d7a34bc21b..97b88b1732 100644
--- a/spec/unit/provider_spec.rb
+++ b/spec/unit/provider_spec.rb
@@ -114,9 +114,7 @@ describe Chef::Provider do
end
it "does not re-load recipes when creating the temporary run context" do
- # we actually want to test that RunContext#load is never called, but we
- # can't stub all instances of an object with rspec's mocks. :/
- allow(Chef::RunContext).to receive(:new).and_raise("not supposed to happen")
+ expect_any_instance_of(Chef::RunContext).not_to receive(:load)
snitch = Proc.new {temporary_collection = @run_context.resource_collection}
@provider.send(:recipe_eval, &snitch)
end