summaryrefslogtreecommitdiff
path: root/lib/chef
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2015-04-11 12:48:22 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2015-04-15 17:50:15 -0700
commite3a6565927e854cd5968bd3a6bd2248ec1245549 (patch)
tree590bfa3f9c3a4992096c0ccb679fcc7deda74243 /lib/chef
parenta959404b15ba6bdc98063cfa0c70e6f9eec9ccee (diff)
downloadchef-e3a6565927e854cd5968bd3a6bd2248ec1245549.tar.gz
add resource_resolver and resource_priority_map
also wire them up through the Chef class.
Diffstat (limited to 'lib/chef')
-rw-r--r--lib/chef/chef_class.rb130
-rw-r--r--lib/chef/client.rb12
-rw-r--r--lib/chef/mixin/provides.rb32
-rw-r--r--lib/chef/platform/provider_priority_map.rb23
-rw-r--r--lib/chef/platform/resource_priority_map.rb37
-rw-r--r--lib/chef/policy_builder/expand_node_object.rb20
-rw-r--r--lib/chef/policy_builder/policyfile.rb1
-rw-r--r--lib/chef/provider.rb25
-rw-r--r--lib/chef/provider_resolver.rb15
-rw-r--r--lib/chef/resource.rb44
-rw-r--r--lib/chef/resource_resolver.rb101
11 files changed, 362 insertions, 78 deletions
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb
new file mode 100644
index 0000000000..d3f7ee55c7
--- /dev/null
+++ b/lib/chef/chef_class.rb
@@ -0,0 +1,130 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Copyright:: Copyright (c) 2015 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.
+
+# NOTE: This class is not intended for internal use by the chef-client code. Classes in
+# chef-client should still have objects like the node and run_context injected into them
+# via their initializers. This class is still global state and will complicate writing
+# unit tests for internal Chef objects. It is intended to be used only by recipe code.
+
+# NOTE: When adding require lines here you are creating tight coupling on a global that may be
+# included in many different situations and ultimately that ends in tears with circular requires.
+# Note the way that the run_context, provider_priority_map and resource_priority_map are "dependency
+# injected" into this class by other objects and do not reference the class symbols in those files
+# directly and we do not need to require those files here.
+
+class Chef
+ class << self
+
+ #
+ # Public API
+ #
+
+ # Get the node object
+ #
+ # @return [Chef::Node] node object of the chef-client run
+ attr_reader :node
+
+ # Get the run context
+ #
+ # @return [Chef::RunContext] run_context of the chef-client run
+ attr_reader :run_context
+
+ # Get the array of providers associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ # @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node
+ def get_provider_priority_array(resource_name)
+ @provider_priority_map.get_priority_array(node, resource_name).dup
+ end
+
+ # Get the array of resources associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ # @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node
+ def get_resource_priority_array(resource_name)
+ @resource_priority_map.get_priority_array(node, resource_name).dup
+ end
+
+ # Set the array of providers associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node
+ # @param filter [Hash] Chef::Nodearray-style filter
+ # @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)
+ @provider_priority_map.set_priority_array(resource_name, priority_array, *filter).dup
+ end
+
+ # Get the array of resources associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node
+ # @param filter [Hash] Chef::Nodearray-style filter
+ # @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)
+ @resource_priority_map.set_priority_array(resource_name, priority_array, *filter).dup
+ end
+
+ #
+ # Dependency Injection API (Private not Public)
+ # [ in the ruby sense these have to be public methods, but they are
+ # *NOT* for public consumption ]
+ #
+
+ # Sets the resource_priority_map
+ #
+ # @api private
+ # @param resource_priority_map [Chef::Platform::ResourcePriorityMap]
+ def set_resource_priority_map(resource_priority_map)
+ @resource_priority_map = resource_priority_map
+ end
+
+ # Sets the provider_priority_map
+ #
+ # @api private
+ # @param provider_priority_map [Chef::Platform::providerPriorityMap]
+ def set_provider_priority_map(provider_priority_map)
+ @provider_priority_map = provider_priority_map
+ end
+
+ # Sets the node object
+ #
+ # @api private
+ # @param node [Chef::Node]
+ def set_node(node)
+ @node = node
+ end
+
+ # Sets the run_context object
+ #
+ # @api private
+ # @param run_context [Chef::RunContext]
+ def set_run_context(run_context)
+ @run_context = run_context
+ end
+
+ # Resets the internal state
+ #
+ # @api private
+ def reset!
+ @run_context = nil
+ @node = nil
+ @provider_priority_map = nil
+ @resource_priority_map = nil
+ end
+ end
+end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index a4f15c271f..098345a9e2 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -224,21 +224,21 @@ class Chef
end
# Instantiates a Chef::Node object, possibly loading the node's prior state
- # when using chef-client. Delegates to policy_builder
+ # when using chef-client. Delegates to policy_builder. Injects the built node
+ # into the Chef class.
#
- #
- # === Returns
- # Chef::Node:: The node object for this chef run
+ # @return [Chef::Node] The node object for this Chef run
def load_node
policy_builder.load_node
@node = policy_builder.node
+ Chef.set_node(@node)
+ node
end
# Mutates the `node` object to prepare it for the chef run. Delegates to
# policy_builder
#
- # === Returns
- # Chef::Node:: The updated node object
+ # @return [Chef::Node] The updated node object
def build_node
policy_builder.build_node
@run_status = Chef::RunStatus.new(node, events)
diff --git a/lib/chef/mixin/provides.rb b/lib/chef/mixin/provides.rb
new file mode 100644
index 0000000000..e5bb2c2005
--- /dev/null
+++ b/lib/chef/mixin/provides.rb
@@ -0,0 +1,32 @@
+
+require 'chef/mixin/descendants_tracker'
+
+class Chef
+ module Mixin
+ module Provides
+ include Chef::Mixin::DescendantsTracker
+
+ def node_map
+ @node_map ||= Chef::NodeMap.new
+ end
+
+ def provides(short_name, opts={}, &block)
+ if !short_name.kind_of?(Symbol)
+ # YAGNI: this is probably completely unnecessary and can be removed?
+ Chef::Log.deprecation "Passing a non-Symbol to Chef::Resource#provides will be removed"
+ if short_name.kind_of?(String)
+ short_name.downcase!
+ short_name.gsub!(/\s/, "_")
+ end
+ short_name = short_name.to_sym
+ end
+ node_map.set(short_name, true, opts, &block)
+ end
+
+ # provides a node on the resource (early binding)
+ def provides?(node, resource_name)
+ node_map.get(node, resource_name)
+ end
+ end
+ end
+end
diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb
index 2517f46dfd..1539f61900 100644
--- a/lib/chef/platform/provider_priority_map.rb
+++ b/lib/chef/platform/provider_priority_map.rb
@@ -1,6 +1,4 @@
-require 'chef/providers'
-
class Chef
class Platform
class ProviderPriorityMap
@@ -10,7 +8,22 @@ class Chef
load_default_map
end
+ 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)
+ priority(resource_name.to_sym, priority_array.to_a, *filter)
+ end
+
+ def priority(*args)
+ priority_map.set(*args)
+ end
+
+ private
+
def load_default_map
+ require 'chef/providers'
#
# Linux
@@ -71,13 +84,9 @@ class Chef
end
def priority_map
+ require 'chef/node_map'
@priority_map ||= Chef::NodeMap.new
end
-
- def priority(*args)
- priority_map.set(*args)
- end
-
end
end
end
diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb
new file mode 100644
index 0000000000..fc43b3e7db
--- /dev/null
+++ b/lib/chef/platform/resource_priority_map.rb
@@ -0,0 +1,37 @@
+class Chef
+ class Platform
+ class ResourcePriorityMap
+ include Singleton
+
+ def initialize
+ load_default_map
+ end
+
+ 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)
+ priority resource_name.to_sym, priority_array.to_a, *filter
+ end
+
+ def priority(*args)
+ priority_map.set(*args)
+ end
+
+ private
+
+ def load_default_map
+ require 'chef/resources'
+
+ # MacOSX
+ priority :package, Chef::Resource::HomebrewPackage, os: "darwin"
+ end
+
+ def priority_map
+ require 'chef/node_map'
+ @priority_map ||= Chef::NodeMap.new
+ end
+ end
+ end
+end
diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb
index 2ee8a23258..aa761926d3 100644
--- a/lib/chef/policy_builder/expand_node_object.rb
+++ b/lib/chef/policy_builder/expand_node_object.rb
@@ -24,6 +24,7 @@ require 'chef/rest'
require 'chef/run_context'
require 'chef/config'
require 'chef/node'
+require 'chef/chef_class'
class Chef
module PolicyBuilder
@@ -54,6 +55,21 @@ class Chef
@run_list_expansion = nil
end
+ # This method injects the run_context and provider and resource priority
+ # maps into the Chef class. The run_context has to be injected here, the provider and
+ # resource maps could be moved if a better place can be found to do this work.
+ #
+ # @param run_context [Chef::RunContext] the run_context to inject
+ def setup_chef_class(run_context)
+ # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
+ require 'chef/platform/provider_priority_map'
+ require 'chef/platform/resource_priority_map'
+
+ Chef.set_run_context(run_context)
+ Chef.set_provider_priority_map(Chef::Platform::ProviderPriorityMap.instance)
+ Chef.set_resource_priority_map(Chef::Platform::ResourcePriorityMap.instance)
+ end
+
def setup_run_context(specific_recipes=nil)
if Chef::Config[:solo]
Chef::Cookbook::FileVendor.fetch_from_disk(Chef::Config[:cookbook_path])
@@ -68,6 +84,10 @@ class Chef
run_context = Chef::RunContext.new(node, cookbook_collection, @events)
end
+ # TODO: this is really obviously not the place for this
+ # FIXME: need same edits
+ setup_chef_class(run_context)
+
# TODO: this is not the place for this. It should be in Runner or
# CookbookCompiler or something.
run_context.load(@run_list_expansion)
diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb
index 0c7c07f9f3..ac25b549be 100644
--- a/lib/chef/policy_builder/policyfile.rb
+++ b/lib/chef/policy_builder/policyfile.rb
@@ -385,4 +385,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 680fe9782f..65a56cf726 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -22,7 +22,7 @@ require 'chef/mixin/convert_to_class_name'
require 'chef/mixin/enforce_ownership_and_permissions'
require 'chef/mixin/why_run'
require 'chef/mixin/shell_out'
-require 'chef/mixin/descendants_tracker'
+require 'chef/mixin/provides'
require 'chef/platform/service_helpers'
require 'chef/node_map'
@@ -30,26 +30,11 @@ class Chef
class Provider
include Chef::Mixin::WhyRun
include Chef::Mixin::ShellOut
- extend Chef::Mixin::DescendantsTracker
+ extend Chef::Mixin::Provides
- class << self
- def node_map
- @node_map ||= Chef::NodeMap.new
- end
-
- def provides(resource_name, opts={}, &block)
- node_map.set(resource_name.to_sym, true, opts, &block)
- end
-
- # provides a node on the resource (early binding)
- def provides?(node, resource)
- node_map.get(node, resource.resource_name)
- end
-
- # supports the given resource and action (late binding)
- def supports?(resource, action)
- true
- end
+ # supports the given resource and action (late binding)
+ def self.supports?(resource, action)
+ true
end
attr_accessor :new_resource
diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb
index d83a3e0468..867c3deca8 100644
--- a/lib/chef/provider_resolver.rb
+++ b/lib/chef/provider_resolver.rb
@@ -47,7 +47,7 @@ class Chef
def enabled_handlers
@enabled_handlers ||=
providers.select do |klass|
- klass.provides?(node, resource)
+ klass.provides?(node, resource.resource_name)
end.sort {|a,b| a.to_s <=> b.to_s }
end
@@ -84,8 +84,13 @@ class Chef
if handlers.count >= 2
# this magic stack ranks the providers by where they appear in the provider_priority_map, it is mostly used
# to pick amongst N different ways to start init scripts on different debian/ubuntu systems.
- priority_list = [ get_provider_priority_map(resource.resource_name, node) ].flatten.compact
+ priority_list = [ get_priority_array(node, resource.resource_name) ].flatten.compact
handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i }
+ if priority_list.index(handlers.first).nil?
+ # if we had more than one and we picked one with a precidence of infinity that means that the resource_priority_map
+ # entry for this resource is missing -- we should probably raise here and force resolution of the ambiguity.
+ Chef::Log.warn "Ambiguous provider precedence: #{handlers}, please use Chef.set_provider_priority_array to provide determinism"
+ end
handlers = [ handlers.first ]
end
@@ -106,12 +111,12 @@ class Chef
end
# dep injection hooks
- def get_provider_priority_map(resource_name, node)
- provider_priority_map.get(node, resource_name)
+ def get_priority_array(node, resource_name)
+ provider_priority_map.get_priority_array(node, resource_name)
end
def provider_priority_map
- Chef::Platform::ProviderPriorityMap.instance.priority_map
+ Chef::Platform::ProviderPriorityMap.instance
end
end
end
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index ea220b6c70..d934ec8c47 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -33,7 +33,7 @@ require 'chef/platform'
require 'chef/resource/resource_notification'
require 'chef/mixin/deprecation'
-require 'chef/mixin/descendants_tracker'
+require 'chef/mixin/provides'
class Chef
class Resource
@@ -46,6 +46,7 @@ class Chef
include Chef::DSL::PlatformIntrospection
include Chef::DSL::RegistryHelper
include Chef::DSL::RebootPending
+ extend Chef::Mixin::Provides
#
# The node the current Chef run is using.
@@ -879,7 +880,6 @@ class Chef
include Chef::Mixin::ConvertToClassName
extend Chef::Mixin::ConvertToClassName
- extend Chef::Mixin::DescendantsTracker
# XXX: this is required for definition params inside of the scope of a
# subresource to work correctly.
@@ -1016,6 +1016,7 @@ class Chef
end
def provider_for_action(action)
+ require 'chef/provider_resolver'
provider = Chef::ProviderResolver.new(node, self, action).resolve.new(self, run_context)
provider.action = action
provider
@@ -1080,33 +1081,6 @@ class Chef
end
end
- # Maps a short_name (and optionally a platform and version) to a
- # Chef::Resource. This allows finer grained per platform resource
- # attributes and the end of overloaded resource definitions
- # (I'm looking at you Chef::Resource::Package)
- # Ex:
- # class WindowsFile < Chef::Resource
- # provides :file, os: "linux", platform_family: "rhel", platform: "redhat"
- # provides :file, os: "!windows
- # provides :file, os: [ "linux", "aix" ]
- # provides :file, os: "solaris2" do |node|
- # node['platform_version'].to_f <= 5.11
- # end
- # # ...other stuff
- # end
- #
- def self.provides(short_name, opts={}, &block)
- short_name_sym = short_name
- if short_name.kind_of?(String)
- # YAGNI: this is probably completely unnecessary and can be removed?
- Chef::Log.warn "[DEPRECATION] Passing a String to Chef::Resource#provides will be removed"
- short_name.downcase!
- short_name.gsub!(/\s/, "_")
- short_name_sym = short_name.to_sym
- end
- node_map.set(short_name_sym, constantize(self.name), opts, &block)
- end
-
# Returns a resource based on a short_name and node
#
# ==== Parameters
@@ -1116,16 +1090,12 @@ class Chef
# === Returns
# <Chef::Resource>:: returns the proper Chef::Resource class
def self.resource_for_node(short_name, node)
- klass = node_map.get(node, short_name) ||
- resource_matching_short_name(short_name)
+ require 'chef/resource_resolver'
+ klass = Chef::ResourceResolver.new(node, short_name).resolve
raise Chef::Exceptions::NoSuchResourceType.new(short_name, node) if klass.nil?
klass
end
- def self.node_map
- @@node_map ||= NodeMap.new
- end
-
# Returns the class of a Chef::Resource based on the short name
# ==== Parameters
# short_name<Symbol>:: short_name of the resource (ie :directory)
@@ -1156,7 +1126,3 @@ class Chef
end
end
end
-
-# We require this at the BOTTOM of this file to avoid circular requires (it is used
-# at runtime but not load time)
-require 'chef/provider_resolver'
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
new file mode 100644
index 0000000000..ff9d7aeb9f
--- /dev/null
+++ b/lib/chef/resource_resolver.rb
@@ -0,0 +1,101 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Copyright:: Copyright (c) 2015 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/platform/resource_priority_map'
+
+class Chef
+ class ResourceResolver
+
+ attr_reader :node
+ attr_reader :resource
+ attr_reader :action
+
+ def initialize(node, resource)
+ @node = node
+ @resource = resource
+ end
+
+ # return a deterministically sorted list of Chef::Resource subclasses
+ def resources
+ @resources ||= Chef::Resource.descendants
+ end
+
+ def resolve
+ maybe_dynamic_resource_resolution(resource) ||
+ maybe_chef_platform_lookup(resource)
+ end
+
+ # this cut looks at if the resource can handle the resource type on the node
+ def enabled_handlers
+ @enabled_handlers ||=
+ resources.select do |klass|
+ klass.provides?(node, resource)
+ end.sort {|a,b| a.to_s <=> b.to_s }
+ @enabled_handlers
+ end
+
+ private
+
+ # try dynamically finding a resource based on querying the resources to see what they support
+ def maybe_dynamic_resource_resolution(resource)
+ # log this so we know what resources will work for the generic resource on the node (early cut)
+ Chef::Log.debug "resources for generic #{resource} resource enabled on node include: #{enabled_handlers}"
+
+ # if none of the resources specifically support the resource, we still need to pick one of the resources that are
+ # enabled on the node to handle the why-run use case.
+ handlers = enabled_handlers
+
+ if handlers.count >= 2
+ # this magic stack ranks the resources by where they appear in the resource_priority_map
+ priority_list = [ get_priority_array(node, resource) ].flatten.compact
+ handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i }
+ if priority_list.index(handlers.first).nil?
+ # if we had more than one and we picked one with a precidence of infinity that means that the resource_priority_map
+ # entry for this resource is missing -- we should probably raise here and force resolution of the ambiguity.
+ Chef::Log.warn "Ambiguous resource precedence: #{handlers}, please use Chef.set_resource_priority_array to provide determinism"
+ end
+ handlers = [ handlers.first ]
+ end
+
+ Chef::Log.debug "resources that survived replacement include: #{handlers}"
+
+ raise Chef::Exceptions::AmbiguousResourceResolution.new(resource, handlers) if handlers.count >= 2
+
+ Chef::Log.debug "dynamic resource resolver FAILED to resolve a resouce" if handlers.empty?
+
+ return nil if handlers.empty?
+
+ handlers[0]
+ end
+
+ # try the old static lookup of resources by mangling name to resource klass
+ def maybe_chef_platform_lookup(resource)
+ Chef::Resource.resource_matching_short_name(resource)
+ end
+
+ # dep injection hooks
+ def get_priority_array(node, resource_name)
+ resource_priority_map.get_priority_array(node, resource_name)
+ end
+
+ def resource_priority_map
+ Chef::Platform::ResourcePriorityMap.instance
+ end
+ end
+end