diff options
author | John Keiser <john@johnkeiser.com> | 2015-05-13 14:56:09 -0700 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2015-05-13 14:56:09 -0700 |
commit | b086721ca70750277c407fd0cc573a08f076649f (patch) | |
tree | ba58fa54264d520f6d4b9ae201176f5727e462ad | |
parent | c46776d45cd5a732441a4dc0bf0552e71bb500ee (diff) | |
parent | 34638bf82798bdcf01c28fbde6ac68dd1949c301 (diff) | |
download | chef-b086721ca70750277c407fd0cc573a08f076649f.tar.gz |
Deprecate Chef::Resource::FooBar -> "foo_bar" DSL convention;
use methods to handle DSL rather than method_missing
76 files changed, 1165 insertions, 303 deletions
diff --git a/.gitignore b/.gitignore index ecba9f4030..6588499305 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ pkg *~ # you should check in your Gemfile.lock in applications, and not in gems +external_tests/*.lock Gemfile.lock Gemfile.local diff --git a/CHANGELOG.md b/CHANGELOG.md index a8d53622ef..a933d1b70a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ * [Issue #3055](https://github.com/chef/chef/issues/3055) Fix regex parsing for recipe failures on Windows * [pr#3345](https://github.com/chef/chef/pull/3345) Windows Event log logger * [pr#3336](https://github.com/chef/chef/pull/3336) Remote file understands UNC paths +* [pr#3269](https://github.com/chef/chef/pull/3269): Deprecate automatic recipe + DSL for classes in `Chef::Resource` ## 12.3.0 @@ -78,14 +80,14 @@ ## 12.1.2 * [Issue 3022](https://github.com/chef/chef/issues/3022): Homebrew Cask install fails - FIXME (remove on 12.2.0 release): 3022 was only merged to 12-stable and #3077 or its descendant should fix this + FIXME (remove on 12.2.0 release): 3022 was only merged to 12-stable and #3077 or its descendant should fix this * [Issue 3059](https://github.com/chef/chef/issues/3059): Chef 12.1.1 yum_package silently fails * [Issue 3078](https://github.com/chef/chef/issues/3078): Compat break in audit-mode changes ## 12.1.1 * [**Phil Dibowitz**](https://github.com/jaymzh): [Issue 3008](https://github.com/chef/chef/issues/3008) Allow people to pass in `source` to package -* [Issue 3011](https://github.com/chef/chef/issues/3011) `package` provider base should include +* [Issue 3011](https://github.com/chef/chef/issues/3011) `package` provider base should include `Chef::Mixin::Command` as there are still providers that use it. * [**Ranjib Dey**](https://github.com/ranjib): [Issue 3019](https://github.com/chef/chef/issues/3019) Fix data fetching when explicit attributes are passed diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md index 6b58871418..b0a8e0aaaf 100644 --- a/DOC_CHANGES.md +++ b/DOC_CHANGES.md @@ -5,3 +5,30 @@ Example Doc Change: ### Headline for the required change Description of the required change. --> + +### Resources must now use `provides` to declare recipe DSL + +Resources declared in `Chef::Resource` namespace will no longer get recipe DSL +automatically. Instead, explicit `provides` is required in order to have DSL: + +```ruby +module MyModule + class MyResource < Chef::Resource + provides :my_resource + end +end +``` + +Authors of HWRPs need to be aware that in the future all resources and providers will be required to include a provides line. Starting with Chef 12.4.0 any HWRPs in the `Chef::Resource` or `Chef::Provider` namespaces that do not have provides lines will trigger deprecation warning messages when called. The LWRPBase code does `provides` automatically so LWRP authors and users who write classes that inherit from LWRPBase do not need to explicitly include provides lines. + +Users are encouraged to declare resources in their own namespaces instead of putting them in the special `Chef::Resource` namespace. + +### LWRPs are no longer automatically placed in the `Chef::Resource` namespace + +Starting with Chef 12.4.0, accessing an LWRP class by name from the `Chef::Resource` namespace will trigger a deprecation warning message. This means that if your cookbook includes the LWRP `mycookbook/resources/myresource.rb`, you will no longer be able to extend or reference `Chef::Resource::MycookbookMyresource` in Ruby code. LWRP recipe DSL does not change: the LWRP will still be available to recipes as `mycookbook_myresource`. + +You can still get the LWRP class by calling `Chef::ResourceResolver.resolve(:mycookbook_myresource)`. + +The primary aim here is clearing out the `Chef::Resource` namespace. + +References to these classes is deprecated (and will emit a warning) in Chef 12, and will be removed in Chef 13. @@ -10,6 +10,7 @@ end group(:development, :test) do gem "simplecov" gem 'rack', "~> 1.5.1" + gem 'cheffish', "~> 1.2" gem 'ruby-shadow', :platforms => :ruby unless RUBY_PLATFORM.downcase.match(/(aix|cygwin)/) end @@ -22,7 +22,8 @@ require File.dirname(__FILE__) + '/lib/chef/version' require 'rubygems' require 'rubygems/package_task' require 'rdoc/task' -require './tasks/rspec.rb' +require_relative 'tasks/rspec' +require_relative 'tasks/external_tests' GEM_NAME = "chef" diff --git a/chef.gemspec b/chef.gemspec index 7cf6380062..a02548c1da 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -44,9 +44,9 @@ Gem::Specification.new do |s| s.add_dependency "syslog-logger", "~> 1.6" s.add_development_dependency "rack" + s.add_development_dependency "cheffish", "~> 1.1" - # Rake 10.2 drops Ruby 1.8 support - s.add_development_dependency "rake", "~> 10.1.0" + s.add_development_dependency "rake", "~> 10.1" s.bindir = "bin" s.executables = %w( chef-client chef-solo knife chef-shell chef-apply ) diff --git a/external_tests/chef-rewind.gemfile b/external_tests/chef-rewind.gemfile new file mode 100644 index 0000000000..39f7d6e0e8 --- /dev/null +++ b/external_tests/chef-rewind.gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +gemspec(name: 'chef', path: "../") + +gem 'chef-rewind', github: 'thommay/chef-rewind' diff --git a/external_tests/chef-sugar.gemfile b/external_tests/chef-sugar.gemfile new file mode 100644 index 0000000000..31ef3bb5b8 --- /dev/null +++ b/external_tests/chef-sugar.gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gemspec(name: 'chef', path: "../") + +gem 'chef-sugar', github: 'sethvargo/chef-sugar' +gem 'chefspec' diff --git a/external_tests/chefspec.gemfile b/external_tests/chefspec.gemfile new file mode 100644 index 0000000000..fb7878afbd --- /dev/null +++ b/external_tests/chefspec.gemfile @@ -0,0 +1,7 @@ +source 'https://rubygems.org' + +gemspec(name: 'chef', path: "../") + +gem 'chefspec', github: 'sethvargo/chefspec', group: :development +gem 'aruba' +gem 'yard' diff --git a/external_tests/foodcritic.gemfile b/external_tests/foodcritic.gemfile new file mode 100644 index 0000000000..a2b71a0d8c --- /dev/null +++ b/external_tests/foodcritic.gemfile @@ -0,0 +1,9 @@ +source 'https://rubygems.org' + +gemspec(name: 'chef', path: "../") + +gem 'foodcritic', github: 'acrmp/foodcritic' +gem 'cucumber' +gem 'rubocop' +gem 'simplecov' +gem 'minitest' diff --git a/external_tests/halite.gemfile b/external_tests/halite.gemfile new file mode 100644 index 0000000000..cd8cd05668 --- /dev/null +++ b/external_tests/halite.gemfile @@ -0,0 +1,8 @@ +source 'https://rubygems.org' + +gemspec(name: 'chef', path: "../") + +gem 'poise', github: 'poise/poise' +gem 'halite', github: 'poise/halite' +gem 'poise-boiler', github: 'poise/poise-boiler' +gem 'rspec-command', github: 'coderanger/rspec-command' diff --git a/external_tests/poise.gemfile b/external_tests/poise.gemfile new file mode 100644 index 0000000000..7d274b7a29 --- /dev/null +++ b/external_tests/poise.gemfile @@ -0,0 +1,7 @@ +source 'https://rubygems.org' + +gemspec(name: 'chef', path: "../") + +gem 'poise', github: 'poise/poise' +gem 'halite', github: 'poise/halite' +gem 'poise-boiler', github: 'poise/poise-boiler' diff --git a/lib/chef/dsl/definitions.rb b/lib/chef/dsl/definitions.rb new file mode 100644 index 0000000000..1358f67720 --- /dev/null +++ b/lib/chef/dsl/definitions.rb @@ -0,0 +1,44 @@ +class Chef + module DSL + # + # Module containing a method for each declared definition + # + # Depends on declare_resource(name, created_at, &block) + # + # @api private + # + module Definitions + def self.add_definition(dsl_name) + module_eval <<-EOM, __FILE__, __LINE__+1 + def #{dsl_name}(*args, &block) + evaluate_resource_definition(#{dsl_name.inspect}, *args, &block) + end + EOM + end + + # @api private + def has_resource_definition?(name) + run_context.definitions.has_key?(name) + end + + # Processes the arguments and block as a resource definition. + # + # @api private + def evaluate_resource_definition(definition_name, *args, &block) + + # This dupes the high level object, but we still need to dup the params + new_def = run_context.definitions[definition_name].dup + + new_def.params = new_def.params.dup + new_def.node = run_context.node + # This sets up the parameter overrides + new_def.instance_eval(&block) if block + + new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context) + new_recipe.params = new_def.params + new_recipe.params[:name] = args[0] + new_recipe.instance_eval(&new_def.recipe) + end + end + end +end diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb index c22f053292..97f5088b5d 100644 --- a/lib/chef/dsl/recipe.rb +++ b/lib/chef/dsl/recipe.rb @@ -21,6 +21,8 @@ require 'chef/mixin/convert_to_class_name' require 'chef/exceptions' require 'chef/resource_builder' require 'chef/mixin/shell_out' +require 'chef/dsl/resources' +require 'chef/dsl/definitions' class Chef module DSL @@ -31,49 +33,62 @@ class Chef module Recipe include Chef::Mixin::ShellOut - include Chef::Mixin::ConvertToClassName + # method_missing must live for backcompat purposes until Chef 13. def method_missing(method_symbol, *args, &block) - # If we have a definition that matches, we want to use that instead. This should - # let you do some really crazy over-riding of "native" types, if you really want - # to. - if has_resource_definition?(method_symbol) - evaluate_resource_definition(method_symbol, *args, &block) - elsif have_resource_class_for?(method_symbol) - # Otherwise, we're rocking the regular resource call route. - declare_resource(method_symbol, args[0], caller[0], &block) - else - 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 + # + # 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.deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13.") + Chef::Log.deprecation("Use public_send() or send() instead.") + return send(method_symbol, *args, &block) end - end - - def has_resource_definition?(name) - run_context.definitions.has_key?(name) - end - - # Processes the arguments and block as a resource definition. - def evaluate_resource_definition(definition_name, *args, &block) - # This dupes the high level object, but we still need to dup the params - new_def = run_context.definitions[definition_name].dup + # + # 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.deprecation("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 - new_def.params = new_def.params.dup - new_def.node = run_context.node - # This sets up the parameter overrides - new_def.instance_eval(&block) if block + # + # See if the resource exists anyway. If the user had set + # Chef::Resource::Blah = <resource>, 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.deprecation("#{resource_class} is marked as providing DSL #{method_symbol}, but provides #{method_symbol.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.") + Chef::DSL::Resources.add_resource_dsl(method_symbol) + end + return send(method_symbol, *args, &block) + end - new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context) - new_recipe.params = new_def.params - new_recipe.params[:name] = args[0] - new_recipe.instance_eval(&new_def.recipe) + 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::Definitions + # # Instantiates a resource (via #build_resource), then adds it to the # resource collection. Note that resource classes are looked up directly, diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb new file mode 100644 index 0000000000..4072ff2c89 --- /dev/null +++ b/lib/chef/dsl/resources.rb @@ -0,0 +1,28 @@ +class Chef + module DSL + # + # Module containing a method for each globally declared Resource + # + # Depends on declare_resource(name, created_at, &block) + # + # @api private + module Resources + def self.add_resource_dsl(dsl_name) + begin + 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 + rescue SyntaxError + define_method(dsl_name.to_sym) do |name, created_at=nil, &block| + declare_resource(dsl_name, name, created_at || caller[0], &block) + end + end + end + def self.remove_resource_dsl(dsl_name) + remove_method(dsl_name) if method_defined?(dsl_name) + end + end + end +end diff --git a/lib/chef/mixin/provides.rb b/lib/chef/mixin/provides.rb index bc25af62b2..c39c53a190 100644 --- a/lib/chef/mixin/provides.rb +++ b/lib/chef/mixin/provides.rb @@ -23,11 +23,18 @@ class Chef node_map.set(short_name, true, opts, &block) end - # provides a node on the resource (early binding) + # Check whether this resource provides the resource_name DSL for the given + # node def provides?(node, resource_name) resource_name = resource_name.resource_name if resource_name.is_a?(Chef::Resource) node_map.get(node, resource_name) end + + # Get the list of recipe DSL this resource is responsible for on the given + # node. + def provided_as(node) + node_map.list(node) + end end end end diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index 0d7285729f..00458b6485 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -460,16 +460,20 @@ class Chef pmap.has_key?(rtkey) ? pmap[rtkey] : nil end + include Chef::Mixin::ConvertToClassName + def resource_matching_provider(platform, version, resource_type) if resource_type.kind_of?(Chef::Resource) + class_name = resource_type.class.to_s.split('::').last + begin - Chef::Provider.const_get(resource_type.class.to_s.split('::').last) + result = Chef::Provider.const_get(class_name) + Chef::Log.warn("Class Chef::Provider::#{class_name} does not declare 'provides #{convert_to_snake_case(class_name).to_sym.inspect}'.") + Chef::Log.warn("This will no longer work in Chef 13: you must use 'provides' to provide DSL.") rescue NameError - nil end - else - nil end + result end end diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index 65a56cf726..3ab4d4d2b1 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -191,5 +191,33 @@ class Chef end end + module DeprecatedLWRPClass + def const_missing(class_name) + if deprecated_constants[class_name.to_sym] + Chef::Log.deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.") + deprecated_constants[class_name.to_sym] + else + raise NameError, "uninitialized constant Chef::Provider::#{class_name}" + end + end + + # @api private + def register_deprecated_lwrp_class(provider_class, class_name) + # Register Chef::Provider::MyProvider with deprecation warnings if you + # try to access it + if Chef::Provider.const_defined?(class_name, false) + Chef::Log.warn "Chef::Provider::#{class_name} already exists! Cannot create deprecation class for #{provider_class}" + else + deprecated_constants[class_name.to_sym] = provider_class + end + end + + private + + def deprecated_constants + @deprecated_constants ||= {} + end + end + extend DeprecatedLWRPClass end end diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb index 0750c0420b..01c61e4253 100644 --- a/lib/chef/provider/cron/unix.rb +++ b/lib/chef/provider/cron/unix.rb @@ -20,6 +20,7 @@ require 'chef/log' require 'chef/provider' +require 'chef/provider/cron' class Chef class Provider diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb index 492ddda6da..38430ced6d 100644 --- a/lib/chef/provider/lwrp_base.rb +++ b/lib/chef/provider/lwrp_base.rb @@ -69,9 +69,6 @@ class Chef end - extend Chef::Mixin::ConvertToClassName - extend Chef::Mixin::FromFile - include Chef::DSL::Recipe # These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore. @@ -80,71 +77,92 @@ class Chef include Chef::DSL::PlatformIntrospection include Chef::DSL::DataQuery - def self.build_from_file(cookbook_name, filename, run_context) - provider_class = nil - provider_name = filename_to_qualified_string(cookbook_name, filename) + # no-op `load_current_resource`. Allows simple LWRP providers to work + # without defining this method explicitly (silences + # Chef::Exceptions::Override exception) + def load_current_resource + end + + # class methods + class <<self + include Chef::Mixin::ConvertToClassName + include Chef::Mixin::FromFile + + def build_from_file(cookbook_name, filename, run_context) + if LWRPBase.loaded_lwrps[filename] + Chef::Log.info("LWRP provider #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.") + return loaded_lwrps[filename] + end - class_name = convert_to_class_name(provider_name) + resource_name = filename_to_qualified_string(cookbook_name, filename) - if Chef::Provider.const_defined?(class_name, false) - Chef::Log.info("#{class_name} light-weight provider is already initialized -- Skipping loading #{filename}!") - Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.") - provider_class = Chef::Provider.const_get(class_name) - else + # We load the class first to give it a chance to set its own name provider_class = Class.new(self) - Chef::Provider.const_set(class_name, provider_class) + provider_class.provides resource_name.to_sym provider_class.class_from_file(filename) - Chef::Log.debug("Loaded contents of #{filename} into a provider named #{provider_name} defined in Chef::Provider::#{class_name}") - end - provider_class - end + # Respect resource_name set inside the LWRP + provider_class.instance_eval do + define_method(:to_s) do + "LWRP provider #{resource_name} from cookbook #{cookbook_name}" + end + define_method(:inspect) { to_s } + end - # Enables inline evaluation of resources in provider actions. - # - # Without this option, any resources declared inside the LWRP are added - # to the resource collection after the current position at the time the - # action is executed. Because they are added to the primary resource - # collection for the chef run, they can notify other resources outside - # the LWRP, and potentially be notified by resources outside the LWRP - # (but this is complicated by the fact that they don't exist until the - # provider executes). In this mode, it is impossible to correctly set the - # updated_by_last_action flag on the parent LWRP resource, since it - # executes and returns before its component resources are run. - # - # With this option enabled, each action creates a temporary run_context - # with its own resource collection, evaluates the action's code in that - # context, and then converges the resources created. If any resources - # were updated, then this provider's new_resource will be marked updated. - # - # In this mode, resources created within the LWRP cannot interact with - # external resources via notifies, though notifications to other - # resources within the LWRP will work. Delayed notifications are executed - # at the conclusion of the provider's action, *not* at the end of the - # main chef run. - # - # This mode of evaluation is experimental, but is believed to be a better - # set of tradeoffs than the append-after mode, so it will likely become - # the default in a future major release of Chef. - # - def self.use_inline_resources - extend InlineResources::ClassMethods - include InlineResources - end + Chef::Log.debug("Loaded contents of #{filename} into provider #{resource_name} (#{provider_class})") + + LWRPBase.loaded_lwrps[filename] = true - # DSL for defining a provider's actions. - def self.action(name, &block) - define_method("action_#{name}") do - instance_eval(&block) + Chef::Provider.register_deprecated_lwrp_class(provider_class, convert_to_class_name(resource_name)) + + provider_class end - end - # no-op `load_current_resource`. Allows simple LWRP providers to work - # without defining this method explicitly (silences - # Chef::Exceptions::Override exception) - def load_current_resource - end + # Enables inline evaluation of resources in provider actions. + # + # Without this option, any resources declared inside the LWRP are added + # to the resource collection after the current position at the time the + # action is executed. Because they are added to the primary resource + # collection for the chef run, they can notify other resources outside + # the LWRP, and potentially be notified by resources outside the LWRP + # (but this is complicated by the fact that they don't exist until the + # provider executes). In this mode, it is impossible to correctly set the + # updated_by_last_action flag on the parent LWRP resource, since it + # executes and returns before its component resources are run. + # + # With this option enabled, each action creates a temporary run_context + # with its own resource collection, evaluates the action's code in that + # context, and then converges the resources created. If any resources + # were updated, then this provider's new_resource will be marked updated. + # + # In this mode, resources created within the LWRP cannot interact with + # external resources via notifies, though notifications to other + # resources within the LWRP will work. Delayed notifications are executed + # at the conclusion of the provider's action, *not* at the end of the + # main chef run. + # + # This mode of evaluation is experimental, but is believed to be a better + # set of tradeoffs than the append-after mode, so it will likely become + # the default in a future major release of Chef. + # + def use_inline_resources + extend InlineResources::ClassMethods + include InlineResources + end + + # DSL for defining a provider's actions. + def action(name, &block) + define_method("action_#{name}") do + instance_eval(&block) + end + end + + protected + def loaded_lwrps + @loaded_lwrps ||= {} + end + end end end end diff --git a/lib/chef/provider/ohai.rb b/lib/chef/provider/ohai.rb index a6b5ab5daa..b7f4aa704b 100644 --- a/lib/chef/provider/ohai.rb +++ b/lib/chef/provider/ohai.rb @@ -21,6 +21,7 @@ require 'ohai' class Chef class Provider class Ohai < Chef::Provider + provides :ohai def whyrun_supported? true diff --git a/lib/chef/provider/reboot.rb b/lib/chef/provider/reboot.rb index 8dde4653ec..22e77dcc13 100644 --- a/lib/chef/provider/reboot.rb +++ b/lib/chef/provider/reboot.rb @@ -22,6 +22,7 @@ require 'chef/provider' class Chef class Provider class Reboot < Chef::Provider + provides :reboot def whyrun_supported? true diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb index 94f4e2655b..cd62f7c56f 100644 --- a/lib/chef/provider/registry_key.rb +++ b/lib/chef/provider/registry_key.rb @@ -31,6 +31,8 @@ class Chef class Provider class RegistryKey < Chef::Provider + provides :registry_key + include Chef::Mixin::Checksum def whyrun_supported? diff --git a/lib/chef/provider/remote_file.rb b/lib/chef/provider/remote_file.rb index da2573dacb..c4643edc0b 100644 --- a/lib/chef/provider/remote_file.rb +++ b/lib/chef/provider/remote_file.rb @@ -24,6 +24,7 @@ require 'chef/deprecation/warnings' class Chef class Provider class RemoteFile < Chef::Provider::File + provides :remote_file extend Chef::Deprecation::Warnings include Chef::Deprecation::Provider::RemoteFile diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb index 5e887225e4..7c7bac0443 100644 --- a/lib/chef/provider_resolver.rb +++ b/lib/chef/provider_resolver.rb @@ -20,6 +20,93 @@ require 'chef/exceptions' require 'chef/platform/provider_priority_map' class Chef + # + # Provider Resolution + # =================== + # + # When you type `service 'myservice' { action :restart }` in a recipe, a whole + # string of events happens eventually leading to convergence. The overview of + # that process is described in `Chef::DSL::Recipe`. Provider resolution is + # the process of taking a Resource object and an action, and determining the + # Provider class that should be instantiated to handle the action. + # + # The process happens in three steps: + # + # Explicit Provider on the Resource + # --------------------------------- + # If the resource has its `provider` set, that is used. + # + # Dynamic Provider Matches + # ------------------------ + # In this stage, we call `provides?` to see if the Provider supports the + # resource on this platform, and then we call `supports?` to determine if it + # can handle the action. It's a little more complicated than that, though: + # + # ### Provider.provides? + # + # First, we go through all known provider classes (all descendants of + # `Chef::Provider`), and call `provides?(node, resource)` to determine if it + # supports this action for this resource on this OS. We get a list of all + # matches. + # + # #### Defining provides + # + # The typical way of getting `provides?` is for the Provider class to call + # `provides :name`. + # + # The Provider may pass the OS, platform family, platform, and platform version + # to `provides`, and they will be matched against the values in the `node` + # object. The Provider may also pass a block, which allows for custom logic + # to decide whether it provides the resource or not. + # + # Some Providers also override `provides?` with custom logic. + # + # ### Provider.supports? + # + # Once we have the list of willing providers, we filter it by calling their + # `supports?(resource, action)` method to see if they support the specific + # action (`:create`, `:delete`) or not. + # + # If no provider supports the specific action, we fall back to the full list + # of matches from step 1. (TODO The comment says it's for why run. I'm not + # sure what that means specifically yet.) + # + # ### Priority lists: Chef.get_provider_priority_array + # + # Once we have the list of matches, we look at `Chef.get_provider_priority_array(node, resource)` + # to see if anyone has set a *priority list*. This method takes + # the the first matching priority list for this OS (which is the last list + # that was registered). + # + # If any of our matches are on the priority list, we take the first one. + # + # If there is no priority list or no matches on it, we take the first result + # alphabetically by class name. + # + # Chef::Platform Provider Map + # --------------------------- + # If we still have no matches, we try `Chef::Platform.find_provider_for_node(node, resource)`. + # This does two new things: + # + # ### System Provider Map + # + # The system provider map is a large Hash loaded during `require` time, + # which shows system-specific providers by os/platform, and platform_version. + # It keys off of `node[:platform] || node[:os]`, and `node[:platform_version] + # || node[:os_version] || node[:os_release]`. The version uses typical gem + # constraints like > and <=. + # + # The first platform+version match wins over the first platform-only match, + # which wins over the default. + # + # ### Chef::Provider::FooBar + # + # As a last resort, if there are *still* no classes, the system transforms the + # DSL name `foo_bar` into `Chef::Provider::FooBar`, and returns the class if + # it is there and descends from `Chef::Provider`. + # + # NOTE: this behavior is now deprecated. + # class ProviderResolver attr_reader :node diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 3d319617d1..2f5c2b7798 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -22,6 +22,7 @@ require 'chef/dsl/platform_introspection' require 'chef/dsl/data_query' require 'chef/dsl/registry_helper' require 'chef/dsl/reboot_pending' +require 'chef/dsl/resources' require 'chef/mixin/convert_to_class_name' require 'chef/guard_interpreter/resource_guard_interpreter' require 'chef/resource/conditional' @@ -31,6 +32,8 @@ require 'chef/node_map' require 'chef/node' require 'chef/platform' require 'chef/resource/resource_notification' +require 'chef/provider_resolver' +require 'chef/resource_resolver' require 'chef/mixin/deprecation' require 'chef/mixin/provides' @@ -604,7 +607,7 @@ class Chef return "suppressed sensitive resource output" if sensitive ivars = instance_variables.map { |ivar| ivar.to_sym } - HIDDEN_IVARS text = "# Declared in #{@source_line}\n\n" - text << self.class.dsl_name + "(\"#{name}\") do\n" + text << "#{resource_name}(\"#{name}\") do\n" ivars.each do |ivar| if (value = instance_variable_get(ivar)) && !(value.respond_to?(:empty?) && value.empty?) value_string = value.respond_to?(:to_text) ? value.to_text : value.inspect @@ -820,7 +823,11 @@ class Chef # # @return [String] The DSL name of this resource. def self.dsl_name - convert_to_snake_case(name, 'Chef::Resource') + Chef::Log.deprecation "Resource.dsl_name is deprecated and will be removed in Chef 11. Use resource.resource_name instead." + if name + name = self.name.split('::')[-1] + convert_to_snake_case(name) + end end # @@ -969,6 +976,12 @@ class Chef end end + def self.provides(name, *args, &block) + result = super + Chef::DSL::Resources.add_resource_dsl(name) + result + end + # Helper for #notifies def validate_resource_spec!(resource_spec) run_context.resource_collection.validate_lookup_spec!(resource_spec) @@ -1025,7 +1038,6 @@ 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 @@ -1099,30 +1111,68 @@ class Chef # === Returns # <Chef::Resource>:: returns the proper Chef::Resource class def self.resource_for_node(short_name, node) - require 'chef/resource_resolver' klass = Chef::ResourceResolver.new(node, short_name).resolve raise Chef::Exceptions::NoSuchResourceType.new(short_name, node) if klass.nil? klass end + # # Returns the class of a Chef::Resource based on the short name + # Only returns the *canonical* class with the given name, not the one that + # would be picked by the ResourceResolver. + # # ==== Parameters # short_name<Symbol>:: short_name of the resource (ie :directory) # # === Returns # <Chef::Resource>:: returns the proper Chef::Resource class + # + # @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) - Chef::Resource.const_get(rname) + 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 end - private + # Implement deprecated LWRP class + module DeprecatedLWRPClass + # @api private + def register_deprecated_lwrp_class(resource_class, class_name) + if Chef::Resource.const_defined?(class_name, false) + Chef::Log.warn "#{class_name} already exists! Cannot create deprecation class for #{resource_class}" + else + deprecated_constants[class_name.to_sym] = resource_class + end + end + + def const_missing(class_name) + if deprecated_constants[class_name.to_sym] + Chef::Log.deprecation("Using an LWRP by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.") + deprecated_constants[class_name.to_sym] + else + raise NameError, "uninitialized constant Chef::Resource::#{class_name}" + end + end + + private + + def deprecated_constants + @deprecated_constants ||= {} + end + end + extend DeprecatedLWRPClass - def lookup_provider_constant(name) + # @api private + def lookup_provider_constant(name, action=:nothing) begin self.class.provider_base.const_get(convert_to_class_name(name.to_s)) rescue NameError => e diff --git a/lib/chef/resource/bash.rb b/lib/chef/resource/bash.rb index 0add0ce501..366d8c7bd6 100644 --- a/lib/chef/resource/bash.rb +++ b/lib/chef/resource/bash.rb @@ -22,6 +22,7 @@ require 'chef/provider/script' class Chef class Resource class Bash < Chef::Resource::Script + provides :bash def initialize(name, run_context=nil) super diff --git a/lib/chef/resource/bff_package.rb b/lib/chef/resource/bff_package.rb index 917f0d1d50..d4139e7ffe 100644 --- a/lib/chef/resource/bff_package.rb +++ b/lib/chef/resource/bff_package.rb @@ -22,6 +22,7 @@ require 'chef/provider/package/aix' class Chef class Resource class BffPackage < Chef::Resource::Package + provides :bff_package def initialize(name, run_context=nil) super @@ -31,5 +32,3 @@ class Chef end end end - - diff --git a/lib/chef/resource/breakpoint.rb b/lib/chef/resource/breakpoint.rb index b2210262d2..5a55858f71 100644 --- a/lib/chef/resource/breakpoint.rb +++ b/lib/chef/resource/breakpoint.rb @@ -22,6 +22,7 @@ require 'chef/resource' class Chef class Resource class Breakpoint < Chef::Resource + provides :breakpoint def initialize(action="break", *args) @name = caller.first diff --git a/lib/chef/resource/csh.rb b/lib/chef/resource/csh.rb index 36659c349b..d37f1a8e0c 100644 --- a/lib/chef/resource/csh.rb +++ b/lib/chef/resource/csh.rb @@ -22,6 +22,7 @@ require 'chef/provider/script' class Chef class Resource class Csh < Chef::Resource::Script + provides :csh def initialize(name, run_context=nil) super diff --git a/lib/chef/resource/deploy.rb b/lib/chef/resource/deploy.rb index 4252aa230f..55e3547b25 100644 --- a/lib/chef/resource/deploy.rb +++ b/lib/chef/resource/deploy.rb @@ -50,6 +50,7 @@ class Chef # release directory. Callback files can contain chef code (resources, etc.) # class Deploy < Chef::Resource + provides :deploy provider_base Chef::Provider::Deploy diff --git a/lib/chef/resource/erl_call.rb b/lib/chef/resource/erl_call.rb index 24009d51c7..75422c55a1 100644 --- a/lib/chef/resource/erl_call.rb +++ b/lib/chef/resource/erl_call.rb @@ -23,6 +23,7 @@ require 'chef/provider/erl_call' class Chef class Resource class ErlCall < Chef::Resource + provides :erl_call # erl_call : http://erlang.org/doc/man/erl_call.html diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb index 9f8b629fb8..8fc97d748f 100644 --- a/lib/chef/resource/execute.rb +++ b/lib/chef/resource/execute.rb @@ -23,6 +23,7 @@ require 'chef/provider/execute' class Chef class Resource class Execute < Chef::Resource + provides :execute identity_attr :command diff --git a/lib/chef/resource/http_request.rb b/lib/chef/resource/http_request.rb index ccb0a26629..5986ebd4a0 100644 --- a/lib/chef/resource/http_request.rb +++ b/lib/chef/resource/http_request.rb @@ -23,6 +23,7 @@ require 'chef/provider/http_request' class Chef class Resource class HttpRequest < Chef::Resource + provides :http_request identity_attr :url diff --git a/lib/chef/resource/ifconfig.rb b/lib/chef/resource/ifconfig.rb index c289ddadbe..60feba1704 100644 --- a/lib/chef/resource/ifconfig.rb +++ b/lib/chef/resource/ifconfig.rb @@ -22,6 +22,7 @@ require 'chef/resource' class Chef class Resource class Ifconfig < Chef::Resource + provides :ifconfig identity_attr :device @@ -145,5 +146,3 @@ class Chef end end - - diff --git a/lib/chef/resource/log.rb b/lib/chef/resource/log.rb index 7f970a87a4..87be01aaa9 100644 --- a/lib/chef/resource/log.rb +++ b/lib/chef/resource/log.rb @@ -23,6 +23,7 @@ require 'chef/provider/log' class Chef class Resource class Log < Chef::Resource + provides :log identity_attr :message @@ -75,5 +76,3 @@ class Chef end end end - - diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb index ce72e98028..c79c205285 100644 --- a/lib/chef/resource/lwrp_base.rb +++ b/lib/chef/resource/lwrp_base.rb @@ -19,6 +19,13 @@ # require 'chef/resource' +require 'chef/resource_resolver' +require 'chef/node' +require 'chef/log' +require 'chef/exceptions' +require 'chef/mixin/convert_to_class_name' +require 'chef/mixin/from_file' +require 'chef/mixin/params_validate' # for DelayedEvaluator class Chef class Resource @@ -30,121 +37,134 @@ class Chef NULL_ARG = Object.new - extend Chef::Mixin::ConvertToClassName - extend Chef::Mixin::FromFile + # Class methods + class <<self - # Evaluates the LWRP resource file and instantiates a new Resource class. - def self.build_from_file(cookbook_name, filename, run_context) - resource_class = nil - rname = filename_to_qualified_string(cookbook_name, filename) + include Chef::Mixin::ConvertToClassName + include Chef::Mixin::FromFile - class_name = convert_to_class_name(rname) - if Resource.const_defined?(class_name, false) - Chef::Log.info("#{class_name} light-weight resource is already initialized -- Skipping loading #{filename}!") - Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.") - resource_class = Resource.const_get(class_name) - else - resource_class = Class.new(self) + attr_accessor :loaded_lwrps + + def build_from_file(cookbook_name, filename, run_context) + if LWRPBase.loaded_lwrps[filename] + Chef::Log.info("LWRP resource #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.") + return loaded_lwrps[filename] + end - Chef::Resource.const_set(class_name, resource_class) - resource_class.resource_name = rname + resource_name = filename_to_qualified_string(cookbook_name, filename) + + # We load the class first to give it a chance to set its own name + resource_class = Class.new(self) + resource_class.resource_name = resource_name resource_class.run_context = run_context + resource_class.provides resource_name.to_sym resource_class.class_from_file(filename) - Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}") - end + # Respect resource_name set inside the LWRP + resource_class.instance_eval do + define_method(:to_s) do + "LWRP resource #{resource_name} from cookbook #{cookbook_name}" + end + define_method(:inspect) { to_s } + end - resource_class - end + Chef::Log.debug("Loaded contents of #{filename} into resource #{resource_name} (#{resource_class})") - # Set the resource name for this LWRP - def self.resource_name(arg = NULL_ARG) - if arg.equal?(NULL_ARG) - @resource_name - else - @resource_name = arg - end - end + LWRPBase.loaded_lwrps[filename] = true - class << self - alias_method :resource_name=, :resource_name - end + Chef::Resource.register_deprecated_lwrp_class(resource_class, convert_to_class_name(resource_name)) - # Define an attribute on this resource, including optional validation - # parameters. - def self.attribute(attr_name, validation_opts={}) - define_method(attr_name) do |arg=nil| - set_or_return(attr_name.to_sym, arg, validation_opts) + resource_class end - end - # Sets the default action - def self.default_action(action_name=NULL_ARG) - unless action_name.equal?(NULL_ARG) - @actions ||= [] - if action_name.is_a?(Array) - action = action_name.map { |arg| arg.to_sym } - @actions = actions | action - @default_action = action + def resource_name(arg = NULL_ARG) + if arg.equal?(NULL_ARG) + @resource_name else - action = action_name.to_sym - @actions.push(action) unless @actions.include?(action) - @default_action = action + @resource_name = arg end end - @default_action ||= from_superclass(:default_action) - end + alias_method :resource_name=, :resource_name + + # Define an attribute on this resource, including optional validation + # parameters. + def attribute(attr_name, validation_opts={}) + define_method(attr_name) do |arg=nil| + set_or_return(attr_name.to_sym, arg, validation_opts) + end + end + + # Sets the default action + def default_action(action_name=NULL_ARG) + unless action_name.equal?(NULL_ARG) + @actions ||= [] + if action_name.is_a?(Array) + action = action_name.map { |arg| arg.to_sym } + @actions = actions | action + @default_action = action + else + action = action_name.to_sym + @actions.push(action) unless @actions.include?(action) + @default_action = action + end + end + + @default_action ||= from_superclass(:default_action) + end - # Adds +action_names+ to the list of valid actions for this resource. - def self.actions(*action_names) - if action_names.empty? - defined?(@actions) ? @actions : from_superclass(:actions, []).dup - else - # BC-compat way for checking if actions have already been defined - if defined?(@actions) - @actions.push(*action_names) + # Adds +action_names+ to the list of valid actions for this resource. + def actions(*action_names) + if action_names.empty? + defined?(@actions) ? @actions : from_superclass(:actions, []).dup else - @actions = action_names + # BC-compat way for checking if actions have already been defined + if defined?(@actions) + @actions.push(*action_names) + else + @actions = action_names + end end end - end - # @deprecated - def self.valid_actions(*args) - Chef::Log.warn("`valid_actions' is deprecated, please use actions `instead'!") - actions(*args) - end + # @deprecated + def valid_actions(*args) + Chef::Log.warn("`valid_actions' is deprecated, please use actions `instead'!") + actions(*args) + end - # Set the run context on the class. Used to provide access to the node - # during class definition. - def self.run_context=(run_context) - @run_context = run_context - end + # Set the run context on the class. Used to provide access to the node + # during class definition. + attr_accessor :run_context - def self.run_context - @run_context - end + def node + run_context ? run_context.node : nil + end - def self.node - run_context.node - end + def lazy(&block) + DelayedEvaluator.new(&block) + end - def self.lazy(&block) - DelayedEvaluator.new(&block) - end + protected - private + def loaded_lwrps + @loaded_lwrps ||= {} + end + + private - # Get the value from the superclass, if it responds, otherwise return - # +nil+. Since class instance variables are **not** inherited upon - # subclassing, this is a required check to ensure Chef pulls the - # +default_action+ and other DSL-y methods when extending LWRP::Base. - def self.from_superclass(m, default = nil) - return default if superclass == Chef::Resource::LWRPBase - superclass.respond_to?(m) ? superclass.send(m) : default + # Get the value from the superclass, if it responds, otherwise return + # +nil+. Since class instance variables are **not** inherited upon + # subclassing, this is a required check to ensure Chef pulls the + # +default_action+ and other DSL-y methods when extending LWRP::Base. + def from_superclass(m, default = nil) + return default if superclass == Chef::Resource::LWRPBase + superclass.respond_to?(m) ? superclass.send(m) : default + end end + private + # Default initializer. Sets the default action and allowed actions. def initialize(name, run_context=nil) super(name, run_context) @@ -159,7 +179,6 @@ class Chef @action = self.class.default_action allowed_actions.push(self.class.actions).flatten! end - end end end diff --git a/lib/chef/resource/ohai.rb b/lib/chef/resource/ohai.rb index b567db40f9..e2d12ce395 100644 --- a/lib/chef/resource/ohai.rb +++ b/lib/chef/resource/ohai.rb @@ -20,6 +20,7 @@ class Chef class Resource class Ohai < Chef::Resource + provides :ohai identity_attr :name diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb index f4f49b543b..5bea894a02 100644 --- a/lib/chef/resource/package.rb +++ b/lib/chef/resource/package.rb @@ -22,6 +22,7 @@ require 'chef/resource' class Chef class Resource class Package < Chef::Resource + provides :package identity_attr :package_name diff --git a/lib/chef/resource/perl.rb b/lib/chef/resource/perl.rb index c4bdb6e130..cb741d145a 100644 --- a/lib/chef/resource/perl.rb +++ b/lib/chef/resource/perl.rb @@ -22,6 +22,7 @@ require 'chef/provider/script' class Chef class Resource class Perl < Chef::Resource::Script + provides :perl def initialize(name, run_context=nil) super diff --git a/lib/chef/resource/portage_package.rb b/lib/chef/resource/portage_package.rb index 42c03560b6..b03b69796a 100644 --- a/lib/chef/resource/portage_package.rb +++ b/lib/chef/resource/portage_package.rb @@ -21,6 +21,7 @@ require 'chef/resource/package' class Chef class Resource class PortagePackage < Chef::Resource::Package + provides :portage_package def initialize(name, run_context=nil) super diff --git a/lib/chef/resource/python.rb b/lib/chef/resource/python.rb index b1f23d13ce..fffd4d75f6 100644 --- a/lib/chef/resource/python.rb +++ b/lib/chef/resource/python.rb @@ -21,6 +21,7 @@ require 'chef/provider/script' class Chef class Resource class Python < Chef::Resource::Script + provides :python def initialize(name, run_context=nil) super diff --git a/lib/chef/resource/reboot.rb b/lib/chef/resource/reboot.rb index c111b23d2e..7cd53450ed 100644 --- a/lib/chef/resource/reboot.rb +++ b/lib/chef/resource/reboot.rb @@ -24,6 +24,8 @@ require 'chef/resource' class Chef class Resource class Reboot < Chef::Resource + provides :reboot + def initialize(name, run_context=nil) super @resource_name = :reboot diff --git a/lib/chef/resource/registry_key.rb b/lib/chef/resource/registry_key.rb index 8126ccf126..cc8d05dd53 100644 --- a/lib/chef/resource/registry_key.rb +++ b/lib/chef/resource/registry_key.rb @@ -22,6 +22,7 @@ require 'chef/digester' class Chef class Resource class RegistryKey < Chef::Resource + provides :registry_key identity_attr :key state_attrs :values diff --git a/lib/chef/resource/route.rb b/lib/chef/resource/route.rb index 942905d138..8f9172060b 100644 --- a/lib/chef/resource/route.rb +++ b/lib/chef/resource/route.rb @@ -22,6 +22,7 @@ require 'chef/resource' class Chef class Resource class Route < Chef::Resource + provides :route identity_attr :target @@ -136,5 +137,3 @@ class Chef end end end - - diff --git a/lib/chef/resource/ruby.rb b/lib/chef/resource/ruby.rb index 2b2aa0249d..2b7644562a 100644 --- a/lib/chef/resource/ruby.rb +++ b/lib/chef/resource/ruby.rb @@ -22,6 +22,7 @@ require 'chef/provider/script' class Chef class Resource class Ruby < Chef::Resource::Script + provides :ruby def initialize(name, run_context=nil) super diff --git a/lib/chef/resource/ruby_block.rb b/lib/chef/resource/ruby_block.rb index a9cbf234cf..07eec5599d 100644 --- a/lib/chef/resource/ruby_block.rb +++ b/lib/chef/resource/ruby_block.rb @@ -23,6 +23,7 @@ require 'chef/provider/ruby_block' class Chef class Resource class RubyBlock < Chef::Resource + provides :ruby_block identity_attr :block_name diff --git a/lib/chef/resource/scm.rb b/lib/chef/resource/scm.rb index 87c217b4cc..d41764d595 100644 --- a/lib/chef/resource/scm.rb +++ b/lib/chef/resource/scm.rb @@ -22,6 +22,7 @@ require 'chef/resource' class Chef class Resource class Scm < Chef::Resource + provides :scm identity_attr :destination diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb index fd0fd5a7fd..e2fbb29d0f 100644 --- a/lib/chef/resource/script.rb +++ b/lib/chef/resource/script.rb @@ -23,6 +23,7 @@ require 'chef/provider/script' class Chef class Resource class Script < Chef::Resource::Execute + provides :script # Chef-13: go back to using :name as the identity attr identity_attr :command diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb index 36df7c859a..47d2ab9e12 100644 --- a/lib/chef/resource/service.rb +++ b/lib/chef/resource/service.rb @@ -22,6 +22,7 @@ require 'chef/resource' class Chef class Resource class Service < Chef::Resource + provides :service identity_attr :service_name diff --git a/lib/chef/resource/subversion.rb b/lib/chef/resource/subversion.rb index 3afbe0baaf..e5a2e9d1a5 100644 --- a/lib/chef/resource/subversion.rb +++ b/lib/chef/resource/subversion.rb @@ -22,6 +22,7 @@ require "chef/resource/scm" class Chef class Resource class Subversion < Chef::Resource::Scm + provides :subversion def initialize(name, run_context=nil) super diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/resource/whyrun_safe_ruby_block.rb index 6fa5383f5d..f512dc67fc 100644 --- a/lib/chef/resource/whyrun_safe_ruby_block.rb +++ b/lib/chef/resource/whyrun_safe_ruby_block.rb @@ -19,6 +19,7 @@ class Chef class Resource class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock + provides :whyrun_safe_ruby_block def initialize(name, run_context=nil) super diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb index 6b0827b77c..1af7a48fe4 100644 --- a/lib/chef/resource/windows_script.rb +++ b/lib/chef/resource/windows_script.rb @@ -22,6 +22,7 @@ require 'chef/mixin/windows_architecture_helper' class Chef class Resource class WindowsScript < Chef::Resource::Script + # This is an abstract resource meant to be subclasses; thus no 'provides' set_guard_inherited_attributes(:architecture) diff --git a/lib/chef/resource_definition.rb b/lib/chef/resource_definition.rb index 9d6844129c..cffabb6786 100644 --- a/lib/chef/resource_definition.rb +++ b/lib/chef/resource_definition.rb @@ -50,6 +50,7 @@ class Chef else raise ArgumentError, "You must pass a block to a definition." end + Chef::DSL::Definitions.add_definition(name) true end diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb index 96cc01d814..7829bb4d70 100644 --- a/lib/chef/resource_reporter.rb +++ b/lib/chef/resource_reporter.rb @@ -59,7 +59,7 @@ class Chef # attrs. def for_json as_hash = {} - as_hash["type"] = new_resource.class.dsl_name + as_hash["type"] = new_resource.resource_name.to_sym as_hash["name"] = new_resource.name.to_s as_hash["id"] = new_resource.identity.to_s as_hash["after"] = new_resource.state_for_resource_reporter diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb index ff9d7aeb9f..a987b236c2 100644 --- a/lib/chef/resource_resolver.rb +++ b/lib/chef/resource_resolver.rb @@ -18,9 +18,11 @@ require 'chef/exceptions' 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 @@ -28,7 +30,7 @@ class Chef def initialize(node, resource) @node = node - @resource = resource + @resource = resource.to_sym end # return a deterministically sorted list of Chef::Resource subclasses @@ -37,8 +39,8 @@ class Chef end def resolve - maybe_dynamic_resource_resolution(resource) || - maybe_chef_platform_lookup(resource) + maybe_dynamic_resource_resolution || + maybe_chef_platform_lookup end # this cut looks at if the resource can handle the resource type on the node @@ -47,21 +49,29 @@ class Chef resources.select do |klass| klass.provides?(node, resource) end.sort {|a,b| a.to_s <=> b.to_s } - @enabled_handlers + 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 private # try dynamically finding a resource based on querying the resources to see what they support - def maybe_dynamic_resource_resolution(resource) - # log this so we know what resources will work for the generic resource on the node (early cut) + 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}" # if none of the resources specifically support the resource, we still need to pick one of the resources that are # enabled on the node to handle the why-run use case. handlers = enabled_handlers - if handlers.count >= 2 + 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 } @@ -70,14 +80,14 @@ class Chef # entry for this resource is missing -- we should probably raise here and force resolution of the ambiguity. Chef::Log.warn "Ambiguous resource precedence: #{handlers}, please use Chef.set_resource_priority_array to provide determinism" end - handlers = [ handlers.first ] + 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? + Chef::Log.debug "dynamic resource resolver FAILED to resolve a resource" if handlers.empty? return nil if handlers.empty? @@ -85,7 +95,7 @@ class Chef end # try the old static lookup of resources by mangling name to resource klass - def maybe_chef_platform_lookup(resource) + def maybe_chef_platform_lookup Chef::Resource.resource_matching_short_name(resource) end diff --git a/spec/data/lwrp/providers/buck_passer.rb b/spec/data/lwrp/providers/buck_passer.rb index 9792e2c824..2bbca07bf7 100644 --- a/spec/data/lwrp/providers/buck_passer.rb +++ b/spec/data/lwrp/providers/buck_passer.rb @@ -1,12 +1,28 @@ provides :buck_passer +def without_deprecation_warnings(&block) + old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + begin + block.call + ensure + Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors + end +end + action :pass_buck do lwrp_foo :prepared_thumbs do action :prepare_thumbs - provider :lwrp_thumb_twiddler + # We know there will be a deprecation error here; head it off + without_deprecation_warnings do + provider :lwrp_thumb_twiddler + end end lwrp_foo :twiddled_thumbs do action :twiddle_thumbs - provider :lwrp_thumb_twiddler + # We know there will be a deprecation error here; head it off + without_deprecation_warnings do + provider :lwrp_thumb_twiddler + end end end diff --git a/spec/data/lwrp/providers/buck_passer_2.rb b/spec/data/lwrp/providers/buck_passer_2.rb index d34da3c378..c3bab7266f 100644 --- a/spec/data/lwrp/providers/buck_passer_2.rb +++ b/spec/data/lwrp/providers/buck_passer_2.rb @@ -1,10 +1,26 @@ +def without_deprecation_warnings(&block) + old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + begin + block.call + ensure + Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors + end +end + action :pass_buck do lwrp_bar :prepared_eyes do action :prepare_eyes - provider :lwrp_paint_drying_watcher + # We know there will be a deprecation error here; head it off + without_deprecation_warnings do + provider :lwrp_paint_drying_watcher + end end lwrp_bar :dried_paint_watched do action :watch_paint_dry - provider :lwrp_paint_drying_watcher + # We know there will be a deprecation error here; head it off + without_deprecation_warnings do + provider :lwrp_paint_drying_watcher + end end end diff --git a/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb b/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb index f5841fb01c..77c1111ff5 100644 --- a/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb +++ b/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb @@ -3,11 +3,23 @@ # are passed properly (as demonstrated by the call to generate_new_name). attr_reader :enclosed_resource +def without_deprecation_warnings(&block) + old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + begin + block.call + ensure + Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors + end +end + action :twiddle_thumbs do @enclosed_resource = lwrp_foo :foo do monkey generate_new_name(new_resource.monkey){ 'the monkey' } - action :twiddle_thumbs - provider :lwrp_monkey_name_printer + # We know there will be a deprecation error here; head it off + without_deprecation_warnings do + provider :lwrp_monkey_name_printer + end end end diff --git a/spec/data/lwrp_override/resources/foo.rb b/spec/data/lwrp_override/resources/foo.rb index 14decb9634..2fc13d32fd 100644 --- a/spec/data/lwrp_override/resources/foo.rb +++ b/spec/data/lwrp_override/resources/foo.rb @@ -3,3 +3,8 @@ actions :never_execute attribute :ever, :kind_of => String + +class ::Chef + def method_created_by_override_lwrp_foo + end +end diff --git a/spec/integration/recipes/lwrp_inline_resources_spec.rb b/spec/integration/recipes/lwrp_inline_resources_spec.rb index b4c4e6ca11..e70605d3d3 100644 --- a/spec/integration/recipes/lwrp_inline_resources_spec.rb +++ b/spec/integration/recipes/lwrp_inline_resources_spec.rb @@ -5,7 +5,7 @@ describe "LWRPs with inline resources" do include IntegrationSupport include Chef::Mixin::ShellOut - let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") } + let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) } # Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the # following constraints are satisfied: diff --git a/spec/integration/recipes/provider_choice.rb b/spec/integration/recipes/provider_choice.rb new file mode 100644 index 0000000000..999765a1de --- /dev/null +++ b/spec/integration/recipes/provider_choice.rb @@ -0,0 +1,41 @@ +require 'support/shared/integration/integration_helper' + +describe "Recipe DSL methods" do + include IntegrationSupport + + context "With resource class providing 'provider_thingy'" do + before :context do + class Chef::Resource::ProviderThingy < Chef::Resource + def initialize(*args, &block) + super + @action = :create + @allowed_actions = [ :create ] + @resource_name = 'provider_thingy' + end + provides :provider_thingy + def to_s + "provider_thingy resource class" + end + end + end + context "And class Chef::Provider::ProviderThingy with no provides" do + before :context do + class Chef::Provider::ProviderThingy < Chef::Provider + def load_current_resource + end + def action_create + Chef::Log.warn("hello from #{self.class.name}") + end + end + end + + it "provider_thingy 'blah' runs the provider and warns" do + recipe = converge { + provider_thingy 'blah' do; end + } + expect(recipe.logged_warnings).to match /hello from Chef::Provider::ProviderThingy/ + expect(recipe.logged_warnings).to match /you must use 'provides' to provide DSL/i + end + end + end +end diff --git a/spec/integration/recipes/recipe_dsl_spec.rb b/spec/integration/recipes/recipe_dsl_spec.rb new file mode 100644 index 0000000000..66eeb62ae8 --- /dev/null +++ b/spec/integration/recipes/recipe_dsl_spec.rb @@ -0,0 +1,259 @@ +require 'support/shared/integration/integration_helper' + +describe "Recipe DSL methods" do + include IntegrationSupport + + context "With resource 'base_thingy' declared as BaseThingy" do + before(:context) { + + class BaseThingy < Chef::Resource + def initialize(*args, &block) + super + @resource_name = 'base_thingy' + @allowed_actions = [ :create ] + @action = :create + end + + class<<self + attr_accessor :created_resource + attr_accessor :created_provider + end + + def provider + Provider + end + class Provider < Chef::Provider + def load_current_resource + end + def action_create + BaseThingy.created_resource = new_resource.class + BaseThingy.created_provider = self.class + end + end + end + + # Modules to put stuff in + module Foo; end + module Foo::Bar; end + + } + + before :each do + BaseThingy.created_resource = nil + BaseThingy.created_provider = nil + end + + context "Deprecated automatic resource DSL" do + before do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + end + + context "With a resource 'backcompat_thingy' declared in Chef::Resource and Chef::Provider" do + before(:context) { + + class Chef::Resource::BackcompatThingy < Chef::Resource + def initialize(*args, &block) + super + @resource_name = 'backcompat_thingy' + @allowed_actions = [ :create ] + @action = :create + end + end + class Chef::Provider::BackcompatThingy < Chef::Provider + def load_current_resource + end + def action_create + BaseThingy.created_resource = new_resource.class + BaseThingy.created_provider = self.class + end + end + + } + + it "backcompat_thingy creates a Chef::Resource::BackcompatThingy" 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(BaseThingy.created_resource).to eq Chef::Resource::BackcompatThingy + expect(BaseThingy.created_provider).to eq Chef::Provider::BackcompatThingy + end + + context "And another resource 'backcompat_thingy' in BackcompatThingy with 'provides'" do + before(:context) { + + class Foo::BackcompatThingy < BaseThingy + provides :backcompat_thingy + end + + } + + it "backcompat_thingy creates a BackcompatThingy" do + recipe = converge { + backcompat_thingy 'blah' do; end + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).not_to be_nil + end + end + end + + context "With a resource named Foo::Bar::Thingy" do + before(:context) { + + class Foo::Bar::Thingy < BaseThingy; end + + } + + it "thingy does not work" do + expect_converge { + thingy 'blah' do; end + }.to raise_error(NoMethodError) + end + end + end + + context "provides" do + context "When MySupplier provides :hemlock" do + before(:context) { + + class Foo::MySupplier < BaseThingy + provides :hemlock + end + + } + + it "my_supplier does not work in a recipe" do + expect_converge { + my_supplier 'blah' do; end + }.to raise_error(NoMethodError) + end + + it "hemlock works in a recipe" do + expect_recipe { + hemlock 'blah' do; end + }.to emit_no_warnings_or_errors + expect(BaseThingy.created_resource).to eq Foo::MySupplier + end + end + + context "When Thingy3 provides :thingy3" do + before(:context) { + + class Foo::Thingy3 < BaseThingy + provides :thingy3 + end + + } + + it "thingy3 works in a recipe" do + expect_recipe { + thingy3 'blah' do; end + }.to emit_no_warnings_or_errors + expect(BaseThingy.created_resource).to eq Foo::Thingy3 + end + + context "And Thingy4 provides :thingy3" do + before(:context) { + + class Foo::Thingy4 < Chef::Resource + provides :thingy3 + end + + } + + it "thingy3 works in a recipe and yields " do + recipe = converge { + thingy3 'blah' do; end + } + expect(recipe.logged_warnings).to match /ambiguous resource precedence/i + expect(BaseThingy.created_resource).not_to be_nil + end + + it "thingy4 does not work in a recipe" do + expect_converge { + thingy4 'blah' do; end + }.to raise_error(NoMethodError) + end + end + end + + context "When Thingy5 provides :thingy5, :twizzle and :twizzle2" do + before(:context) { + + class Foo::Thingy5 < BaseThingy + provides :thingy5 + provides :twizzle + provides :twizzle2 + end + + } + + it "thingy5 works in a recipe and yields Thingy5" do + expect_recipe { + thingy5 'blah' do; end + }.to emit_no_warnings_or_errors + expect(BaseThingy.created_resource).to eq Foo::Thingy5 + end + + it "twizzle works in a recipe and yields Thingy5" do + expect_recipe { + twizzle 'blah' do; end + }.to emit_no_warnings_or_errors + expect(BaseThingy.created_resource).to eq Foo::Thingy5 + end + + it "twizzle2 works in a recipe and yields Thingy5" do + expect_recipe { + twizzle2 'blah' do; end + }.to emit_no_warnings_or_errors + expect(BaseThingy.created_resource).to eq Foo::Thingy5 + end + end + + context "With platform-specific resources 'my_super_thingy_foo' and 'my_super_thingy_bar'" do + before(:context) { + class MySuperThingyFoo < BaseThingy + provides :my_super_thingy, platform: 'foo' + end + + class MySuperThingyBar < BaseThingy + provides :my_super_thingy, platform: 'bar' + end + } + + it "A run with platform 'foo' uses MySuperThingyFoo" do + r = Cheffish::ChefRun.new(chef_config) + r.client.run_context.node.automatic['platform'] = 'foo' + r.compile_recipe { + my_super_thingy 'blah' do; end + } + r.converge + expect(r).to emit_no_warnings_or_errors + expect(BaseThingy.created_resource).to eq MySuperThingyFoo + end + + it "A run with platform 'bar' uses MySuperThingyBar" do + r = Cheffish::ChefRun.new(chef_config) + r.client.run_context.node.automatic['platform'] = 'bar' + r.compile_recipe { + my_super_thingy 'blah' do; end + } + r.converge + expect(r).to emit_no_warnings_or_errors + expect(BaseThingy.created_resource).to eq MySuperThingyBar + end + + it "A run with platform 'x' reports that my_super_thingy is not supported" do + r = Cheffish::ChefRun.new(chef_config) + r.client.run_context.node.automatic['platform'] = 'x' + expect { + r.compile_recipe { + my_super_thingy 'blah' do; end + } + }.to raise_error(Chef::Exceptions::NoSuchResourceType) + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fb284c721b..7cc9b8f7d6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -54,6 +54,9 @@ Dir['lib/chef/knife/**/*.rb']. map {|f| f.gsub(%r[\.rb$], '') }. each {|f| require f } +require 'chef/resource_resolver' +require 'chef/provider_resolver' + require 'chef/mixins' require 'chef/dsl' require 'chef/application' diff --git a/spec/support/lib/chef/resource/cat.rb b/spec/support/lib/chef/resource/cat.rb index ecca50cb53..641ce28795 100644 --- a/spec/support/lib/chef/resource/cat.rb +++ b/spec/support/lib/chef/resource/cat.rb @@ -19,6 +19,7 @@ class Chef class Resource class Cat < Chef::Resource + provides :cat attr_accessor :action diff --git a/spec/support/lib/chef/resource/one_two_three_four.rb b/spec/support/lib/chef/resource/one_two_three_four.rb index 296d2cd970..d7e5ea095e 100644 --- a/spec/support/lib/chef/resource/one_two_three_four.rb +++ b/spec/support/lib/chef/resource/one_two_three_four.rb @@ -19,6 +19,8 @@ class Chef class Resource class OneTwoThreeFour < Chef::Resource + provides :one_two_three_four + attr_reader :i_can_count def initialize(name, run_context) diff --git a/spec/support/lib/chef/resource/zen_follower.rb b/spec/support/lib/chef/resource/zen_follower.rb index ddc289e48d..590aa0827b 100644 --- a/spec/support/lib/chef/resource/zen_follower.rb +++ b/spec/support/lib/chef/resource/zen_follower.rb @@ -21,6 +21,7 @@ require 'chef/json_compat' class Chef class Resource class ZenFollower < Chef::Resource + provides :zen_follower provides :follower, platform: "zen" diff --git a/spec/support/lib/chef/resource/zen_master.rb b/spec/support/lib/chef/resource/zen_master.rb index d47d174e28..145dd70e64 100644 --- a/spec/support/lib/chef/resource/zen_master.rb +++ b/spec/support/lib/chef/resource/zen_master.rb @@ -22,6 +22,8 @@ require 'chef/json_compat' class Chef class Resource class ZenMaster < Chef::Resource + provides :zen_master + attr_reader :peace def initialize(name, run_context=nil) diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb index e6942c62af..927ff2f42b 100644 --- a/spec/support/shared/integration/integration_helper.rb +++ b/spec/support/shared/integration/integration_helper.rb @@ -22,14 +22,19 @@ require 'fileutils' require 'chef/config' require 'chef/json_compat' require 'chef/server_api' -require 'chef_zero/rspec' require 'support/shared/integration/knife_support' require 'support/shared/integration/app_server_support' +require 'cheffish/rspec/chef_run_support' require 'spec_helper' module IntegrationSupport include ChefZero::RSpec + def self.included(includer_class) + includer_class.extend(Cheffish::RSpec::ChefRunSupport) + includer_class.extend(ClassMethods) + end + module ClassMethods include ChefZero::RSpec @@ -49,10 +54,6 @@ module IntegrationSupport end end - def self.included(includer_class) - includer_class.extend(ClassMethods) - end - def api Chef::ServerAPI.new end diff --git a/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb index 4cf3ba827a..02e4eb2fae 100644 --- a/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb +++ b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb @@ -24,6 +24,7 @@ describe Chef::GuardInterpreter::ResourceGuardInterpreter do node.default["kernel"] = Hash.new node.default["kernel"][:machine] = :x86_64.to_s + node.automatic[:os] = 'windows' node end @@ -144,4 +145,3 @@ describe Chef::GuardInterpreter::ResourceGuardInterpreter do end end end - diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb index ec39174da6..ff7f8e7d7b 100644 --- a/spec/unit/lwrp_spec.rb +++ b/spec/unit/lwrp_spec.rb @@ -17,20 +17,40 @@ # require 'spec_helper' +require 'tmpdir' +require 'fileutils' +require 'chef/mixin/convert_to_class_name' module LwrpConstScopingConflict end describe "LWRP" do + include Chef::Mixin::ConvertToClassName + before do @original_VERBOSE = $VERBOSE $VERBOSE = nil + Chef::Resource::LWRPBase.class_eval { @loaded_lwrps = {} } end after do $VERBOSE = @original_VERBOSE end + def get_lwrp(name) + Chef::Resource.resource_for_node(name, Chef::Node.new) + end + + def get_lwrp_provider(name) + old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + begin + Chef::Provider.const_get(convert_to_class_name(name.to_s)) + ensure + Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors + end + end + describe "when overriding an existing class" do before :each do allow($stderr).to receive(:write) @@ -43,7 +63,6 @@ describe "LWRP" do expect(Chef::Log).not_to receive(:debug).with(/anymore/) Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) Object.send(:remove_const, 'LwrpFoo') - Chef::Resource.send(:remove_const, 'LwrpFoo') end it "should not skip loading a provider when there's a top level symbol of the same name" do @@ -53,7 +72,6 @@ describe "LWRP" do expect(Chef::Log).not_to receive(:debug).with(/anymore/) Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) Object.send(:remove_const, 'LwrpBuckPasser') - Chef::Provider.send(:remove_const, 'LwrpBuckPasser') end # @todo: we need a before block to manually remove_const all of the LWRPs that we @@ -67,7 +85,6 @@ describe "LWRP" do Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| expect(Chef::Log).to receive(:info).with(/Skipping/) - expect(Chef::Log).to receive(:debug).with(/anymore/) Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end end @@ -79,7 +96,6 @@ describe "LWRP" do Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file| expect(Chef::Log).to receive(:info).with(/Skipping/) - expect(Chef::Log).to receive(:debug).with(/anymore/) Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) end end @@ -90,7 +106,7 @@ describe "LWRP" do Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end - first_lwr_foo_class = Chef::Resource::LwrpFoo + first_lwr_foo_class = get_lwrp(:lwrp_foo) expect(Chef::Resource.resource_classes).to include(first_lwr_foo_class) Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) @@ -106,40 +122,70 @@ describe "LWRP" do end + context "When an LWRP resource lwrp_foo is loaded" do + before do + @tmpdir = Dir.mktmpdir("lwrp_test") + @lwrp_path = File.join(@tmpdir, "foo.rb") + content = IO.read(File.expand_path("../../data/lwrp/resources/foo.rb", __FILE__)) + IO.write(@lwrp_path, content) + Chef::Resource::LWRPBase.build_from_file("lwrp", @lwrp_path, nil) + @original_resource = Chef::Resource.resource_for_node(:lwrp_foo, Chef::Node.new) + end + + after do + FileUtils.remove_entry @tmpdir + end + + context "And the LWRP is asked to load again, this time with different code" do + before do + content = IO.read(File.expand_path("../../data/lwrp_override/resources/foo.rb", __FILE__)) + IO.write(@lwrp_path, content) + Chef::Resource::LWRPBase.build_from_file("lwrp", @lwrp_path, nil) + end + + it "Should load the old content, and not the new" do + resource = Chef::Resource.resource_for_node(:lwrp_foo, Chef::Node.new) + expect(resource).to eq @original_resource + expect(resource.default_action).to eq :pass_buck + expect(Chef.method_defined?(:method_created_by_override_lwrp_foo)).to be_falsey + end + end + end + describe "Lightweight Chef::Resource" do before do Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end + end - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "resources", "*"))].each do |file| - Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) - end + it "should load the resource into a properly-named class and emit a warning about deprecation when accessing it" do + expect { Chef::Resource::LwrpFoo }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) end - it "should load the resource into a properly-named class" do - expect(Chef::Resource.const_get("LwrpFoo")).to be_kind_of(Class) + it "should be resolvable with Chef::ResourceResolver.resolve(:lwrp_foo)" do + expect(Chef::ResourceResolver.resolve(:lwrp_foo, node: Chef::Node.new)).to eq(get_lwrp(:lwrp_foo)) end it "should set resource_name" do - expect(Chef::Resource::LwrpFoo.new("blah").resource_name).to eql(:lwrp_foo) + expect(get_lwrp(:lwrp_foo).new("blah").resource_name).to eql(:lwrp_foo) end it "should add the specified actions to the allowed_actions array" do - expect(Chef::Resource::LwrpFoo.new("blah").allowed_actions).to include(:pass_buck, :twiddle_thumbs) + expect(get_lwrp(:lwrp_foo).new("blah").allowed_actions).to include(:pass_buck, :twiddle_thumbs) end it "should set the specified action as the default action" do - expect(Chef::Resource::LwrpFoo.new("blah").action).to eq(:pass_buck) + expect(get_lwrp(:lwrp_foo).new("blah").action).to eq(:pass_buck) end it "should create a method for each attribute" do - expect(Chef::Resource::LwrpFoo.new("blah").methods.map{ |m| m.to_sym}).to include(:monkey) + expect(get_lwrp(:lwrp_foo).new("blah").methods.map{ |m| m.to_sym}).to include(:monkey) end it "should build attribute methods that respect validation rules" do - expect { Chef::Resource::LwrpFoo.new("blah").monkey(42) }.to raise_error(ArgumentError) + expect { get_lwrp(:lwrp_foo).new("blah").monkey(42) }.to raise_error(ArgumentError) end it "should have access to the run context and node during class definition" do @@ -151,7 +197,7 @@ describe "LWRP" do Chef::Resource::LWRPBase.build_from_file("lwrp", file, run_context) end - cls = Chef::Resource.const_get("LwrpNodeattr") + cls = get_lwrp(:lwrp_nodeattr) expect(cls.node).to be_kind_of(Chef::Node) expect(cls.run_context).to be_kind_of(Chef::RunContext) expect(cls.node[:penguin_name]).to eql("jackass") @@ -291,48 +337,48 @@ describe "LWRP" do end before(:each) do - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file| + Dir[File.expand_path(File.expand_path("../../data/lwrp/resources/*", __FILE__))].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, @run_context) end - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "resources", "*"))].each do |file| - Chef::Resource::LWRPBase.build_from_file("lwrp", file, @run_context) - end - - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "providers", "*"))].each do |file| - Chef::Provider::LWRPBase.build_from_file("lwrp", file, @run_context) - end - - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "providers", "*"))].each do |file| + Dir[File.expand_path(File.expand_path("../../data/lwrp/providers/*", __FILE__))].each do |file| Chef::Provider::LWRPBase.build_from_file("lwrp", file, @run_context) end - end it "should properly handle a new_resource reference" do - resource = Chef::Resource::LwrpFoo.new("morpheus") + resource = get_lwrp(:lwrp_foo).new("morpheus", @run_context) resource.monkey("bob") - resource.provider(:lwrp_monkey_name_printer) - resource.run_context = @run_context + resource.provider(get_lwrp_provider(:lwrp_monkey_name_printer)) provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs) provider.action_twiddle_thumbs end - it "should load the provider into a properly-named class" do - expect(Chef::Provider.const_get("LwrpBuckPasser")).to be_kind_of(Class) - end + context "resource class created" do + before do + @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + end + after do + Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors + end - it "should create a method for each attribute" do - new_resource = double("new resource").as_null_object - expect(Chef::Provider::LwrpBuckPasser.new(nil, new_resource).methods.map{|m|m.to_sym}).to include(:action_pass_buck) - expect(Chef::Provider::LwrpThumbTwiddler.new(nil, new_resource).methods.map{|m|m.to_sym}).to include(:action_twiddle_thumbs) + it "should load the provider into a properly-named class" do + expect(Chef::Provider.const_get("LwrpBuckPasser")).to be_kind_of(Class) + expect(Chef::Provider::LwrpBuckPasser <= Chef::Provider::LWRPBase).to be_truthy + end + + it "should create a method for each action" do + expect(get_lwrp_provider(:lwrp_buck_passer).instance_methods).to include(:action_pass_buck) + expect(get_lwrp_provider(:lwrp_thumb_twiddler).instance_methods).to include(:action_twiddle_thumbs) + end end it "should insert resources embedded in the provider into the middle of the resource collection" do - injector = Chef::Resource::LwrpFoo.new("morpheus", @run_context) + injector = get_lwrp(:lwrp_foo).new("morpheus", @run_context) injector.action(:pass_buck) - injector.provider(:lwrp_buck_passer) + injector.provider(get_lwrp_provider(:lwrp_buck_passer)) dummy = Chef::Resource::ZenMaster.new("keanu reeves", @run_context) dummy.provider(Chef::Provider::Easy) @run_context.resource_collection.insert(injector) @@ -347,13 +393,13 @@ describe "LWRP" do end it "should insert embedded resources from multiple providers, including from the last position, properly into the resource collection" do - injector = Chef::Resource::LwrpFoo.new("morpheus", @run_context) + injector = get_lwrp(:lwrp_foo).new("morpheus", @run_context) injector.action(:pass_buck) - injector.provider(:lwrp_buck_passer) + injector.provider(get_lwrp_provider(:lwrp_buck_passer)) - injector2 = Chef::Resource::LwrpBar.new("tank", @run_context) + injector2 = get_lwrp(:lwrp_bar).new("tank", @run_context) injector2.action(:pass_buck) - injector2.provider(:lwrp_buck_passer_2) + injector2.provider(get_lwrp_provider(:lwrp_buck_passer_2)) dummy = Chef::Resource::ZenMaster.new("keanu reeves", @run_context) dummy.provider(Chef::Provider::Easy) @@ -374,9 +420,9 @@ describe "LWRP" do end it "should properly handle a new_resource reference" do - resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context) + resource = get_lwrp(:lwrp_foo).new("morpheus", @run_context) resource.monkey("bob") - resource.provider(:lwrp_monkey_name_printer) + resource.provider(get_lwrp_provider(:lwrp_monkey_name_printer)) provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs) provider.action_twiddle_thumbs @@ -385,9 +431,9 @@ describe "LWRP" do end it "should properly handle an embedded Resource accessing the enclosing Provider's scope" do - resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context) + resource = get_lwrp(:lwrp_foo).new("morpheus", @run_context) resource.monkey("bob") - resource.provider(:lwrp_embedded_resource_accesses_providers_scope) + resource.provider(get_lwrp_provider(:lwrp_embedded_resource_accesses_providers_scope)) provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs) #provider = @runner.build_provider(resource) @@ -404,10 +450,10 @@ describe "LWRP" do # Side effect of lwrp_inline_compiler provider for testing notifications. $interior_ruby_block_2 = nil # resource type doesn't matter, so make an existing resource type work with provider. - @resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context) + @resource = get_lwrp(:lwrp_foo).new("morpheus", @run_context) @resource.allowed_actions << :test @resource.action(:test) - @resource.provider(:lwrp_inline_compiler) + @resource.provider(get_lwrp_provider(:lwrp_inline_compiler)) end it "does not add interior resources to the exterior resource collection" do diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb index 7442f4477e..9c4fb49497 100644 --- a/spec/unit/recipe_spec.rb +++ b/spec/unit/recipe_spec.rb @@ -83,7 +83,7 @@ describe Chef::Recipe do it "should require a name argument" do expect { recipe.cat - }.to raise_error(ArgumentError, "You must supply a name when declaring a cat resource") + }.to raise_error(ArgumentError) end it "should allow regular errors (not NameErrors) to pass unchanged" do diff --git a/spec/unit/resource/batch_spec.rb b/spec/unit/resource/batch_spec.rb index 4a056b8735..b8c2897f42 100644 --- a/spec/unit/resource/batch_spec.rb +++ b/spec/unit/resource/batch_spec.rb @@ -25,6 +25,7 @@ describe Chef::Resource::Batch do node.default["kernel"] = Hash.new node.default["kernel"][:machine] = :x86_64.to_s + node.automatic[:os] = 'windows' run_context = Chef::RunContext.new(node, nil, nil) diff --git a/spec/unit/resource/powershell_spec.rb b/spec/unit/resource/powershell_spec.rb index c263172ae6..2505c4a3d7 100644 --- a/spec/unit/resource/powershell_spec.rb +++ b/spec/unit/resource/powershell_spec.rb @@ -25,6 +25,7 @@ describe Chef::Resource::PowershellScript do node.default["kernel"] = Hash.new node.default["kernel"][:machine] = :x86_64.to_s + node.automatic[:os] = 'windows' run_context = Chef::RunContext.new(node, nil, nil) diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 6b2d6c89d3..0479778f55 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -731,35 +731,51 @@ describe Chef::Resource do end - describe "lookups from the platform map" do - let(:klz1) { Class.new(Chef::Resource) } - let(:klz2) { Class.new(Chef::Resource) } + describe "resource_for_node" do + describe "lookups from the platform map" do + let(:klz1) { Class.new(Chef::Resource) } + + before(:each) do + Chef::Resource::Klz1 = klz1 + @node = Chef::Node.new + @node.name("bumblebee") + @node.automatic[:platform] = "autobots" + @node.automatic[:platform_version] = "6.1" + Object.const_set('Soundwave', klz1) + klz1.provides :soundwave + end - before(:each) do - Chef::Resource::Klz1 = klz1 - Chef::Resource::Klz2 = klz2 - @node = Chef::Node.new - @node.name("bumblebee") - @node.automatic[:platform] = "autobots" - @node.automatic[:platform_version] = "6.1" - Object.const_set('Soundwave', klz1) - klz2.provides :dinobot, :on_platforms => ['autobots'] - Object.const_set('Grimlock', klz2) - end + after(:each) do + Object.send(:remove_const, :Soundwave) + Chef::Resource.send(:remove_const, :Klz1) + end - after(:each) do - Object.send(:remove_const, :Soundwave) - Object.send(:remove_const, :Grimlock) - Chef::Resource.send(:remove_const, :Klz1) - Chef::Resource.send(:remove_const, :Klz2) + it "returns a resource by short_name if nothing else matches" do + expect(Chef::Resource.resource_for_node(:soundwave, @node)).to eql(klz1) + end end - describe "resource_for_node" do - it "returns a resource by short_name and node" do - expect(Chef::Resource.resource_for_node(:dinobot, @node)).to eql(Grimlock) + describe "lookups from the platform map" do + let(:klz2) { Class.new(Chef::Resource) } + + before(:each) do + Chef::Resource::Klz2 = klz2 + @node = Chef::Node.new + @node.name("bumblebee") + @node.automatic[:platform] = "autobots" + @node.automatic[:platform_version] = "6.1" + klz2.provides :dinobot, :on_platforms => ['autobots'] + Object.const_set('Grimlock', klz2) + klz2.provides :grimlock end - it "returns a resource by short_name if nothing else matches" do - expect(Chef::Resource.resource_for_node(:soundwave, @node)).to eql(Soundwave) + + after(:each) do + Object.send(:remove_const, :Grimlock) + Chef::Resource.send(:remove_const, :Klz2) + end + + it "returns a resource by short_name and node" do + expect(Chef::Resource.resource_for_node(:dinobot, @node)).to eql(klz2) end end diff --git a/tasks/external_tests.rb b/tasks/external_tests.rb new file mode 100644 index 0000000000..2ff991ddf7 --- /dev/null +++ b/tasks/external_tests.rb @@ -0,0 +1,29 @@ +task :chef_sugar_spec do + gem_path = Bundler.environment.specs['chef-sugar'].first.full_gem_path + system("cd #{gem_path} && rake") +end + +task :foodcritic_spec do + gem_path = Bundler.environment.specs['foodcritic'].first.full_gem_path + system("cd #{gem_path} && rake test") +end + +task :chefspec_spec do + gem_path = Bundler.environment.specs['chefspec'].first.full_gem_path + system("cd #{gem_path} && rake") +end + +task :chef_rewind_spec do + gem_path = Bundler.environment.specs['chef-rewind'].first.full_gem_path + system("cd #{gem_path} && rake spec") +end + +task :poise_spec do + gem_path = Bundler.environment.specs['poise'].first.full_gem_path + system("cd #{gem_path} && rake spec") +end + +task :halite_spec do + gem_path = Bundler.environment.specs['halite'].first.full_gem_path + system("cd #{gem_path} && rake spec") +end |