Vendor the external cookbooks

Knife-Zero doesn't include Berkshelf support, so vendoring everything in
the repo is convenient again
This commit is contained in:
Greg Karékinian
2019-10-13 18:32:56 +02:00
parent aa66743166
commit 049d5dd006
1245 changed files with 100630 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
class Chef
NOT_PASSED = Object.new if !defined?(NOT_PASSED)
# Earlier versions of Chef didn't have this message
module ChefCompatDeprecation
def log_deprecation(message, location=nil)
if !location
# Pick the first caller that is *not* part of the Chef or ChefCompat gem,
# that's the thing the user wrote.
chef_compat_gem_path = File.expand_path("../../..", __FILE__)
chef_gem_path = File.expand_path("../..",::Chef::Resource.instance_method(:initialize).source_location[0])
caller(0..10).each do |c|
if !c.start_with?(chef_gem_path) && !c.start_with?(chef_compat_gem_path)
location = c
break
end
end
end
begin
super
# Bleagh. `super_method` doesn't exist on older rubies and I haven't
# figured out a way to check for its existence otherwise.
rescue NoMethodError
Chef::Log.warn(message)
end
end
end
class<<self
prepend ChefCompatDeprecation
end
end

View File

@@ -0,0 +1,10 @@
require 'chef/exceptions'
class Chef
class Exceptions
# Used in Resource::ActionClass#load_current_resource to denote that
# the resource doesn't actually exist (for example, the file does not exist)
class CurrentValueDoesNotExist < RuntimeError; end unless defined?(CurrentValueDoesNotExist)
class CannotValidateStaticallyError < RuntimeError; end unless defined?(CannotValidateStaticallyError)
end
end

View File

@@ -0,0 +1,30 @@
require 'chef/log'
# for now we have to patch this in everything
class Chef
class Log
def self.caller_location
# Pick the first caller that is *not* part of the Chef gem, that's the
# thing the user wrote.
compat_resource_path = File.expand_path("../../../../..", __FILE__)
chef_gem_path = Gem.loaded_specs['chef'].full_gem_path
caller(0..20).find { |c| !c.start_with?(compat_resource_path) && !c.start_with?(chef_gem_path) }
end
end
end
if Gem::Requirement.new('< 12.13.37').satisfied_by?(Gem::Version.new(Chef::VERSION))
# FIXME: why does this not match the implementation in Chef itself?
class Chef
class Log
module ChefCompatDeprecation
def deprecation(message, location=nil)
Chef.log_deprecation(message, location)
end
end
extend ChefCompatDeprecation
end
end
end

View File

@@ -0,0 +1,17 @@
if Chef::VERSION.to_f >= 12.5 && Chef::VERSION.to_f <= 12.8
require 'chef/mixin/params_validate'
class Chef
module Mixin
module ParamsValidate
class SetOrReturnProperty < Chef::Property
# 12.9 introduced a new optional parameter to `get()` to avoid a nil-set warning.
# When their method gets called with 2 args, we need to ignore and call with 1.
alias_method :_original_get2, :get
def get(resource, *args)
_original_get2(resource)
end
end
end
end
end
end

View File

@@ -0,0 +1,9 @@
class Chef
class Node
unless method_defined?(:set_cookbook_attribute)
def set_cookbook_attribute
# this implementation deliberately left blank - we don't need to do anything we just need to not fail
end
end
end
end

View File

@@ -0,0 +1,15 @@
# 12.9 introduced a new optional parameter to `get()` to avoid a nil-set warning.
# We need to mimick it here.
if Chef::VERSION.to_f >= 12.5 && Chef::VERSION.to_f <= 12.8
require 'chef/property'
class Chef
class Property
# 12.9 introduced a new optional parameter to `get()` to avoid a nil-set warning.
# When their method gets called with 2 args, we need to ignore and call with 1.
alias_method :_original_get, :get
def get(resource, *args)
_original_get(resource)
end
end
end
end

View File

