summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorRanjib Dey <ranjib@pagerduty.com>2015-06-23 23:13:12 -0700
committerRanjib Dey <ranjib@pagerduty.com>2015-06-24 10:12:45 -0700
commit720f3331f794a2ad31bee2b1113ac99fada85389 (patch)
tree4f03dfd86d9fb543cb2eaf1b6a4c3848d356b11f /lib
parent9f0ea8aa0ec05819e242dedaa85fe731dca3146c (diff)
parentab34e3cd83d545b5da19113d723eeebcab1e77e2 (diff)
downloadchef-720f3331f794a2ad31bee2b1113ac99fada85389.tar.gz
Merge remote-tracking branch 'origin/master' into chef_handler
Diffstat (limited to 'lib')
-rw-r--r--lib/chef/chef_class.rb8
-rw-r--r--lib/chef/client.rb18
-rw-r--r--lib/chef/dsl/recipe.rb22
-rw-r--r--lib/chef/event_dispatch/base.rb56
-rw-r--r--lib/chef/event_dispatch/dsl.rb13
-rw-r--r--lib/chef/mixin/params_validate.rb479
-rw-r--r--lib/chef/node_map.rb101
-rw-r--r--lib/chef/platform/priority_map.rb54
-rw-r--r--lib/chef/platform/provider_mapping.rb4
-rw-r--r--lib/chef/platform/provider_priority_map.rb24
-rw-r--r--lib/chef/platform/resource_priority_map.rb27
-rw-r--r--lib/chef/provider.rb143
-rw-r--r--lib/chef/provider/deploy.rb8
-rw-r--r--lib/chef/provider/lwrp_base.rb76
-rw-r--r--lib/chef/provider/service/debian.rb2
-rw-r--r--lib/chef/provider/service/insserv.rb2
-rw-r--r--lib/chef/provider/service/invokercd.rb2
-rw-r--r--lib/chef/provider/service/redhat.rb2
-rw-r--r--lib/chef/provider/service/upstart.rb2
-rw-r--r--lib/chef/provider/user.rb2
-rw-r--r--lib/chef/recipe.rb9
-rw-r--r--lib/chef/resource.rb281
-rw-r--r--lib/chef/resource/lwrp_base.rb11
-rw-r--r--lib/chef/resource/macports_package.rb3
-rw-r--r--lib/chef/resource/package.rb5
-rw-r--r--lib/chef/resource_resolver.rb1
-rw-r--r--lib/chef/run_context.rb480
-rw-r--r--lib/chef/version.rb2
28 files changed, 1351 insertions, 486 deletions
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb
index d9c141b85d..c017fb157c 100644
--- a/lib/chef/chef_class.rb
+++ b/lib/chef/chef_class.rb
@@ -65,7 +65,7 @@ class Chef
# @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node
#
def get_provider_priority_array(resource_name)
- result = provider_priority_map.get_priority_array(node, resource_name)
+ result = provider_priority_map.get_priority_array(node, resource_name.to_sym)
result = result.dup if result
result
end
@@ -78,7 +78,7 @@ class Chef
# @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node
#
def get_resource_priority_array(resource_name)
- result = resource_priority_map.get_priority_array(node, resource_name)
+ result = resource_priority_map.get_priority_array(node, resource_name.to_sym)
result = result.dup if result
result
end
@@ -93,7 +93,7 @@ class Chef
# @return [Array<Class>] Modified Priority Array of Provider Classes to use for the resource_name on the node
#
def set_provider_priority_array(resource_name, priority_array, *filter, &block)
- result = provider_priority_map.set_priority_array(resource_name, priority_array, *filter, &block)
+ result = provider_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
result = result.dup if result
result
end
@@ -108,7 +108,7 @@ class Chef
# @return [Array<Class>] Modified Priority Array of Resource Classes to use for the resource_name on the node
#
def set_resource_priority_array(resource_name, priority_array, *filter, &block)
- result = resource_priority_map.set_priority_array(resource_name, priority_array, *filter, &block)
+ result = resource_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
result = result.dup if result
result
end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 86e92585e3..3c86f52b4a 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -309,12 +309,18 @@ class Chef
# with the proper exit status code and everything gets raised
# as a RunFailedWrappingError
if run_error || converge_error || audit_error
- error = if run_error == converge_error
- Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
- else
- Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
- end
- error.fill_backtrace
+ error = if Chef::Config[:audit_mode] == :disabled
+ run_error || converge_error
+ else
+ e = if run_error == converge_error
+ Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
+ else
+ Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
+ end
+ e.fill_backtrace
+ e
+ end
+
Chef::Application.debug_stacktrace(error)
raise error
end
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index d69f0a8f11..d00d0df247 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 FullDSL
+ 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..50aee63450 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 (many per action)
+ # 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/event_dispatch/dsl.rb b/lib/chef/event_dispatch/dsl.rb
index 98854a4716..c6f21c9b45 100644
--- a/lib/chef/event_dispatch/dsl.rb
+++ b/lib/chef/event_dispatch/dsl.rb
@@ -26,15 +26,14 @@ class Chef
def initialize(name)
klass = Class.new(Chef::EventDispatch::Base) do
- def self.name
- @@name
- end
+ attr_reader :name
end
- klass.class_variable_set(:@@name, name)
@handler = klass.new
- # Use current event.register API to add anonymous handler if
- # run_context and associated event dispatcher is set, else fallback to
- # Chef::Config[:hanlder].
+ @handler.instance_variable_set(:@name, name)
+
+ # Use event.register API to add anonymous handler if Chef.run_context
+ # and associated event dispatcher is set, else fallback to
+ # Chef::Config[:hanlder]
if Chef.run_context && Chef.run_context.events
Chef::Log.debug("Registering handler '#{name}' using events api")
Chef.run_context.events.register(handler)
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index 78d72dc801..f7d52a19cf 100644
--- a/lib/chef/mixin/params_validate.rb
+++ b/lib/chef/mixin/params_validate.rb
@@ -16,6 +16,8 @@
# limitations under the License.
class Chef
+ NOT_PASSED = Object.new
+
class DelayedEvaluator < Proc
end
module Mixin
@@ -65,7 +67,7 @@ class Chef
true
when Hash
validation.each do |check, carg|
- check_method = "_pv_#{check.to_s}"
+ check_method = "_pv_#{check}"
if self.respond_to?(check_method, true)
self.send(check_method, opts, key, carg)
else
@@ -81,162 +83,407 @@ class Chef
DelayedEvaluator.new(&block)
end
- def set_or_return(symbol, arg, validation)
- iv_symbol = "@#{symbol.to_s}".to_sym
- if arg == nil && self.instance_variable_defined?(iv_symbol) == true
- ivar = self.instance_variable_get(iv_symbol)
- if(ivar.is_a?(DelayedEvaluator))
- validate({ symbol => ivar.call }, { symbol => validation })[symbol]
- else
- ivar
- end
+ def set_or_return(symbol, value, validation)
+ symbol = symbol.to_sym
+ iv_symbol = :"@#{symbol}"
+
+ # Steal default, coerce, name_property and required from validation
+ # so that we can handle the order in which they are applied
+ validation = validation.dup
+ if validation.has_key?(:default)
+ default = validation.delete(:default)
+ elsif validation.has_key?('default')
+ default = validation.delete('default')
else
- if(arg.is_a?(DelayedEvaluator))
- val = arg
+ default = NOT_PASSED
+ end
+ coerce = validation.delete(:coerce)
+ coerce ||= validation.delete('coerce')
+ name_property = validation.delete(:name_property)
+ name_property ||= validation.delete('name_property')
+ name_property ||= validation.delete(:name_attribute)
+ name_property ||= validation.delete('name_attribute')
+ required = validation.delete(:required)
+ required ||= validation.delete('required')
+
+ opts = {}
+ # If the user passed NOT_PASSED, or passed nil, then this is a get.
+ if value == NOT_PASSED || (value.nil? && !explicitly_allows_nil?(symbol, validation))
+
+ # Get the value if there is one
+ if self.instance_variable_defined?(iv_symbol)
+ opts[symbol] = self.instance_variable_get(iv_symbol)
+
+ # Handle lazy values
+ if opts[symbol].is_a?(DelayedEvaluator)
+ if opts[symbol].arity >= 1
+ opts[symbol] = opts[symbol].call(self)
+ else
+ opts[symbol] = opts[symbol].call
+ end
+
+ # Coerce and validate the default value
+ _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
+ _pv_coerce(opts, symbol, coerce) if coerce
+ validate(opts, { symbol => validation })
+ end
+
+ # Get the default value
else
- val = validate({ symbol => arg }, { symbol => validation })[symbol]
+ _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
+ _pv_default(opts, symbol, default) unless default == NOT_PASSED
+ _pv_name_property(opts, symbol, name_property)
+
+ if opts.has_key?(symbol)
+ # Handle lazy defaults.
+ if opts[symbol].is_a?(DelayedEvaluator)
+ if opts[symbol].arity >= 1
+ opts[symbol] = opts[symbol].call(self)
+ else
+ opts[symbol] = instance_eval(&opts[symbol])
+ end
+ end
- # Handle the case where the "default" was a DelayedEvaluator. In
- # this case, the block yields an optional parameter of +self+,
- # which is the equivalent of "new_resource"
- if val.is_a?(DelayedEvaluator)
- val = val.call(self)
+ # Coerce and validate the default value
+ _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
+ _pv_coerce(opts, symbol, coerce) if coerce
+ # We presently do not validate defaults, for backwards compatibility.
+# validate(opts, { symbol => validation })
+
+ # Defaults are presently "stickily" set on the instance
+ self.instance_variable_set(iv_symbol, opts[symbol])
end
end
- self.instance_variable_set(iv_symbol, val)
+
+ # Set the value
+ else
+ opts[symbol] = value
+ unless opts[symbol].is_a?(DelayedEvaluator)
+ # Coerce and validate the value
+ _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
+ _pv_coerce(opts, symbol, coerce) if coerce
+ validate(opts, { symbol => validation })
+ end
+
+ self.instance_variable_set(iv_symbol, opts[symbol])
end
+
+ opts[symbol]
end
private
- # Return the value of a parameter, or nil if it doesn't exist.
- def _pv_opts_lookup(opts, key)
- if opts.has_key?(key.to_s)
- opts[key.to_s]
- elsif opts.has_key?(key.to_sym)
- opts[key.to_sym]
- else
- nil
- end
+ def explicitly_allows_nil?(key, validation)
+ validation.has_key?(:is) && _pv_is({ key => nil }, key, validation[:is], raise_error: false)
+ end
+
+ # Return the value of a parameter, or nil if it doesn't exist.
+ def _pv_opts_lookup(opts, key)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s]
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym]
+ else
+ nil
+ end
+ end
+
+ # Raise an exception if the parameter is not found.
+ def _pv_required(opts, key, is_required=true, explicitly_allows_nil=false)
+ if is_required
+ return true if opts.has_key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?)
+ return true if opts.has_key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?)
+ raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
end
+ end
- # Raise an exception if the parameter is not found.
- def _pv_required(opts, key, is_required=true)
- if is_required
- if (opts.has_key?(key.to_s) && !opts[key.to_s].nil?) ||
- (opts.has_key?(key.to_sym) && !opts[key.to_sym].nil?)
- true
- else
- raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
- end
+ #
+ # List of things values must be equal to.
+ #
+ # Uses Ruby's `==` to evaluate (equal_to == value). At least one must
+ # match for the value to be valid.
+ #
+ # `nil` passes this validation automatically.
+ #
+ # @return [Array,nil] List of things values must be equal to, or nil if
+ # equal_to is unspecified.
+ #
+ def _pv_equal_to(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ to_be = Array(to_be)
+ to_be.each do |tb|
+ return true if value == tb
end
+ raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
end
+ end
- def _pv_equal_to(opts, key, to_be)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- passes = false
- Array(to_be).each do |tb|
- passes = true if value == tb
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
- end
+ #
+ # List of things values must be instances of.
+ #
+ # Uses value.kind_of?(kind_of) to evaluate. At least one must match for
+ # the value to be valid.
+ #
+ # `nil` automatically passes this validation.
+ #
+ def _pv_kind_of(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ to_be = Array(to_be)
+ to_be.each do |tb|
+ return true if value.kind_of?(tb)
end
+ raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
end
+ end
- # Raise an exception if the parameter is not a kind_of?(to_be)
- def _pv_kind_of(opts, key, to_be)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- passes = false
- Array(to_be).each do |tb|
- passes = true if value.kind_of?(tb)
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
+ #
+ # List of method names values must respond to.
+ #
+ # Uses value.respond_to?(respond_to) to evaluate. At least one must match
+ # for the value to be valid.
+ #
+ def _pv_respond_to(opts, key, method_name_list)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ Array(method_name_list).each do |method_name|
+ unless value.respond_to?(method_name)
+ raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
end
end
end
+ end
- # Raise an exception if the parameter does not respond to a given set of methods.
- def _pv_respond_to(opts, key, method_name_list)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- Array(method_name_list).each do |method_name|
- unless value.respond_to?(method_name)
- raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
+ #
+ # List of things that must not be true about the value.
+ #
+ # Calls `value.<thing>?` All responses must be false for the value to be
+ # valid.
+ # Values which do not respond to <thing>? are considered valid (because if
+ # a value doesn't respond to `:readable?`, then it probably isn't
+ # readable.)
+ #
+ # @example
+ # ```ruby
+ # property :x, cannot_be: [ :nil, :empty ]
+ # x [ 1, 2 ] #=> valid
+ # x 1 #=> valid
+ # x [] #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ def _pv_cannot_be(opts, key, predicate_method_base_name)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ Array(predicate_method_base_name).each do |method_name|
+ predicate_method = :"#{method_name}?"
+
+ if value.respond_to?(predicate_method)
+ if value.send(predicate_method)
+ raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
end
end
end
end
+ end
- # Assert that parameter returns false when passed a predicate method.
- # For example, :cannot_be => :blank will raise a Exceptions::ValidationFailed
- # error value.blank? returns a 'truthy' (not nil or false) value.
- #
- # Note, this will *PASS* if the object doesn't respond to the method.
- # So, to make sure a value is not nil and not blank, you need to do
- # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true)
- def _pv_cannot_be(opts, key, predicate_method_base_name)
- value = _pv_opts_lookup(opts, key)
- predicate_method = (predicate_method_base_name.to_s + "?").to_sym
-
- if value.respond_to?(predicate_method)
- if value.send(predicate_method)
- raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
- end
- end
+ #
+ # The default value for a property.
+ #
+ # When the property is not assigned, this will be used.
+ #
+ # If this is a lazy value, it will either be passed the resource as a value,
+ # or if the lazy proc does not take parameters, it will be run in the
+ # context of the instance with instance_eval.
+ #
+ # @example
+ # ```ruby
+ # property :x, default: 10
+ # ```
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, default: lazy { x+2 }
+ # ```
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, default: lazy { |r| r.x+2 }
+ # ```
+ #
+ def _pv_default(opts, key, default_value)
+ value = _pv_opts_lookup(opts, key)
+ if value.nil?
+ default_value = default_value.freeze if !default_value.is_a?(DelayedEvaluator)
+ opts[key] = default_value
end
+ end
- # Assign a default value to a parameter.
- def _pv_default(opts, key, default_value)
- value = _pv_opts_lookup(opts, key)
- if value == nil
- opts[key] = default_value
+ #
+ # List of regexes values that must match.
+ #
+ # Uses regex.match() to evaluate. At least one must match for the value to
+ # be valid.
+ #
+ # `nil` passes regex validation automatically.
+ #
+ # @example
+ # ```ruby
+ # property :x, regex: [ /abc/, /xyz/ ]
+ # ```
+ #
+ def _pv_regex(opts, key, regex)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ Array(regex).each do |r|
+ return true if r.match(value.to_s)
end
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
end
+ end
- # Check a parameter against a regular expression.
- def _pv_regex(opts, key, regex)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- passes = false
- [ regex ].flatten.each do |r|
- if value != nil
- if r.match(value.to_s)
- passes = true
- end
- end
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
+ #
+ # List of procs we pass the value to.
+ #
+ # All procs must return true for the value to be valid. If any procs do
+ # not return true, the key will be used for the message: `"Property x's
+ # value :y <message>"`.
+ #
+ # @example
+ # ```ruby
+ # property :x, callbacks: { "is bigger than 10" => proc { |v| v <= 10 }, "is not awesome" => proc { |v| !v.awesome }}
+ # ```
+ #
+ def _pv_callbacks(opts, key, callbacks)
+ raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ callbacks.each do |message, zeproc|
+ if zeproc.call(value) != true
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
end
end
end
+ end
- # Check a parameter against a hash of proc's.
- def _pv_callbacks(opts, key, callbacks)
- raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- callbacks.each do |message, zeproc|
- if zeproc.call(value) != true
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
- end
- end
+ #
+ # Allows a parameter to default to the value of the resource name.
+ #
+ # @example
+ # ```ruby
+ # property :x, name_property: true
+ # ```
+ #
+ def _pv_name_property(opts, key, is_name_property=true)
+ if is_name_property
+ if opts[key].nil?
+ opts[key] = self.instance_variable_get("@name")
end
end
+ end
+ alias :_pv_name_attribute :_pv_name_property
- # Allow a parameter to default to @name
- def _pv_name_attribute(opts, key, is_name_attribute=true)
- if is_name_attribute
- if opts[key] == nil
- opts[key] = self.instance_variable_get("@name")
- end
+ #
+ # List of valid things values can be.
+ #
+ # Uses Ruby's `===` to evaluate (is === value). At least one must match
+ # for the value to be valid.
+ #
+ # If a proc is passed, it is instance_eval'd in the resource, passed the
+ # value, and must return a truthy or falsey value.
+ #
+ # @example Class
+ # ```ruby
+ # property :x, String
+ # x 'valid' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ #
+ # @example Value
+ # ```ruby
+ # property :x, [ :a, :b, :c, nil ]
+ # x :a #=> valid
+ # x nil #=> valid
+ # ```
+ #
+ # @example Regex
+ # ```ruby
+ # property :x, /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example Proc
+ # ```ruby
+ # property :x, proc { |x| x > y }
+ # property :y, default: 2
+ # x 3 #=> valid
+ # x 1 #=> invalid
+ # ```
+ #
+ # @example PropertyType
+ # ```ruby
+ # type = PropertyType.new(is: String)
+ # property :x, type
+ # x 'foo' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example RSpec Matcher
+ # ```ruby
+ # include RSpec::Matchers
+ # property :x, a_string_matching /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ def _pv_is(opts, key, to_be, raise_error: true)
+ return true if !opts.has_key?(key.to_s) && !opts.has_key?(key.to_sym)
+ value = _pv_opts_lookup(opts, key)
+ to_be = [ to_be ].flatten(1)
+ to_be.each do |tb|
+ if tb.is_a?(Proc)
+ return true if instance_exec(value, &tb)
+ else
+ return true if tb === value
end
end
+
+ if raise_error
+ raise Exceptions::ValidationFailed, "Option #{key} must be one of: #{to_be.join(", ")}! You passed #{value.inspect}."
+ else
+ false
+ end
+ end
+
+ #
+ # Method to mess with a value before it is validated and stored.
+ #
+ # Allows you to transform values into a canonical form that is easy to
+ # work with.
+ #
+ # This is passed the value to transform, and is run in the context of the
+ # instance (so it has access to other resource properties). It must return
+ # the value that will be stored in the instance.
+ #
+ # @example
+ # ```ruby
+ # property :x, Integer, coerce: { |v| v.to_i }
+ # ```
+ #
+ def _pv_coerce(opts, key, coercer)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s] = instance_exec(opts[key], &coercer)
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym] = instance_exec(opts[key], &coercer)
+ end
+ end
end
end
end
-
diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb
index f547018a38..d5eed7c215 100644
--- a/lib/chef/node_map.rb
+++ b/lib/chef/node_map.rb
@@ -38,30 +38,35 @@ class Chef
#
# @return [NodeMap] Returns self for possible chaining
#
- def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, &block)
+ def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, override: nil, &block)
Chef::Log.deprecation "The on_platform option to node_map has been deprecated" if on_platform
Chef::Log.deprecation "The on_platforms option to node_map has been deprecated" if on_platforms
platform ||= on_platform || on_platforms
- filters = { platform: platform, platform_version: platform_version, platform_family: platform_family, os: os }
- new_matcher = { filters: filters, block: block, value: value, canonical: canonical }
- @map[key] ||= []
- # Decide where to insert the matcher; the new value is preferred over
- # anything more specific (see `priority_of`) and is preferred over older
- # values of the same specificity. (So all other things being equal,
- # newest wins.)
+ filters = {}
+ filters[:platform] = platform if platform
+ filters[:platform_version] = platform_version if platform_version
+ filters[:platform_family] = platform_family if platform_family
+ filters[:os] = os if os
+ new_matcher = { value: value, filters: filters }
+ new_matcher[:block] = block if block
+ new_matcher[:canonical] = canonical if canonical
+ new_matcher[:override] = override if override
+
+ # The map is sorted in order of preference already; we just need to find
+ # our place in it (just before the first value with the same preference level).
insert_at = nil
- @map[key].each_with_index do |matcher, index|
- if specificity(new_matcher) >= specificity(matcher)
- insert_at = index
- break
- end
+ @map[key] ||= []
+ @map[key].each_with_index do |matcher,index|
+ cmp = compare_matchers(key, new_matcher, matcher)
+ insert_at ||= index if cmp && cmp <= 0
end
if insert_at
@map[key].insert(insert_at, new_matcher)
else
@map[key] << new_matcher
end
- self
+ insert_at ||= @map[key].size - 1
+ @map
end
#
@@ -116,30 +121,7 @@ class Chef
remaining
end
- private
-
- #
- # Gives a value for "how specific" the matcher is.
- # Things which specify more specific filters get a higher number
- # (platform_version > platform > platform_family > os); things
- # with a block have higher specificity than similar things without
- # a block.
- #
- def specificity(matcher)
- if matcher[:filters][:platform_version]
- specificity = 8
- elsif matcher[:filters][:platform]
- specificity = 6
- elsif matcher[:filters][:platform_family]
- specificity = 4
- elsif matcher[:filters][:os]
- specificity = 2
- else
- specificity = 0
- end
- specificity += 1 if matcher[:block]
- specificity
- end
+ protected
#
# Succeeds if:
@@ -197,5 +179,48 @@ class Chef
return true if canonical.nil?
!!canonical == !!matcher[:canonical]
end
+
+ def compare_matchers(key, new_matcher, matcher)
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:block] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_version] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_family] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:os] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:override] }
+ return cmp if cmp != 0
+ # If all things are identical, return 0
+ 0
+ end
+
+ def compare_matcher_properties(new_matcher, matcher)
+ a = yield(new_matcher)
+ b = yield(matcher)
+
+ # Check for blcacklists ('!windows'). Those always come *after* positive
+ # whitelists.
+ a_negated = Array(a).any? { |f| f.is_a?(String) && f.start_with?('!') }
+ b_negated = Array(b).any? { |f| f.is_a?(String) && f.start_with?('!') }
+ if a_negated != b_negated
+ return 1 if a_negated
+ return -1 if b_negated
+ end
+
+ # We treat false / true and nil / not-nil with the same comparison
+ a = nil if a == false
+ b = nil if b == false
+ cmp = a <=> b
+ # This is the case where one is non-nil, and one is nil. The one that is
+ # nil is "greater" (i.e. it should come last).
+ if cmp.nil?
+ return 1 if a.nil?
+ return -1 if b.nil?
+ end
+ cmp
+ end
end
end
diff --git a/lib/chef/platform/priority_map.rb b/lib/chef/platform/priority_map.rb
new file mode 100644
index 0000000000..d559eece78
--- /dev/null
+++ b/lib/chef/platform/priority_map.rb
@@ -0,0 +1,54 @@
+require 'chef/node_map'
+
+class Chef
+ class Platform
+ class PriorityMap < Chef::NodeMap
+ def priority(resource_name, priority_array, *filter)
+ set_priority_array(resource_name.to_sym, priority_array, *filter)
+ end
+
+ # @api private
+ def get_priority_array(node, key)
+ get(node, key)
+ end
+
+ # @api private
+ def set_priority_array(key, priority_array, *filter, &block)
+ priority_array = Array(priority_array)
+ set(key, priority_array, *filter, &block)
+ priority_array
+ end
+
+ # @api private
+ def list_handlers(node, key, **filters)
+ list(node, key, **filters).flatten(1).uniq
+ end
+
+ #
+ # Priority maps have one extra precedence: priority arrays override "provides,"
+ # and "provides" lines with identical filters sort by class name (ascending).
+ #
+ def compare_matchers(key, new_matcher, matcher)
+ # Priority arrays come before "provides"
+ if new_matcher[:value].is_a?(Array) != matcher[:value].is_a?(Array)
+ return new_matcher[:value].is_a?(Array) ? -1 : 1
+ end
+
+ cmp = super
+ if cmp == 0
+ # Sort by class name (ascending) as well, if all other properties
+ # are exactly equal
+ if new_matcher[:value].is_a?(Class) && !new_matcher[:override]
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:value].name }
+ if cmp < 0
+ Chef::Log.warn "You are overriding #{key} on #{new_matcher[:filters].inspect} with #{new_matcher[:value].inspect}: used to be #{matcher[:value].inspect}. Use override: true if this is what you intended."
+ elsif cmp > 0
+ Chef::Log.warn "You declared a new resource #{new_matcher[:value].inspect} for resource #{key}, but it comes alphabetically after #{matcher[:value].inspect} and has the same filters (#{new_matcher[:filters].inspect}), so it will not be used. Use override: true if you want to use it for #{key}."
+ end
+ end
+ end
+ cmp
+ end
+ end
+ end
+end
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index e3a894c8ac..af17d8e1b4 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -201,8 +201,8 @@ class Chef
begin
result = Chef::Provider.const_get(class_name)
- Chef::Log.warn("Class Chef::Provider::#{class_name} does not declare 'provides #{convert_to_snake_case(class_name).to_sym.inspect}'.")
- Chef::Log.warn("This will no longer work in Chef 13: you must use 'provides' to provide DSL.")
+ Chef::Log.warn("Class Chef::Provider::#{class_name} does not declare 'resource_name #{convert_to_snake_case(class_name).to_sym.inspect}'.")
+ Chef::Log.warn("This will no longer work in Chef 13: you must use 'resource_name' to provide DSL.")
rescue NameError
end
end
diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb
index 9d703c9178..5599c74c2d 100644
--- a/lib/chef/platform/provider_priority_map.rb
+++ b/lib/chef/platform/provider_priority_map.rb
@@ -1,29 +1,11 @@
require 'singleton'
+require 'chef/platform/priority_map'
class Chef
class Platform
- class ProviderPriorityMap
+ # @api private
+ class ProviderPriorityMap < Chef::Platform::PriorityMap
include Singleton
-
- def get_priority_array(node, resource_name)
- priority_map.get(node, resource_name.to_sym)
- end
-
- def set_priority_array(resource_name, priority_array, *filter, &block)
- priority_map.set(resource_name.to_sym, Array(priority_array), *filter, &block)
- end
-
- # @api private
- def list_handlers(node, resource_name)
- priority_map.list(node, resource_name.to_sym).flatten(1).uniq
- end
-
- private
-
- def priority_map
- require 'chef/node_map'
- @priority_map ||= Chef::NodeMap.new
- end
end
end
end
diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb
index fb08debc53..aa57e3ddf0 100644
--- a/lib/chef/platform/resource_priority_map.rb
+++ b/lib/chef/platform/resource_priority_map.rb
@@ -1,34 +1,17 @@
require 'singleton'
+require 'chef/platform/priority_map'
class Chef
class Platform
- class ResourcePriorityMap
+ # @api private
+ class ResourcePriorityMap < Chef::Platform::PriorityMap
include Singleton
- def get_priority_array(node, resource_name, canonical: nil)
- priority_map.get(node, resource_name.to_sym, canonical: canonical)
- end
-
- def set_priority_array(resource_name, priority_array, *filter, &block)
- priority_map.set(resource_name.to_sym, Array(priority_array), *filter, &block)
- end
-
# @api private
- def delete_canonical(resource_name, resource_class)
- priority_map.delete_canonical(resource_name, resource_class)
- end
-
- # @api private
- def list_handlers(*args)
- priority_map.list(*args).flatten(1).uniq
+ def get_priority_array(node, resource_name, canonical: nil)
+ super(node, resource_name.to_sym, canonical: canonical)
end
- private
-
- def priority_map
- require 'chef/node_map'
- @priority_map ||= Chef::NodeMap.new
- end
end
end
end
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index e50e374804..280277d947 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}")
@@ -176,13 +176,128 @@ class Chef
end
def self.provides(short_name, opts={}, &block)
- Chef.set_provider_priority_array(short_name, self, opts, &block)
+ Chef.provider_priority_map.set(short_name, self, opts, &block)
end
def self.provides?(node, resource)
Chef::ProviderResolver.new(node, resource, :nothing).provided_by?(self)
end
+ #
+ # Include attributes, public and protected methods from this Resource in
+ # the provider.
+ #
+ # If this is set to true, delegate methods are included in the provider so
+ # that you can call (for example) `attrname` and it will call
+ # `new_resource.attrname`.
+ #
+ # 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::FullDSL
+ end
+
protected
def converge_actions
@@ -200,12 +315,14 @@ class Chef
# manipulating notifies.
converge_by ("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
+ saved_run_context = run_context
+ begin
+ @run_context = run_context.create_child
+ instance_eval(&block)
+ Chef::Runner.new(run_context).converge
+ ensure
+ @run_context = saved_run_context
+ end
end
end
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
index 19e7c01ab1..6d9b7f4397 100644
--- a/lib/chef/provider/deploy.rb
+++ b/lib/chef/provider/deploy.rb
@@ -373,11 +373,9 @@ class Chef
end
def gem_resource_collection_runner
- gems_collection = Chef::ResourceCollection.new
- gem_packages.each { |rbgem| gems_collection.insert(rbgem) }
- gems_run_context = run_context.dup
- gems_run_context.resource_collection = gems_collection
- Chef::Runner.new(gems_run_context)
+ 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 b5efbb284d..a96c382a01 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -28,52 +28,10 @@ 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, using
- # recipe_eval_with_update_check to execute the given block.
- def action(name, &block)
- define_method("action_#{name}") do
- recipe_eval_with_update_check(&block)
- end
- end
- end
-
- # Executes the given block in a temporary run_context with its own
- # resource collection. After the block is executed, any resources
- # declared inside are converged, and if any are updated, the
- # new_resource will be marked updated.
- def recipe_eval_with_update_check(&block)
- saved_run_context = @run_context
- temp_run_context = @run_context.dup
- @run_context = temp_run_context
- @run_context.resource_collection = Chef::ResourceCollection.new
-
- return_value = instance_eval(&block)
- Chef::Runner.new(@run_context).converge
- return_value
- ensure
- @run_context = saved_run_context
- if temp_run_context.resource_collection.any? {|r| r.updated? }
- new_resource.updated_by_last_action(true)
- end
- end
-
- end
-
include Chef::DSL::Recipe
# These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
- # They are not included by its replacment, Chef::DSL::Recipe, but
+ # They are not included by its replacement, Chef::DSL::Recipe, but
# they may be used in existing LWRPs.
include Chef::DSL::PlatformIntrospection
include Chef::DSL::DataQuery
@@ -122,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/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 01505924cb..c67f3f05da 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -25,8 +25,6 @@ class Chef
UPDATE_RC_D_ENABLED_MATCHES = /\/rc[\dS].d\/S|not installed/i
UPDATE_RC_D_PRIORITIES = /\/rc([\dS]).d\/([SK])(\d\d)/i
- provides :service, platform_family: "debian"
-
def self.provides?(node, resource)
super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian)
end
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
index 31965a4bc6..4534e33f32 100644
--- a/lib/chef/provider/service/insserv.rb
+++ b/lib/chef/provider/service/insserv.rb
@@ -24,8 +24,6 @@ class Chef
class Service
class Insserv < Chef::Provider::Service::Init
- provides :service, os: "linux"
-
def self.provides?(node, resource)
super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv)
end
diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb
index 5ff24e0dbb..fdf4cbc256 100644
--- a/lib/chef/provider/service/invokercd.rb
+++ b/lib/chef/provider/service/invokercd.rb
@@ -23,8 +23,6 @@ class Chef
class Service
class Invokercd < Chef::Provider::Service::Init
- provides :service, platform_family: "debian"
-
def self.provides?(node, resource)
super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd)
end
diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb
index 850953125e..a8deb13aec 100644
--- a/lib/chef/provider/service/redhat.rb
+++ b/lib/chef/provider/service/redhat.rb
@@ -26,8 +26,6 @@ class Chef
CHKCONFIG_ON = /\d:on/
CHKCONFIG_MISSING = /No such/
- provides :service, platform_family: [ "rhel", "fedora", "suse" ]
-
def self.provides?(node, resource)
super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
end
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
index 8d4aa41035..d8ce6af649 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -27,8 +27,6 @@ class Chef
class Upstart < Chef::Provider::Service::Simple
UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
- provides :service, os: "linux"
-
def self.provides?(node, resource)
super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
end
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
index ad92a72a0a..244b11db98 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -23,8 +23,6 @@ require 'etc'
class Chef
class Provider
class User < Chef::Provider
- provides :user
-
include Chef::Mixin::Command
attr_accessor :user_exists, :locked
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index b4d37c2d61..262560f754 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -36,14 +36,7 @@ class Chef
# A Recipe object is the context in which Chef recipes are evaluated.
class Recipe
- 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
+ include Chef::DSL::Recipe::FullDSL
include Chef::Mixin::FromFile
include Chef::Mixin::Deprecation
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 7fe8a52d95..8d2532dac4 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -58,8 +58,6 @@ class Chef
include Chef::Mixin::ShellOut
include Chef::Mixin::PowershellOut
- NULL_ARG = Object.new
-
#
# The node the current Chef run is using.
#
@@ -187,8 +185,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
@@ -531,9 +528,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.
@@ -688,26 +683,133 @@ class Chef
#
# The provider class for this resource.
#
+ # If `action :x do ... end` has been declared on this resource or its
+ # superclasses, this will return the `action_provider_class`.
+ #
# If this is not set, `provider_for_action` will dynamically determine the
# provider.
#
# @param arg [String, Symbol, Class] Sets the provider class for this resource.
# If passed a String or Symbol, e.g. `:file` or `"file"`, looks up the
# provider based on the name.
+ #
# @return The provider class for this resource.
#
+ # @see Chef::Resource.action_provider_class
+ #
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 ])
+ set_or_return(:provider, klass, kind_of: [ Class ]) ||
+ self.class.action_provider_class
end
def provider=(arg)
provider(arg)
end
+ #
+ # Create a property on this resource class.
+ #
+ # If a superclass has this property, or if this property has already been
+ # defined by this resource, this will *override* the previous value.
+ #
+ # @param name [Symbol] The name of the property.
+ # @param type [Object,Array<Object>] The type(s) of this property.
+ # If present, this is prepended to the `is` validation option.
+ # @param options [Hash<Symbol,Object>] Validation options.
+ # @option options [Object,Array] :is An object, or list of
+ # objects, that must match the value using Ruby's `===` operator
+ # (`options[:is].any? { |v| v === value }`).
+ # @option options [Object,Array] :equal_to An object, or list
+ # of objects, that must be equal to the value using Ruby's `==`
+ # operator (`options[:is].any? { |v| v == value }`)
+ # @option options [Regexp,Array<Regexp>] :regex An object, or
+ # list of objects, that must match the value with `regex.match(value)`.
+ # @option options [Class,Array<Class>] :kind_of A class, or
+ # list of classes, that the value must be an instance of.
+ # @option options [Hash<String,Proc>] :callbacks A hash of
+ # messages -> procs, all of which match the value. The proc must
+ # return a truthy or falsey value (true means it matches).
+ # @option options [Symbol,Array<Symbol>] :respond_to A method
+ # name, or list of method names, the value must respond to.
+ # @option options [Symbol,Array<Symbol>] :cannot_be A property,
+ # or a list of properties, that the value cannot have (such as `:nil` or
+ # `:empty`). The method with a questionmark at the end is called on the
+ # value (e.g. `value.empty?`). If the value does not have this method,
+ # it is considered valid (i.e. if you don't respond to `empty?` we
+ # assume you are not empty).
+ # @option options [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # @option options [Boolean] :required `true` if this property
+ # must be present; `false` otherwise. This is checked after the resource
+ # is fully initialized.
+ # @option options [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set.
+ # @option options [Boolean] :name_attribute Same as `name_property`.
+ # @option options [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties).
+ #
+ # @example Bare property
+ # property :x
+ #
+ # @example With just a type
+ # property :x, String
+ #
+ # @example With just options
+ # property :x, default: 'hi'
+ #
+ # @example With type and options
+ # property :x, String, default: 'hi'
+ #
+ def self.property(name, type=NOT_PASSED, **options)
+ name = name.to_sym
+
+ if type != NOT_PASSED
+ if options[:is]
+ options[:is] = ([ type ] + [ options[:is] ]).flatten(1)
+ else
+ options[:is] = type
+ end
+ end
+
+ define_method(name) do |value=NOT_PASSED|
+ set_or_return(name, value, options)
+ end
+ define_method("#{name}=") do |value|
+ set_or_return(name, value, options)
+ end
+ end
+
+ #
+ # Whether this property has been set (or whether it has a default that has
+ # been retrieved).
+ #
+ def property_is_set?(name)
+ name = name.to_sym
+ instance_variable_defined?("@#{name}")
+ end
+
+ #
+ # Create a lazy value for assignment to a default value.
+ #
+ # @param block The block to run when the value is retrieved.
+ #
+ # @return [Chef::DelayedEvaluator] The lazy value
+ #
+ def self.lazy(&block)
+ DelayedEvaluator.new(&block)
+ end
+
# 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
@@ -773,8 +875,8 @@ class Chef
# have.
#
attr_accessor :allowed_actions
- def allowed_actions(value=NULL_ARG)
- if value != NULL_ARG
+ def allowed_actions(value=NOT_PASSED)
+ if value != NOT_PASSED
self.allowed_actions = value
end
@allowed_actions
@@ -908,9 +1010,9 @@ class Chef
#
# @return [Symbol] The name of this resource type (e.g. `:execute`).
#
- def self.resource_name(name=NULL_ARG)
+ def self.resource_name(name=NOT_PASSED)
# Setter
- if name != NULL_ARG
+ if name != NOT_PASSED
remove_canonical_dsl
# Set the resource_name and call provides
@@ -924,13 +1026,7 @@ class Chef
else
@resource_name = nil
end
- else
- # set resource_name automatically if it's not set
- if !instance_variable_defined?(:@resource_name) && self.name
- resource_name convert_to_snake_case(self.name.split('::')[-1])
- end
end
-
@resource_name
end
def self.resource_name=(name)
@@ -938,6 +1034,19 @@ class Chef
end
#
+ # Use the class name as the resource name.
+ #
+ # Munges the last part of the class name from camel case to snake case,
+ # and sets the resource_name to that:
+ #
+ # A::B::BlahDBlah -> blah_d_blah
+ #
+ def self.use_automatic_resource_name
+ automatic_name = convert_to_snake_case(self.name.split('::')[-1])
+ resource_name automatic_name
+ end
+
+ #
# The module where Chef should look for providers for this resource.
# The provider for `MyResource` will be looked up using
# `provider_base::MyResource`. Defaults to `Chef::Provider`.
@@ -965,7 +1074,7 @@ class Chef
#
# @param actions [Array<Symbol>] The list of actions to add to allowed_actions.
#
- # @return [Arrau<Symbol>] The list of actions, as symbols.
+ # @return [Array<Symbol>] The list of actions, as symbols.
#
def self.allowed_actions(*actions)
@allowed_actions ||=
@@ -974,10 +1083,10 @@ class Chef
else
[ :nothing ]
end
- @allowed_actions |= actions
+ @allowed_actions |= actions.flatten
end
def self.allowed_actions=(value)
- @allowed_actions = value
+ @allowed_actions = value.uniq
end
#
@@ -993,8 +1102,8 @@ class Chef
#
# @return [Symbol,Array<Symbol>] The default actions for the resource.
#
- def self.default_action(action_name=NULL_ARG)
- unless action_name.equal?(NULL_ARG)
+ def self.default_action(action_name=NOT_PASSED)
+ unless action_name.equal?(NOT_PASSED)
if action_name.is_a?(Array)
@default_action = action_name.map { |arg| arg.to_sym }
else
@@ -1013,6 +1122,91 @@ class Chef
end
end
def self.default_action=(action_name)
+ default_action action_name
+ end
+
+ #
+ # Define an action on this resource.
+ #
+ # The action is defined as a *recipe* block that will be compiled and then
+ # 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.
+ #
+ # The first action defined (besides `:nothing`) will become the default
+ # action for the resource.
+ #
+ # @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
+ new_action_provider_class.action(action, &recipe_block)
+ self.allowed_actions += [ action ]
+ default_action action if default_action == :nothing
+ end
+
+ #
+ # The action provider class is an automatic `Provider` created to handle
+ # actions declared by `action :x do ... end`.
+ #
+ # This class will be returned by `resource.provider` if `resource.provider`
+ # is not set. `provider_for_action` will also use this instead of calling
+ # out to `Chef::ProviderResolver`.
+ #
+ # If the user has not declared actions on this class or its superclasses
+ # using `action :x do ... end`, then there is no need for this class and
+ # `action_provider_class` will be `nil`.
+ #
+ # @api private
+ #
+ def self.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
+ new_action_provider_class
+ end
+ end
+
+ #
+ # Ensure the action provider class actually gets created. This is called
+ # when the user does `action :x do ... end`.
+ #
+ # @api private
+ def self.new_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
+ def self.default_action=(action_name)
default_action(action_name)
end
@@ -1087,7 +1281,7 @@ class Chef
class << self
# back-compat
- # NOTE: that we do not support unregistering classes as descendents like
+ # NOTE: that we do not support unregistering classes as descendants like
# we used to for LWRP unloading because that was horrible and removed in
# Chef-12.
# @deprecated
@@ -1111,7 +1305,12 @@ class Chef
def self.inherited(child)
super
@sorted_descendants = nil
- child.resource_name
+ # set resource_name automatically if it's not set
+ if child.name && !child.resource_name
+ if child.name =~ /^Chef::Resource::(\w+)$/
+ child.resource_name(convert_to_snake_case($1))
+ end
+ end
end
@@ -1143,7 +1342,7 @@ class Chef
remove_canonical_dsl
end
- result = Chef.set_resource_priority_array(name, self, options, &block)
+ result = Chef.resource_priority_map.set(name, self, options, &block)
Chef::DSL::Resources.add_resource_dsl(name)
result
end
@@ -1208,7 +1407,8 @@ class Chef
end
def provider_for_action(action)
- provider = Chef::ProviderResolver.new(node, self, action).resolve.new(self, run_context)
+ provider_class = Chef::ProviderResolver.new(node, self, action).resolve
+ provider = provider_class.new(self, run_context)
provider.action = action
provider
end
@@ -1318,23 +1518,22 @@ class Chef
# when Chef::Resource::MyLwrp
# end
#
- resource_subclass = class_eval <<-EOM, __FILE__, __LINE__+1
- class Chef::Resource::#{class_name} < resource_class
- resource_name nil # we do not actually provide anything
- def initialize(*args, &block)
- Chef::Log.deprecation("Using an LWRP by its name (#{class_name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.")
+ resource_subclass = Class.new(resource_class) do
+ resource_name nil # we do not actually provide anything
+ def initialize(*args, &block)
+ Chef::Log.deprecation("Using an LWRP by its name (#{self.class.name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.")
+ super
+ end
+ def self.resource_name(*args)
+ if args.empty?
+ @resource_name ||= superclass.resource_name
+ else
super
end
- def self.resource_name(*args)
- if args.empty?
- @resource_name ||= superclass.resource_name
- else
- super
- end
- end
- self
end
- EOM
+ self
+ end
+ eval("Chef::Resource::#{class_name} = resource_subclass")
# Make case, is_a and kind_of work with the new subclass, for backcompat.
# Any subclass of Chef::Resource::ResourceClass is already a subclass of resource_class
# Any subclass of resource_class is considered a subclass of Chef::Resource::ResourceClass
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index c486233020..443e0ed819 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -74,19 +74,14 @@ class Chef
resource_class
end
- # Define an attribute on this resource, including optional validation
- # parameters.
- def attribute(attr_name, validation_opts={})
- define_method(attr_name) do |arg=nil|
- set_or_return(attr_name.to_sym, arg, validation_opts)
- end
- end
+ alias :attribute :property
# Adds +action_names+ to the list of valid actions for this resource.
# Does not include superclass's action list when appending.
def actions(*action_names)
+ action_names = action_names.flatten
if !action_names.empty? && !@allowed_actions
- self.allowed_actions = action_names
+ self.allowed_actions = ([ :nothing ] + action_names).uniq
else
allowed_actions(*action_names)
end
diff --git a/lib/chef/resource/macports_package.rb b/lib/chef/resource/macports_package.rb
index 937839b6e1..5843016897 100644
--- a/lib/chef/resource/macports_package.rb
+++ b/lib/chef/resource/macports_package.rb
@@ -16,10 +16,11 @@
# limitations under the License.
#
+require 'chef/resource/package'
+
class Chef
class Resource
class MacportsPackage < Chef::Resource::Package
- provides :package, os: "darwin"
end
end
end
diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb
index 1c6da75678..5be1c34b89 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -100,8 +100,3 @@ class Chef
end
end
end
-
-require 'chef/chef_class'
-require 'chef/resource/homebrew_package'
-
-Chef.set_resource_priority_array :package, Chef::Resource::HomebrewPackage, os: "darwin"
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
index 31b39f7e24..9df627beb2 100644
--- a/lib/chef/resource_resolver.rb
+++ b/lib/chef/resource_resolver.rb
@@ -18,6 +18,7 @@
require 'chef/exceptions'
require 'chef/platform/resource_priority_map'
+require 'chef/mixin/convert_to_class_name'
class Chef
class ResourceResolver
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index 44b05f0cc0..f55d1740b1 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -25,118 +25,223 @@ require 'chef/log'
require 'chef/recipe'
require 'chef/run_context/cookbook_compiler'
require 'chef/event_dispatch/events_output_stream'
+require 'forwardable'
class Chef
# == Chef::RunContext
# Value object that loads and tracks the context of a Chef run
class RunContext
+ #
+ # Global state
+ #
- # Chef::Node object for this run
+ #
+ # The node for this run
+ #
+ # @return [Chef::Node]
+ #
attr_reader :node
- # Chef::CookbookCollection for this run
+ #
+ # The set of cookbooks involved in this run
+ #
+ # @return [Chef::CookbookCollection]
+ #
attr_reader :cookbook_collection
+ #
# Resource Definitions for this run. Populated when the files in
# +definitions/+ are evaluated (this is triggered by #load).
+ #
+ # @return [Array[Chef::ResourceDefinition]]
+ #
attr_reader :definitions
- ###
- # These need to be settable so deploy can run a resource_collection
- # independent of any cookbooks via +recipe_eval+
+ #
+ # Event dispatcher for this run.
+ #
+ # @return [Chef::EventDispatch::Dispatcher]
+ #
+ attr_reader :events
- # The Chef::ResourceCollection for this run. Populated by evaluating
- # recipes, which is triggered by #load. (See also: CookbookCompiler)
- attr_accessor :resource_collection
+ #
+ # Hash of factoids for a reboot request.
+ #
+ # @return [Hash]
+ #
+ attr_accessor :reboot_info
+ #
+ # Scoped state
+ #
+
+ #
+ # The parent run context.
+ #
+ # @return [Chef::RunContext] The parent run context, or `nil` if this is the
+ # root context.
+ #
+ attr_reader :parent_run_context
+
+ #
+ # The collection of resources intended to be converged (and able to be
+ # notified).
+ #
+ # @return [Chef::ResourceCollection]
+ #
+ # @see CookbookCompiler
+ #
+ attr_reader :resource_collection
+
+ #
# The list of control groups to execute during the audit phase
- attr_accessor :audits
+ #
+ attr_reader :audits
+ #
+ # Notification handling
+ #
+
+ #
# A Hash containing the immediate notifications triggered by resources
# during the converge phase of the chef run.
- attr_accessor :immediate_notification_collection
+ #
+ # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+ # <notifying resource name> => <list of notifications it sent>
+ #
+ attr_reader :immediate_notification_collection
+ #
# A Hash containing the delayed (end of run) notifications triggered by
# resources during the converge phase of the chef run.
- attr_accessor :delayed_notification_collection
-
- # Event dispatcher for this run.
- attr_reader :events
-
- # Hash of factoids for a reboot request.
- attr_reader :reboot_info
+ #
+ # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+ # <notifying resource name> => <list of notifications it sent>
+ #
+ 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.
#
- # === Returns
- # object<Chef::RunContext>:: Duh. :)
+ # @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 = node
@cookbook_collection = cookbook_collection
- @resource_collection = Chef::ResourceCollection.new
- @audits = {}
- @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
- @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
- @definitions = Hash.new
- @loaded_recipes = {}
- @loaded_attributes = {}
@events = events
- @reboot_info = {}
- @node.run_context = self
- @node.set_cookbook_attribute
+ node.run_context = self
+ node.set_cookbook_attribute
+
+ @definitions = Hash.new
+ @loaded_recipes_hash = {}
+ @loaded_attributes_hash = {}
+ @reboot_info = {}
@cookbook_compiler = nil
+
+ initialize_child_state
end
- # Triggers the compile phase of the chef run. Implemented by
- # Chef::RunContext::CookbookCompiler
+ #
+ # 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
+ cookbook_compiler.compile
end
- # Adds an immediate notification to the
- # +immediate_notification_collection+. The notification should be a
- # Chef::Resource::Notification or duck type.
+ #
+ # Initialize state that applies to both Chef::RunContext and Chef::ChildRunContext
+ #
+ def initialize_child_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
+
+ #
+ # Adds an immediate notification to the +immediate_notification_collection+.
+ #
+ # @param [Chef::Resource::Notification] The notification to add.
+ #
def notifies_immediately(notification)
nr = notification.notifying_resource
if nr.instance_of?(Chef::Resource)
- @immediate_notification_collection[nr.name] << notification
+ immediate_notification_collection[nr.name] << notification
else
- @immediate_notification_collection[nr.declared_key] << notification
+ immediate_notification_collection[nr.declared_key] << notification
end
end
- # Adds a delayed notification to the +delayed_notification_collection+. The
- # notification should be a Chef::Resource::Notification or duck type.
+ #
+ # Adds a delayed notification to the +delayed_notification_collection+.
+ #
+ # @param [Chef::Resource::Notification] The notification to add.
+ #
def notifies_delayed(notification)
nr = notification.notifying_resource
if nr.instance_of?(Chef::Resource)
- @delayed_notification_collection[nr.name] << notification
+ delayed_notification_collection[nr.name] << notification
else
- @delayed_notification_collection[nr.declared_key] << notification
+ delayed_notification_collection[nr.declared_key] << notification
end
end
+ #
+ # Get the list of immediate notifications sent by the given resource.
+ #
+ # TODO seriously, this is actually wrong. resource.name is not unique,
+ # you need the type as well.
+ #
+ # @return [Array[Notification]]
+ #
def immediate_notifications(resource)
if resource.instance_of?(Chef::Resource)
- return @immediate_notification_collection[resource.name]
+ return immediate_notification_collection[resource.name]
else
- return @immediate_notification_collection[resource.declared_key]
+ return immediate_notification_collection[resource.declared_key]
end
end
+ #
+ # Get the list of delayed (end of run) notifications sent by the given
+ # resource.
+ #
+ # TODO seriously, this is actually wrong. resource.name is not unique,
+ # you need the type as well.
+ #
+ # @return [Array[Notification]]
+ #
def delayed_notifications(resource)
if resource.instance_of?(Chef::Resource)
- return @delayed_notification_collection[resource.name]
+ return delayed_notification_collection[resource.name]
else
- return @delayed_notification_collection[resource.declared_key]
+ return delayed_notification_collection[resource.declared_key]
end
end
+ #
+ # Cookbook and recipe loading
+ #
+
+ #
# Evaluates the recipes +recipe_names+. Used by DSL::IncludeRecipe
+ #
+ # @param recipe_names [Array[String]] The list of recipe names (e.g.
+ # 'my_cookbook' or 'my_cookbook::my_resource').
+ # @param current_cookbook The cookbook we are currently running in.
+ #
+ # @see DSL::IncludeRecipe#include_recipe
+ #
def include_recipe(*recipe_names, current_cookbook: nil)
result_recipes = Array.new
recipe_names.flatten.each do |recipe_name|
@@ -147,7 +252,21 @@ class Chef
result_recipes
end
+ #
# Evaluates the recipe +recipe_name+. Used by DSL::IncludeRecipe
+ #
+ # TODO I am sort of confused why we have both this and include_recipe ...
+ # I don't see anything different beyond accepting and returning an
+ # array of recipes.
+ #
+ # @param recipe_names [Array[String]] The recipe name (e.g 'my_cookbook' or
+ # 'my_cookbook::my_resource').
+ # @param current_cookbook The cookbook we are currently running in.
+ #
+ # @return A truthy value if the load occurred; `false` if already loaded.
+ #
+ # @see DSL::IncludeRecipe#load_recipe
+ #
def load_recipe(recipe_name, current_cookbook: nil)
Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe")
@@ -175,6 +294,15 @@ ERROR_MESSAGE
end
end
+ #
+ # Load the given recipe from a filename.
+ #
+ # @param recipe_file [String] The recipe filename.
+ #
+ # @return [Chef::Recipe] The loaded recipe.
+ #
+ # @raise [Chef::Exceptions::RecipeNotFound] If the file does not exist.
+ #
def load_recipe_file(recipe_file)
if !File.exist?(recipe_file)
raise Chef::Exceptions::RecipeNotFound, "could not find recipe file #{recipe_file}"
@@ -186,8 +314,19 @@ ERROR_MESSAGE
recipe
end
- # Looks up an attribute file given the +cookbook_name+ and
- # +attr_file_name+. Used by DSL::IncludeAttribute
+ #
+ # Look up an attribute filename.
+ #
+ # @param cookbook_name [String] The cookbook name of the attribute file.
+ # @param attr_file_name [String] The attribute file's name (not path).
+ #
+ # @return [String] The filename.
+ #
+ # @see DSL::IncludeAttribute#include_attribute
+ #
+ # @raise [Chef::Exceptions::CookbookNotFound] If the cookbook could not be found.
+ # @raise [Chef::Exceptions::AttributeNotFound] If the attribute file could not be found.
+ #
def resolve_attribute(cookbook_name, attr_file_name)
cookbook = cookbook_collection[cookbook_name]
raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{cookbook_name} while loading attribute #{name}" unless cookbook
@@ -198,76 +337,152 @@ ERROR_MESSAGE
attribute_filename
end
- # An Array of all recipes that have been loaded. This is stored internally
- # as a Hash, so ordering is predictable.
#
- # Recipe names are given in fully qualified form, e.g., the recipe "nginx"
- # will be given as "nginx::default"
+ # A list of all recipes that have been loaded.
+ #
+ # This is stored internally as a Hash, so ordering is predictable.
+ #
+ # TODO is the above statement true in a 1.9+ ruby world? Is it relevant?
+ #
+ # @return [Array[String]] A list of recipes in fully qualified form, e.g.
+ # the recipe "nginx" will be given as "nginx::default".
+ #
+ # @see #loaded_recipe? To determine if a particular recipe has been loaded.
#
- # To determine if a particular recipe has been loaded, use #loaded_recipe?
def loaded_recipes
- @loaded_recipes.keys
+ loaded_recipes_hash.keys
end
- # An Array of all attributes files that have been loaded. Stored internally
- # using a Hash, so order is predictable.
#
- # Attribute file names are given in fully qualified form, e.g.,
- # "nginx::default" instead of "nginx".
+ # A list of all attributes files that have been loaded.
+ #
+ # Stored internally using a Hash, so order is predictable.
+ #
+ # TODO is the above statement true in a 1.9+ ruby world? Is it relevant?
+ #
+ # @return [Array[String]] A list of attribute file names in fully qualified
+ # form, e.g. the "nginx" will be given as "nginx::default".
+ #
def loaded_attributes
- @loaded_attributes.keys
+ loaded_attributes_hash.keys
end
+ #
+ # Find out if a given recipe has been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param recipe [String] Recipe name.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_fully_qualified_recipe?(cookbook, recipe)
- @loaded_recipes.has_key?("#{cookbook}::#{recipe}")
+ loaded_recipes_hash.has_key?("#{cookbook}::#{recipe}")
end
- # Returns true if +recipe+ has been loaded, false otherwise. Default recipe
- # names are expanded, so `loaded_recipe?("nginx")` and
- # `loaded_recipe?("nginx::default")` are valid and give identical results.
+ #
+ # Find out if a given recipe has been loaded.
+ #
+ # @param recipe [String] Recipe name. "nginx" and "nginx::default" yield
+ # the same results.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_recipe?(recipe)
cookbook, recipe_name = Chef::Recipe.parse_recipe_name(recipe)
loaded_fully_qualified_recipe?(cookbook, recipe_name)
end
+ #
+ # Mark a given recipe as having been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param recipe [String] Recipe name.
+ #
+ def loaded_recipe(cookbook, recipe)
+ loaded_recipes_hash["#{cookbook}::#{recipe}"] = true
+ end
+
+ #
+ # Find out if a given attribute file has been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param attribute_file [String] Attribute file name.
+ #
+ # @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
+ #
+ # Mark a given attribute file as having been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param attribute_file [String] Attribute file name.
+ #
def loaded_attribute(cookbook, attribute_file)
- @loaded_attributes["#{cookbook}::#{attribute_file}"] = true
+ loaded_attributes_hash["#{cookbook}::#{attribute_file}"] = true
end
##
# Cookbook File Introspection
+ #
+ # Find out if the cookbook has the given template.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param template_name [String] Template name.
+ #
+ # @return [Boolean] `true` if the template is in the cookbook, `false`
+ # otherwise.
+ # @see Chef::CookbookVersion#has_template_for_node?
+ #
def has_template_in_cookbook?(cookbook, template_name)
cookbook = cookbook_collection[cookbook]
cookbook.has_template_for_node?(node, template_name)
end
+ #
+ # Find out if the cookbook has the given file.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param cb_file_name [String] File name.
+ #
+ # @return [Boolean] `true` if the file is in the cookbook, `false`
+ # otherwise.
+ # @see Chef::CookbookVersion#has_cookbook_file_for_node?
+ #
def has_cookbook_file_in_cookbook?(cookbook, cb_file_name)
cookbook = cookbook_collection[cookbook]
cookbook.has_cookbook_file_for_node?(node, cb_file_name)
end
- # Delegates to CookbookCompiler#unreachable_cookbook?
- # Used to raise an error when attempting to load a recipe belonging to a
- # cookbook that is not in the dependency graph. See also: CHEF-4367
+ #
+ # Find out whether the given cookbook is in the cookbook dependency graph.
+ #
+ # @param cookbook_name [String] Cookbook name.
+ #
+ # @return [Boolean] `true` if the cookbook is reachable, `false` otherwise.
+ #
+ # @see Chef::CookbookCompiler#unreachable_cookbook?
def unreachable_cookbook?(cookbook_name)
- @cookbook_compiler.unreachable_cookbook?(cookbook_name)
+ cookbook_compiler.unreachable_cookbook?(cookbook_name)
end
+ #
# Open a stream object that can be printed into and will dispatch to events
#
- # == Arguments
- # options is a hash with these possible options:
- # - name: a string that identifies the stream to the user. Preferably short.
+ # @param name [String] The name of the stream.
+ # @param options [Hash] Other options for the stream.
+ #
+ # @return [EventDispatch::EventsOutputStream] The created stream.
#
- # Pass a block and the stream will be yielded to it, and close on its own
- # at the end of the block.
- def open_stream(options = {})
- stream = EventDispatch::EventsOutputStream.new(events, options)
+ # @yield If a block is passed, it will be run and the stream will be closed
+ # afterwards.
+ # @yieldparam stream [EventDispatch::EventsOutputStream] The created stream.
+ #
+ def open_stream(name: nil, **options)
+ stream = EventDispatch::EventsOutputStream.new(events, name: name, **options)
if block_given?
begin
yield stream
@@ -280,31 +495,130 @@ ERROR_MESSAGE
end
# there are options for how to handle multiple calls to these functions:
- # 1. first call always wins (never change @reboot_info once set).
- # 2. last call always wins (happily change @reboot_info whenever).
+ # 1. first call always wins (never change reboot_info once set).
+ # 2. last call always wins (happily change reboot_info whenever).
# 3. raise an exception on the first conflict.
# 4. disable reboot after this run if anyone ever calls :cancel.
# 5. raise an exception on any second call.
# 6. ?
def request_reboot(reboot_info)
- Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to #{reboot_info.inspect}"
+ Chef::Log::info "Changing reboot status from #{self.reboot_info.inspect} to #{reboot_info.inspect}"
@reboot_info = reboot_info
end
def cancel_reboot
- Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to {}"
+ Chef::Log::info "Changing reboot status from #{reboot_info.inspect} to {}"
@reboot_info = {}
end
def reboot_requested?
- @reboot_info.size > 0
+ reboot_info.size > 0
+ end
+
+ #
+ # Create a child RunContext.
+ #
+ def create_child
+ ChildRunContext.new(self)
end
- private
+ protected
- def loaded_recipe(cookbook, recipe)
- @loaded_recipes["#{cookbook}::#{recipe}"] = true
+ 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 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
+
+ def immediate_notification_collection=(value)
+ Chef::Log.deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ end
+
+ def delayed_notification_collection=(value)
+ Chef::Log.deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ end
end
+ prepend Deprecated
+
+
+ #
+ # A child run context. Delegates all root context calls to its parent.
+ #
+ # @api private
+ #
+ class ChildRunContext < RunContext
+ extend Forwardable
+ def_delegators :parent_run_context, *%w(
+ cancel_reboot
+ config
+ cookbook_collection
+ cookbook_compiler
+ definitions
+ events
+ has_cookbook_file_in_cookbook?
+ has_template_in_cookbook?
+ load
+ loaded_attribute
+ loaded_attributes
+ loaded_attributes_hash
+ loaded_fully_qualified_attribute?
+ loaded_fully_qualified_recipe?
+ loaded_recipe
+ loaded_recipe?
+ loaded_recipes
+ loaded_recipes_hash
+ node
+ open_stream
+ reboot_info
+ reboot_info=
+ reboot_requested?
+ request_reboot
+ resolve_attribute
+ unreachable_cookbook?
+ )
+
+ def initialize(parent_run_context)
+ @parent_run_context = 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_child_state
+ end
+ CHILD_STATE = %w(
+ audits
+ audits=
+ create_child
+ delayed_notification_collection
+ delayed_notification_collection=
+ delayed_notifications
+ immediate_notification_collection
+ immediate_notification_collection=
+ immediate_notifications
+ include_recipe
+ initialize_child_state
+ load_recipe
+ load_recipe_file
+ notifies_immediately
+ notifies_delayed
+ parent_run_context
+ resource_collection
+ resource_collection=
+ ).map { |x| x.to_sym }
+
+ # Verify that we didn't miss any methods
+ missing_methods = superclass.instance_methods(false) - instance_methods(false) - CHILD_STATE
+ if !missing_methods.empty?
+ raise "ERROR: not all methods of RunContext accounted for in ChildRunContext! All methods must be marked as child methods with CHILD_STATE or delegated to the parent_run_context. Missing #{missing_methods.join(", ")}."
+ end
+ end
end
end
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index 80fd422c55..743a99824d 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -21,7 +21,7 @@
class Chef
CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
- VERSION = '12.4.0.rc.2'
+ VERSION = '12.5.0.current.0'
end
#