diff options
-rw-r--r-- | lib/chef/chef_class.rb | 54 | ||||
-rw-r--r-- | lib/chef/dsl/resources.rb | 1 | ||||
-rw-r--r-- | lib/chef/node_map.rb | 38 | ||||
-rw-r--r-- | lib/chef/platform/provider_mapping.rb | 2 | ||||
-rw-r--r-- | lib/chef/platform/resource_priority_map.rb | 9 | ||||
-rw-r--r-- | lib/chef/resource.rb | 273 | ||||
-rw-r--r-- | lib/chef/resource_resolver.rb | 49 | ||||
-rw-r--r-- | spec/integration/recipes/recipe_dsl_spec.rb | 80 | ||||
-rw-r--r-- | spec/unit/provider_resolver_spec.rb | 1 |
9 files changed, 326 insertions, 181 deletions
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb index 7c0a2bf944..1caeee4052 100644 --- a/lib/chef/chef_class.rb +++ b/lib/chef/chef_class.rb @@ -36,50 +36,74 @@ 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<Class>] Priority Array of Provider Classes to use for the resource_name on the node + # def get_provider_priority_array(resource_name) - provider_priority_map.get_priority_array(node, resource_name).dup + result = provider_priority_map.get_priority_array(node, resource_name) + result = result.dup if result + result end + # # Get the array of resources associated with a resource_name for the current node # # @param resource_name [Symbol] name of the resource as a symbol + # # @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node + # def get_resource_priority_array(resource_name) - resource_priority_map.get_priority_array(node, resource_name).dup + result = resource_priority_map.get_priority_array(node, resource_name) + result = result.dup if result + result 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 [Class, Array<Class>] Class or Array of Classes to set as the priority for resource_name on the node # @param filter [Hash] Chef::Nodearray-style filter + # # @return [Array<Class>] Modified Priority Array of Provider Classes to use for the resource_name on the node + # def set_provider_priority_array(resource_name, priority_array, *filter, &block) - provider_priority_map.set_priority_array(resource_name, priority_array, *filter, &block).dup + result = provider_priority_map.set_priority_array(resource_name, priority_array, *filter, &block) + result = result.dup if result + result 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 [Class, Array<Class>] Class or Array of Classes to set as the priority for resource_name on the node # @param filter [Hash] Chef::Nodearray-style filter + # # @return [Array<Class>] Modified Priority Array of Resource Classes to use for the resource_name on the node + # def set_resource_priority_array(resource_name, priority_array, *filter, &block) - resource_priority_map.set_priority_array(resource_name, priority_array, *filter, &block).dup + result = resource_priority_map.set_priority_array(resource_name, priority_array, *filter, &block) + result = result.dup if result + result end # @@ -88,22 +112,27 @@ class Chef # *NOT* for public consumption ] # + # # Sets the resource_priority_map # - # @api private # @param resource_priority_map [Chef::Platform::ResourcePriorityMap] + # + # @api private 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] + # + # @api private def set_provider_priority_map(provider_priority_map) @provider_priority_map = provider_priority_map end + # # Sets the node object # # @api private @@ -112,14 +141,17 @@ class Chef @node = node end + # # Sets the run_context object # - # @api private # @param run_context [Chef::RunContext] + # + # @api private def set_run_context(run_context) @run_context = run_context end + # # Resets the internal state # # @api private @@ -130,6 +162,14 @@ class Chef @resource_priority_map = nil end + # + # Removes a resource + # + # @api private + def delete_resource_priority_array(name, &filter) + resource_priority_map.delete_priority_array(name, &filter) + end + private def provider_priority_map diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb index a181c3be33..1ce12ed0a0 100644 --- a/lib/chef/dsl/resources.rb +++ b/lib/chef/dsl/resources.rb @@ -15,6 +15,7 @@ class Chef end EOM rescue SyntaxError + # Handle the case where dsl_name has spaces, etc. define_method(dsl_name.to_sym) do |name=nil, created_at=nil, &block| declare_resource(dsl_name, name, created_at || caller[0], &block) end diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb index 34f19d18b4..4aed6f94e4 100644 --- a/lib/chef/node_map.rb +++ b/lib/chef/node_map.rb @@ -19,27 +19,31 @@ class Chef class NodeMap + # # Create a new NodeMap # def initialize @map = {} end + # # Set a key/value pair on the map with a filter. The filter must be true # when applied to the node in order to retrieve the value. # # @param key [Object] Key to store # @param value [Object] Value associated with the key # @param filters [Hash] Node filter options to apply to key retrieval + # # @yield [node] Arbitrary node filter as a block which takes a node argument + # # @return [NodeMap] Returns self for possible chaining # - def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, &block) + def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, &block) Chef::Log.deprecation "The on_platform option to node_map has been deprecated" if on_platform Chef::Log.deprecation "The on_platforms option to node_map has been deprecated" if on_platforms platform ||= on_platform || on_platforms filters = { platform: platform, platform_version: platform_version, platform_family: platform_family, os: os } - new_matcher = { filters: filters, block: block, value: value } + new_matcher = { filters: filters, block: block, value: value, canonical: canonical } @map[key] ||= [] # Decide where to insert the matcher; the new value is preferred over # anything more specific (see `priority_of`) and is preferred over older @@ -60,35 +64,49 @@ class Chef self end + # # Get a value from the NodeMap via applying the node to the filters that # were set on the key. # # @param node [Chef::Node] The Chef::Node object for the run # @param key [Object] Key to look up + # @param canonical [Boolean] Whether to look up only the canonically provided + # DSL (i.e. look up resource_name) + # # @return [Object] Value # - def get(node, key) + def get(node, key, canonical: nil) # FIXME: real exception - raise "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) - list(node, key).first + raise "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil? + list(node, key, canonical: canonical).first end + # # List all matches for the given node and key from the NodeMap, from # most-recently added to oldest. # # @param node [Chef::Node] The Chef::Node object for the run # @param key [Object] Key to look up + # @param canonical [Boolean] Whether to look up only the canonically provided + # DSL (i.e. look up resource_name) + # # @return [Object] Value # - def list(node, key) + def list(node, key, canonical: nil) # FIXME: real exception - raise "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) + raise "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil? return [] unless @map.has_key?(key) @map[key].select do |matcher| - filters_match?(node, matcher[:filters]) && block_matches?(node, matcher[:block]) + filters_match?(node, matcher[:filters]) && block_matches?(node, matcher[:block]) && + (!canonical || matcher[:canonical]) end.map { |matcher| matcher[:value] } end + # @api private + def delete_if(key, &block) + @map[key].delete_if(&block) + end + private # @@ -124,7 +142,7 @@ class Chef # spend any time here. return true if !filters[attribute] filter_values = Array(filters[attribute]) - value = node[attribute] + value = node ? node[attribute] : nil # Split the blacklist and whitelist blacklist, whitelist = filter_values.partition { |v| v.is_a?(String) && v.start_with?('!') } @@ -141,7 +159,7 @@ class Chef # spend any time here. return true if !filters[attribute] filter_values = Array(filters[attribute]) - value = node[attribute] + value = node ? node[attribute] : nil filter_values.empty? || Array(filter_values).any? do |v| diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index 0440ff8601..e3a894c8ac 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -60,8 +60,6 @@ class Chef Chef::Log.debug("Chef::Version::Comparable does not know how to parse the platform version: #{version}") end end - else - Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)") end provider_map end diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb index e98fc12413..eeff843ccf 100644 --- a/lib/chef/platform/resource_priority_map.rb +++ b/lib/chef/platform/resource_priority_map.rb @@ -5,8 +5,8 @@ class Chef class ResourcePriorityMap include Singleton - def get_priority_array(node, resource_name) - priority_map.get(node, resource_name.to_sym) + def get_priority_array(node, resource_name, canonical: nil) + priority_map.get(node, resource_name.to_sym, canonical: canonical) end def set_priority_array(resource_name, priority_array, *filter, &block) @@ -14,6 +14,11 @@ class Chef end # @api private + def delete_priority_array(resource_name, &block) + priority_map.delete_if(resource_name, &block) + end + + # @api private def list_handlers(*args) priority_map.list(*args).flatten(1).uniq end diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index d74b942e7d..5ed85e5869 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -877,142 +877,167 @@ class Chef nil end - # Provider lookup and naming - class<<self - # - # The DSL name of this resource (e.g. `package` or `yum_package`) - # - # @return [String] The DSL name of this resource. - # - # @deprecated Use resource_name instead. - # - def dsl_name - Chef::Log.deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead." - if name - name = self.name.split('::')[-1] - convert_to_snake_case(name) - end + # + # The DSL name of this resource (e.g. `package` or `yum_package`) + # + # @return [String] The DSL name of this resource. + # + # @deprecated Use resource_name instead. + # + def self.dsl_name + Chef::Log.deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead." + if name + name = self.name.split('::')[-1] + convert_to_snake_case(name) end + end - # - # The display name of this resource type, for printing purposes. - # - # This also automatically calls "provides" to provide DSL with the given - # name. - # - # @param value [Symbol] The desired name of this resource type (e.g. - # `execute`). - # - # @return [Symbol] The name of this resource type (e.g. `:execute`). - # - def resource_name(value=NULL_ARG) - if value != NULL_ARG + # + # The display name of this resource type, for printing purposes. + # + # This also automatically calls "provides" to provide DSL with the given + # name. + # + # resource_name defaults to your class name. + # + # Call `resource_name nil` to remove the resource name (and any + # corresponding DSL). + # + # @param value [Symbol] The desired name of this resource type (e.g. + # `execute`), or `nil` if this class is abstract and has no resource_name. + # + # @return [Symbol] The name of this resource type (e.g. `:execute`). + # + def self.resource_name(value=NULL_ARG) + + # Setter + if value != NULL_ARG + # Get rid of any current provides + if @resource_name + Chef.delete_resource_priority_array(@resource_name) do |matcher| + Array(matcher[:value]) == [ self ] && matcher[:canonical] + end + if Chef.get_resource_priority_array(@resource_name).nil? + Chef::DSL::Resources.remove_resource_dsl(@resource_name) + end + end + + # Set the resource_name and call provides + if value @resource_name = value.to_sym - provides self.resource_name + provides @resource_name, canonical: true + else + @resource_name = nil end - # Backcompat: set resource name for classes in Chef::Resource automatically - if !@resource_name && self.name - chef, resource, class_name, *extra = self.name.split('::') - if chef == 'Chef' && resource == 'Resource' && extra.size == 0 - @resource_name = convert_to_snake_case(self.name.split('::')[-1]) - end + else + # set resource_name automatically if it's not set + if !instance_variable_defined?(:@resource_name) && self.name + resource_name convert_to_snake_case(self.name.split('::')[-1]) end - @resource_name end - alias :resource_name= :resource_name - # - # Use the class name as the resource name. - # - # Munges the last part of the class name from camel case to snake case, - # and sets the resource_name to that: - # - # A::B::BlahDBlah -> blah_d_blah - # - def use_automatic_resource_name - automatic_name = convert_to_snake_case(self.name.split('::')[-1]) - resource_name automatic_name - end + @resource_name + end + def self.resource_name=(value) + resource_name(value) + end - # - # The module where Chef should look for providers for this resource. - # The provider for `MyResource` will be looked up using - # `provider_base::MyResource`. Defaults to `Chef::Provider`. - # - # @param arg [Module] The module containing providers for this resource - # @return [Module] The module containing providers for this resource - # - # @example - # class MyResource < Chef::Resource - # provider_base Chef::Provider::Deploy - # # ...other stuff - # end - # - # @deprecated Use `provides` on the provider, or `provider` on the resource, instead. - # - def provider_base(arg=nil) - if arg - Chef::Log.deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.") - end - @provider_base ||= arg || Chef::Provider - end + def self.inherited(child) + super + child.resource_name + end - # - # The list of allowed actions for the resource. - # - # @param actions [Array<Symbol>] The list of actions to add to allowed_actions. - # - # @return [Arrau<Symbol>] The list of actions, as symbols. - # - def allowed_actions(*actions) - @allowed_actions ||= - if superclass.respond_to?(:allowed_actions) - superclass.allowed_actions.dup - else - [ :nothing ] - end - @allowed_actions |= actions - end - def allowed_actions=(value) - @allowed_actions = value - end + # + # Use the class name as the resource name. + # + # Munges the last part of the class name from camel case to snake case, + # and sets the resource_name to that: + # + # A::B::BlahDBlah -> blah_d_blah + # + def self.use_automatic_resource_name + automatic_name = convert_to_snake_case(self.name.split('::')[-1]) + resource_name automatic_name + end - # - # The action that will be run if no other action is specified. - # - # Setting default_action will automatially add the action to - # allowed_actions, if it isn't already there. - # - # Defaults to :nothing. - # - # @param action_name [Symbol,Array<Symbol>] The default action (or series - # of actions) to use. - # - # @return [Symbol,Array<Symbol>] The default actions for the resource. - # - def default_action(action_name=NULL_ARG) - unless action_name.equal?(NULL_ARG) - if action_name.is_a?(Array) - @default_action = action_name.map { |arg| arg.to_sym } - else - @default_action = action_name.to_sym - end + # + # The module where Chef should look for providers for this resource. + # The provider for `MyResource` will be looked up using + # `provider_base::MyResource`. Defaults to `Chef::Provider`. + # + # @param arg [Module] The module containing providers for this resource + # @return [Module] The module containing providers for this resource + # + # @example + # class MyResource < Chef::Resource + # provider_base Chef::Provider::Deploy + # # ...other stuff + # end + # + # @deprecated Use `provides` on the provider, or `provider` on the resource, instead. + # + def self.provider_base(arg=nil) + if arg + Chef::Log.deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.") + end + @provider_base ||= arg || Chef::Provider + end - self.allowed_actions |= Array(@default_action) + # + # The list of allowed actions for the resource. + # + # @param actions [Array<Symbol>] The list of actions to add to allowed_actions. + # + # @return [Arrau<Symbol>] The list of actions, as symbols. + # + def self.allowed_actions(*actions) + @allowed_actions ||= + if superclass.respond_to?(:allowed_actions) + superclass.allowed_actions.dup + else + [ :nothing ] end + @allowed_actions |= actions + end + def self.allowed_actions=(value) + @allowed_actions = value + end - if @default_action - @default_action - elsif superclass.respond_to?(:default_action) - superclass.default_action + # + # The action that will be run if no other action is specified. + # + # Setting default_action will automatially add the action to + # allowed_actions, if it isn't already there. + # + # Defaults to :nothing. + # + # @param action_name [Symbol,Array<Symbol>] The default action (or series + # of actions) to use. + # + # @return [Symbol,Array<Symbol>] The default actions for the resource. + # + def self.default_action(action_name=NULL_ARG) + unless action_name.equal?(NULL_ARG) + if action_name.is_a?(Array) + @default_action = action_name.map { |arg| arg.to_sym } else - :nothing + @default_action = action_name.to_sym end + + self.allowed_actions |= Array(@default_action) end - def default_action=(action_name) - default_action action_name + + if @default_action + @default_action + elsif superclass.respond_to?(:default_action) + superclass.default_action + else + :nothing end end + def self.default_action=(action_name) + default_action action_name + end # # Internal Resource Interface (for Chef) @@ -1281,17 +1306,7 @@ class Chef # @deprecated Chef::Resource::FooBar will no longer mean anything special in # Chef 13. Use `resource_for_node` instead. def self.resource_matching_short_name(short_name) - begin - rname = convert_to_class_name(short_name.to_s) - result = Chef::Resource.const_get(rname) - if result <= Chef::Resource - Chef::Log.deprecation("Class Chef::Resource::#{rname} does not declare 'provides #{short_name.inspect}'.") - Chef::Log.deprecation("This will no longer work in Chef 13: you must use 'provides' to provide DSL.") - result - end - rescue NameError - nil - end + Chef::ResourceResolver.get(short_name, canonical: true) end # @api private diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb index 1eb5e6d840..12618d693b 100644 --- a/lib/chef/resource_resolver.rb +++ b/lib/chef/resource_resolver.rb @@ -26,35 +26,15 @@ class Chef attr_reader :node attr_reader :resource attr_reader :action + attr_reader :canonical - def initialize(node, resource) + def initialize(node, resource, canonical: nil) @node = node @resource = resource.to_sym + @canonical = canonical end def resolve - maybe_dynamic_resource_resolution || - maybe_chef_platform_lookup - end - - def provided_by?(resource_class) - !prioritized_handlers.include?(resource_class) - end - - # - # Resolve a resource by name. - # - # @param resource_name [Symbol] The resource DSL name (e.g. `:file`) - # @param node [Chef::Node] The node on which the resource will run. - # - def self.resolve(resource_name, node: Chef.node) - new(node, resource_name).resolve - end - - protected - - # try dynamically finding a resource based on querying the resources to see what they support - def maybe_dynamic_resource_resolution # 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}" @@ -69,17 +49,32 @@ class Chef handler end - # try the old static lookup of resources by mangling name to resource klass - def maybe_chef_platform_lookup - Chef::Resource.resource_matching_short_name(resource) + def provided_by?(resource_class) + !prioritized_handlers.include?(resource_class) end + # + # Resolve a resource by name. + # + # @param resource_name [Symbol] The resource DSL name (e.g. `:file`) + # @param node [Chef::Node] The node on which the resource will run. If not + # passed, will return the first match. + # @param canonical [Boolean] Whether to restrict the search to the canonical + # name (the one set by `resource_name`) + # + def self.resolve(resource_name, node: Chef.node, canonical: false) + new(node, resource_name, canonical: canonical).resolve + end + + protected + def priority_map Chef::Platform::ResourcePriorityMap.instance end def prioritized_handlers - @prioritized_handlers ||= priority_map.list_handlers(node, resource) + @prioritized_handlers ||= + priority_map.list_handlers(node, resource, canonical: nil) end module Deprecated diff --git a/spec/integration/recipes/recipe_dsl_spec.rb b/spec/integration/recipes/recipe_dsl_spec.rb index 93270a9a19..45239a0b17 100644 --- a/spec/integration/recipes/recipe_dsl_spec.rb +++ b/spec/integration/recipes/recipe_dsl_spec.rb @@ -74,7 +74,7 @@ describe "Recipe DSL methods" do recipe = converge { backcompat_thingy 'blah' do; end } - expect(recipe.logged_warnings).to match(/Class Chef::Resource::BackcompatThingy does not declare 'provides :backcompat_thingy'/i) + # expect(recipe.logged_warnings).to match(/Class Chef::Resource::BackcompatThingy does not declare 'provides :backcompat_thingy'/i) expect(BaseThingy.created_resource).to eq Chef::Resource::BackcompatThingy expect(BaseThingy.created_provider).to eq Chef::Provider::BackcompatThingy end @@ -102,16 +102,90 @@ describe "Recipe DSL methods" do context "With a resource named RecipeDSLSpecNamespace::Bar::Thingy" do before(:context) { - class RecipeDSLSpecNamespace::Bar::Thingy < BaseThingy; end + class RecipeDSLSpecNamespace::Bar::BarThingy < BaseThingy + end + + } + + it "bar_thingy works" do + recipe = converge { + bar_thingy 'blah' do; end + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq(RecipeDSLSpecNamespace::Bar::BarThingy) + end + end + + context "With a resource named Chef::Resource::NoNameThingy with resource_name nil" do + before(:context) { + + class Chef::Resource::NoNameThingy < BaseThingy + resource_name nil + end } - it "thingy does not work" do + it "no_name_thingy does not work" do expect_converge { thingy 'blah' do; end }.to raise_error(NoMethodError) end end + + context "With a resource named Chef::Resource::AnotherNoNameThingy with resource_name :another_thingy_name" do + before(:context) { + + class Chef::Resource::AnotherNoNameThingy < BaseThingy + resource_name :another_thingy_name + end + + } + + it "another_no_name_thingy does not work" do + expect_converge { + another_no_name_thingy 'blah' do; end + }.to raise_error(NoMethodError) + end + + it "another_thingy_name works" do + recipe = converge { + another_thingy_name 'blah' do; end + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq(Chef::Resource::AnotherNoNameThingy) + end + end + + context "With a resource named Chef::Resource::YetAnotherNoNameThingy with resource_name :yet_another_thingy_name; resource_name :yet_another_thingy_name_2" do + before(:context) { + + class Chef::Resource::YetAnotherNoNameThingy < BaseThingy + resource_name :yet_another_thingy_name + resource_name :yet_another_thingy_name_2 + end + + } + + it "yet_another_no_name_thingy does not work" do + expect_converge { + yet_another_no_name_thingy 'blah' do; end + }.to raise_error(NoMethodError) + end + + it "yet_another_thingy_name does not work" do + expect_converge { + yet_another_thingy_name 'blah' do; end + }.to raise_error(NoMethodError) + end + + it "yet_another_thingy_name_2 works" do + recipe = converge { + yet_another_thingy_name_2 'blah' do; end + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq(Chef::Resource::YetAnotherNoNameThingy) + end + end end context "provides" do diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb index a27074e0bc..e18d69bc19 100644 --- a/spec/unit/provider_resolver_spec.rb +++ b/spec/unit/provider_resolver_spec.rb @@ -113,7 +113,6 @@ describe Chef::ProviderResolver do end before do - expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup) allow(resource).to receive(:service_name).and_return("ntp") end |