@@ -0,0 +1,65 @@
require 'chef/provider'
require 'chef/provider/lwrp_base'
class Chef::Provider
if !defined?(InlineResources)
InlineResources = Chef::Provider::LWRPBase::InlineResources
end
module InlineResources
require 'chef/dsl/recipe'
require 'chef/dsl/platform_introspection'
require 'chef/dsl/data_query'
require 'chef/dsl/include_recipe'
include Chef::DSL::Recipe
include Chef::DSL::PlatformIntrospection
include Chef::DSL::DataQuery
include Chef::DSL::IncludeRecipe
unless Chef::Provider::InlineResources::ClassMethods.instance_method(:action).source_location[0] =~ /chefspec/
# Don't override action if chefspec is doing its thing
module ::ChefCompat
module Monkeypatches
module InlineResources
module ClassMethods
def action(name, &block)
super(name) { send("compile_action_#{name}") }
# We put the action in its own method so that super() works.
define_method("compile_action_#{name}", &block)
end
end
end
end
end
module ClassMethods
prepend ChefCompat::Monkeypatches::InlineResources::ClassMethods
end
end
end
end
class Chef
class Provider
class LWRPBase < Provider
if defined?(InlineResources)
module InlineResources
# since we upgrade the Chef::Runner and Chef::RunContext globally to >= 12.14 style classes, we need to also
# fix the use_inline_resources LWRPBase wrapper that creates a sub-resource collection with the ugpraded code
# from the Chef::Provider subclasses that do similar things in post-12.5 chef.
def recipe_eval_with_update_check(&block)
old_run_context = run_context
@run_context = run_context.create_child
return_value = instance_eval(&block)
Chef::Runner.new(run_context).converge
return_value
ensure
if run_context.resource_collection.any? { |r| r.updated? }
new_resource.updated_by_last_action(true)
end
@run_context = old_run_context
end
end
end
end
end
end

View File

@@ -0,0 +1,118 @@
#
# NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
#
# THIS IS A FILE AUTOGENERATED BY 'rake update' DO NOT EDIT!!!!
#
# NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
#
if Gem::Requirement.new('< 12.16.42').satisfied_by?(Gem::Version.new(Chef::VERSION))
#--
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Copyright:: Copyright 2008-2016, Chef Software 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/dsl/recipe"
require "chef/mixin/from_file"
require "chef/mixin/deprecation"
class Chef
# == Chef::Recipe
# A Recipe object is the context in which Chef recipes are evaluated.
class Recipe
attr_accessor :cookbook_name, :recipe_name, :recipe, :params, :run_context
include Chef::DSL::Recipe
include Chef::Mixin::FromFile
include Chef::Mixin::Deprecation
# Parses a potentially fully-qualified recipe name into its
# cookbook name and recipe short name.
#
# For example:
# "aws::elastic_ip" returns [:aws, "elastic_ip"]
# "aws" returns [:aws, "default"]
# "::elastic_ip" returns [ current_cookbook, "elastic_ip" ]
#--
# TODO: Duplicates functionality of RunListItem
def self.parse_recipe_name(recipe_name, current_cookbook: nil)
case recipe_name
when /(.+?)::(.+)/
[ $1.to_sym, $2 ]
when /^::(.+)/
raise "current_cookbook is nil, cannot resolve #{recipe_name}" if current_cookbook.nil?
[ current_cookbook.to_sym, $1 ]
else
[ recipe_name.to_sym, "default" ]
end
end
def initialize(cookbook_name, recipe_name, run_context)
@cookbook_name = cookbook_name
@recipe_name = recipe_name
@run_context = run_context
# TODO: 5/19/2010 cw/tim: determine whether this can be removed
@params = Hash.new
end
# Used in DSL mixins
def node
run_context.node
end
# Used by the DSL to look up resources when executing in the context of a
# recipe.
def resources(*args)
run_context.resource_collection.find(*args)
end
# This was moved to Chef::Node#tag, redirecting here for compatibility
def tag(*tags)
run_context.node.tag(*tags)
end
# Returns true if the node is tagged with *all* of the supplied +tags+.
#
# === Parameters
# tags<Array>:: A list of tags
#
# === Returns
# true<TrueClass>:: If all the parameters are present
# false<FalseClass>:: If any of the parameters are missing
def tagged?(*tags)
tags.each do |tag|
return false unless run_context.node.tags.include?(tag)
end
true
end
# Removes the list of tags from the node.
#
# === Parameters
# tags<Array>:: A list of tags
#
# === Returns
# tags<Array>:: The current list of run_context.node.tags
def untag(*tags)
tags.each do |tag|
run_context.node.tags.delete(tag)
end
end
end
end
end

View File

