diff options
author | John Keiser <john@johnkeiser.com> | 2015-06-30 21:14:28 -0600 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2015-07-06 12:04:47 -0600 |
commit | 591b5599f5412faa382e45d035d76535cd93736a (patch) | |
tree | 3494785822024b7c4acc97adc4883c7a73e2eaaf | |
parent | cfd2b1fa1b26e8b0aa92a5ba8bd294b96379b2fa (diff) | |
download | chef-591b5599f5412faa382e45d035d76535cd93736a.tar.gz |
Re-separate priority map and DSL handler map so that provides has veto power over priority
34 files changed, 796 insertions, 517 deletions
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb index a0c74f7aec..563e06434b 100644 --- a/lib/chef/chef_class.rb +++ b/lib/chef/chef_class.rb @@ -28,6 +28,8 @@ require 'chef/platform/provider_priority_map' require 'chef/platform/resource_priority_map' +require 'chef/platform/provider_handler_map' +require 'chef/platform/resource_handler_map' class Chef class << self @@ -160,20 +162,26 @@ class Chef @node = nil @provider_priority_map = nil @resource_priority_map = nil + @provider_dsl_map = nil + @resource_dsl_map = nil end # @api private def provider_priority_map - @provider_priority_map ||= begin - # these slurp in the resource+provider world, so be exceedingly lazy about requiring them - Chef::Platform::ProviderPriorityMap.instance - end + # these slurp in the resource+provider world, so be exceedingly lazy about requiring them + @provider_priority_map ||= Chef::Platform::ProviderPriorityMap.instance end # @api private def resource_priority_map - @resource_priority_map ||= begin - Chef::Platform::ResourcePriorityMap.instance - end + @resource_priority_map ||= Chef::Platform::ResourcePriorityMap.instance + end + # @api private + def provider_handler_map + @provider_handler_map ||= Chef::Platform::ProviderHandlerMap.instance + end + # @api private + def resource_handler_map + @resource_handler_map ||= Chef::Platform::ResourceHandlerMap.instance end end diff --git a/lib/chef/platform/handler_map.rb b/lib/chef/platform/handler_map.rb new file mode 100644 index 0000000000..001eb3dc8f --- /dev/null +++ b/lib/chef/platform/handler_map.rb @@ -0,0 +1,45 @@ +# +# Author:: John Keiser (<jkeiser@chef.io>) +# Copyright:: Copyright (c) 2015 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/node_map' + +class Chef + class Platform + class HandlerMap < Chef::NodeMap + # + # "provides" lines with identical filters sort by class name (ascending). + # + def compare_matchers(key, new_matcher, matcher) + cmp = super + if cmp == 0 + # Sort by class name (ascending) as well, if all other properties + # are exactly equal + if new_matcher[:value].is_a?(Class) && !new_matcher[:override] + cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:value].name } + if cmp < 0 + Chef::Log.warn "You are overriding #{key} on #{new_matcher[:filters].inspect} with #{new_matcher[:value].inspect}: used to be #{matcher[:value].inspect}. Use override: true if this is what you intended." + elsif cmp > 0 + Chef::Log.warn "You declared a new resource #{new_matcher[:value].inspect} for resource #{key}, but it comes alphabetically after #{matcher[:value].inspect} and has the same filters (#{new_matcher[:filters].inspect}), so it will not be used. Use override: true if you want to use it for #{key}." + end + end + end + cmp + end + end + end +end diff --git a/lib/chef/platform/priority_map.rb b/lib/chef/platform/priority_map.rb index 73554eafe1..0b050deb59 100644 --- a/lib/chef/platform/priority_map.rb +++ b/lib/chef/platform/priority_map.rb @@ -1,3 +1,21 @@ +# +# Author:: John Keiser (<jkeiser@chef.io>) +# Copyright:: Copyright (c) 2015 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + require 'chef/node_map' class Chef @@ -18,43 +36,6 @@ class Chef set(key, priority_array, *filter, &block) priority_array end - - # @api private - def list_handlers(node, key, **filters) - list(node, key, **filters).flatten(1).uniq - end - - # @api private - def includes_handler?(key, handler) - return false if !map.has_key?(key) - map[key].any? { |m| h = m[:value]; h.is_a?(Array) ? h.include?(handler) : h == handler } - end - - # - # Priority maps have one extra precedence: priority arrays override "provides," - # and "provides" lines with identical filters sort by class name (ascending). - # - def compare_matchers(key, new_matcher, matcher) - # Priority arrays come before "provides" - if new_matcher[:value].is_a?(Array) != matcher[:value].is_a?(Array) - return new_matcher[:value].is_a?(Array) ? -1 : 1 - end - - cmp = super - if cmp == 0 - # Sort by class name (ascending) as well, if all other properties - # are exactly equal - if new_matcher[:value].is_a?(Class) && !new_matcher[:override] - cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:value].name } - if cmp < 0 - Chef::Log.warn "You are overriding #{key} on #{new_matcher[:filters].inspect} with #{new_matcher[:value].inspect}: used to be #{matcher[:value].inspect}. Use override: true if this is what you intended." - elsif cmp > 0 - Chef::Log.warn "You declared a new resource #{new_matcher[:value].inspect} for resource #{key}, but it comes alphabetically after #{matcher[:value].inspect} and has the same filters (#{new_matcher[:filters].inspect}), so it will not be used. Use override: true if you want to use it for #{key}." - end - end - end - cmp - end end end end diff --git a/lib/chef/platform/provider_handler_map.rb b/lib/chef/platform/provider_handler_map.rb new file mode 100644 index 0000000000..37321268aa --- /dev/null +++ b/lib/chef/platform/provider_handler_map.rb @@ -0,0 +1,11 @@ +require 'singleton' +require 'chef/platform/handler_map' + +class Chef + class Platform + # @api private + class ProviderHandlerMap < Chef::Platform::HandlerMap + include Singleton + end + end +end diff --git a/lib/chef/platform/resource_handler_map.rb b/lib/chef/platform/resource_handler_map.rb new file mode 100644 index 0000000000..532d4ff656 --- /dev/null +++ b/lib/chef/platform/resource_handler_map.rb @@ -0,0 +1,11 @@ +require 'singleton' +require 'chef/platform/handler_map' + +class Chef + class Platform + # @api private + class ResourceHandlerMap < Chef::Platform::HandlerMap + include Singleton + end + end +end diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb index aa57e3ddf0..5cc86fd2e7 100644 --- a/lib/chef/platform/resource_priority_map.rb +++ b/lib/chef/platform/resource_priority_map.rb @@ -6,12 +6,6 @@ class Chef # @api private class ResourcePriorityMap < Chef::Platform::PriorityMap include Singleton - - # @api private - def get_priority_array(node, resource_name, canonical: nil) - super(node, resource_name.to_sym, canonical: canonical) - end - end end end diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index 280277d947..dcfc92645b 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -176,7 +176,7 @@ class Chef end def self.provides(short_name, opts={}, &block) - Chef.provider_priority_map.set(short_name, self, opts, &block) + Chef.provider_handler_map.set(short_name, self, opts, &block) end def self.provides?(node, resource) diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb index aca8d0dc3b..880104bff7 100644 --- a/lib/chef/provider/package.rb +++ b/lib/chef/provider/package.rb @@ -491,37 +491,6 @@ class Chef end end - # Set provider priority - require 'chef/chef_class' - require 'chef/provider/package/dpkg' - require 'chef/provider/package/homebrew' - require 'chef/provider/package/macports' - require 'chef/provider/package/apt' - require 'chef/provider/package/yum' - require 'chef/provider/package/zypper' - require 'chef/provider/package/portage' - require 'chef/provider/package/pacman' - require 'chef/provider/package/ips' - require 'chef/provider/package/solaris' - require 'chef/provider/package/smartos' - require 'chef/provider/package/aix' - require 'chef/provider/package/paludis' - - Chef.set_provider_priority_array :package, [ Homebrew, Macports ], os: "darwin" - - Chef.set_provider_priority_array :package, Apt, platform_family: "debian" - Chef.set_provider_priority_array :package, Yum, platform_family: %w(rhel fedora) - Chef.set_provider_priority_array :package, Zypper, platform_family: "suse" - Chef.set_provider_priority_array :package, Portage, platform: "gentoo" - Chef.set_provider_priority_array :package, Pacman, platform: "arch" - Chef.set_provider_priority_array :package, Ips, platform: %w(openindiana opensolaris omnios solaris2) - Chef.set_provider_priority_array :package, Solaris, platform: "nexentacore" - Chef.set_provider_priority_array :package, Solaris, platform: "solaris2", platform_version: '< 5.11' - - Chef.set_provider_priority_array :package, SmartOS, platform: "smartos" - Chef.set_provider_priority_array :package, Aix, platform: "aix" - Chef.set_provider_priority_array :package, Paludis, platform: "exherbo" - private def shell_out_with_timeout(*command_args) diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb index b97db9d061..5165f4b4ea 100644 --- a/lib/chef/provider/package/aix.rb +++ b/lib/chef/provider/package/aix.rb @@ -26,6 +26,7 @@ class Chef class Package class Aix < Chef::Provider::Package + provides :package, os: "aix" provides :bff_package, os: "aix" include Chef::Mixin::GetSourceFromPackage diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb index bd6ed283bf..e109c9966a 100644 --- a/lib/chef/provider/package/apt.rb +++ b/lib/chef/provider/package/apt.rb @@ -25,6 +25,7 @@ class Chef class Package class Apt < Chef::Provider::Package + provides :package, platform_family: "debian" provides :apt_package, os: "linux" # return [Hash] mapping of package name to Boolean value diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb index beede1c916..e5c45f0a62 100644 --- a/lib/chef/provider/package/homebrew.rb +++ b/lib/chef/provider/package/homebrew.rb @@ -26,6 +26,7 @@ class Chef class Package class Homebrew < Chef::Provider::Package + provides :package, os: "darwin", override: true provides :homebrew_package include Chef::Mixin::HomebrewUser diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb index 4d7f4a3583..96c2e711d4 100644 --- a/lib/chef/provider/package/ips.rb +++ b/lib/chef/provider/package/ips.rb @@ -27,6 +27,7 @@ class Chef class Package class Ips < Chef::Provider::Package + provides :package, platform: %w(openindiana opensolaris omnios solaris2) provides :ips_package, os: "solaris2" attr_accessor :virtual diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb index e945211540..c7ea71ac8c 100644 --- a/lib/chef/provider/package/macports.rb +++ b/lib/chef/provider/package/macports.rb @@ -3,6 +3,7 @@ class Chef class Package class Macports < Chef::Provider::Package + provides :package, os: "darwin" provides :macports_package def load_current_resource diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb index bf03e54656..01e3a9cc01 100644 --- a/lib/chef/provider/package/pacman.rb +++ b/lib/chef/provider/package/pacman.rb @@ -25,6 +25,7 @@ class Chef class Package class Pacman < Chef::Provider::Package + provides :package, platform: "arch" provides :pacman_package, os: "linux" def load_current_resource diff --git a/lib/chef/provider/package/paludis.rb b/lib/chef/provider/package/paludis.rb index 407e0d0110..2d6302515b 100644 --- a/lib/chef/provider/package/paludis.rb +++ b/lib/chef/provider/package/paludis.rb @@ -24,6 +24,7 @@ class Chef class Package class Paludis < Chef::Provider::Package + provides :package, platform: "exherbo" provides :paludis_package, os: "linux" def load_current_resource diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb index 4ba0160bb0..95782a6774 100644 --- a/lib/chef/provider/package/portage.rb +++ b/lib/chef/provider/package/portage.rb @@ -25,6 +25,8 @@ class Chef class Provider class Package class Portage < Chef::Provider::Package + + provides :package, platform: "gentoo" provides :portage_package PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)} diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb index 0d5b801c96..71b8a9b9e1 100644 --- a/lib/chef/provider/package/smartos.rb +++ b/lib/chef/provider/package/smartos.rb @@ -29,6 +29,7 @@ class Chef class SmartOS < Chef::Provider::Package attr_accessor :is_virtual_package + provides :package, platform: "smartos" provides :smartos_package, os: "solaris2", platform_family: "smartos" def load_current_resource diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb index 9b10403344..e62f37d27b 100644 --- a/lib/chef/provider/package/solaris.rb +++ b/lib/chef/provider/package/solaris.rb @@ -27,6 +27,8 @@ class Chef include Chef::Mixin::GetSourceFromPackage + provides :package, platform: "nexentacore" + provides :package, platform: "solaris2", platform_version: '< 5.11' provides :solaris_package, os: "solaris2" # def initialize(*args) diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb index 85c2ba683c..e8c0483741 100644 --- a/lib/chef/provider/package/yum.rb +++ b/lib/chef/provider/package/yum.rb @@ -28,6 +28,7 @@ class Chef class Package class Yum < Chef::Provider::Package + provides :package, platform_family: %w(rhel fedora) provides :yum_package, os: "linux" class RPMUtils diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb index c2a3ac4ba8..ac42304ffb 100644 --- a/lib/chef/provider/package/zypper.rb +++ b/lib/chef/provider/package/zypper.rb @@ -29,6 +29,7 @@ class Chef class Package class Zypper < Chef::Provider::Package + provides :package, platform_family: "suse" provides :zypper_package, os: "linux" def load_current_resource diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb index 9c523b5e66..15dd72cb04 100644 --- a/lib/chef/provider/service.rb +++ b/lib/chef/provider/service.rb @@ -188,29 +188,11 @@ class Chef require 'chef/provider/service/upstart' require 'chef/provider/service/debian' require 'chef/provider/service/invokercd' - require 'chef/provider/service/freebsd' - require 'chef/provider/service/openbsd' - require 'chef/provider/service/solaris' - require 'chef/provider/service/macosx' - - def self.os(os, *providers) - Chef.set_provider_priority_array(:service, providers, os: os) - end - def self.platform_family(platform_family, *providers) - Chef.set_provider_priority_array(:service, providers, platform_family: platform_family) - end - - os %w(freebsd netbsd), Freebsd - os %w(openbsd), Openbsd - os %w(solaris2), Solaris - os %w(darwin), Macosx - os %w(linux), Systemd, Insserv, Redhat - - platform_family %w(arch), Systemd, Arch - platform_family %w(gentoo), Systemd, Gentoo - platform_family %w(debian), Systemd, Upstart, Insserv, Debian, Invokercd - platform_family %w(rhel fedora suse), Systemd, Insserv, Redhat + Chef.set_provider_priority_array :service, [ Systemd, Arch ], platform_family: 'arch' + Chef.set_provider_priority_array :service, [ Systemd, Gentoo ], platform_family: 'gentoo' + Chef.set_provider_priority_array :service, [ Systemd, Upstart, Insserv, Debian, Invokercd ], platform_family: 'debian' + Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w(rhel fedora suse) end end end diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb index c67f3f05da..46c23fdd34 100644 --- a/lib/chef/provider/service/debian.rb +++ b/lib/chef/provider/service/debian.rb @@ -22,6 +22,8 @@ class Chef class Provider class Service class Debian < Chef::Provider::Service::Init + provides :service, platform_family: 'debian' + UPDATE_RC_D_ENABLED_MATCHES = /\/rc[\dS].d\/S|not installed/i UPDATE_RC_D_PRIORITIES = /\/rc([\dS]).d\/([SK])(\d\d)/i diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb index 4534e33f32..2fd2eac38e 100644 --- a/lib/chef/provider/service/insserv.rb +++ b/lib/chef/provider/service/insserv.rb @@ -24,6 +24,8 @@ class Chef class Service class Insserv < Chef::Provider::Service::Init + provides :service, platform_family: %w(debian rhel fedora suse) + def self.provides?(node, resource) super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv) end diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb index fdf4cbc256..39022546b0 100644 --- a/lib/chef/provider/service/invokercd.rb +++ b/lib/chef/provider/service/invokercd.rb @@ -23,6 +23,8 @@ class Chef class Service class Invokercd < Chef::Provider::Service::Init + provides :service, platform_family: 'debian', override: true + def self.provides?(node, resource) super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd) end diff --git a/lib/chef/provider/service/openbsd.rb b/lib/chef/provider/service/openbsd.rb index d509ee10ff..173191e01c 100644 --- a/lib/chef/provider/service/openbsd.rb +++ b/lib/chef/provider/service/openbsd.rb @@ -26,7 +26,7 @@ class Chef class Service class Openbsd < Chef::Provider::Service::Init - provides :service, os: [ "openbsd" ] + provides :service, os: "openbsd" include Chef::Mixin::ShellOut diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb index a8deb13aec..2330d88eb7 100644 --- a/lib/chef/provider/service/redhat.rb +++ b/lib/chef/provider/service/redhat.rb @@ -23,6 +23,8 @@ class Chef class Service class Redhat < Chef::Provider::Service::Init + provides :service, platform_family: %w(rhel fedora suse) + CHKCONFIG_ON = /\d:on/ CHKCONFIG_MISSING = /No such/ diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb index d8ce6af649..8809d1c708 100644 --- a/lib/chef/provider/service/upstart.rb +++ b/lib/chef/provider/service/upstart.rb @@ -25,6 +25,9 @@ class Chef class Provider class Service class Upstart < Chef::Provider::Service::Simple + + provides :service, platform_family: 'debian', override: true + UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/ def self.provides?(node, resource) diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb index 8e731ff96a..2fd9d7e8eb 100644 --- a/lib/chef/provider_resolver.rb +++ b/lib/chef/provider_resolver.rb @@ -68,27 +68,39 @@ class Chef potential_handlers.include?(provider_class) end - def self.includes_handler?(resource_name, provider_class) - priority_map.includes_handler?(resource_name, provider_class) - end - def enabled_handlers - @enabled_handlers ||= - potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource) } + potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource) } end # TODO deprecate this and allow actions to be passed as a filter to # `provides` so we don't have to have two separate things. # @api private def supported_handlers - @supported_handlers ||= - enabled_handlers.select { |handler| handler.supports?(resource, action) } + enabled_handlers.select { |handler| handler.supports?(resource, action) } end private def potential_handlers - priority_map.list_handlers(node, resource.resource_name) + handler_map.list(node, resource.resource_name).uniq + end + + # The list of handlers, with any in the priority_map moved to the front + def prioritized_handlers + @prioritized_handlers ||= begin + supported_handlers = self.supported_handlers + if supported_handlers.empty? + # 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. FIXME we should only do this in why-run mode then. + Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway." + supported_handlers = enabled_handlers + end + + prioritized = priority_map.list(node, resource.resource_name).flatten(1) + prioritized &= supported_handlers # Filter the priority map by the actual enabled handlers + prioritized |= supported_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set) + prioritized + end end # if resource.provider is set, just return one of those objects @@ -101,15 +113,7 @@ class Chef def maybe_dynamic_provider_resolution(resource, action) Chef::Log.debug "Providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}" - handlers = supported_handlers - if handlers.empty? - # 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. FIXME we should only do this in why-run mode then. - Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway." - handlers = enabled_handlers - end - - handler = handlers.first + handler = prioritized_handlers.first if handler Chef::Log.debug "Provider for action #{action} on resource #{resource} is #{handler}" @@ -125,12 +129,12 @@ class Chef Chef::Platform.find_provider_for_node(node, resource) end - def self.priority_map + def priority_map Chef.provider_priority_map end - def priority_map - Chef.provider_priority_map + def handler_map + Chef.provider_handler_map end def overrode_provides?(handler) diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 5e9ba42703..ac6f5e8923 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -1550,7 +1550,7 @@ class Chef remove_canonical_dsl end - result = Chef.resource_priority_map.set(name, self, options, &block) + result = Chef.resource_handler_map.set(name, self, options, &block) Chef::DSL::Resources.add_resource_dsl(name) result end @@ -1742,7 +1742,7 @@ class Chef def self.remove_canonical_dsl if @resource_name - remaining = Chef.resource_priority_map.delete_canonical(@resource_name, self) + remaining = Chef.resource_handler_map.delete_canonical(@resource_name, self) if !remaining Chef::DSL::Resources.remove_resource_dsl(@resource_name) end diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb index 2f27e93a3b..47b3df18af 100644 --- a/lib/chef/resource_resolver.rb +++ b/lib/chef/resource_resolver.rb @@ -83,9 +83,9 @@ class Chef # @api private use Chef::ResourceResolver.resolve instead. 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_name} resource enabled on node include: #{enabled_handlers}" + Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}" - handler = enabled_handlers.first + handler = prioritized_handlers.first if handler Chef::Log.debug "Resource for #{resource_name} is #{handler}" @@ -98,8 +98,8 @@ class Chef # @api private def list - Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{enabled_handlers}" - enabled_handlers + Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}" + prioritized_handlers end # @@ -118,7 +118,7 @@ class Chef # # @api private def self.includes_handler?(resource_name, resource_class) - priority_map.includes_handler?(resource_name, resource_class) + handler_map.list(nil, resource_name).include?(resource_class) end protected @@ -127,19 +127,38 @@ class Chef Chef.resource_priority_map end + def self.handler_map + Chef.resource_handler_map + end + def priority_map Chef.resource_priority_map end + def handler_map + Chef.resource_handler_map + end + # @api private def potential_handlers - priority_map.list_handlers(node, resource_name, canonical: canonical) + handler_map.list(node, resource_name, canonical: canonical).uniq end def enabled_handlers potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource_name) } end + def prioritized_handlers + @prioritized_handlers ||= begin + enabled_handlers = self.enabled_handlers + + prioritized = priority_map.list(node, resource_name, canonical: canonical).flatten(1) + prioritized &= enabled_handlers # Filter the priority map by the actual enabled handlers + prioritized |= enabled_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set) + prioritized + end + end + def overrode_provides?(handler) handler.method(:provides?).owner != Chef::Resource.method(:provides?).owner end @@ -151,17 +170,15 @@ class Chef end def enabled_handlers - @enabled_handlers ||= begin - handlers = super - if handlers.empty? - handlers = resources.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource_name) } - handlers.each do |handler| - Chef::Log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!") - Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") - end + handlers = super + if handlers.empty? + handlers = resources.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource_name) } + handlers.each do |handler| + Chef::Log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!") + Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") end - handlers end + handlers end end prepend Deprecated diff --git a/spec/integration/recipes/recipe_dsl_spec.rb b/spec/integration/recipes/recipe_dsl_spec.rb index fa38808e3e..4e80c5a738 100644 --- a/spec/integration/recipes/recipe_dsl_spec.rb +++ b/spec/integration/recipes/recipe_dsl_spec.rb @@ -401,370 +401,6 @@ describe "Recipe DSL methods" do }.to raise_error(NoMethodError) end end - - context "with a resource named 'B' with resource name :two_classes_one_dsl" do - let(:two_classes_one_dsl) { :"two_classes_one_dsl#{Namer.current_index}" } - let(:resource_class) { - result = Class.new(BaseThingy) do - def self.name - "B" - end - def self.to_s; name; end - def self.inspect; name.inspect; end - end - result.resource_name two_classes_one_dsl - result - } - before { resource_class } # pull on it so it gets defined before the recipe runs - - context "and another resource named 'A' with resource_name :two_classes_one_dsl" do - let(:resource_class_a) { - result = Class.new(BaseThingy) do - def self.name - "A" - end - def self.to_s; name; end - def self.inspect; name.inspect; end - end - result.resource_name two_classes_one_dsl - result - } - before { resource_class_a } # pull on it so it gets defined before the recipe runs - - it "two_classes_one_dsl resolves to A (alphabetically earliest)" do - two_classes_one_dsl = self.two_classes_one_dsl - recipe = converge { - instance_eval("#{two_classes_one_dsl} 'blah'") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_resource).to eq resource_class_a - end - - it "resource_matching_short_name returns B" do - expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class_a - end - end - - context "and another resource named 'Z' with resource_name :two_classes_one_dsl" do - let(:resource_class_z) { - result = Class.new(BaseThingy) do - def self.name - "Z" - end - def self.to_s; name; end - def self.inspect; name.inspect; end - end - result.resource_name two_classes_one_dsl - result - } - before { resource_class_z } # pull on it so it gets defined before the recipe runs - - it "two_classes_one_dsl resolves to B (alphabetically earliest)" do - two_classes_one_dsl = self.two_classes_one_dsl - recipe = converge { - instance_eval("#{two_classes_one_dsl} 'blah'") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_resource).to eq resource_class - end - - it "resource_matching_short_name returns B" do - expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class - end - end - - context "and another resource Blarghle with provides :two_classes_one_dsl, os: 'blarghle'" do - let(:resource_class_blarghle) { - result = Class.new(BaseThingy) do - def self.name - "Blarghle" - end - def self.to_s; name; end - def self.inspect; name.inspect; end - end - result.resource_name two_classes_one_dsl - result.provides two_classes_one_dsl, os: 'blarghle' - result - } - before { resource_class_blarghle } # pull on it so it gets defined before the recipe runs - - it "on os = blarghle, two_classes_one_dsl resolves to Blarghle" do - two_classes_one_dsl = self.two_classes_one_dsl - recipe = converge { - # this is an ugly way to test, make Cheffish expose node attrs - run_context.node.automatic[:os] = 'blarghle' - instance_eval("#{two_classes_one_dsl} 'blah' do; end") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_resource).to eq resource_class_blarghle - end - - it "on os = linux, two_classes_one_dsl resolves to B" do - two_classes_one_dsl = self.two_classes_one_dsl - recipe = converge { - # this is an ugly way to test, make Cheffish expose node attrs - run_context.node.automatic[:os] = 'linux' - instance_eval("#{two_classes_one_dsl} 'blah' do; end") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_resource).to eq resource_class - end - end - end - - context "with a resource MyResource" do - let(:resource_class) { Class.new(BaseThingy) do - def self.called_provides - @called_provides - end - def to_s - "MyResource" - end - end } - let(:my_resource) { :"my_resource#{Namer.current_index}" } - let(:blarghle_blarghle_little_star) { :"blarghle_blarghle_little_star#{Namer.current_index}" } - - context "with resource_name :my_resource" do - before { - resource_class.resource_name my_resource - } - - context "with provides? returning true to my_resource" do - before { - my_resource = self.my_resource - resource_class.define_singleton_method(:provides?) do |node, resource_name| - @called_provides = true - resource_name == my_resource - end - } - - it "my_resource returns the resource and calls provides?, but does not emit a warning" do - dsl_name = self.my_resource - recipe = converge { - instance_eval("#{dsl_name} 'foo'") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_resource).to eq resource_class - expect(resource_class.called_provides).to be_truthy - end - end - - context "with provides? returning true to blarghle_blarghle_little_star and not resource_name" do - before do - blarghle_blarghle_little_star = self.blarghle_blarghle_little_star - resource_class.define_singleton_method(:provides?) do |node, resource_name| - @called_provides = true - resource_name == blarghle_blarghle_little_star - end - end - - it "my_resource does not return the resource" do - dsl_name = self.my_resource - expect_converge { - instance_eval("#{dsl_name} 'foo'") - }.to raise_error(Chef::Exceptions::NoSuchResourceType) - expect(resource_class.called_provides).to be_truthy - end - - it "blarghle_blarghle_little_star 'foo' returns the resource and emits a warning" do - dsl_name = self.blarghle_blarghle_little_star - recipe = converge { - instance_eval("#{dsl_name} 'foo'") - } - expect(recipe.logged_warnings).to include "WARN: #{resource_class}.provides? returned true when asked if it provides DSL #{dsl_name}, but provides :#{dsl_name} was never called!" - expect(BaseThingy.created_resource).to eq resource_class - expect(resource_class.called_provides).to be_truthy - end - end - - context "and a provider" do - let(:provider_class) do - Class.new(BaseThingy::Provider) do - def self.name - "MyProvider" - end - def self.to_s; name; end - def self.inspect; name.inspect; end - def self.called_provides - @called_provides - end - end - end - - before do - resource_class.send(:define_method, :provider) { nil } - end - - context "that provides :my_resource" do - before do - provider_class.provides my_resource - end - - context "with supports? returning true" do - before do - provider_class.define_singleton_method(:supports?) { |resource,action| true } - end - - it "my_resource runs the provider and does not emit a warning" do - my_resource = self.my_resource - recipe = converge { - instance_eval("#{my_resource} 'foo'") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_provider).to eq provider_class - end - - context "and another provider supporting :my_resource with supports? false" do - let(:provider_class2) do - Class.new(BaseThingy::Provider) do - def self.name - "MyProvider2" - end - def self.to_s; name; end - def self.inspect; name.inspect; end - def self.called_provides - @called_provides - end - provides my_resource - def self.supports?(resource, action) - false - end - end - end - - it "my_resource runs the first provider" do - my_resource = self.my_resource - recipe = converge { - instance_eval("#{my_resource} 'foo'") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_provider).to eq provider_class - end - end - end - - context "with supports? returning false" do - before do - provider_class.define_singleton_method(:supports?) { |resource,action| false } - end - - # TODO no warning? ick - it "my_resource runs the provider anyway" do - my_resource = self.my_resource - recipe = converge { - instance_eval("#{my_resource} 'foo'") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_provider).to eq provider_class - end - - context "and another provider supporting :my_resource with supports? true" do - let(:provider_class2) do - my_resource = self.my_resource - Class.new(BaseThingy::Provider) do - def self.name - "MyProvider2" - end - def self.to_s; name; end - def self.inspect; name.inspect; end - def self.called_provides - @called_provides - end - provides my_resource - def self.supports?(resource, action) - true - end - end - end - before { provider_class2 } # make sure the provider class shows up - - it "my_resource runs the other provider" do - my_resource = self.my_resource - recipe = converge { - instance_eval("#{my_resource} 'foo'") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_provider).to eq provider_class2 - end - end - end - end - - context "with provides? returning true" do - before { - my_resource = self.my_resource - provider_class.define_singleton_method(:provides?) do |node, resource| - @called_provides = true - resource.declared_type == my_resource - end - } - - context "that provides :my_resource" do - before { - provider_class.provides my_resource - } - - it "my_resource calls the provider (and calls provides?), but does not emit a warning" do - my_resource = self.my_resource - recipe = converge { - instance_eval("#{my_resource} 'foo'") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_provider).to eq provider_class - expect(provider_class.called_provides).to be_truthy - end - end - - context "that does not call provides :my_resource" do - it "my_resource calls the provider (and calls provides?), and emits a warning" do - my_resource = self.my_resource - recipe = converge { - instance_eval("#{my_resource} 'foo'") - } - expect(recipe.logged_warnings).to include("WARN: #{provider_class}.provides? returned true when asked if it provides DSL #{my_resource}, but provides :#{my_resource} was never called!") - expect(BaseThingy.created_provider).to eq provider_class - expect(provider_class.called_provides).to be_truthy - end - end - end - - context "with provides? returning false to my_resource" do - before { - my_resource = self.my_resource - provider_class.define_singleton_method(:provides?) do |node, resource| - @called_provides = true - false - end - } - - context "that provides :my_resource" do - before { - provider_class.provides my_resource - } - - it "my_resource fails to find a provider (and calls provides)" do - my_resource = self.my_resource - expect_converge { - instance_eval("#{my_resource} 'foo'") - }.to raise_error(Chef::Exceptions::ProviderNotFound) - expect(provider_class.called_provides).to be_truthy - end - end - - context "that does not provide :my_resource" do - it "my_resource fails to find a provider (and calls provides)" do - my_resource = self.my_resource - expect_converge { - instance_eval("#{my_resource} 'foo'") - }.to raise_error(Chef::Exceptions::ProviderNotFound) - expect(provider_class.called_provides).to be_truthy - end - end - end - end - end - end - end end @@ -1175,6 +811,601 @@ describe "Recipe DSL methods" do end end end + + context "with a resource named 'B' with resource name :two_classes_one_dsl" do + let(:two_classes_one_dsl) { :"two_classes_one_dsl#{Namer.current_index}" } + let(:resource_class) { + result = Class.new(BaseThingy) do + def self.name + "B" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result.resource_name two_classes_one_dsl + result + } + before { resource_class } # pull on it so it gets defined before the recipe runs + + context "and another resource named 'A' with resource_name :two_classes_one_dsl" do + let(:resource_class_a) { + result = Class.new(BaseThingy) do + def self.name + "A" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result.resource_name two_classes_one_dsl + result + } + before { resource_class_a } # pull on it so it gets defined before the recipe runs + + it "two_classes_one_dsl resolves to A (alphabetically earliest)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class_a + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class_a + end + end + + context "and another resource named 'Z' with resource_name :two_classes_one_dsl" do + let(:resource_class_z) { + result = Class.new(BaseThingy) do + def self.name + "Z" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result.resource_name two_classes_one_dsl + result + } + before { resource_class_z } # pull on it so it gets defined before the recipe runs + + it "two_classes_one_dsl resolves to B (alphabetically earliest)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + + context "and a priority array [ Z, B ]" do + before do + Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z, resource_class ]) + end + + it "two_classes_one_dsl resolves to Z (respects the priority array)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class_z + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + + context "when Z provides(:two_classes_one_dsl) { false }" do + before do + resource_class_z.provides(two_classes_one_dsl) { false } + end + + it "two_classes_one_dsl resolves to B (picks the next thing in the priority array)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + end + end + + context "and priority arrays [ B ] and [ Z ]" do + before do + Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class ]) + Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z ]) + end + + it "two_classes_one_dsl resolves to Z (respects the most recent priority array)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class_z + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + + context "when Z provides(:two_classes_one_dsl) { false }" do + before do + resource_class_z.provides(two_classes_one_dsl) { false } + end + + it "two_classes_one_dsl resolves to B (picks the first match from the other priority array)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + end + end + + context "and a priority array [ Z ]" do + before do + Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z ]) + end + + context "when Z provides(:two_classes_one_dsl) { false }" do + before do + resource_class_z.provides(two_classes_one_dsl) { false } + end + + it "two_classes_one_dsl resolves to B (picks the first match outside the priority array)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + end + end + + end + + context "and a provider named 'B' which provides :two_classes_one_dsl" do + before do + resource_class.send(:define_method, :provider) { nil } + end + + let(:provider_class) { + result = Class.new(BaseThingy::Provider) do + def self.name + "B" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result.provides two_classes_one_dsl + result + } + before { provider_class } # pull on it so it gets defined before the recipe runs + + context "and another provider named 'A'" do + let(:provider_class_a) { + result = Class.new(BaseThingy::Provider) do + def self.name + "A" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result + } + context "which provides :two_classes_one_dsl" do + before { provider_class_a.provides two_classes_one_dsl } + + it "two_classes_one_dsl resolves to A (alphabetically earliest)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class_a + end + end + context "which provides(:two_classes_one_dsl) { false }" do + before { provider_class_a.provides(two_classes_one_dsl) { false } } + + it "two_classes_one_dsl resolves to B (since A declined)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + end + end + + context "and another provider named 'Z'" do + let(:provider_class_z) { + result = Class.new(BaseThingy::Provider) do + def self.name + "Z" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result + } + before { provider_class_z } # pull on it so it gets defined before the recipe runs + + context "which provides :two_classes_one_dsl" do + before { provider_class_z.provides two_classes_one_dsl } + + it "two_classes_one_dsl resolves to B (alphabetically earliest)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + + context "with a priority array [ Z, B ]" do + before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z, provider_class ] } + + it "two_classes_one_dsl resolves to Z (respects the priority map)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class_z + end + end + end + + context "which provides(:two_classes_one_dsl) { false }" do + before { provider_class_z.provides(two_classes_one_dsl) { false } } + + context "with a priority array [ Z, B ]" do + before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z, provider_class ] } + + it "two_classes_one_dsl resolves to B (the next one in the priority map)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + end + + context "with priority arrays [ B ] and [ Z ]" do + before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z ] } + before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class ] } + + it "two_classes_one_dsl resolves to B (the one in the next priority map)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + end + end + end + end + + context "and another resource Blarghle with provides :two_classes_one_dsl, os: 'blarghle'" do + let(:resource_class_blarghle) { + result = Class.new(BaseThingy) do + def self.name + "Blarghle" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result.resource_name two_classes_one_dsl + result.provides two_classes_one_dsl, os: 'blarghle' + result + } + before { resource_class_blarghle } # pull on it so it gets defined before the recipe runs + + it "on os = blarghle, two_classes_one_dsl resolves to Blarghle" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + # this is an ugly way to test, make Cheffish expose node attrs + run_context.node.automatic[:os] = 'blarghle' + instance_eval("#{two_classes_one_dsl} 'blah' do; end") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class_blarghle + end + + it "on os = linux, two_classes_one_dsl resolves to B" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + # this is an ugly way to test, make Cheffish expose node attrs + run_context.node.automatic[:os] = 'linux' + instance_eval("#{two_classes_one_dsl} 'blah' do; end") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + end + end + end + + context "with a resource MyResource" do + let(:resource_class) { Class.new(BaseThingy) do + def self.called_provides + @called_provides + end + def to_s + "MyResource" + end + end } + let(:my_resource) { :"my_resource#{Namer.current_index}" } + let(:blarghle_blarghle_little_star) { :"blarghle_blarghle_little_star#{Namer.current_index}" } + + context "with resource_name :my_resource" do + before { + resource_class.resource_name my_resource + } + + context "with provides? returning true to my_resource" do + before { + my_resource = self.my_resource + resource_class.define_singleton_method(:provides?) do |node, resource_name| + @called_provides = true + resource_name == my_resource + end + } + + it "my_resource returns the resource and calls provides?, but does not emit a warning" do + dsl_name = self.my_resource + recipe = converge { + instance_eval("#{dsl_name} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + expect(resource_class.called_provides).to be_truthy + end + end + + context "with provides? returning true to blarghle_blarghle_little_star and not resource_name" do + before do + blarghle_blarghle_little_star = self.blarghle_blarghle_little_star + resource_class.define_singleton_method(:provides?) do |node, resource_name| + @called_provides = true + resource_name == blarghle_blarghle_little_star + end + end + + it "my_resource does not return the resource" do + dsl_name = self.my_resource + expect_converge { + instance_eval("#{dsl_name} 'foo'") + }.to raise_error(Chef::Exceptions::NoSuchResourceType) + expect(resource_class.called_provides).to be_truthy + end + + it "blarghle_blarghle_little_star 'foo' returns the resource and emits a warning" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + dsl_name = self.blarghle_blarghle_little_star + recipe = converge { + instance_eval("#{dsl_name} 'foo'") + } + expect(recipe.logged_warnings).to include "WARN: #{resource_class}.provides? returned true when asked if it provides DSL #{dsl_name}, but provides :#{dsl_name} was never called!" + expect(BaseThingy.created_resource).to eq resource_class + expect(resource_class.called_provides).to be_truthy + end + end + + context "and a provider" do + let(:provider_class) do + Class.new(BaseThingy::Provider) do + def self.name + "MyProvider" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + def self.called_provides + @called_provides + end + end + end + + before do + resource_class.send(:define_method, :provider) { nil } + end + + context "that provides :my_resource" do + before do + provider_class.provides my_resource + end + + context "with supports? returning true" do + before do + provider_class.define_singleton_method(:supports?) { |resource,action| true } + end + + it "my_resource runs the provider and does not emit a warning" do + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + + context "and another provider supporting :my_resource with supports? false" do + let(:provider_class2) do + Class.new(BaseThingy::Provider) do + def self.name + "MyProvider2" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + def self.called_provides + @called_provides + end + provides my_resource + def self.supports?(resource, action) + false + end + end + end + + it "my_resource runs the first provider" do + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + end + end + + context "with supports? returning false" do + before do + provider_class.define_singleton_method(:supports?) { |resource,action| false } + end + + # TODO no warning? ick + it "my_resource runs the provider anyway" do + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + + context "and another provider supporting :my_resource with supports? true" do + let(:provider_class2) do + my_resource = self.my_resource + Class.new(BaseThingy::Provider) do + def self.name + "MyProvider2" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + def self.called_provides + @called_provides + end + provides my_resource + def self.supports?(resource, action) + true + end + end + end + before { provider_class2 } # make sure the provider class shows up + + it "my_resource runs the other provider" do + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class2 + end + end + end + end + + context "with provides? returning true" do + before { + my_resource = self.my_resource + provider_class.define_singleton_method(:provides?) do |node, resource| + @called_provides = true + resource.declared_type == my_resource + end + } + + context "that provides :my_resource" do + before { + provider_class.provides my_resource + } + + it "my_resource calls the provider (and calls provides?), but does not emit a warning" do + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + expect(provider_class.called_provides).to be_truthy + end + end + + context "that does not call provides :my_resource" do + it "my_resource calls the provider (and calls provides?), and emits a warning" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to include("WARN: #{provider_class}.provides? returned true when asked if it provides DSL #{my_resource}, but provides :#{my_resource} was never called!") + expect(BaseThingy.created_provider).to eq provider_class + expect(provider_class.called_provides).to be_truthy + end + end + end + + context "with provides? returning false to my_resource" do + before { + my_resource = self.my_resource + provider_class.define_singleton_method(:provides?) do |node, resource| + @called_provides = true + false + end + } + + context "that provides :my_resource" do + before { + provider_class.provides my_resource + } + + it "my_resource fails to find a provider (and calls provides)" do + my_resource = self.my_resource + expect_converge { + instance_eval("#{my_resource} 'foo'") + }.to raise_error(Chef::Exceptions::ProviderNotFound) + expect(provider_class.called_provides).to be_truthy + end + end + + context "that does not provide :my_resource" do + it "my_resource fails to find a provider (and calls provides)" do + my_resource = self.my_resource + expect_converge { + instance_eval("#{my_resource} 'foo'") + }.to raise_error(Chef::Exceptions::ProviderNotFound) + expect(provider_class.called_provides).to be_truthy + end + end + end + end + end + end end before(:all) { Namer.current_index = 0 } diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb index 545524eb10..bcb64cb21e 100644 --- a/spec/unit/lwrp_spec.rb +++ b/spec/unit/lwrp_spec.rb @@ -439,7 +439,7 @@ describe "LWRP" do end it "sets itself as a provider for a resource of the same name" do - found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :lwrp_buck_passer) + found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :lwrp_buck_passer) # we bypass the per-file loading to get the file to load each time, # which creates the LWRP class repeatedly. New things get prepended to # the list of providers. @@ -451,7 +451,7 @@ describe "LWRP" do let(:lwrp_cookbok_name) { "l_w_r_p" } it "sets itself as a provider for a resource of the same name" do - found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :l_w_r_p_buck_passer) + found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer) expect(found_providers.size).to eq(1) expect(found_providers.last).to eq(get_lwrp_provider(:l_w_r_p_buck_passer)) end @@ -462,10 +462,10 @@ describe "LWRP" do let(:lwrp_cookbok_name) { "l-w-r-p" } it "sets itself as a provider for a resource of the same name" do - incorrect_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :'l-w-r-p_buck_passer') + incorrect_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :'l-w-r-p_buck_passer') expect(incorrect_providers).to eq([]) - found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :l_w_r_p_buck_passer) + found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer) expect(found_providers.first).to eq(get_lwrp_provider(:l_w_r_p_buck_passer)) end end diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb index e09b12a20a..88df4a20cc 100644 --- a/spec/unit/provider_resolver_spec.rb +++ b/spec/unit/provider_resolver_spec.rb @@ -90,13 +90,14 @@ describe Chef::ProviderResolver do end def self.expect_providers(**providers) - providers.each do |name, expected_provider| + providers.each do |name, expected| describe name.to_s do let(:resource_name) { name } tags = [] + expected_provider = nil expected_resource = nil - Array(expected_provider).each do |p| + Array(expected).each do |p| if p.is_a?(Class) && p <= Chef::Provider expected_provider = p elsif p.is_a?(Class) && p <= Chef::Resource @@ -525,8 +526,8 @@ describe Chef::ProviderResolver do # We want to check that these are unsupported: apt_package: nil, bff_package: nil, - dsc_script: nil, dpkg_package: nil, + dsc_script: nil, ips_package: nil, pacman_package: nil, paludis_package: nil, diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 1377950c99..b9ba80068b 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -810,21 +810,21 @@ describe Chef::Resource do end it 'adds mappings for a single platform' do - expect(Chef.resource_priority_map).to receive(:set).with( + expect(Chef.resource_handler_map).to receive(:set).with( :dinobot, Chef::Resource::Klz, { platform: ['autobots'] } ) klz.provides :dinobot, platform: ['autobots'] end it 'adds mappings for multiple platforms' do - expect(Chef.resource_priority_map).to receive(:set).with( + expect(Chef.resource_handler_map).to receive(:set).with( :energy, Chef::Resource::Klz, { platform: ['autobots', 'decepticons']} ) klz.provides :energy, platform: ['autobots', 'decepticons'] end it 'adds mappings for all platforms' do - expect(Chef.resource_priority_map).to receive(:set).with( + expect(Chef.resource_handler_map).to receive(:set).with( :tape_deck, Chef::Resource::Klz, {} ) klz.provides :tape_deck |