diff options
author | John Keiser <john@johnkeiser.com> | 2015-05-15 23:06:51 -0700 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2015-06-23 14:42:27 -0700 |
commit | e8a80c674bfb67b6839eb8498a73da61d0e9cdc0 (patch) | |
tree | 010453ba5d9d7f787a7c5e92afac0deee6cca0cd /lib/chef | |
parent | 87a8b49efbccb6934ff2bacb8f8df53d1caf5e46 (diff) | |
download | chef-e8a80c674bfb67b6839eb8498a73da61d0e9cdc0.tar.gz |
Create the `action :name do ... end` syntax for Resource
Diffstat (limited to 'lib/chef')
-rw-r--r-- | lib/chef/dsl/recipe.rb | 22 | ||||
-rw-r--r-- | lib/chef/event_dispatch/base.rb | 56 | ||||
-rw-r--r-- | lib/chef/provider.rb | 122 | ||||
-rw-r--r-- | lib/chef/provider/deploy.rb | 6 | ||||
-rw-r--r-- | lib/chef/provider/lwrp_base.rb | 69 | ||||
-rw-r--r-- | lib/chef/resource.rb | 119 | ||||
-rw-r--r-- | lib/chef/run_context.rb | 114 |
7 files changed, 309 insertions, 199 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 |