@@ -0,0 +1,20 @@
require 'chef/recipe'
require 'chef_compat/recipe'
class Chef::Recipe
# If the cookbook depends on compat_resource, create a ChefCompat::Recipe object
# instead of Chef::Recipe, for the extra goodies.
def self.new(cookbook_name, recipe_name, run_context)
if run_context &&
cookbook_name &&
recipe_name &&
run_context.cookbook_collection &&
run_context.cookbook_collection[cookbook_name] &&
run_context.cookbook_collection[cookbook_name].metadata.dependencies.has_key?('compat_resource') &&
self != ::ChefCompat::Recipe
::ChefCompat::Recipe.new(cookbook_name, recipe_name, run_context)
else
super
end
end
end

View File

@@ -0,0 +1,156 @@
# this is NOT an AUTOGENERATED file
require 'chef/resource'
class Chef
class Resource
class UnresolvedSubscribes < self
# The full key ise given as the name in {Resource#subscribes}
alias_method :to_s, :name
alias_method :declared_key, :name
end
#
# Force a delayed notification into this resource's run_context.
#
# This should most likely be paired with action :nothing
#
# @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`)
#
def delayed_action(arg)
arg = Array(arg).map(&:to_sym)
arg.map do |action|
validate(
{ action: action },
{ action: { kind_of: Symbol, equal_to: allowed_actions } }
)
# the resource effectively sends a delayed notification to itself
run_context.add_delayed_action(Notification.new(self, action, self))
end
end
def subscribes(action, resources, timing = :delayed)
resources = [resources].flatten
resources.each do |resource|
if resource.is_a?(String)
resource = UnresolvedSubscribes.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 notifies(action, resource_spec, timing = :delayed)
# when using old-style resources(:template => "/foo.txt") style, you
# could end up with multiple resources.
validate_resource_spec!(resource_spec)
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)
when "before"
notifies_before(action, resource)
else
raise ArgumentError, "invalid timing: #{timing} for notifies(#{action}, #{resources.inspect}, #{timing}) resource #{self} "\
"Valid timings are: :delayed, :immediate, :immediately, :before"
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.before_notifications(self).each { |n|
n.resolve_resource_reference(run_context.resource_collection)
}
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
# Helper for #notifies
def notifies_before(action, resource_spec)
run_context.notifies_before(Notification.new(resource_spec, action, self))
end
# Helper for #notifies
def notifies_immediately(action, resource_spec)
run_context.notifies_immediately(Notification.new(resource_spec, action, self))
end
# Helper for #notifies
def notifies_delayed(action, resource_spec)
run_context.notifies_delayed(Notification.new(resource_spec, action, self))
end
#
# Get the current actual value of this resource.
#
# This does not cache--a new value will be returned each time.
#
# @return A new copy of the resource, with values filled in from the actual
# current value.
#
def current_value
provider = provider_for_action(Array(action).first)
if provider.whyrun_mode? && !provider.whyrun_supported?
raise "Cannot retrieve #{self.class.current_resource} in why-run mode: #{provider} does not support why-run"
end
provider.load_current_resource
provider.current_resource
end
# These methods are necessary for new resources to initialize old ones properly
attr_reader :resource_initializing
def resource_initializing=(value)
if value
@resource_initializing = value
else
remove_instance_variable(:@resource_initializing)
end
end
if !respond_to?(:resource_name)
def self.resource_name(name=Chef::NOT_PASSED)
# Setter
if name != Chef::NOT_PASSED
# remove_canonical_dsl
# Set the resource_name and call provides
if name
name = name.to_sym
# If our class is not already providing this name, provide it.
# Commented out: use of resource_name and provides will need to be
# mutually exclusive in this world, generally.
# if !Chef::ResourceResolver.includes_handler?(name, self)
provides name#, canonical: true
# end
@resource_name = name
else
@resource_name = nil
end
end
@resource_name
end
def self.resource_name=(name)
resource_name(name)
end
end
end
end

View File

