From 7b67135065d813940a8675dd080c28d06db04f09 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Wed, 29 Apr 2015 08:11:22 -0700 Subject: Spike (not working) on modules instead of priority/provider maps --- lib/chef/chef_class.rb | 109 ++++++++++++----------------- lib/chef/dsl/providers.rb | 83 ++++++++++++++++++++++ lib/chef/dsl/recipe.rb | 89 ++++++++++------------- lib/chef/dsl/resource_creation.rb | 26 +++++++ lib/chef/dsl/resources.rb | 87 ++++++++++++++++++++--- lib/chef/mixin/callers.rb | 14 ++++ lib/chef/mixin/provides.rb | 103 ++++++++++++++++++++++----- lib/chef/node_filter.rb | 40 +++++++++++ lib/chef/node_map.rb | 79 ++++++++++++++------- lib/chef/platform/provider_priority_map.rb | 72 ------------------- lib/chef/provider.rb | 7 ++ lib/chef/provider_resolver.rb | 104 ++------------------------- lib/chef/resource.rb | 85 +++++++++------------- lib/chef/resource_resolver.rb | 103 ++------------------------- lib/chef/run_context.rb | 46 ++++++++++++ 15 files changed, 551 insertions(+), 496 deletions(-) create mode 100644 lib/chef/dsl/providers.rb create mode 100644 lib/chef/dsl/resource_creation.rb create mode 100644 lib/chef/mixin/callers.rb create mode 100644 lib/chef/node_filter.rb diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb index d3f7ee55c7..f0c7693a49 100644 --- a/lib/chef/chef_class.rb +++ b/lib/chef/chef_class.rb @@ -33,50 +33,16 @@ class Chef # 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] 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] 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] Array of Classes to set as the priority for resource_name on the node - # @param filter [Hash] Chef::Nodearray-style filter - # @return [Array] 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 + # Get the node object # - # @param resource_name [Symbol] name of the resource as a symbol - # @param priority_array [Array] Array of Classes to set as the priority for resource_name on the node - # @param filter [Hash] Chef::Nodearray-style filter - # @return [Array] 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 + # @return [Chef::Node] node object of the chef-client run + def node + run_context.node end # @@ -85,30 +51,6 @@ class Chef # *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 @@ -122,9 +64,46 @@ class Chef # @api private def reset! @run_context = nil - @node = nil - @provider_priority_map = nil - @resource_priority_map = nil end + + module BackcompatBreak + # 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] Priority Array of Provider Classes to use for the resource_name on the node + def get_provider_priority_array(resource_name) + raise NotImplementedError, "this will no longer work" + 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] Priority Array of Resource Classes to use for the resource_name on the node + def get_resource_priority_array(resource_name) + raise NotImplementedError, "this will no longer work" + 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] Array of Classes to set as the priority for resource_name on the node + # @param filter [Hash] Chef::Nodearray-style filter + # @return [Array] 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) + raise NotImplementedError, "this will no longer work" + 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] Array of Classes to set as the priority for resource_name on the node + # @param filter [Hash] Chef::Nodearray-style filter + # @return [Array] 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) + raise NotImplementedError, "this will no longer work" + end + + end + include BackcompatBreak end end diff --git a/lib/chef/dsl/providers.rb b/lib/chef/dsl/providers.rb new file mode 100644 index 0000000000..2e2b14b245 --- /dev/null +++ b/lib/chef/dsl/providers.rb @@ -0,0 +1,83 @@ +class Chef + module DSL + # + # Methods in this module each return one Provider class (or none if there + # are no providers associated with the given DSL and action). + # + # run_context.provider_classes.service #=> Chef::Provider::RunitService + # + module Providers + module DeclaredProviders + end + include DeclaredProviders + + # + # Platform-specific services with nice priority order + # + def service + return systemd_service if systemd_service + + # + # Check for the BSDs, Solaris and Mac + # + case run_context.node[:os] + when 'freebsd', 'netbsd' + freebsd_service + when 'openbsd' + openbsd_service + when 'solaris2' + solaris_service + when 'darwin' + macosx_service + when 'linux' + + # + # Different Linuxes are different + # + case run_context.node[:platform] + when 'arch' + arch_service + when 'gentoo' + gentoo_service + when 'debian' + # on debian-ish system if an upstart script exists that must win over sysv types + upstart_service || + insserv_service || + debian_service || + invokercd_service + when 'rhel', 'fedora', 'suse' + insserv_service || + redhat_service + else + insserv_service || + redhat_service + end + + end || + super + end + + # + # Platform-specific priority for packages + # + def package + case run_context.node[:os] + when 'darwin' + homebrew_package + end || + super + end + + def self.add_provider(name, priority, &block) + node_map.set(name, priority: priority, &block) + end + + private + + # This is the level at which we declare selectors + def self.node_map + @node_map ||= {} + end + end + end +end diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb index e17e008432..a5206819c4 100644 --- a/lib/chef/dsl/recipe.rb +++ b/lib/chef/dsl/recipe.rb @@ -34,59 +34,7 @@ class Chef include Chef::Mixin::ShellOut - # method_missing must live for backcompat purposes until Chef 13. - def method_missing(method_symbol, *args, &block) - # - # If there is already DSL for this, someone must have called - # method_missing manually. Not a fan. Not. A. Fan. - # - if respond_to?(method_symbol) - Chef::Log.warn("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13.") - Chef::Log.warn("Use public_send() or send() instead.") - return send(method_symbol, *args, &block) - end - - # - # If a definition exists, then Chef::DSL::Definitions.add_definition was - # never called. DEPRECATED. - # - if run_context.definitions.has_key?(method_symbol.to_sym) - Chef::Log.warn("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.") - Chef::DSL::Definitions.add_definition(method_symbol) - return send(method_symbol, *args, &block) - end - - # - # See if the resource exists anyway. If the user had set - # Chef::Resource::Blah = , a deprecation warning will be - # emitted and the DSL method 'blah' will be added to the DSL. - # - resource_class = Chef::ResourceResolver.new(run_context ? run_context.node : nil, method_symbol).resolve - if resource_class - # - # If the DSL method was *not* added, this is the case where the - # matching class implements 'provides?' and matches resources that it - # never declared "provides" for (which means we would never have - # created DSL). Anything where we don't create DSL is deprecated. - # - if !respond_to?(method_symbol) - Chef::Log.warn("#{resource_class} is marked as providing DSL #{method_symbol}, but provides #{method_symbol.inspect} was never called!") - Chef::Log.warn("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") - Chef::DSL::Resources.add_resource_dsl(method_symbol) - end - return send(method_symbol, *args, &block) - end - - begin - super - rescue NoMethodError - raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}" - rescue NameError - raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}" - end - end - - include Chef::DSL::Resources + include Chef::DSL::ResourceCreation include Chef::DSL::Definitions # @@ -183,6 +131,41 @@ class Chef raise Chef::Exceptions::ResourceNotFound, "exec was called, but you probably meant to use an execute resource. If not, please call Kernel#exec explicitly. The exec block called was \"#{args}\"" end + module Deprecated + # method_missing must live for backcompat purposes until Chef 13. + def method_missing(method_symbol, *args, &block) + # + # If there is already DSL for this, someone must have called + # method_missing manually. Not a fan. Not. A. Fan. + # + if respond_to?(method_symbol) + Chef::Log.warn("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13.") + Chef::Log.warn("Use public_send() or send() instead.") + return send(method_symbol, *args, &block) + end + + # + # If a definition exists, then Chef::DSL::Definitions.add_definition was + # never called. DEPRECATED. + # + if run_context.definitions.has_key?(method_symbol.to_sym) + Chef::Log.warn("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.") + Chef::DSL::Definitions.add_definition(method_symbol) + return send(method_symbol, *args, &block) + end + + begin + super + rescue NoMethodError + raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}" + rescue NameError + raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}" + end + end + end + + include Deprecated + end end end diff --git a/lib/chef/dsl/resource_creation.rb b/lib/chef/dsl/resource_creation.rb new file mode 100644 index 0000000000..b43636e61d --- /dev/null +++ b/lib/chef/dsl/resource_creation.rb @@ -0,0 +1,26 @@ +require 'chef/dsl/dsl_module' + +class Chef + module DSL + # + # Methods in this module each return one Resource class (or none if there + # are no resources associated with the given DSL). + # + # run_context.resource_classes.service #=> Chef::Resource::RunitService + # + module ResourceCreation + module AutomaticResourceCreation + end + include AutomaticResourceCreation + + module Deprecated + def method_missing(name, *args, &block) + if Chef::DSL::Resources.find_deprecated_classes(name) + public_send(name, *args, &block) + end + end + end + include Deprecated + end + end +end diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb index 482b14e3aa..b309f96295 100644 --- a/lib/chef/dsl/resources.rb +++ b/lib/chef/dsl/resources.rb @@ -1,22 +1,87 @@ +require 'chef/dsl/dsl_module' + class Chef module DSL # - # Module containing a method for each globally declared Resource + # Methods in this module each return one Resource class (or none if there + # are no resources associated with the given DSL). # - # Depends on declare_resource(name, created_at, &block) + # run_context.resource_classes.service #=> Chef::Resource::RunitService # - # @api private module Resources - def self.add_resource_dsl(dsl_name) - module_eval <<-EOM, __FILE__, __LINE__+1 - def #{dsl_name}(name, created_at=nil, &block) - declare_resource(#{dsl_name.inspect}, name, created_at || caller[0], &block) - end - EOM + module DeclaredResources + end + include DeclaredResources + + def self.node_map + @node_map ||= {} + end + + # + # Ensures that any resource classes added here are reflected in resource + # creation DSL. + # + module ReflectChangesInResourceCreation + # When resources are added here, we add the corresponding method to ResourceCreation + def method_added(name) + Chef::DSL::ResourceCreation::AutomaticResourceCreation.module_eval <<-EOM, __FILE__, __LINE__+1 + def #{name}(name, &block) + declare_resource(#{name.inspect}, name, caller[0], &block) + end + EOM + end + + def method_removed(name) + Chef::DSL::ResourceCreation::AutomaticResourceCreation.remove_method(name) + end + end + + module DeclaredResources + extend ReflectChangesInResourceCreation end - def self.remove_resource_dsl(dsl_name) - remove_method dsl_name if method_defined?(dsl_name) + + extend ReflectChangesInResourceCreation + + + module Deprecated + include Chef::Mixin::ConvertToClassName + + # + # Handles the deprecated search for Chef::Resource::Blah + # and for scanning all resources that have `provides?` but + # do not have `provides`. + # + def find_deprecated_resource_classes(name) + class_name = convert_to_class_name(method_symbol) + if Chef::Resource.const_defined?(class_name) + Chef::Log.warn("Class Chef::Resource::#{class_name} does not declare `provides #{resource.inspect}`.") + Chef::Log.warn("This will no longer work in Chef 13: you must use `provides` to provide DSL.") + chef_resource_class << Chef::Resource.const_get(class_name) + provider.provides chef_resource_class + end + providers = Chef::Resource.descendants.select { |r| r.provides?(node, method_symbol) } + if !providers.empty? + resource_classes += providers + Chef::Log.warn("#{resource_class} is marked as providing DSL #{method_symbol}, but provides #{method_symbol.inspect} was never called!") + Chef::Log.warn("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") + providers.each do |provider| + provider.provides(name) { provider.provides?(run_context.node, name) } unless chef_resource_class == provider + end + Chef::DSL::Resources.add_resource_dsl(method_symbol) + end + + if Chef::Resource.const_defined?(class_name) + end + if + end + def method_missing(name, *args, &block) + if find_deprecated_reosurce_classes(name) + public_send(name, *args, &block) + end + end end + include Deprecated + end end end diff --git a/lib/chef/mixin/callers.rb b/lib/chef/mixin/callers.rb new file mode 100644 index 0000000000..d6eea96780 --- /dev/null +++ b/lib/chef/mixin/callers.rb @@ -0,0 +1,14 @@ +class Chef + module Mixin + module Callers + def parse_caller(at) + if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at + file = Regexp.last_match[1] + line = Regexp.last_match[2].to_i + method = Regexp.last_match[3] + [file, line, method] + end + end + end + end +end diff --git a/lib/chef/mixin/provides.rb b/lib/chef/mixin/provides.rb index b40a1bc23b..bc2fcded26 100644 --- a/lib/chef/mixin/provides.rb +++ b/lib/chef/mixin/provides.rb @@ -1,37 +1,102 @@ require 'chef/mixin/descendants_tracker' +require 'set' class Chef module Mixin + # + # Lets objects cooperate to create a "named provider module," allowing many + # Resources / Providers / what have you to say "I would like to handle this + # DSL, but only on OS X / Linux / etc." + # module Provides - include Chef::Mixin::DescendantsTracker - - def node_map - @node_map ||= Chef::NodeMap.new + # + # The DSL module we provide DSL to. + # + # @return [DSLModule] The DSL module we provide DSL to. + # + def provides_dsl_module + raise NotImplementedError, "#{self.class}.dsl_module" 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/, "_") + # + # Mark this resource as a provider for the given DSL. + # + # @param short_name [Symbol] The name of the DSL, e.g. `:file` + # @param os [String, Array[String]] The operating system on which you + # provide the DSL. Corresponds to `node[:os]`. May + # include positive matches ('ubuntu'), negative matches ('!windows') or + # :all. + # @param platform [String, Array[String]] The platform on which you + # provide the DSL. Corresponds to `node[:platform]`. May + # include positive matches ('ubuntu'), negative matches ('!windows') or + # :all. + # @param platform_family [String, Array[String]] The platform family on + # which you provide the DSL. Corresponds to `node[:platform_family]`. May + # include positive matches ('ubuntu'), negative matches ('!windows') or + # :all. + # + def provides(short_name, os: nil, platform: nil, platform_family: nil, &block) + short_name = short_name.to_sym + provides_dsl_module.node_map.set(short_name, self, opts, &block) + + provides_dsl_module.module_eval <<-EOM, __FILE__, __LINE__+1 + def #{name} + provides_dsl_module.node_map.get(short_name, run_context.node) end - short_name = short_name.to_sym - end - node_map.set(short_name, true, opts, &block) + EOM end def provides_nothing - node_map.clear + provides_dsl_module.node_map.remove_if { |name, value, *other| value == self } end - # Check whether this resource provides the resource_name DSL for the given - # node - def provides?(node, resource_name) - node_map.get(node, resource_name) + module Deprecated + include Chef::Mixin::DescendantsTracker + + def node_map(warn: true) + Chef::Log.deprecation "Referencing node_map on #{self.class} is deprecated and will stop working in a future major version." + @node_map ||= Chef::NodeMap.new + end + + # Check whether this resource provides the resource_name DSL for the given + # node + def provides?(node, resource_name) + node_map(warn: false).get(node, resource_name) + end + + def provides(short_name, platform: nil, os: nil, platform_family: nil, + on_platform: nil, on_platforms: nil, + &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 + + if on_platform + Chef::Log.deprecation "The #{key} option to provides has been deprecated" + end + if on_platforms + Chef::Log.deprecation "The #{key} option to provides has been deprecated" + end + + super(short_name, os: os, platform_family: platform_family, + platform: platform || on_platform || on_platforms) + + node_map.set(short_name, true, opts, &block) + end + + def provides_nothing + node_map(warn: false).clear + super + end end + prepend Deprecated end end end diff --git a/lib/chef/node_filter.rb b/lib/chef/node_filter.rb new file mode 100644 index 0000000000..7df59b572b --- /dev/null +++ b/lib/chef/node_filter.rb @@ -0,0 +1,40 @@ +class Chef + class NodeMapValue + def initialize(&filter) + end + + def <=>(other) + priority <=> other.priority + end + def priority + Float::INFINITY + end + def value + filter.call(run_context) + end + end + + class FilteredValue + def initialize(name, os: nil, platform: nil, platform_family: nil, &filter) + @name = name + @os = os + @platform = platform + @platform_family = platform_family + super(&filter) + end + + def <=>(other) + if other.is_a?(FilteredValue) + if platform_family != other.platform_family + return platform_family ? -1 : 1 + end + if platform != other.platform + return platform ? -1 : 1 + end + if os != other.os + return os ? -1 : 1 + end + end + end + end +end diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb index 17fda4a271..527a33c2f5 100644 --- a/lib/chef/node_map.rb +++ b/lib/chef/node_map.rb @@ -47,14 +47,16 @@ class Chef # @yield [node] Arbitrary node filter as a block which takes a node argument # @return [NodeMap] Returns self for possible chaining # - def set(key, value, filters = {}, &block) - validate_filter!(filters) - deprecate_filter!(filters) - @map[key] ||= [] + def set(key, node_filter, + priority: Float::INFINITY, + &block) + Chef::Log.warn "The #{key} option to node_map has been deprecated" if DEPRECATED_OPTS.include?(key) + + map[key] ||= SortedSet.new() # we match on the first value we find, so we want to unshift so that the # last setter wins # FIXME: need a test for this behavior - @map[key].unshift({ filters: filters, block: block, value: value }) + map[key].unshift({ filters: filters, block: block, value: value }) self end @@ -70,14 +72,22 @@ class Chef raise "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) return nil unless @map.has_key?(key) @map[key].each do |matcher| - if filters_match?(node, matcher[:filters]) && - block_matches?(node, matcher[:block]) - return matcher[:value] + if filters_match?(node, matcher[:filters]) + result = block.nil? || block.call(node) + if result + return result == true ? matcher[:value] : result + end end end nil end + def delete_value(value) + @map.each do |key, matcher| + matchers.delete_if { |matcher| matcher[:value] == value } + end + end + def clear result = @map.keys @map.clear @@ -86,21 +96,6 @@ class Chef private - # only allow valid filter options - def validate_filter!(filters) - filters.each_key do |key| - # FIXME: real exception - raise "Bad key #{key} in Chef::NodeMap filter expression" unless VALID_OPTS.include?(key) - end - end - - # warn on deprecated filter options - def deprecate_filter!(filters) - filters.each_key do |key| - Chef::Log.warn "The #{key} option to node_map has been deprecated" if DEPRECATED_OPTS.include?(key) - end - end - # @todo: this works fine, but is probably hard to understand def negative_match(filter, param) # We support strings prefaced by '!' to mean 'not'. In particular, this is most useful @@ -144,9 +139,41 @@ class Chef return true end - def block_matches?(node, block) - return true if block.nil? - block.call node + module Deprecated + def set(name, value = nil, + on_platform: nil, + on_platforms: nil, + platform: nil, + os: nil, + platform_family: nil, + priority: Float::INFINITY, + &block) + if on_platform + Chef::Log.deprecation "The on_platform option to node_map has been deprecated" + end + if on_platforms + Chef::Log.deprecation "The on_platforms option to node_map has been deprecated" + end + + # platform: 'ubuntu' > platform_family: 'debian' > os: 'linux' + priority ||= 2 if platform + priority ||= 3 if platform_family + priority ||= 4 if os + priority ||= 5 + + if os || platform_family || platform + block = proc do |node| + return false if os && negative_match?(os, node[:os]) + return false if platform_family && negative_match?(platform_family, node[:platform_family]) + return false if platform && negative_match?(platform, node[:platform]) + result = block.call + return value || result + end + end + + super(name, value, priority: priority, &block) + end end + prepend Deprecated end end diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb index 1539f61900..c3b20fe02d 100644 --- a/lib/chef/platform/provider_priority_map.rb +++ b/lib/chef/platform/provider_priority_map.rb @@ -4,10 +4,6 @@ class Chef class ProviderPriorityMap include Singleton - def initialize - load_default_map - end - def get_priority_array(node, resource_name) priority_map.get(node, resource_name.to_sym) end @@ -19,74 +15,6 @@ class Chef def priority(*args) priority_map.set(*args) end - - private - - def load_default_map - require 'chef/providers' - - # - # Linux - # - - # default block for linux O/Sen must come before platform_family exceptions - priority :service, [ - Chef::Provider::Service::Systemd, - Chef::Provider::Service::Insserv, - Chef::Provider::Service::Redhat, - ], os: "linux" - - priority :service, [ - Chef::Provider::Service::Systemd, - Chef::Provider::Service::Arch, - ], platform_family: "arch" - - priority :service, [ - Chef::Provider::Service::Systemd, - Chef::Provider::Service::Gentoo, - ], platform_family: "gentoo" - - priority :service, [ - # we can determine what systemd supports accurately - Chef::Provider::Service::Systemd, - # on debian-ish system if an upstart script exists that must win over sysv types - Chef::Provider::Service::Upstart, - Chef::Provider::Service::Insserv, - Chef::Provider::Service::Debian, - Chef::Provider::Service::Invokercd, - ], platform_family: "debian" - - priority :service, [ - Chef::Provider::Service::Systemd, - Chef::Provider::Service::Insserv, - Chef::Provider::Service::Redhat, - ], platform_family: [ "rhel", "fedora", "suse" ] - - # - # BSDen - # - - priority :service, Chef::Provider::Service::Freebsd, os: [ "freebsd", "netbsd" ] - priority :service, Chef::Provider::Service::Openbsd, os: [ "openbsd" ] - - # - # Solaris-en - # - - priority :service, Chef::Provider::Service::Solaris, os: "solaris2" - - # - # Mac - # - - priority :service, Chef::Provider::Service::Macosx, os: "darwin" - priority :package, Chef::Provider::Package::Homebrew, os: "darwin" - end - - 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 65a56cf726..bd76e9e414 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -165,6 +165,13 @@ class Chef converge_actions.add_action(descriptions, &block) end + # + # The module Chef::Mixin::Provides will use to register providers. + # + def self.provides_dsl_module + Chef::DSL::Resources::DeclaredProviders + end + protected def converge_actions diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb index 867c3deca8..f3de2fbfc5 100644 --- a/lib/chef/provider_resolver.rb +++ b/lib/chef/provider_resolver.rb @@ -16,107 +16,13 @@ # limitations under the License. # -require 'chef/exceptions' -require 'chef/platform/provider_priority_map' - class Chef - class ProviderResolver - - attr_reader :node - attr_reader :resource - attr_reader :action - - def initialize(node, resource, action) - @node = node - @resource = resource - @action = action - end - - # return a deterministically sorted list of Chef::Provider subclasses - def providers - @providers ||= Chef::Provider.descendants - end - - def resolve - maybe_explicit_provider(resource) || - maybe_dynamic_provider_resolution(resource, action) || - maybe_chef_platform_lookup(resource) - end - - # this cut looks at if the provider can handle the resource type on the node - def enabled_handlers - @enabled_handlers ||= - providers.select do |klass| - klass.provides?(node, resource.resource_name) - end.sort {|a,b| a.to_s <=> b.to_s } - end - - # this cut looks at if the provider can handle the specific resource and action - def supported_handlers - @supported_handlers ||= - enabled_handlers.select do |klass| - klass.supports?(resource, action) - end - end - - private - - # if resource.provider is set, just return one of those objects - def maybe_explicit_provider(resource) - return nil unless resource.provider - resource.provider - end - - # try dynamically finding a provider based on querying the providers to see what they support - def maybe_dynamic_provider_resolution(resource, action) - # log this so we know what providers will work for the generic resource on the node (early cut) - Chef::Log.debug "providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}" - - # what providers were excluded by machine state (late cut) - Chef::Log.debug "providers that refused resource #{resource} were: #{enabled_handlers - supported_handlers}" - Chef::Log.debug "providers that support resource #{resource} include: #{supported_handlers}" - - # if none of the providers specifically support the resource, we still need to pick one of the providers that are - # enabled on the node to handle the why-run use case. - handlers = supported_handlers.empty? ? enabled_handlers : supported_handlers - Chef::Log.debug "no providers supported the resource, falling back to enabled handlers" if supported_handlers.empty? - - 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_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 ] + module Deprecated + class ProviderResolver < Struct[:node, :resource] + def resolve + resource.provider || Chef.run_context.provider_classes.public_send(arg) end - - Chef::Log.debug "providers that survived replacement include: #{handlers}" - - raise Chef::Exceptions::AmbiguousProviderResolution.new(resource, handlers) if handlers.count >= 2 - - Chef::Log.debug "dynamic provider resolver FAILED to resolve a provider" if handlers.empty? - - return nil if handlers.empty? - - handlers[0] - end - - # try the old static lookup of providers by platform - def maybe_chef_platform_lookup(resource) - Chef::Platform.find_provider_for_node(node, resource) - end - - # dep injection hooks - 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 end end + ProviderResolver = Deprecated::ProviderResolver end diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index b20ee05cdb..d7dad9e734 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -675,7 +675,7 @@ class Chef # def provider(arg=nil) klass = if arg.kind_of?(String) || arg.kind_of?(Symbol) - lookup_provider_constant(arg) + Chef.run_context.provider_classes.public_send(arg) else arg end @@ -807,17 +807,6 @@ class Chef @updated = true_or_false end - # - # The DSL name of this resource (e.g. `package` or `yum_package`) - # - # @return [String] The DSL name of this resource. - def self.dsl_name - if name - name = self.name.split('::')[-1] - convert_to_snake_case(name) - end - end - # # The name of this resource (e.g. `file`) # @@ -944,15 +933,6 @@ class Chef run_context.notifies_delayed(Notification.new(resource_spec, action, self)) end - class << self - # back-compat - # NOTE: that we do not support unregistering classes as descendents like - # we used to for LWRP unloading because that was horrible and removed in - # Chef-12. - alias :resource_classes :descendants - alias :find_subclass_by_name :find_descendants_by_name - end - # If an unknown method is invoked, determine whether the enclosing Provider's # lexical scope can fulfill the request. E.g. This happens when the Resource's # block invokes new_resource. @@ -964,22 +944,6 @@ class Chef end end - def self.provides(name, *args, &block) - super - Chef::DSL::Resources.add_resource_dsl(name) - end - - def self.provides_nothing - unprovided_names = super - - unprovided_names.each do |name| - resource = resource_matching_short_name(name) - if !resource || resource == self - Chef::DSL::Resources.remove_resource_dsl(name) - end - end - end - # Helper for #notifies def validate_resource_spec!(resource_spec) run_context.resource_collection.validate_lookup_spec!(resource_spec) @@ -1036,10 +1000,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 + Chef.run_context.provider_classes.public_send(resource.resource_name) end # ??? TODO Seems unused. Delete? @@ -1129,20 +1090,38 @@ class Chef Chef::ResourceResolver.new(Chef::Node.new, short_name).resolve end - private - - def lookup_provider_constant(name) - begin - self.class.provider_base.const_get(convert_to_class_name(name.to_s)) - rescue NameError => e - if e.to_s =~ /#{Regexp.escape(self.class.provider_base.to_s)}/ - raise ArgumentError, "No provider found to match '#{name}'" - else - raise e + module Deprecated + # + # The DSL name of this resource (e.g. `package` or `yum_package`) + # + # @return [String] The DSL name of this resource. + def self.dsl_name + if name + name = self.name.split('::')[-1] + convert_to_snake_case(name) end end end + include Deprecated + + module DeprecatedClassMethods + include DescendantTracker + # back-compat + # NOTE: that we do not support unregistering classes as descendents like + # we used to for LWRP unloading because that was horrible and removed in + # Chef-12. + alias :resource_classes :descendants + alias :find_subclass_by_name :find_descendants_by_name + end + extend DeprecatedClassMethods + + protected + + # + # The module Chef::Mixin::Provides will use to register resources. + # + def self.provides_dsl_module + Chef::DSL::Resources::DeclaredResources + end end end - -require 'chef/resource_resolver' diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb index c9bf2e189a..88ee218924 100644 --- a/lib/chef/resource_resolver.rb +++ b/lib/chef/resource_resolver.rb @@ -21,105 +21,12 @@ require 'chef/platform/resource_priority_map' require 'chef/mixin/convert_to_class_name' class Chef - class ResourceResolver - include Chef::Mixin::ConvertToClassName - - 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 - # 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.size >= 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[0..0] - 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 - - # this cut looks at if the resource can handle the resource type on the node - def enabled_handlers - @enabled_handlers ||= begin - enabled_handlers = resources.select do |klass| - klass.provides?(node, resource) - end.sort {|a,b| a.to_s <=> b.to_s } - - # Add Chef::Resource::X as a possibility if it is not a handler already - check_for_deprecated_chef_resource_class(enabled_handlers) - - enabled_handlers + module Deprecated + class ResourceResolver < Struct[:node, :resource] + def resolve + Chef.run_context.resource_classes.public_send(resource) end end - - private - - # - # Checks if the Chef::Resource::* class corresponding to the DSL name - # exists, emits a deprecation warning, marks it as providing the given - # short name and adds it to the DSL. This is used for method_missing - # deprecation and ResourceResolver checking, when people have created - # anonymous classes and assigned them to Chef::Resource::X. - # - # Returns the matched class, if it exists. - # - # @api private - def check_for_deprecated_chef_resource_class(enabled_handlers) - # If Chef::Resource::MyResource exists, but was not set, it won't have a - # DSL name. Add the DSL method and warn about this pattern. - class_name = convert_to_class_name(resource.to_s) - if Chef::Resource.const_defined?(class_name) - # If Chef::Resource::X already exists, and is *not* already marked as - # providing this resource, mark it as providing the resource and add it - # to the list of handlers for next time. - resource_class = Chef::Resource.const_get(class_name) - if resource_class <= Chef::Resource && !enabled_handlers.include?(resource_class) - enabled_handlers << resource_class - Chef::Log.warn("Class Chef::Resource::#{class_name} does not declare `provides #{resource.inspect}`.") - Chef::Log.warn("This will no longer work in Chef 13: you must use `provides` to provide DSL.") - resource_class.provides resource.to_sym - end - end - 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 + ResourceResolver = Deprecated::ResourceResolver end diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index 4f0215bfd4..29edaa262b 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -299,11 +299,57 @@ ERROR_MESSAGE @reboot_info.size > 0 end + # + # The list of resource classes for this Chef run. + # + # @example + # ```ruby + # run_context.resource_classes.file + # #=> Chef::Resource::File + # ``` + # + def resource_classes + @resource_classes ||= ResourceClasses.new(run_context) + end + + # + # The list of provider classes for this Chef run. + # + # @example + # ```ruby + # run_context.provider_classes.file + # #=> Chef::Provider::File + # ``` + # + def provider_classes + run_context = self + @provider_classes ||= Object.new.tap do + @run_context = run_context + attr_reader :run_context + extend Chef::DSL::Providers + end + end + private def loaded_recipe(cookbook, recipe) @loaded_recipes["#{cookbook}::#{recipe}"] = true end + class ResourceClasses + def initialize(run_context) + @run_context = run_context + end + attr_reader :run_context + include Chef::DSL::Resources + end + + class ProviderClasses + def initialize(run_context) + @run_context = run_context + end + attr_reader :run_context + include Chef::DSL::Providers + end end end -- cgit v1.2.1