summaryrefslogtreecommitdiff
path: root/lib/chef/provider.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/provider.rb')
-rw-r--r--lib/chef/provider.rb233
1 files changed, 233 insertions, 0 deletions
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
new file mode 100644
index 0000000000..7bbcc3b915
--- /dev/null
+++ b/lib/chef/provider.rb
@@ -0,0 +1,233 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/mixin/from_file'
+require 'chef/mixin/convert_to_class_name'
+require 'chef/dsl/recipe'
+require 'chef/mixin/enforce_ownership_and_permissions'
+require 'chef/mixin/why_run'
+class Chef
+ class Provider
+ include Chef::DSL::Recipe
+ include Chef::Mixin::WhyRun
+ include Chef::Mixin::EnforceOwnershipAndPermissions
+
+ attr_accessor :new_resource
+ attr_accessor :current_resource
+ attr_accessor :run_context
+
+ #--
+ # TODO: this should be a reader, and the action should be passed in the
+ # constructor; however, many/most subclasses override the constructor so
+ # changing the arity would be a breaking change. Change this at the next
+ # break, e.g., Chef 11.
+ attr_accessor :action
+
+ def whyrun_supported?
+ false
+ end
+
+ def initialize(new_resource, run_context)
+ @new_resource = new_resource
+ @action = action
+ @current_resource = nil
+ @run_context = run_context
+ @converge_actions = nil
+ end
+
+ def whyrun_mode?
+ Chef::Config[:why_run]
+ end
+
+ def whyrun_supported?
+ false
+ end
+
+ def node
+ run_context && run_context.node
+ end
+
+ # Used by providers supporting embedded recipes
+ def resource_collection
+ run_context && run_context.resource_collection
+ end
+
+ def cookbook_name
+ new_resource.cookbook_name
+ end
+
+ def load_current_resource
+ raise Chef::Exceptions::Override, "You must override load_current_resource in #{self.to_s}"
+ end
+
+ def define_resource_requirements
+ end
+
+ def cleanup_after_converge
+ end
+
+ def action_nothing
+ Chef::Log.debug("Doing nothing for #{@new_resource.to_s}")
+ true
+ end
+
+ def events
+ run_context.events
+ end
+
+ def run_action(action=nil)
+ @action = action unless action.nil?
+
+ # TODO: it would be preferable to get the action to be executed in the
+ # constructor...
+
+ # user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode
+ if !whyrun_mode? || whyrun_supported?
+ 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
+ process_resource_requirements
+
+ # user-defined providers including LWRPs may
+ # not include whyrun support - if they don't support it
+ # we can't execute any actions while we're running in
+ # whyrun mode. Instead we 'fake' whyrun by documenting that
+ # 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?
+ events.resource_bypassed(@new_resource, @action, self)
+ else
+ send("action_#{@action}")
+ end
+ converge
+
+ cleanup_after_converge
+ end
+
+ def process_resource_requirements
+ requirements.run(:all_actions) unless @action == :nothing
+ requirements.run(@action)
+ end
+
+ def converge
+ converge_actions.converge!
+ if converge_actions.empty? && !@new_resource.updated_by_last_action?
+ events.resource_up_to_date(@new_resource, @action)
+ else
+ events.resource_updated(@new_resource, @action)
+ new_resource.updated_by_last_action(true)
+ end
+ end
+
+ def requirements
+ @requirements ||= ResourceRequirements.new(@new_resource, run_context)
+ end
+
+ protected
+
+ def converge_actions
+ @converge_actions ||= ConvergeActions.new(@new_resource, run_context, @action)
+ end
+
+ def converge_by(descriptions, &block)
+ converge_actions.add_action(descriptions, &block)
+ end
+
+
+ def recipe_eval(&block)
+ # This block has new resource definitions within it, which
+ # essentially makes it an in-line Chef run. Save our current
+ # run_context and create one anew, so the new Chef run only
+ # executes the embedded resources.
+ #
+ # TODO: timh,cw: 2010-5-14: This means that the resources within
+ # this block cannot interact with resources outside, e.g.,
+ # manipulating notifies.
+
+ converge_by ("would evaluate block and run any associated actions") do
+ saved_run_context = @run_context
+ @run_context = @run_context.dup
+ @run_context.resource_collection = Chef::ResourceCollection.new
+ instance_eval(&block)
+ Chef::Runner.new(@run_context).converge
+ @run_context = saved_run_context
+ end
+ end
+
+ public
+
+ class << self
+ include Chef::Mixin::ConvertToClassName
+
+ def build_from_file(cookbook_name, filename, run_context)
+ pname = filename_to_qualified_string(cookbook_name, filename)
+
+ # Add log entry if we override an existing light-weight provider.
+ class_name = convert_to_class_name(pname)
+ overriding = Chef::Provider.const_defined?(class_name)
+ Chef::Log.info("#{class_name} light-weight provider already initialized -- overriding!") if overriding
+
+ new_provider_class = Class.new self do |cls|
+
+ include Chef::DSL::Recipe
+
+ # These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
+ # They are not included by its replacment, Chef::DSL::Recipe, but
+ # they may be used in existing LWRPs.
+ include Chef::DSL::PlatformIntrospection
+ include Chef::DSL::DataQuery
+
+ def load_current_resource
+ # silence Chef::Exceptions::Override exception
+ end
+
+ class << cls
+ include Chef::Mixin::FromFile
+
+ # setup DSL's shortcut methods
+ def action(name, &block)
+ define_method("action_#{name.to_s}") do
+ instance_eval(&block)
+ end
+ end
+ end
+
+ # load provider definition from file
+ cls.class_from_file(filename)
+ end
+
+ # register new class as a Chef::Provider
+ pname = filename_to_qualified_string(cookbook_name, filename)
+ class_name = convert_to_class_name(pname)
+ Chef::Provider.const_set(class_name, new_provider_class)
+ Chef::Log.debug("Loaded contents of #{filename} into a provider named #{pname} defined in Chef::Provider::#{class_name}")
+
+ new_provider_class
+ end
+ end
+
+ end
+end