@@ -0,0 +1,60 @@
require 'chef_compat/resource/lwrp_base'
require 'chef/resource/lwrp_base'
module ChefCompat
module Monkeypatches
#
# NOTE: LOTS OF METAPROGRAMMING HERE. NOT FOR FAINT OF HEART.
#
# Add an empty module to Class so we can temporarily override it in build_from_file
module Class
end
class<<::Class
prepend(ChefCompat::Monkeypatches::Class)
end
module Chef
module Resource
module LWRPBase
def build_from_file(cookbook_name, filename, run_context)
# If the cookbook this LWRP is from depends on compat_resource, fix its LWRPs up real good
if run_context.cookbook_collection[cookbook_name].metadata.dependencies.has_key?('compat_resource')
# All cookbooks do Class.new(Chef::Resource::LWRPBase). Change Class.new
# temporarily to translate Chef::Resource::LWRPBase to ChefCompat::Resource
ChefCompat::Monkeypatches::Class.module_eval do
def new(*args, &block)
# Trick it! Use ChefCompat::Resource instead of Chef::Resource::LWRPBase
if args == [ ::Chef::Resource::LWRPBase ]
ChefCompat::Monkeypatches::Class.module_eval do
remove_method(:new) if method_defined?(:new)
end
args = [ ChefCompat::Resource::LWRPBase ]
end
super(*args, &block)
end
end
begin
# Call the actual build_from_file
super
ensure
class<<ChefCompat::Monkeypatches::Class
remove_method(:new) if method_defined?(:new)
end
end
else
# Call the actual build_from_file
super
end
end
end
class <<::Chef::Resource::LWRPBase
prepend(LWRPBase)
end
end
end
end
end

View File

@@ -0,0 +1,167 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
# Copyright:: Copyright 2015-2016, Chef Software, 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.
#
# XXX: we now have two copies of this file in the compat_resource cookbook. I'm uncertain if this is a
# bug or a feature, and I suspect it is actually a feature. The point of this file is that for all
# resources and cookbooks the global Chef::ResourceBuilder class must be upgraded to at least the
# 12.10.24 version. The point of the other copy is that for compat_resource cookbooks all their
# resources should be using the lastest version that has been sync'd. So these two files should
# diverge as times goes on. I believe that is the correct behavior and that we want to have both
# files in this cookbook.
# NOTE: this was extracted from the Recipe DSL mixin, relevant specs are in spec/unit/recipe_spec.rb
if Gem::Requirement.new("< 12.10.24").satisfied_by?(Gem::Version.new(Chef::VERSION))
begin
require 'chef/resource_builder'
# we use the LoadError this creates on early 12.x to not monkeypatch chef client versions that don't have Chef::ResourceBuilder
# (it is lazily included and doesn't appear until compile time so we can't resolve the symbol during library loading)
class Chef
class ResourceBuilder
attr_reader :type
attr_reader :name
attr_reader :created_at
attr_reader :params
attr_reader :run_context
attr_reader :cookbook_name
attr_reader :recipe_name
attr_reader :enclosing_provider
attr_reader :resource
# FIXME (ruby-2.1 syntax): most of these are mandatory
def initialize(type:nil, name:nil, created_at: nil, params: nil, run_context: nil, cookbook_name: nil, recipe_name: nil, enclosing_provider: nil)
@type = type
@name = name
@created_at = created_at
@params = params
@run_context = run_context
@cookbook_name = cookbook_name
@recipe_name = recipe_name
@enclosing_provider = enclosing_provider
end
def build(&block)
raise ArgumentError, "You must supply a name when declaring a #{type} resource" if name.nil?
@resource = resource_class.new(name, run_context)
if resource.resource_name.nil?
raise Chef::Exceptions::InvalidResourceSpecification, "#{resource}.resource_name is `nil`! Did you forget to put `provides :blah` or `resource_name :blah` in your resource class?"
end
resource.source_line = created_at
resource.declared_type = type
# If we have a resource like this one, we want to steal its state
# This behavior is very counter-intuitive and should be removed.
# See CHEF-3694, https://tickets.opscode.com/browse/CHEF-3694
# Moved to this location to resolve CHEF-5052, https://tickets.opscode.com/browse/CHEF-5052
if prior_resource
resource.load_from(prior_resource)
end
resource.cookbook_name = cookbook_name
resource.recipe_name = recipe_name
# Determine whether this resource is being created in the context of an enclosing Provider
resource.enclosing_provider = enclosing_provider
# XXX: this is required for definition params inside of the scope of a
# subresource to work correctly.
resource.params = params
# Evaluate resource attribute DSL
if block_given?
resource.resource_initializing = true
begin
resource.instance_eval(&block)
ensure
resource.resource_initializing = false
end
end
# emit a cloned resource warning if it is warranted
if prior_resource
if is_trivial_resource?(prior_resource) && identicalish_resources?(prior_resource, resource)
emit_harmless_cloning_debug
else
emit_cloned_resource_warning
end
end
# Run optional resource hook
resource.after_created
resource
end
private
def resource_class
# Checks the new platform => short_name => resource mapping initially
# then fall back to the older approach (Chef::Resource.const_get) for
# backward compatibility
@resource_class ||= Chef::Resource.resource_for_node(type, run_context.node)
end
def is_trivial_resource?(resource)
identicalish_resources?(resource_class.new(name, run_context), resource)
end
# this is an equality test specific to checking for 3694 cloning warnings
def identicalish_resources?(first, second)
skipped_ivars = [ :@source_line, :@cookbook_name, :@recipe_name, :@params, :@elapsed_time, :@declared_type ]
checked_ivars = ( first.instance_variables | second.instance_variables ) - skipped_ivars
non_matching_ivars = checked_ivars.reject do |iv|
if iv == :@action && ( [first.instance_variable_get(iv)].flatten == [:nothing] || [second.instance_variable_get(iv)].flatten == [:nothing] )
# :nothing action on either side of the comparison always matches
true
else
first.instance_variable_get(iv) == second.instance_variable_get(iv)
end
end
Chef::Log.debug("ivars which did not match with the prior resource: #{non_matching_ivars}")
non_matching_ivars.empty?
end
def emit_cloned_resource_warning
Chef::Log.warn("Cloning resource attributes for #{resource} from prior resource (CHEF-3694)")
Chef::Log.warn("Previous #{prior_resource}: #{prior_resource.source_line}") if prior_resource.source_line
Chef::Log.warn("Current #{resource}: #{resource.source_line}") if resource.source_line
end
def emit_harmless_cloning_debug
Chef::Log.debug("Harmless resource cloning from #{prior_resource}:#{prior_resource.source_line} to #{resource}:#{resource.source_line}")
end
def prior_resource
@prior_resource ||=
begin
key = "#{type}[#{name}]"
run_context.resource_collection.lookup_local(key)
rescue Chef::Exceptions::ResourceNotFound
nil
end
end
end
end
rescue LoadError
# cool we're just on early chef 12.x, nothing to do -- we don't have to worry because there's also not parent_run_context pointer, so we don't have to
# use lookup_local to avoid resource cloning shit out of the parent run_context. the resource collection's lookup() method will always use lookup_local
# over lookup_recursive.
end
end

