summaryrefslogtreecommitdiff
path: root/lib/chef/resource.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/resource.rb')
-rw-r--r--lib/chef/resource.rb853
1 files changed, 853 insertions, 0 deletions
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
new file mode 100644
index 0000000000..9a1b983360
--- /dev/null
+++ b/lib/chef/resource.rb
@@ -0,0 +1,853 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2008 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/params_validate'
+require 'chef/mixin/check_helper'
+require 'chef/dsl/platform_introspection'
+require 'chef/mixin/convert_to_class_name'
+require 'chef/resource/conditional'
+require 'chef/resource_collection'
+require 'chef/resource_platform_map'
+require 'chef/node'
+
+require 'chef/mixin/deprecation'
+
+class Chef
+ class Resource
+ class Notification < Struct.new(:resource, :action, :notifying_resource)
+
+ def duplicates?(other_notification)
+ unless other_notification.respond_to?(:resource) && other_notification.respond_to?(:action)
+ msg = "only duck-types of Chef::Resource::Notification can be checked for duplication "\
+ "you gave #{other_notification.inspect}"
+ raise ArgumentError, msg
+ end
+ other_notification.resource == resource && other_notification.action == action
+ end
+
+ # If resource and/or notifying_resource is not a resource object, this will look them up in the resource collection
+ # and fix the references from strings to actual Resource objects.
+ def resolve_resource_reference(resource_collection)
+ return resource if resource.kind_of?(Chef::Resource) && notifying_resource.kind_of?(Chef::Resource)
+
+ if not(resource.kind_of?(Chef::Resource))
+ fix_resource_reference(resource_collection)
+ end
+
+ if not(notifying_resource.kind_of?(Chef::Resource))
+ fix_notifier_reference(resource_collection)
+ end
+ end
+
+ # This will look up the resource if it is not a Resource Object. It will complain if it finds multiple
+ # resources, can't find a resource, or gets invalid syntax.
+ def fix_resource_reference(resource_collection)
+ 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, "\
+ "but can only notify one resource. Notifying resource was defined on #{notifying_resource.source_line}"
+ raise Chef::Exceptions::InvalidResourceReference, msg
+ end
+ 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
+ rescue Chef::Exceptions::InvalidResourceSpecification => e
+ err = Chef::Exceptions::InvalidResourceSpecification.new(<<-F)
+Resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \
+but #{resource.inspect} is not valid syntax to look up a resource in the resource collection. Notification \
+is defined near #{notifying_resource.source_line}
+F
+ err.set_backtrace(e.backtrace)
+ raise err
+ end
+
+ # This will look up the notifying_resource if it is not a Resource Object. It will complain if it finds multiple
+ # resources, can't find a resource, or gets invalid syntax.
+ def fix_notifier_reference(resource_collection)
+ matching_notifier = resource_collection.find(notifying_resource)
+ if Array(matching_notifier).size > 1
+ msg = "Notification #{self} from #{notifying_resource} was created with a reference to multiple notifying "\
+ "resources, but can only originate from one resource. Destination resource was defined "\
+ "on #{resource.source_line}"
+ raise Chef::Exceptions::InvalidResourceReference, msg
+ end
+ self.notifying_resource = matching_notifier
+
+ rescue Chef::Exceptions::ResourceNotFound => e
+ err = Chef::Exceptions::ResourceNotFound.new(<<-FAIL)
+Resource #{resource} is configured to receive notifications from #{notifying_resource} with action #{action}, \
+but #{notifying_resource} cannot be found in the resource collection. #{resource} is defined in \
+#{resource.source_line}
+FAIL
+ err.set_backtrace(e.backtrace)
+ raise err
+ rescue Chef::Exceptions::InvalidResourceSpecification => e
+ err = Chef::Exceptions::InvalidResourceSpecification.new(<<-F)
+Resource #{resource} is configured to receive notifications from #{notifying_resource} with action #{action}, \
+but #{notifying_resource.inspect} is not valid syntax to look up a resource in the resource collection. Notification \
+is defined near #{resource.source_line}
+F
+ err.set_backtrace(e.backtrace)
+ raise err
+ end
+
+ end
+
+ FORBIDDEN_IVARS = [:@run_context, :@node, :@not_if, :@only_if, :@enclosing_provider]
+ HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@node, :@not_if, :@only_if, :@elapsed_time, :@enclosing_provider]
+
+ include Chef::Mixin::CheckHelper
+ include Chef::Mixin::ParamsValidate
+ include Chef::DSL::PlatformIntrospection
+ include Chef::Mixin::ConvertToClassName
+ include Chef::Mixin::Deprecation
+
+
+ # Set or return the list of "state attributes" implemented by the Resource
+ # subclass. State attributes are attributes that describe the desired state
+ # of the system, such as file permissions or ownership. In general, state
+ # attributes are attributes that could be populated by examining the state
+ # of the system (e.g., File.stat can tell you the permissions on an
+ # existing file). Contrarily, attributes that are not "state attributes"
+ # usually modify the way Chef itself behaves, for example by providing
+ # additional options for a package manager to use when installing a
+ # package.
+ #
+ # This list is used by the Chef client auditing system to extract
+ # information from resources to describe changes made to the system.
+ def self.state_attrs(*attr_names)
+ @state_attrs ||= []
+ @state_attrs = attr_names unless attr_names.empty?
+
+ # Return *all* state_attrs that this class has, including inherited ones
+ if superclass.respond_to?(:state_attrs)
+ superclass.state_attrs + @state_attrs
+ else
+ @state_attrs
+ end
+ end
+
+ # Set or return the "identity attribute" for this resource class. This is
+ # generally going to be the "name attribute" for this resource. In other
+ # words, the resource type plus this attribute uniquely identify a given
+ # bit of state that chef manages. For a File resource, this would be the
+ # path, for a package resource, it will be the package name. This will show
+ # up in chef-client's audit records as a searchable field.
+ def self.identity_attr(attr_name=nil)
+ @identity_attr ||= nil
+ @identity_attr = attr_name if attr_name
+
+ # If this class doesn't have an identity attr, we'll defer to the superclass:
+ if @identity_attr || !superclass.respond_to?(:identity_attr)
+ @identity_attr
+ else
+ superclass.identity_attr
+ end
+ end
+
+ def self.dsl_name
+ convert_to_snake_case(name, 'Chef::Resource')
+ end
+
+ attr_accessor :params
+ attr_accessor :provider
+ attr_accessor :allowed_actions
+ attr_accessor :run_context
+ attr_accessor :cookbook_name
+ attr_accessor :recipe_name
+ attr_accessor :enclosing_provider
+ attr_accessor :source_line
+ attr_accessor :retries
+ attr_accessor :retry_delay
+
+ attr_reader :updated
+
+ attr_reader :resource_name
+ attr_reader :not_if_args
+ attr_reader :only_if_args
+
+ attr_reader :elapsed_time
+
+ # Each notify entry is a resource/action pair, modeled as an
+ # Struct with a #resource and #action member
+
+ def initialize(name, run_context=nil)
+ @name = name
+ @run_context = run_context
+ @noop = nil
+ @before = nil
+ @params = Hash.new
+ @provider = nil
+ @allowed_actions = [ :nothing ]
+ @action = :nothing
+ @updated = false
+ @updated_by_last_action = false
+ @supports = {}
+ @ignore_failure = false
+ @retries = 0
+ @retry_delay = 2
+ @not_if = []
+ @only_if = []
+ @source_line = nil
+ @elapsed_time = 0
+
+ @node = run_context ? deprecated_ivar(run_context.node, :node, :warn) : nil
+ end
+
+ # Returns a Hash of attribute => value for the state attributes declared in
+ # the resource's class definition.
+ def state
+ self.class.state_attrs.inject({}) do |state_attrs, attr_name|
+ state_attrs[attr_name] = send(attr_name)
+ state_attrs
+ end
+ end
+
+ # Returns the value of the identity attribute, if declared. Falls back to
+ # #name if no identity attribute is declared.
+ def identity
+ if identity_attr = self.class.identity_attr
+ send(identity_attr)
+ else
+ name
+ end
+ end
+
+
+ def updated=(true_or_false)
+ Chef::Log.warn("Chef::Resource#updated=(true|false) is deprecated. Please call #updated_by_last_action(true|false) instead.")
+ Chef::Log.warn("Called from:")
+ caller[0..3].each {|line| Chef::Log.warn(line)}
+ updated_by_last_action(true_or_false)
+ @updated = true_or_false
+ end
+
+ def node
+ run_context && run_context.node
+ end
+
+ # If an unknown method is invoked, determine whether the enclosing Provider's
+ # lexical scope can fulfill the request. E.g. This happens when the Resource's
+ # block invokes new_resource.
+ def method_missing(method_symbol, *args, &block)
+ if enclosing_provider && enclosing_provider.respond_to?(method_symbol)
+ enclosing_provider.send(method_symbol, *args, &block)
+ else
+ raise NoMethodError, "undefined method `#{method_symbol.to_s}' for #{self.class.to_s}"
+ end
+ end
+
+ def load_prior_resource
+ begin
+ prior_resource = run_context.resource_collection.lookup(self.to_s)
+ Chef::Log.debug("Setting #{self.to_s} to the state of the prior #{self.to_s}")
+ prior_resource.instance_variables.each do |iv|
+ unless iv.to_sym == :@source_line || iv.to_sym == :@action
+ self.instance_variable_set(iv, prior_resource.instance_variable_get(iv))
+ end
+ end
+ true
+ rescue Chef::Exceptions::ResourceNotFound
+ true
+ end
+ end
+
+ def supports(args={})
+ if args.any?
+ @supports = args
+ else
+ @supports
+ end
+ end
+
+ def provider(arg=nil)
+ klass = if arg.kind_of?(String) || arg.kind_of?(Symbol)
+ lookup_provider_constant(arg)
+ else
+ arg
+ end
+ set_or_return(
+ :provider,
+ klass,
+ :kind_of => [ Class ]
+ )
+ end
+
+ def action(arg=nil)
+ if arg
+ action_list = arg.kind_of?(Array) ? arg : [ arg ]
+ action_list = action_list.collect { |a| a.to_sym }
+ action_list.each do |action|
+ validate(
+ {
+ :action => action,
+ },
+ {
+ :action => { :kind_of => Symbol, :equal_to => @allowed_actions },
+ }
+ )
+ end
+ @action = action_list
+ else
+ @action
+ end
+ end
+
+ def name(name=nil)
+ set_if_args(@name, name) do
+ raise ArgumentError, "name must be a string!" unless name.kind_of?(String)
+ @name = name
+ end
+ end
+
+ def noop(tf=nil)
+ set_if_args(@noop, tf) do
+ raise ArgumentError, "noop must be true or false!" unless tf == true || tf == false
+ @noop = tf
+ end
+ end
+
+ def ignore_failure(arg=nil)
+ set_or_return(
+ :ignore_failure,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
+ def retries(arg=nil)
+ set_or_return(
+ :retries,
+ arg,
+ :kind_of => Integer
+ )
+ end
+
+ def retry_delay(arg=nil)
+ set_or_return(
+ :retry_delay,
+ arg,
+ :kind_of => Integer
+ )
+ end
+
+ def epic_fail(arg=nil)
+ ignore_failure(arg)
+ end
+
+ # Sets up a notification from this resource to the resource specified by +resource_spec+.
+ def notifies(action, resource_spec, timing=:delayed)
+ # when using old-style resources(:template => "/foo.txt") style, you
+ # could end up with multiple resources.
+ resources = [ resource_spec ].flatten
+ resources.each do |resource|
+ case timing.to_s
+ when 'delayed'
+ notifies_delayed(action, resource)
+ when 'immediate', 'immediately'
+ notifies_immediately(action, resource)
+ else
+ raise ArgumentError, "invalid timing: #{timing} for notifies(#{action}, #{resources.inspect}, #{timing}) resource #{self} "\
+ "Valid timings are: :delayed, :immediate, :immediately"
+ end
+ end
+
+ true
+ end
+
+ # 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
+ run_context.immediate_notifications(self).each { |n| n.resolve_resource_reference(run_context.resource_collection) }
+ run_context.delayed_notifications(self).each {|n| n.resolve_resource_reference(run_context.resource_collection) }
+ end
+
+ def notifies_immediately(action, resource_spec)
+ run_context.notifies_immediately(Notification.new(resource_spec, action, self))
+ end
+
+ def notifies_delayed(action, resource_spec)
+ run_context.notifies_delayed(Notification.new(resource_spec, action, self))
+ end
+
+ def immediate_notifications
+ run_context.immediate_notifications(self)
+ end
+
+ def delayed_notifications
+ run_context.delayed_notifications(self)
+ end
+
+ def resources(*args)
+ run_context.resource_collection.find(*args)
+ end
+
+ def subscribes(action, resources, timing=:delayed)
+ resources = [resources].flatten
+ resources.each do |resource|
+ if resource.is_a?(String)
+ resource = Chef::Resource.new(resource, run_context)
+ end
+ if resource.run_context.nil?
+ resource.run_context = run_context
+ end
+ resource.notifies(action, self, timing)
+ end
+ true
+ end
+
+ def is(*args)
+ if args.size == 1
+ args.first
+ else
+ return *args
+ end
+ end
+
+ def to_s
+ "#{@resource_name}[#{@name}]"
+ end
+
+ def to_text
+ ivars = instance_variables.map { |ivar| ivar.to_sym } - HIDDEN_IVARS
+ text = "# Declared in #{@source_line}\n\n"
+ text << self.class.dsl_name + "(\"#{name}\") do\n"
+ ivars.each do |ivar|
+ if (value = instance_variable_get(ivar)) && !(value.respond_to?(:empty?) && value.empty?)
+ value_string = value.respond_to?(:to_text) ? value.to_text : value.inspect
+ text << " #{ivar.to_s.sub(/^@/,'')} #{value_string}\n"
+ end
+ end
+ [@not_if, @only_if].flatten.each do |conditional|
+ text << " #{conditional.to_text}\n"
+ end
+ text << "end\n"
+ end
+
+ def inspect
+ ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS
+ ivars.inject("<#{to_s}") do |str, ivar|
+ str << " #{ivar}: #{instance_variable_get(ivar).inspect}"
+ end << ">"
+ end
+
+ # as_json does most of the to_json heavy lifted. It exists here in case activesupport
+ # is loaded. activesupport will call as_json and skip over to_json. This ensure
+ # json is encoded as expected
+ def as_json(*a)
+ safe_ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS
+ instance_vars = Hash.new
+ safe_ivars.each do |iv|
+ instance_vars[iv.to_s.sub(/^@/, '')] = instance_variable_get(iv)
+ end
+ {
+ 'json_class' => self.class.name,
+ 'instance_vars' => instance_vars
+ }
+ end
+
+ # Serialize this object as a hash
+ def to_json(*a)
+ results = as_json
+ results.to_json(*a)
+ end
+
+ def to_hash
+ safe_ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS
+ instance_vars = Hash.new
+ safe_ivars.each do |iv|
+ key = iv.to_s.sub(/^@/,'').to_sym
+ instance_vars[key] = instance_variable_get(iv)
+ end
+ instance_vars
+ end
+
+ # If command is a block, returns true if the block returns true, false if it returns false.
+ # ("Only run this resource if the block is true")
+ #
+ # If the command is not a block, executes the command. If it returns any status other than
+ # 0, it returns false (clearly, a 0 status code is true)
+ #
+ # === Parameters
+ # command<String>:: A a string to execute.
+ # opts<Hash>:: Options control the execution of the command
+ # block<Proc>:: A ruby block to run. Ignored if a command is given.
+ #
+ # === Evaluation
+ # * evaluates to true if the block is true, or if the command returns 0
+ # * evaluates to false if the block is false, or if the command returns a non-zero exit code.
+ def only_if(command=nil, opts={}, &block)
+ if command || block_given?
+ @only_if << Conditional.only_if(command, opts, &block)
+ end
+ @only_if
+ end
+
+ # If command is a block, returns false if the block returns true, true if it returns false.
+ # ("Do not run this resource if the block is true")
+ #
+ # If the command is not a block, executes the command. If it returns a 0 exitstatus, returns false.
+ # ("Do not run this resource if the command returns 0")
+ #
+ # === Parameters
+ # command<String>:: A a string to execute.
+ # opts<Hash>:: Options control the execution of the command
+ # block<Proc>:: A ruby block to run. Ignored if a command is given.
+ #
+ # === Evaluation
+ # * evaluates to true if the block is false, or if the command returns a non-zero exit status.
+ # * evaluates to false if the block is true, or if the command returns a 0 exit status.
+ def not_if(command=nil, opts={}, &block)
+ if command || block_given?
+ @not_if << Conditional.not_if(command, opts, &block)
+ end
+ @not_if
+ end
+
+ def defined_at
+ if cookbook_name && recipe_name && source_line
+ "#{cookbook_name}::#{recipe_name} line #{source_line.split(':')[1]}"
+ elsif source_line
+ file, line_no = source_line.split(':')
+ "#{file} line #{line_no}"
+ else
+ "dynamically defined"
+ end
+ end
+
+ def cookbook_version
+ if cookbook_name
+ run_context.cookbook_collection[cookbook_name]
+ end
+ end
+
+ def events
+ run_context.events
+ end
+
+ def run_action(action, notification_type=nil, notifying_resource=nil)
+ # reset state in case of multiple actions on the same resource.
+ @elapsed_time = 0
+ start_time = Time.now
+ events.resource_action_start(self, action, notification_type, notifying_resource)
+ # Try to resolve lazy/forward references in notifications again to handle
+ # the case where the resource was defined lazily (ie. in a ruby_block)
+ resolve_notification_references
+ validate_action(action)
+
+ if Chef::Config[:verbose_logging] || Chef::Log.level == :debug
+ # This can be noisy
+ Chef::Log.info("Processing #{self} action #{action} (#{defined_at})")
+ end
+
+ # ensure that we don't leave @updated_by_last_action set to true
+ # on accident
+ updated_by_last_action(false)
+
+ begin
+ return if should_skip?(action)
+ provider_for_action(action).run_action
+ rescue Exception => e
+ if ignore_failure
+ Chef::Log.error("#{self} (#{defined_at}) had an error: #{e.message}; ignore_failure is set, continuing")
+ events.resource_failed(self, action, e)
+ elsif retries > 0
+ events.resource_failed_retriable(self, action, retries, e)
+ @retries -= 1
+ Chef::Log.info("Retrying execution of #{self}, #{retries} attempt(s) left")
+ sleep retry_delay
+ retry
+ else
+ events.resource_failed(self, action, e)
+ raise customize_exception(e)
+ end
+ ensure
+ @elapsed_time = Time.now - start_time
+ events.resource_completed(self)
+ end
+ end
+
+ def validate_action(action)
+ raise ArgumentError, "nil is not a valid action for resource #{self}" if action.nil?
+ end
+
+ def provider_for_action(action)
+ # leverage new platform => short_name => resource
+ # which requires explicitly setting provider in
+ # resource class
+ if self.provider
+ provider = self.provider.new(self, self.run_context)
+ provider.action = action
+ provider
+ else # fall back to old provider resolution
+ Chef::Platform.provider_for_resource(self, action)
+ end
+ end
+
+ def customize_exception(e)
+ new_exception = e.exception("#{self} (#{defined_at}) had an error: #{e.class.name}: #{e.message}")
+ new_exception.set_backtrace(e.backtrace)
+ new_exception
+ end
+ # Evaluates not_if and only_if conditionals. Returns a falsey value if any
+ # of the conditionals indicate that this resource should be skipped, i.e.,
+ # if an only_if evaluates to false or a not_if evaluates to true.
+ #
+ # If this resource should be skipped, returns the first conditional that
+ # "fails" its check. Subsequent conditionals are not evaluated, so in
+ # general it's not a good idea to rely on side effects from not_if or
+ # only_if commands/blocks being evaluated.
+ def should_skip?(action)
+ conditionals = only_if + not_if
+ return false if conditionals.empty?
+
+ conditionals.find do |conditional|
+ if conditional.continue?
+ false
+ else
+ events.resource_skipped(self, action, conditional)
+ Chef::Log.debug("Skipping #{self} due to #{conditional.description}")
+ true
+ end
+ end
+ end
+
+ def updated_by_last_action(true_or_false)
+ @updated ||= true_or_false
+ @updated_by_last_action = true_or_false
+ end
+
+ def updated_by_last_action?
+ @updated_by_last_action
+ end
+
+ def updated?
+ updated
+ end
+
+ def self.json_create(o)
+ resource = self.new(o["instance_vars"]["@name"])
+ o["instance_vars"].each do |k,v|
+ resource.instance_variable_set("@#{k}".to_sym, v)
+ end
+ resource
+ end
+
+ # Hook to allow a resource to run specific code after creation
+ def after_created
+ nil
+ end
+
+ extend Chef::Mixin::ConvertToClassName
+
+ def self.attribute(attr_name, validation_opts={})
+ # This atrocity is the only way to support 1.8 and 1.9 at the same time
+ # When you're ready to drop 1.8 support, do this:
+ # define_method attr_name.to_sym do |arg=nil|
+ # etc.
+ shim_method=<<-SHIM
+ def #{attr_name}(arg=nil)
+ _set_or_return_#{attr_name}(arg)
+ end
+ SHIM
+ class_eval(shim_method)
+
+ define_method("_set_or_return_#{attr_name.to_s}".to_sym) do |arg|
+ set_or_return(attr_name.to_sym, arg, validation_opts)
+ end
+ end
+
+ def self.build_from_file(cookbook_name, filename, run_context)
+ rname = filename_to_qualified_string(cookbook_name, filename)
+
+ # Add log entry if we override an existing light-weight resource.
+ class_name = convert_to_class_name(rname)
+ overriding = Chef::Resource.const_defined?(class_name)
+ Chef::Log.info("#{class_name} light-weight resource already initialized -- overriding!") if overriding
+
+ new_resource_class = Class.new self do |cls|
+
+ # default initialize method that ensures that when initialize is finally
+ # wrapped (see below), super is called in the event that the resource
+ # definer does not implement initialize
+ def initialize(name, run_context)
+ super(name, run_context)
+ end
+
+ @actions_to_create = []
+
+ class << cls
+ include Chef::Mixin::FromFile
+
+ attr_accessor :run_context
+ attr_reader :action_to_set_default
+
+ def node
+ self.run_context.node
+ end
+
+ def actions_to_create
+ @actions_to_create
+ end
+
+ define_method(:default_action) do |action_name|
+ actions_to_create.push(action_name)
+ @action_to_set_default = action_name
+ end
+
+ define_method(:actions) do |*action_names|
+ actions_to_create.push(*action_names)
+ end
+ end
+
+ # set the run context in the class instance variable
+ cls.run_context = run_context
+
+ # load resource definition from file
+ cls.class_from_file(filename)
+
+ # create a new constructor that wraps the old one and adds the actions
+ # specified in the DSL
+ old_init = instance_method(:initialize)
+
+ define_method(:initialize) do |name, *optional_args|
+ args_run_context = optional_args.shift
+ @resource_name = rname.to_sym
+ old_init.bind(self).call(name, args_run_context)
+ @action = self.class.action_to_set_default || @action
+ allowed_actions.push(self.class.actions_to_create).flatten!
+ end
+ end
+
+ # register new class as a Chef::Resource
+ class_name = convert_to_class_name(rname)
+ Chef::Resource.const_set(class_name, new_resource_class)
+ Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}")
+
+ new_resource_class
+ end
+
+ # Resources that want providers namespaced somewhere other than
+ # Chef::Provider can set the namespace with +provider_base+
+ # Ex:
+ # class MyResource < Chef::Resource
+ # provider_base Chef::Provider::Deploy
+ # # ...other stuff
+ # end
+ def self.provider_base(arg=nil)
+ @provider_base ||= arg
+ @provider_base ||= Chef::Provider
+ end
+
+ def self.platform_map
+ @@platform_map ||= PlatformMap.new
+ end
+
+ # Maps a short_name (and optionally a platform and version) to a
+ # Chef::Resource. This allows finer grained per platform resource
+ # attributes and the end of overloaded resource definitions
+ # (I'm looking at you Chef::Resource::Package)
+ # Ex:
+ # class WindowsFile < Chef::Resource
+ # provides :file, :on_platforms => ["windows"]
+ # # ...other stuff
+ # end
+ #
+ # TODO: 2011-11-02 schisamo - platform_version support
+ def self.provides(short_name, opts={})
+ short_name_sym = short_name
+ if short_name.kind_of?(String)
+ short_name.downcase!
+ short_name.gsub!(/\s/, "_")
+ short_name_sym = short_name.to_sym
+ end
+ if opts.has_key?(:on_platforms)
+ platforms = [opts[:on_platforms]].flatten
+ platforms.each do |p|
+ p = :default if :all == p.to_sym
+ platform_map.set(
+ :platform => p.to_sym,
+ :short_name => short_name_sym,
+ :resource => self
+ )
+ end
+ else
+ platform_map.set(
+ :short_name => short_name_sym,
+ :resource => self
+ )
+ end
+ end
+
+ # Returns a resource based on a short_name anda platform and version.
+ #
+ #
+ # ==== Parameters
+ # short_name<Symbol>:: short_name of the resource (ie :directory)
+ # platform<Symbol,String>:: platform name
+ # version<String>:: platform version
+ #
+ # === Returns
+ # <Chef::Resource>:: returns the proper Chef::Resource class
+ def self.resource_for_platform(short_name, platform=nil, version=nil)
+ platform_map.get(short_name, platform, version)
+ end
+
+ # Returns a resource based on a short_name and a node's
+ # platform and version.
+ #
+ # ==== Parameters
+ # short_name<Symbol>:: short_name of the resource (ie :directory)
+ # node<Chef::Node>:: Node object to look up platform and version in
+ #
+ # === Returns
+ # <Chef::Resource>:: returns the proper Chef::Resource class
+ def self.resource_for_node(short_name, node)
+ begin
+ platform, version = Chef::Platform.find_platform_and_version(node)
+ rescue ArgumentError
+ end
+ resource = resource_for_platform(short_name, platform, version)
+ resource
+ end
+
+ private
+
+ def lookup_provider_constant(name)
+ begin
+ self.class.provider_base.const_get(convert_to_class_name(name.to_s))
+ rescue NameError => e
+ if e.to_s =~ /#{Regexp.escape(self.class.provider_base.to_s)}/
+ raise ArgumentError, "No provider found to match '#{name}'"
+ else
+ raise e
+ end
+ end
+ end
+
+ end
+end