View File

@@ -0,0 +1,103 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Copyright:: Copyright 2008-2016, Chef Software 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/resource_collection/resource_set"
require "chef/resource_collection/resource_list"
require "chef/resource_collection"
require "chef/exceptions"
module ChefCompat
module Monkeypatches
module Chef
module ResourceCollection
module RecursiveNotificationLookup
#
# Copied verbatim from Chef 12.10.24
#
attr_accessor :run_context
def initialize(run_context = nil)
super()
@run_context = run_context
end
def lookup_local(key)
resource_set.lookup(key)
end
def find_local(*args)
resource_set.find(*args)
end
def lookup(key)
if run_context.nil?
lookup_local(key)
else
lookup_recursive(run_context, key)
end
end
def find(*args)
if run_context.nil?
find_local(*args)
else
find_recursive(run_context, *args)
end
end
private
def lookup_recursive(rc, key)
rc.resource_collection.send(:resource_set).lookup(key)
rescue ::Chef::Exceptions::ResourceNotFound
raise if !rc.respond_to?(:parent_run_context) || rc.parent_run_context.nil?
lookup_recursive(rc.parent_run_context, key)
end
def find_recursive(rc, *args)
rc.resource_collection.send(:resource_set).find(*args)
rescue ::Chef::Exceptions::ResourceNotFound
raise if !rc.respond_to?(:parent_run_context) || rc.parent_run_context.nil?
find_recursive(rc.parent_run_context, *args)
end
end
module DeleteResources
#
# Copied verbatim from Chef 12.10.24
#
def delete(key)
resource_list.delete(key)
resource_set.delete(key)
end
end
end
end
end
end
class Chef::ResourceCollection
unless method_defined?(:lookup_local)
prepend ChefCompat::Monkeypatches::Chef::ResourceCollection::RecursiveNotificationLookup
end
unless method_defined?(:delete)
prepend ChefCompat::Monkeypatches::Chef::ResourceCollection::DeleteResources
end
end

View File

@@ -0,0 +1,49 @@
#
# Author:: Tyler Ball (<tball@chef.io>)
# Copyright:: Copyright 2014-2016, Chef Software, 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/resource_collection/resource_list"
require "chef/exceptions"
module ChefCompat
module Monkeypatches
module Chef
module ResourceCollection
module ResourceList
module DeleteResource
# Copied verbatim from Chef 12.10.4
def delete(key)
raise ArgumentError, "Must pass a Chef::Resource or String to delete" unless key.is_a?(String) || key.is_a?(Chef::Resource)
key = key.to_s
ret = @resources.reject! { |r| r.to_s == key }
if ret.nil?
raise ::Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{key} (did you define it first?)"
end
ret
end
end
end
end
end
end
end
class Chef::ResourceCollection::ResourceList
unless method_defined?(:delete)
prepend ChefCompat::Monkeypatches::Chef::ResourceCollection::ResourceList::DeleteResource
end
end

View File

@@ -0,0 +1,49 @@
#
# Author:: Tyler Ball (<tball@chef.io>)
# Copyright:: Copyright 2014-2016, Chef Software, 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/resource_collection/resource_list"
require "chef/exceptions"
module ChefCompat
module Monkeypatches
module Chef
module ResourceCollection
module ResourceSet
module DeleteResource
def delete(key)
raise ArgumentError, "Must pass a Chef::Resource or String to delete" unless key.is_a?(String) || key.is_a?(Chef::Resource)
key = key.to_s
res = @resources_by_key.delete(key)
if res == @resources_by_key.default
raise Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{key} (did you define it first?)"
end
res
end
end
end
end
end
end
end
class Chef::ResourceCollection::ResourceSet
unless method_defined?(:delete)
prepend ChefCompat::Monkeypatches::Chef::ResourceCollection::ResourceSet::DeleteResource
end
end

View File

@@ -0,0 +1,691 @@
#
# NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
#
# THIS IS A FILE AUTOGENERATED BY 'rake update' DO NOT EDIT!!!!
#
# NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
#
if Gem::Requirement.new('< 12.16.42').satisfied_by?(Gem::Version.new(Chef::VERSION))
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Tim Hinderliter (<tim@chef.io>)
# Copyright:: Copyright 2008-2016, Chef Software 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/resource_collection"
require "chef/cookbook_version"
require "chef/node"
require "chef/role"
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
#
#
# The node for this run
#
# @return [Chef::Node]
#
attr_reader :node
#
# 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
#
# Event dispatcher for this run.
#
# @return [Chef::EventDispatch::Dispatcher]
#
attr_reader :events
#
# 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 root run context.
#
# @return [Chef::RunContext] The root run context.
#
def root_run_context
rc = self
rc = rc.parent_run_context until rc.parent_run_context.nil?
rc
end
#
# 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_reader :audits
#
# Notification handling
#
#
# A Hash containing the before notifications triggered by resources
# during the converge phase of the chef run.
#
# @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
# <notifying resource name> => <list of notifications it sent>
#
attr_reader :before_notification_collection
#
# A Hash containing the immediate notifications triggered by resources
# during the converge phase of the chef run.
#
# @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.
#
# @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
# <notifying resource name> => <list of notifications it sent>
#
attr_reader :delayed_notification_collection
#
# An Array containing the delayed (end of run) notifications triggered by
# resources during the converge phase of the chef run.
#
# @return [Array[Chef::Resource::Notification]] An array of notification objects
#
attr_reader :delayed_actions
# Creates a new Chef::RunContext object and populates its fields. This object gets
# used by the Chef Server to generate a fully compiled recipe list for a node.
#
# @param node [Chef::Node] The node to run against.
# @param cookbook_collection [Chef::CookbookCollection] The cookbooks
# involved in this run.
# @param events [EventDispatch::Dispatcher] The event dispatcher for this
# run.
#
def initialize(node, cookbook_collection, events)
@node = node
@cookbook_collection = cookbook_collection
@events = events
node.run_context = self
node.set_cookbook_attribute
@definitions = Hash.new
@loaded_recipes_hash = {}
@loaded_attributes_hash = {}
@reboot_info = {}
@cookbook_compiler = nil
@delayed_actions = []
initialize_child_state
end
#
# Triggers the compile phase of the chef run.
#
# @param run_list_expansion [Chef::RunList::RunListExpansion] The run list.
# @see Chef::RunContext::CookbookCompiler
#
def load(run_list_expansion)
@cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
cookbook_compiler.compile
end
#
# Initialize state that applies to both Chef::RunContext and Chef::ChildRunContext
#
def initialize_child_state
@audits = {}
@resource_collection = Chef::ResourceCollection.new(self)
@before_notification_collection = Hash.new { |h, k| h[k] = [] }
@immediate_notification_collection = Hash.new { |h, k| h[k] = [] }
@delayed_notification_collection = Hash.new { |h, k| h[k] = [] }
@delayed_actions = []
end
#
# Adds an before notification to the +before_notification_collection+.
#
# @param [Chef::Resource::Notification] The notification to add.
#
def notifies_before(notification)
# Note for the future, notification.notifying_resource may be an instance
# of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes}
# with a string value.
before_notification_collection[notification.notifying_resource.declared_key] << notification
end
#
# Adds an immediate notification to the +immediate_notification_collection+.
#
# @param [Chef::Resource::Notification] The notification to add.
#
def notifies_immediately(notification)
# Note for the future, notification.notifying_resource may be an instance
# of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes}
# with a string value.
immediate_notification_collection[notification.notifying_resource.declared_key] << notification
end
#
# Adds a delayed notification to the +delayed_notification_collection+.
#
# @param [Chef::Resource::Notification] The notification to add.
#
def notifies_delayed(notification)
# Note for the future, notification.notifying_resource may be an instance
# of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes}
# with a string value.
delayed_notification_collection[notification.notifying_resource.declared_key] << notification
end
#
# Adds a delayed action to the +delayed_actions+.
#
def add_delayed_action(notification)
if delayed_actions.any? { |existing_notification| existing_notification.duplicates?(notification) }
Chef::Log.info( "#{notification.notifying_resource} not queuing delayed action #{notification.action} on #{notification.resource}"\
" (delayed), as it's already been queued")
else
delayed_actions << notification
end
end
#
# Get the list of before notifications sent by the given resource.
#
# @return [Array[Notification]]
#
def before_notifications(resource)
return before_notification_collection[resource.declared_key]
end
#
# Get the list of immediate notifications sent by the given resource.
#
# @return [Array[Notification]]
#
def immediate_notifications(resource)
return immediate_notification_collection[resource.declared_key]
end
#
# Get the list of delayed (end of run) notifications sent by the given
# resource.
#
# @return [Array[Notification]]
#
def delayed_notifications(resource)
return delayed_notification_collection[resource.declared_key]
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|
if result = load_recipe(recipe_name, current_cookbook: current_cookbook)
result_recipes << result
end
end
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")
cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name, current_cookbook: current_cookbook)
if unreachable_cookbook?(cookbook_name) # CHEF-4367
Chef::Log.warn(<<-ERROR_MESSAGE)
MissingCookbookDependency:
Recipe `#{recipe_name}` is not in the run_list, and cookbook '#{cookbook_name}'
is not a dependency of any cookbook in the run_list. To load this recipe,
first add a dependency on cookbook '#{cookbook_name}' in the cookbook you're
including it from in that cookbook's metadata.
ERROR_MESSAGE
end
if loaded_fully_qualified_recipe?(cookbook_name, recipe_short_name)
Chef::Log.debug("I am not loading #{recipe_name}, because I have already seen it.")
false
else
loaded_recipe(cookbook_name, recipe_short_name)
node.loaded_recipe(cookbook_name, recipe_short_name)
cookbook = cookbook_collection[cookbook_name]
cookbook.load_recipe(recipe_short_name, self)
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}"
end
Chef::Log.debug("Loading recipe file #{recipe_file}")
recipe = Chef::Recipe.new("@recipe_files", recipe_file, self)
recipe.from_file(recipe_file)
recipe
end
#
# 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
attribute_filename = cookbook.attribute_filenames_by_short_filename[attr_file_name]
raise Chef::Exceptions::AttributeNotFound, "could not find filename for attribute #{attr_file_name} in cookbook #{cookbook_name}" unless attribute_filename
attribute_filename
end
#
# 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.
#
def loaded_recipes
loaded_recipes_hash.keys
end
#
# 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_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_hash.has_key?("#{cookbook}::#{recipe}")
end
#
# 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_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_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
#
# 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)
end
#
# Open a stream object that can be printed into and will dispatch to events
#
# @param name [String] The name of the stream.
# @param options [Hash] Other options for the stream.
#
# @return [EventDispatch::EventsOutputStream] The created stream.
#
# @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
ensure
stream.close
end
else
stream
end
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).
# 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 #{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 {}"
@reboot_info = {}
end
def reboot_requested?
reboot_info.size > 0
end
#
# Create a child RunContext.
#
def create_child
ChildRunContext.new(self)
end
# @api private
attr_writer :resource_collection
protected
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.")
@audits = value
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.")
@immediate_notification_collection = value
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.")
@delayed_notification_collection = value
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
add_delayed_action
delayed_actions
delayed_notification_collection
delayed_notification_collection=
delayed_notifications
immediate_notification_collection
immediate_notification_collection=
immediate_notifications
before_notification_collection
before_notifications
include_recipe
initialize_child_state
load_recipe
load_recipe_file
notifies_before
notifies_immediately
notifies_delayed
parent_run_context
root_run_context
resource_collection
resource_collection=
}.map { |x| x.to_sym }
# Verify that we didn't miss any methods
unless @__skip_method_checking # hook specifically for compat_resource
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
end
end

View File

@@ -0,0 +1,153 @@
#
# NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
#
# THIS IS A FILE AUTOGENERATED BY 'rake update' DO NOT EDIT!!!!
#
# NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
#
if Gem::Requirement.new('< 12.16.42').satisfied_by?(Gem::Version.new(Chef::VERSION))
#--
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Tim Hinderliter (<tim@chef.io>)
# Copyright:: Copyright 2008-2016, Chef Software 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/exceptions"
require "chef/mixin/params_validate"
require "chef/node"
require "chef/resource_collection"
class Chef
# == Chef::Runner
# This class is responsible for executing the steps in a Chef run.
class Runner
attr_reader :run_context
include Chef::Mixin::ParamsValidate
def initialize(run_context)
@run_context = run_context
end
def delayed_actions
@run_context.delayed_actions
end
def events
@run_context.events
end
# Determine the appropriate provider for the given resource, then
# execute it.
def run_action(resource, action, notification_type = nil, notifying_resource = nil)
# If there are any before notifications, why-run the resource
# and notify anyone who needs notifying
before_notifications = run_context.before_notifications(resource) || []
unless before_notifications.empty?
forced_why_run do
Chef::Log.info("#{resource} running why-run #{action} action to support before action")
resource.run_action(action, notification_type, notifying_resource)
end
if resource.updated_by_last_action?
before_notifications.each do |notification|
Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (before)")
run_action(notification.resource, notification.action, :before, resource)
end
resource.updated_by_last_action(false)
end
end
# Actually run the action for realsies
resource.run_action(action, notification_type, notifying_resource)
# Execute any immediate and queue up any delayed notifications
# associated with the resource, but only if it was updated *this time*
# we ran an action on it.
if resource.updated_by_last_action?
run_context.immediate_notifications(resource).each do |notification|
Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (immediate)")
run_action(notification.resource, notification.action, :immediate, resource)
end
run_context.delayed_notifications(resource).each do |notification|
# send the notification to the run_context of the receiving resource
notification.resource.run_context.add_delayed_action(notification)
end
end
end
# Iterates over the +resource_collection+ in the +run_context+ calling
# +run_action+ for each resource in turn.
def converge
# Resolve all lazy/forward references in notifications
run_context.resource_collection.each do |resource|
resource.resolve_notification_references
end
# Execute each resource.
run_context.resource_collection.execute_each_resource do |resource|
Array(resource.action).each { |action| run_action(resource, action) }
end
rescue Exception => e
Chef::Log.info "Running queued delayed notifications before re-raising exception"
run_delayed_notifications(e)
else
run_delayed_notifications(nil)
true
end
private
# Run all our :delayed actions
def run_delayed_notifications(error = nil)
collected_failures = Exceptions::MultipleFailures.new
collected_failures.client_run_failure(error) unless error.nil?
delayed_actions.each do |notification|
result = run_delayed_notification(notification)
if result.kind_of?(Exception)
collected_failures.notification_failure(result)
end
end
collected_failures.raise!
end
def run_delayed_notification(notification)
Chef::Log.info( "#{notification.notifying_resource} sending #{notification.action}"\
" action to #{notification.resource} (delayed)")
# Struct of resource/action to call
run_action(notification.resource, notification.action, :delayed)
true
rescue Exception => e
e
end
# helper to run a block of code with why_run forced to true and then restore it correctly
def forced_why_run
saved = Chef::Config[:why_run]
Chef::Config[:why_run] = true
yield
ensure
Chef::Config[:why_run] = saved
end
end
end
end