summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--DOC_CHANGES.md76
-rw-r--r--MAINTAINERS.md12
-rw-r--r--MAINTAINERS.toml12
-rw-r--r--RELEASE_NOTES.md88
-rw-r--r--VERSION2
-rw-r--r--chef-config/lib/chef-config/version.rb2
-rw-r--r--kitchen-tests/Gemfile1
-rw-r--r--lib/chef/chef_class.rb8
-rw-r--r--lib/chef/client.rb18
-rw-r--r--lib/chef/dsl/recipe.rb22
-rw-r--r--lib/chef/event_dispatch/base.rb56
-rw-r--r--lib/chef/event_dispatch/dsl.rb13
-rw-r--r--lib/chef/mixin/params_validate.rb479
-rw-r--r--lib/chef/node_map.rb101
-rw-r--r--lib/chef/platform/priority_map.rb54
-rw-r--r--lib/chef/platform/provider_mapping.rb4
-rw-r--r--lib/chef/platform/provider_priority_map.rb24
-rw-r--r--lib/chef/platform/resource_priority_map.rb27
-rw-r--r--lib/chef/provider.rb143
-rw-r--r--lib/chef/provider/deploy.rb8
-rw-r--r--lib/chef/provider/lwrp_base.rb76
-rw-r--r--lib/chef/provider/service/debian.rb2
-rw-r--r--lib/chef/provider/service/insserv.rb2
-rw-r--r--lib/chef/provider/service/invokercd.rb2
-rw-r--r--lib/chef/provider/service/redhat.rb2
-rw-r--r--lib/chef/provider/service/upstart.rb2
-rw-r--r--lib/chef/provider/user.rb2
-rw-r--r--lib/chef/recipe.rb9
-rw-r--r--lib/chef/resource.rb281
-rw-r--r--lib/chef/resource/lwrp_base.rb11
-rw-r--r--lib/chef/resource/macports_package.rb3
-rw-r--r--lib/chef/resource/package.rb5
-rw-r--r--lib/chef/resource_resolver.rb1
-rw-r--r--lib/chef/run_context.rb480
-rw-r--r--lib/chef/version.rb2
-rw-r--r--spec/data/run_context/cookbooks/include/recipes/default.rb24
-rw-r--r--spec/data/run_context/cookbooks/include/recipes/includee.rb3
-rw-r--r--spec/integration/recipes/lwrp_spec.rb8
-rw-r--r--spec/integration/recipes/recipe_dsl_spec.rb293
-rw-r--r--spec/integration/recipes/resource_action_spec.rb343
-rw-r--r--spec/support/shared/shared_examples.rb2
-rw-r--r--spec/unit/api_client_spec.rb2
-rw-r--r--spec/unit/client_spec.rb54
-rw-r--r--spec/unit/cookbook_version_spec.rb2
-rw-r--r--spec/unit/data_bag_item_spec.rb2
-rw-r--r--spec/unit/data_bag_spec.rb2
-rw-r--r--spec/unit/environment_spec.rb2
-rw-r--r--spec/unit/event_dispatch/dsl_spec.rb37
-rw-r--r--spec/unit/exceptions_spec.rb2
-rw-r--r--spec/unit/json_compat_spec.rb2
-rw-r--r--spec/unit/lwrp_spec.rb47
-rw-r--r--spec/unit/node_spec.rb2
-rw-r--r--spec/unit/osc_user_spec.rb2
-rw-r--r--spec/unit/property/state_spec.rb491
-rw-r--r--spec/unit/property/validation_spec.rb652
-rw-r--r--spec/unit/property_spec.rb802
-rw-r--r--spec/unit/provider_resolver_spec.rb1
-rw-r--r--spec/unit/provider_spec.rb4
-rw-r--r--spec/unit/recipe_spec.rb20
-rw-r--r--spec/unit/resource_collection_spec.rb2
-rw-r--r--spec/unit/resource_resolver_spec.rb49
-rw-r--r--spec/unit/resource_spec.rb23
-rw-r--r--spec/unit/role_spec.rb2
-rw-r--r--spec/unit/run_context/child_run_context_spec.rb133
-rw-r--r--spec/unit/run_context_spec.rb7
-rw-r--r--spec/unit/run_list_spec.rb2
-rw-r--r--spec/unit/runner_spec.rb4
-rw-r--r--spec/unit/user_spec.rb2
69 files changed, 4290 insertions, 765 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a3605e3084..808688a28f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,5 @@
+## 12.5.0
+
## 12.4.0
* [**Phil Dibowitz**](https://github.com/jaymzh):
diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md
index 46712f114c..b1121e2bf4 100644
--- a/DOC_CHANGES.md
+++ b/DOC_CHANGES.md
@@ -6,79 +6,3 @@ Example Doc Change:
Description of the required change.
-->
-### Resources now *all* get automatic DSL
-
-When you declare a resource (no matter where) you now get automatic DSL for it, based on your class name:
-
-```ruby
-module MyModule
- class MyResource < Chef::Resource
- # Names the resource "my_resource"
- end
-end
-```
-
-When this happens, the resource can be used in a recipe:
-
-```ruby
-my_resource 'blah' do
-end
-```
-
-If you have an abstract class that should *not* have DSL, set `resource_name` to `nil`:
-
-```ruby
-module MyModule
- # This will not have DSL
- class MyBaseResource < Chef::Resource
- resource_name nil
- end
- # This will have DSL `my_resource`
- class MyResource < MyBaseResource
- end
-end
-```
-
-When you do this, `my_base_resource` will not work in a recipe (but `my_resource` will).
-
-You can still use `provides` to provide other DSL names:
-
-```ruby
-module MyModule
- class MyResource < Chef::Resource
- provides :super_resource
- end
-end
-```
-
-Which enables this recipe:
-
-```ruby
-super_resource 'wowzers' do
-end
-```
-
-(Note that when you use provides in this manner, resource_name will be `my_resource` and declared_type will be `super_resource`. This won't affect most people, but it is worth noting as a matter of explanation.)
-
-Users are encouraged to declare resources in their own namespaces instead of putting them in the `Chef::Resource` namespace.
-
-### Resources may now use `allowed_actions` and `default_action`
-
-Instead of overriding `Chef::Resource.initialize` and setting `@allowed_actions` and `@action` in the constructor, you may now use the `allowed_actions` and `default_action` DSL to declare them:
-
-```ruby
-class MyResource < Chef::Resource
- allowed_actions :create, :delete
- default_action :create
-end
-```
-
-### 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.
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
index e3bf07573a..7604747352 100644
--- a/MAINTAINERS.md
+++ b/MAINTAINERS.md
@@ -35,6 +35,7 @@ another component.
* [Jay Mundrawala](https://github.com/jdmundrawala)
* [Jon Cowie](https://github.com/jonlives)
* [Lamont Granquist](https://github.com/lamont-granquist)
+* [Claire McQuin](https://github.com/mcquin)
* [Steven Murawski](https://github.com/smurawski)
* [Tyler Ball](https://github.com/tyler-ball)
* [Ranjib Dey](https://github.com/ranjib)
@@ -79,10 +80,13 @@ The specific components of Chef related to a given platform - including (but not
## Ubuntu
+### Lieutenant
+
+* [Ranjib Dey](https://github.com/ranjib)
+
### Maintainers
* [Lamont Granquist](https://github.com/lamont-granquist)
-* [Ranjib Dey](https://github.com/ranjib)
* [Thom May](https://github.com/thommay)
## Windows
@@ -130,3 +134,9 @@ The specific components of Chef related to a given platform - including (but not
* [Cory Stephenson](https://github.com/Aevin1387)
* [David Aronsohn](https://github.com/tbunnyman)
+## OpenBSD
+
+### Lieutenant
+
+* [Joe Miller](https://github.com/joemiller)
+
diff --git a/MAINTAINERS.toml b/MAINTAINERS.toml
index f949fd4542..30f7d2711e 100644
--- a/MAINTAINERS.toml
+++ b/MAINTAINERS.toml
@@ -40,6 +40,7 @@ another component.
"jdmundrawala",
"jonlives",
"lamont-granquist",
+ "mcquin",
"smurawski",
"tyler-ball",
"ranjib"
@@ -95,9 +96,10 @@ The specific components of Chef related to a given platform - including (but not
[Org.Components.Subsystems.Ubuntu]
title = "Ubuntu"
+ lieutenant = "ranjib"
+
maintainers = [
"lamont-granquist",
- "ranjib",
"thommay"
]
@@ -144,6 +146,10 @@ The specific components of Chef related to a given platform - including (but not
"tBunnyMan"
]
+ [Org.Components.Subsystems.OpenBSD]
+ title = "OpenBSD"
+
+ lieutenant = "joemiller"
[people]
[people.adamhjk]
@@ -229,3 +235,7 @@ The specific components of Chef related to a given platform - including (but not
[people.ksubrama]
Name = "Kartik Cating-Subramanian"
GitHub = "ksubrama"
+
+ [people.joemiller]
+ Name = "Joe Miller"
+ GitHub = "joemiller"
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 049640d7ab..93a759ba88 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,87 +1 @@
-# Chef Client Release Notes 12.4.0:
-
-## Knife Key Management Commands for Users and Clients
-
-`knife user` and `knife client` now have a suite of subcommands that live under
-the `key` subcommand. These subcommands allow you to list, show, create, delete
-and edit keys for a given user or client. They can be used to implement
-key rotation with multiple expiring keys for a single actor or just
-for basic key management. See `knife user key` and `knife client key`
-for a full list of subcommands and their usage.
-
-## System Loggers
-
-You can now have all Chef logs sent to a logging system of your choice.
-
-### Syslog Logger
-
-Syslog can be used by adding the following line to your chef config
-file:
-
-```ruby
-log_location Chef::Log::Syslog.new("chef-client", ::Syslog::LOG_DAEMON)
-```
-
-THis will write to the `daemon` facility with the originator set as
-`chef-client`.
-
-### Windows Event Logger
-
-The logger can be used by adding the following line to your chef config file:
-
-```ruby
-log_location Chef::Log::WinEvt.new
-```
-
-This will write to the Application log with the source set as Chef.
-
-## RemoteFile resource supports UNC paths on Windows
-
-You can now use UNC paths with `remote_file` on Windows machines. For
-example, you can get `Foo.tar.gz` off of `fooshare` on `foohost` using
-the following resource:
-
-```ruby
-remote_file 'C:\Foo.tar.gz' do
- source "\\\\foohost\\fooshare\\Foo.tar.gz"
-end
-```
-
-## WindowsPackage resource supports URLs
-
-The `windows_package` resource now allows specifying URLs for the source
-attribute. For example, you could install 7zip with the following resource:
-
-```ruby
-windows_package '7zip' do
- source "http://www.7-zip.org/a/7z938-x64.msi"
-end
-```
-
-Internally, this is done by using a `remote_file` resource to download the
-contents at the specified url. If needed, you can modify the attributes of
-the `remote_file` resource using the `remote_file_attributes` attribute.
-The `remote_file_attributes` accepts a hash of attributes that will be set
-on the underlying remote_file. For example, the checksum of the contents can
-be verified using
-
-```ruby
-windows_package '7zip' do
- source "http://www.7-zip.org/a/7z938-x64.msi"
- remote_file_attributes {
- :path => "C:\\7zip.msi",
- :checksum => '7c8e873991c82ad9cfcdbdf45254ea6101e9a645e12977dcd518979e50fdedf3'
- }
-end
-```
-
-To make the transition easier from the Windows cookbook, `windows_package` also
-accepts the `checksum` attribute, and the previous resource could be rewritten
-as:
-
-```ruby
-windows_package '7zip' do
- source "http://www.7-zip.org/a/7z938-x64.msi"
- checksum '7c8e873991c82ad9cfcdbdf45254ea6101e9a645e12977dcd518979e50fdedf3'
-end
-```
+# Chef Client Release Notes 12.5.0:
diff --git a/VERSION b/VERSION
index b53f8861dc..9a03cd310d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-12.4.0.rc.2
+12.5.0.current.0
diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb
index a6bf636540..aee626f240 100644
--- a/chef-config/lib/chef-config/version.rb
+++ b/chef-config/lib/chef-config/version.rb
@@ -20,6 +20,6 @@
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
module ChefConfig
- VERSION = '12.4.0.rc.2'
+ VERSION = '12.5.0.current.0'
end
diff --git a/kitchen-tests/Gemfile b/kitchen-tests/Gemfile
index 988d876417..5e1907ba1f 100644
--- a/kitchen-tests/Gemfile
+++ b/kitchen-tests/Gemfile
@@ -6,4 +6,5 @@ group :end_to_end do
gem 'kitchen-appbundle-updater', '~> 0.0.1'
gem "kitchen-vagrant", '~> 0.17.0'
gem 'kitchen-ec2', github: 'test-kitchen/kitchen-ec2'
+ gem 'vagrant-wrapper'
end
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb
index d9c141b85d..c017fb157c 100644
--- a/lib/chef/chef_class.rb
+++ b/lib/chef/chef_class.rb
@@ -65,7 +65,7 @@ class Chef
# @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node
#
def get_provider_priority_array(resource_name)
- result = provider_priority_map.get_priority_array(node, resource_name)
+ result = provider_priority_map.get_priority_array(node, resource_name.to_sym)
result = result.dup if result
result
end
@@ -78,7 +78,7 @@ class Chef
# @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node
#
def get_resource_priority_array(resource_name)
- result = resource_priority_map.get_priority_array(node, resource_name)
+ result = resource_priority_map.get_priority_array(node, resource_name.to_sym)
result = result.dup if result
result
end
@@ -93,7 +93,7 @@ class Chef
# @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)
- result = provider_priority_map.set_priority_array(resource_name, priority_array, *filter, &block)
+ result = provider_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
result = result.dup if result
result
end
@@ -108,7 +108,7 @@ class Chef
# @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)
- result = resource_priority_map.set_priority_array(resource_name, priority_array, *filter, &block)
+ result = resource_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
result = result.dup if result
result
end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 86e92585e3..3c86f52b4a 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -309,12 +309,18 @@ class Chef
# with the proper exit status code and everything gets raised
# as a RunFailedWrappingError
if run_error || converge_error || audit_error
- error = if run_error == converge_error
- Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
- else
- Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
- end
- error.fill_backtrace
+ error = if Chef::Config[:audit_mode] == :disabled
+ run_error || converge_error
+ else
+ e = if run_error == converge_error
+ Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
+ else
+ Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
+ end
+ e.fill_backtrace
+ e
+ end
+
Chef::Application.debug_stacktrace(error)
raise error
end
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index d69f0a8f11..d00d0df247 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -122,9 +122,9 @@ class Chef
def describe_self_for_error
if respond_to?(:name)
- %Q[`#{self.class.name} "#{name}"']
+ %Q[`#{self.class} "#{name}"']
elsif respond_to?(:recipe_name)
- %Q[`#{self.class.name} "#{recipe_name}"']
+ %Q[`#{self.class} "#{recipe_name}"']
else
to_s
end
@@ -176,6 +176,24 @@ class Chef
raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
end
end
+
+ module FullDSL
+ require 'chef/dsl/data_query'
+ require 'chef/dsl/platform_introspection'
+ require 'chef/dsl/include_recipe'
+ require 'chef/dsl/registry_helper'
+ require 'chef/dsl/reboot_pending'
+ require 'chef/dsl/audit'
+ require 'chef/dsl/powershell'
+ include Chef::DSL::DataQuery
+ include Chef::DSL::PlatformIntrospection
+ include Chef::DSL::IncludeRecipe
+ include Chef::DSL::Recipe
+ include Chef::DSL::RegistryHelper
+ include Chef::DSL::RebootPending
+ include Chef::DSL::Audit
+ include Chef::DSL::Powershell
+ end
end
end
end
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index 73fe25ec13..50aee63450 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -269,26 +269,37 @@ class Chef
# def notifications_resolved
# end
+ #
+ # Resource events and ordering:
+ #
+ # 1. Start the action
+ # - resource_action_start
+ # 2. Check the guard
+ # - resource_skipped: (goto 7) if only_if/not_if say to skip
+ # 3. Load the current resource
+ # - resource_current_state_loaded
+ # - resource_current_state_load_bypassed (if not why-run safe)
+ # 4. Check if why-run safe
+ # - resource_bypassed: (goto 7) if not why-run safe
+ # 5. During processing:
+ # - resource_update_applied: For each actual change (many per action)
+ # 6. Processing complete status:
+ # - resource_failed if the resource threw an exception while running
+ # - resource_failed_retriable: (goto 3) if resource failed and will be retried
+ # - resource_updated if the resource was updated (resource_update_applied will have been called)
+ # - resource_up_to_date if the resource was up to date (no resource_update_applied)
+ # 7. Processing complete:
+ # - resource_completed
+ #
+
# Called before action is executed on a resource.
def resource_action_start(resource, action, notification_type=nil, notifier=nil)
end
- # Called when a resource fails, but will retry.
- def resource_failed_retriable(resource, action, retry_count, exception)
- end
-
- # Called when a resource fails and will not be retried.
- def resource_failed(resource, action, exception)
- end
-
# Called when a resource action has been skipped b/c of a conditional
def resource_skipped(resource, action, conditional)
end
- # Called when a resource action has been completed
- def resource_completed(resource)
- end
-
# Called after #load_current_resource has run.
def resource_current_state_loaded(resource, action, current_resource)
end
@@ -302,21 +313,34 @@ class Chef
def resource_bypassed(resource, action, current_resource)
end
- # Called when a resource has no converge actions, e.g., it was already correct.
- def resource_up_to_date(resource, action)
- end
-
# Called when a change has been made to a resource. May be called multiple
# times per resource, e.g., a file may have its content updated, and then
# its permissions updated.
def resource_update_applied(resource, action, update)
end
+ # Called when a resource fails, but will retry.
+ def resource_failed_retriable(resource, action, retry_count, exception)
+ end
+
+ # Called when a resource fails and will not be retried.
+ def resource_failed(resource, action, exception)
+ end
+
# Called after a resource has been completely converged, but only if
# modifications were made.
def resource_updated(resource, action)
end
+ # Called when a resource has no converge actions, e.g., it was already correct.
+ def resource_up_to_date(resource, action)
+ end
+
+ # Called when a resource action has been completed
+ def resource_completed(resource)
+ end
+
+
# A stream has opened.
def stream_opened(stream, options = {})
end
diff --git a/lib/chef/event_dispatch/dsl.rb b/lib/chef/event_dispatch/dsl.rb
index 98854a4716..c6f21c9b45 100644
--- a/lib/chef/event_dispatch/dsl.rb
+++ b/lib/chef/event_dispatch/dsl.rb
@@ -26,15 +26,14 @@ class Chef
def initialize(name)
klass = Class.new(Chef::EventDispatch::Base) do
- def self.name
- @@name
- end
+ attr_reader :name
end
- klass.class_variable_set(:@@name, name)
@handler = klass.new
- # Use current event.register API to add anonymous handler if
- # run_context and associated event dispatcher is set, else fallback to
- # Chef::Config[:hanlder].
+ @handler.instance_variable_set(:@name, name)
+
+ # Use event.register API to add anonymous handler if Chef.run_context
+ # and associated event dispatcher is set, else fallback to
+ # Chef::Config[:hanlder]
if Chef.run_context && Chef.run_context.events
Chef::Log.debug("Registering handler '#{name}' using events api")
Chef.run_context.events.register(handler)
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index 78d72dc801..f7d52a19cf 100644
--- a/lib/chef/mixin/params_validate.rb
+++ b/lib/chef/mixin/params_validate.rb
@@ -16,6 +16,8 @@
# limitations under the License.
class Chef
+ NOT_PASSED = Object.new
+
class DelayedEvaluator < Proc
end
module Mixin
@@ -65,7 +67,7 @@ class Chef
true
when Hash
validation.each do |check, carg|
- check_method = "_pv_#{check.to_s}"
+ check_method = "_pv_#{check}"
if self.respond_to?(check_method, true)
self.send(check_method, opts, key, carg)
else
@@ -81,162 +83,407 @@ class Chef
DelayedEvaluator.new(&block)
end
- def set_or_return(symbol, arg, validation)
- iv_symbol = "@#{symbol.to_s}".to_sym
- if arg == nil && self.instance_variable_defined?(iv_symbol) == true
- ivar = self.instance_variable_get(iv_symbol)
- if(ivar.is_a?(DelayedEvaluator))
- validate({ symbol => ivar.call }, { symbol => validation })[symbol]
- else
- ivar
- end
+ def set_or_return(symbol, value, validation)
+ symbol = symbol.to_sym
+ iv_symbol = :"@#{symbol}"
+
+ # Steal default, coerce, name_property and required from validation
+ # so that we can handle the order in which they are applied
+ validation = validation.dup
+ if validation.has_key?(:default)
+ default = validation.delete(:default)
+ elsif validation.has_key?('default')
+ default = validation.delete('default')
else
- if(arg.is_a?(DelayedEvaluator))
- val = arg
+ default = NOT_PASSED
+ end
+ coerce = validation.delete(:coerce)
+ coerce ||= validation.delete('coerce')
+ name_property = validation.delete(:name_property)
+ name_property ||= validation.delete('name_property')
+ name_property ||= validation.delete(:name_attribute)
+ name_property ||= validation.delete('name_attribute')
+ required = validation.delete(:required)
+ required ||= validation.delete('required')
+
+ opts = {}
+ # If the user passed NOT_PASSED, or passed nil, then this is a get.
+ if value == NOT_PASSED || (value.nil? && !explicitly_allows_nil?(symbol, validation))
+
+ # Get the value if there is one
+ if self.instance_variable_defined?(iv_symbol)
+ opts[symbol] = self.instance_variable_get(iv_symbol)
+
+ # Handle lazy values
+ if opts[symbol].is_a?(DelayedEvaluator)
+ if opts[symbol].arity >= 1
+ opts[symbol] = opts[symbol].call(self)
+ else
+ opts[symbol] = opts[symbol].call
+ end
+
+ # Coerce and validate the default value
+ _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
+ _pv_coerce(opts, symbol, coerce) if coerce
+ validate(opts, { symbol => validation })
+ end
+
+ # Get the default value
else
- val = validate({ symbol => arg }, { symbol => validation })[symbol]
+ _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
+ _pv_default(opts, symbol, default) unless default == NOT_PASSED
+ _pv_name_property(opts, symbol, name_property)
+
+ if opts.has_key?(symbol)
+ # Handle lazy defaults.
+ if opts[symbol].is_a?(DelayedEvaluator)
+ if opts[symbol].arity >= 1
+ opts[symbol] = opts[symbol].call(self)
+ else
+ opts[symbol] = instance_eval(&opts[symbol])
+ end
+ end
- # Handle the case where the "default" was a DelayedEvaluator. In
- # this case, the block yields an optional parameter of +self+,
- # which is the equivalent of "new_resource"
- if val.is_a?(DelayedEvaluator)
- val = val.call(self)
+ # Coerce and validate the default value
+ _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
+ _pv_coerce(opts, symbol, coerce) if coerce
+ # We presently do not validate defaults, for backwards compatibility.
+# validate(opts, { symbol => validation })
+
+ # Defaults are presently "stickily" set on the instance
+ self.instance_variable_set(iv_symbol, opts[symbol])
end
end
- self.instance_variable_set(iv_symbol, val)
+
+ # Set the value
+ else
+ opts[symbol] = value
+ unless opts[symbol].is_a?(DelayedEvaluator)
+ # Coerce and validate the value
+ _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
+ _pv_coerce(opts, symbol, coerce) if coerce
+ validate(opts, { symbol => validation })
+ end
+
+ self.instance_variable_set(iv_symbol, opts[symbol])
end
+
+ opts[symbol]
end
private
- # Return the value of a parameter, or nil if it doesn't exist.
- def _pv_opts_lookup(opts, key)
- if opts.has_key?(key.to_s)
- opts[key.to_s]
- elsif opts.has_key?(key.to_sym)
- opts[key.to_sym]
- else
- nil
- end
+ def explicitly_allows_nil?(key, validation)
+ validation.has_key?(:is) && _pv_is({ key => nil }, key, validation[:is], raise_error: false)
+ end
+
+ # Return the value of a parameter, or nil if it doesn't exist.
+ def _pv_opts_lookup(opts, key)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s]
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym]
+ else
+ nil
+ end
+ end
+
+ # Raise an exception if the parameter is not found.
+ def _pv_required(opts, key, is_required=true, explicitly_allows_nil=false)
+ if is_required
+ return true if opts.has_key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?)
+ return true if opts.has_key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?)
+ raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
end
+ end
- # Raise an exception if the parameter is not found.
- def _pv_required(opts, key, is_required=true)
- if is_required
- if (opts.has_key?(key.to_s) && !opts[key.to_s].nil?) ||
- (opts.has_key?(key.to_sym) && !opts[key.to_sym].nil?)
- true
- else
- raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
- end
+ #
+ # List of things values must be equal to.
+ #
+ # Uses Ruby's `==` to evaluate (equal_to == value). At least one must
+ # match for the value to be valid.
+ #
+ # `nil` passes this validation automatically.
+ #
+ # @return [Array,nil] List of things values must be equal to, or nil if
+ # equal_to is unspecified.
+ #
+ def _pv_equal_to(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ to_be = Array(to_be)
+ to_be.each do |tb|
+ return true if value == tb
end
+ raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
end
+ end
- def _pv_equal_to(opts, key, to_be)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- passes = false
- Array(to_be).each do |tb|
- passes = true if value == tb
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
- end
+ #
+ # List of things values must be instances of.
+ #
+ # Uses value.kind_of?(kind_of) to evaluate. At least one must match for
+ # the value to be valid.
+ #
+ # `nil` automatically passes this validation.
+ #
+ def _pv_kind_of(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ to_be = Array(to_be)
+ to_be.each do |tb|
+ return true if value.kind_of?(tb)
end
+ raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
end
+ end
- # Raise an exception if the parameter is not a kind_of?(to_be)
- def _pv_kind_of(opts, key, to_be)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- passes = false
- Array(to_be).each do |tb|
- passes = true if value.kind_of?(tb)
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
+ #
+ # List of method names values must respond to.
+ #
+ # Uses value.respond_to?(respond_to) to evaluate. At least one must match
+ # for the value to be valid.
+ #
+ def _pv_respond_to(opts, key, method_name_list)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ Array(method_name_list).each do |method_name|
+ unless value.respond_to?(method_name)
+ raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
end
end
end
+ end
- # Raise an exception if the parameter does not respond to a given set of methods.
- def _pv_respond_to(opts, key, method_name_list)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- Array(method_name_list).each do |method_name|
- unless value.respond_to?(method_name)
- raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
+ #
+ # List of things that must not be true about the value.
+ #
+ # Calls `value.<thing>?` All responses must be false for the value to be
+ # valid.
+ # Values which do not respond to <thing>? are considered valid (because if
+ # a value doesn't respond to `:readable?`, then it probably isn't
+ # readable.)
+ #
+ # @example
+ # ```ruby
+ # property :x, cannot_be: [ :nil, :empty ]
+ # x [ 1, 2 ] #=> valid
+ # x 1 #=> valid
+ # x [] #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ def _pv_cannot_be(opts, key, predicate_method_base_name)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ Array(predicate_method_base_name).each do |method_name|
+ predicate_method = :"#{method_name}?"
+
+ if value.respond_to?(predicate_method)
+ if value.send(predicate_method)
+ raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
end
end
end
end
+ end
- # Assert that parameter returns false when passed a predicate method.
- # For example, :cannot_be => :blank will raise a Exceptions::ValidationFailed
- # error value.blank? returns a 'truthy' (not nil or false) value.
- #
- # Note, this will *PASS* if the object doesn't respond to the method.
- # So, to make sure a value is not nil and not blank, you need to do
- # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true)
- def _pv_cannot_be(opts, key, predicate_method_base_name)
- value = _pv_opts_lookup(opts, key)
- predicate_method = (predicate_method_base_name.to_s + "?").to_sym
-
- if value.respond_to?(predicate_method)
- if value.send(predicate_method)
- raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
- end
- end
+ #
+ # The default value for a property.
+ #
+ # When the property is not assigned, this will be used.
+ #
+ # If this is a lazy value, it will either be passed the resource as a value,
+ # or if the lazy proc does not take parameters, it will be run in the
+ # context of the instance with instance_eval.
+ #
+ # @example
+ # ```ruby
+ # property :x, default: 10
+ # ```
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, default: lazy { x+2 }
+ # ```
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, default: lazy { |r| r.x+2 }
+ # ```
+ #
+ def _pv_default(opts, key, default_value)
+ value = _pv_opts_lookup(opts, key)
+ if value.nil?
+ default_value = default_value.freeze if !default_value.is_a?(DelayedEvaluator)
+ opts[key] = default_value
end
+ end
- # Assign a default value to a parameter.
- def _pv_default(opts, key, default_value)
- value = _pv_opts_lookup(opts, key)
- if value == nil
- opts[key] = default_value
+ #
+ # List of regexes values that must match.
+ #
+ # Uses regex.match() to evaluate. At least one must match for the value to
+ # be valid.
+ #
+ # `nil` passes regex validation automatically.
+ #
+ # @example
+ # ```ruby
+ # property :x, regex: [ /abc/, /xyz/ ]
+ # ```
+ #
+ def _pv_regex(opts, key, regex)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ Array(regex).each do |r|
+ return true if r.match(value.to_s)
end
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
end
+ end
- # Check a parameter against a regular expression.
- def _pv_regex(opts, key, regex)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- passes = false
- [ regex ].flatten.each do |r|
- if value != nil
- if r.match(value.to_s)
- passes = true
- end
- end
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
+ #
+ # List of procs we pass the value to.
+ #
+ # All procs must return true for the value to be valid. If any procs do
+ # not return true, the key will be used for the message: `"Property x's
+ # value :y <message>"`.
+ #
+ # @example
+ # ```ruby
+ # property :x, callbacks: { "is bigger than 10" => proc { |v| v <= 10 }, "is not awesome" => proc { |v| !v.awesome }}
+ # ```
+ #
+ def _pv_callbacks(opts, key, callbacks)
+ raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ callbacks.each do |message, zeproc|
+ if zeproc.call(value) != true
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
end
end
end
+ end
- # Check a parameter against a hash of proc's.
- def _pv_callbacks(opts, key, callbacks)
- raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- callbacks.each do |message, zeproc|
- if zeproc.call(value) != true
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
- end
- end
+ #
+ # Allows a parameter to default to the value of the resource name.
+ #
+ # @example
+ # ```ruby
+ # property :x, name_property: true
+ # ```
+ #
+ def _pv_name_property(opts, key, is_name_property=true)
+ if is_name_property
+ if opts[key].nil?
+ opts[key] = self.instance_variable_get("@name")
end
end
+ end
+ alias :_pv_name_attribute :_pv_name_property
- # Allow a parameter to default to @name
- def _pv_name_attribute(opts, key, is_name_attribute=true)
- if is_name_attribute
- if opts[key] == nil
- opts[key] = self.instance_variable_get("@name")
- end
+ #
+ # List of valid things values can be.
+ #
+ # Uses Ruby's `===` to evaluate (is === value). At least one must match
+ # for the value to be valid.
+ #
+ # If a proc is passed, it is instance_eval'd in the resource, passed the
+ # value, and must return a truthy or falsey value.
+ #
+ # @example Class
+ # ```ruby
+ # property :x, String
+ # x 'valid' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ #
+ # @example Value
+ # ```ruby
+ # property :x, [ :a, :b, :c, nil ]
+ # x :a #=> valid
+ # x nil #=> valid
+ # ```
+ #
+ # @example Regex
+ # ```ruby
+ # property :x, /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example Proc
+ # ```ruby
+ # property :x, proc { |x| x > y }
+ # property :y, default: 2
+ # x 3 #=> valid
+ # x 1 #=> invalid
+ # ```
+ #
+ # @example PropertyType
+ # ```ruby
+ # type = PropertyType.new(is: String)
+ # property :x, type
+ # x 'foo' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example RSpec Matcher
+ # ```ruby
+ # include RSpec::Matchers
+ # property :x, a_string_matching /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ def _pv_is(opts, key, to_be, raise_error: true)
+ return true if !opts.has_key?(key.to_s) && !opts.has_key?(key.to_sym)
+ value = _pv_opts_lookup(opts, key)
+ to_be = [ to_be ].flatten(1)
+ to_be.each do |tb|
+ if tb.is_a?(Proc)
+ return true if instance_exec(value, &tb)
+ else
+ return true if tb === value
end
end
+
+ if raise_error
+ raise Exceptions::ValidationFailed, "Option #{key} must be one of: #{to_be.join(", ")}! You passed #{value.inspect}."
+ else
+ false
+ end
+ end
+
+ #
+ # Method to mess with a value before it is validated and stored.
+ #
+ # Allows you to transform values into a canonical form that is easy to
+ # work with.
+ #
+ # This is passed the value to transform, and is run in the context of the
+ # instance (so it has access to other resource properties). It must return
+ # the value that will be stored in the instance.
+ #
+ # @example
+ # ```ruby
+ # property :x, Integer, coerce: { |v| v.to_i }
+ # ```
+ #
+ def _pv_coerce(opts, key, coercer)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s] = instance_exec(opts[key], &coercer)
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym] = instance_exec(opts[key], &coercer)
+ end
+ end
end
end
end
-
diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb
index f547018a38..d5eed7c215 100644
--- a/lib/chef/node_map.rb
+++ b/lib/chef/node_map.rb
@@ -38,30 +38,35 @@ class Chef
#
# @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, canonical: nil, &block)
+ def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, override: 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, 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
- # values of the same specificity. (So all other things being equal,
- # newest wins.)
+ filters = {}
+ filters[:platform] = platform if platform
+ filters[:platform_version] = platform_version if platform_version
+ filters[:platform_family] = platform_family if platform_family
+ filters[:os] = os if os
+ new_matcher = { value: value, filters: filters }
+ new_matcher[:block] = block if block
+ new_matcher[:canonical] = canonical if canonical
+ new_matcher[:override] = override if override
+
+ # The map is sorted in order of preference already; we just need to find
+ # our place in it (just before the first value with the same preference level).
insert_at = nil
- @map[key].each_with_index do |matcher, index|
- if specificity(new_matcher) >= specificity(matcher)
- insert_at = index
- break
- end
+ @map[key] ||= []
+ @map[key].each_with_index do |matcher,index|
+ cmp = compare_matchers(key, new_matcher, matcher)
+ insert_at ||= index if cmp && cmp <= 0
end
if insert_at
@map[key].insert(insert_at, new_matcher)
else
@map[key] << new_matcher
end
- self
+ insert_at ||= @map[key].size - 1
+ @map
end
#
@@ -116,30 +121,7 @@ class Chef
remaining
end
- private
-
- #
- # Gives a value for "how specific" the matcher is.
- # Things which specify more specific filters get a higher number
- # (platform_version > platform > platform_family > os); things
- # with a block have higher specificity than similar things without
- # a block.
- #
- def specificity(matcher)
- if matcher[:filters][:platform_version]
- specificity = 8
- elsif matcher[:filters][:platform]
- specificity = 6
- elsif matcher[:filters][:platform_family]
- specificity = 4
- elsif matcher[:filters][:os]
- specificity = 2
- else
- specificity = 0
- end
- specificity += 1 if matcher[:block]
- specificity
- end
+ protected
#
# Succeeds if:
@@ -197,5 +179,48 @@ class Chef
return true if canonical.nil?
!!canonical == !!matcher[:canonical]
end
+
+ def compare_matchers(key, new_matcher, matcher)
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:block] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_version] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_family] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:os] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:override] }
+ return cmp if cmp != 0
+ # If all things are identical, return 0
+ 0
+ end
+
+ def compare_matcher_properties(new_matcher, matcher)
+ a = yield(new_matcher)
+ b = yield(matcher)
+
+ # Check for blcacklists ('!windows'). Those always come *after* positive
+ # whitelists.
+ a_negated = Array(a).any? { |f| f.is_a?(String) && f.start_with?('!') }
+ b_negated = Array(b).any? { |f| f.is_a?(String) && f.start_with?('!') }
+ if a_negated != b_negated
+ return 1 if a_negated
+ return -1 if b_negated
+ end
+
+ # We treat false / true and nil / not-nil with the same comparison
+ a = nil if a == false
+ b = nil if b == false
+ cmp = a <=> b
+ # This is the case where one is non-nil, and one is nil. The one that is
+ # nil is "greater" (i.e. it should come last).
+ if cmp.nil?
+ return 1 if a.nil?
+ return -1 if b.nil?
+ end
+ cmp
+ end
end
end
diff --git a/lib/chef/platform/priority_map.rb b/lib/chef/platform/priority_map.rb
new file mode 100644
index 0000000000..d559eece78
--- /dev/null
+++ b/lib/chef/platform/priority_map.rb
@@ -0,0 +1,54 @@
+require 'chef/node_map'
+
+class Chef
+ class Platform
+ class PriorityMap < Chef::NodeMap
+ def priority(resource_name, priority_array, *filter)
+ set_priority_array(resource_name.to_sym, priority_array, *filter)
+ end
+
+ # @api private
+ def get_priority_array(node, key)
+ get(node, key)
+ end
+
+ # @api private
+ def set_priority_array(key, priority_array, *filter, &block)
+ priority_array = Array(priority_array)
+ set(key, priority_array, *filter, &block)
+ priority_array
+ end
+
+ # @api private
+ def list_handlers(node, key, **filters)
+ list(node, key, **filters).flatten(1).uniq
+ end
+
+ #
+ # Priority maps have one extra precedence: priority arrays override "provides,"
+ # and "provides" lines with identical filters sort by class name (ascending).
+ #
+ def compare_matchers(key, new_matcher, matcher)
+ # Priority arrays come before "provides"
+ if new_matcher[:value].is_a?(Array) != matcher[:value].is_a?(Array)
+ return new_matcher[:value].is_a?(Array) ? -1 : 1
+ end
+
+ cmp = super
+ if cmp == 0
+ # Sort by class name (ascending) as well, if all other properties
+ # are exactly equal
+ if new_matcher[:value].is_a?(Class) && !new_matcher[:override]
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:value].name }
+ if cmp < 0
+ Chef::Log.warn "You are overriding #{key} on #{new_matcher[:filters].inspect} with #{new_matcher[:value].inspect}: used to be #{matcher[:value].inspect}. Use override: true if this is what you intended."
+ elsif cmp > 0
+ Chef::Log.warn "You declared a new resource #{new_matcher[:value].inspect} for resource #{key}, but it comes alphabetically after #{matcher[:value].inspect} and has the same filters (#{new_matcher[:filters].inspect}), so it will not be used. Use override: true if you want to use it for #{key}."
+ end
+ end
+ end
+ cmp
+ end
+ end
+ end
+end
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index e3a894c8ac..af17d8e1b4 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -201,8 +201,8 @@ class Chef
begin
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.")
+ Chef::Log.warn("Class Chef::Provider::#{class_name} does not declare 'resource_name #{convert_to_snake_case(class_name).to_sym.inspect}'.")
+ Chef::Log.warn("This will no longer work in Chef 13: you must use 'resource_name' to provide DSL.")
rescue NameError
end
end
diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb
index 9d703c9178..5599c74c2d 100644
--- a/lib/chef/platform/provider_priority_map.rb
+++ b/lib/chef/platform/provider_priority_map.rb
@@ -1,29 +1,11 @@
require 'singleton'
+require 'chef/platform/priority_map'
class Chef
class Platform
- class ProviderPriorityMap
+ # @api private
+ class ProviderPriorityMap < Chef::Platform::PriorityMap
include Singleton
-
- def get_priority_array(node, resource_name)
- priority_map.get(node, resource_name.to_sym)
- end
-
- def set_priority_array(resource_name, priority_array, *filter, &block)
- priority_map.set(resource_name.to_sym, Array(priority_array), *filter, &block)
- end
-
- # @api private
- def list_handlers(node, resource_name)
- priority_map.list(node, resource_name.to_sym).flatten(1).uniq
- end
-
- private
-
- def priority_map
- require 'chef/node_map'
- @priority_map ||= Chef::NodeMap.new
- end
end
end
end
diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb
index fb08debc53..aa57e3ddf0 100644
--- a/lib/chef/platform/resource_priority_map.rb
+++ b/lib/chef/platform/resource_priority_map.rb
@@ -1,34 +1,17 @@
require 'singleton'
+require 'chef/platform/priority_map'
class Chef
class Platform
- class ResourcePriorityMap
+ # @api private
+ class ResourcePriorityMap < Chef::Platform::PriorityMap
include Singleton
- 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)
- priority_map.set(resource_name.to_sym, Array(priority_array), *filter, &block)
- end
-
# @api private
- def delete_canonical(resource_name, resource_class)
- priority_map.delete_canonical(resource_name, resource_class)
- end
-
- # @api private
- def list_handlers(*args)
- priority_map.list(*args).flatten(1).uniq
+ def get_priority_array(node, resource_name, canonical: nil)
+ super(node, resource_name.to_sym, canonical: canonical)
end
- private
-
- def priority_map
- require 'chef/node_map'
- @priority_map ||= Chef::NodeMap.new
- end
end
end
end
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index e50e374804..280277d947 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -26,6 +26,7 @@ require 'chef/mixin/powershell_out'
require 'chef/mixin/provides'
require 'chef/platform/service_helpers'
require 'chef/node_map'
+require 'forwardable'
class Chef
class Provider
@@ -65,6 +66,7 @@ class Chef
@recipe_name = nil
@cookbook_name = nil
+ self.class.include_resource_dsl_module(new_resource)
end
def whyrun_mode?
@@ -119,11 +121,11 @@ class Chef
check_resource_semantics!
# user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode
- if !whyrun_mode? || whyrun_supported?
+ if whyrun_mode? && !whyrun_supported?
+ events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
+ else
load_current_resource
events.resource_current_state_loaded(@new_resource, @action, @current_resource)
- elsif whyrun_mode? && !whyrun_supported?
- events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
end
define_resource_requirements
@@ -136,9 +138,7 @@ class Chef
# we can't execute the action.
# in non-whyrun mode, this will still cause the action to be
# executed normally.
- if whyrun_supported? && !requirements.action_blocked?(@action)
- send("action_#{@action}")
- elsif whyrun_mode?
+ if whyrun_mode? && (!whyrun_supported? || requirements.action_blocked?(@action))
events.resource_bypassed(@new_resource, @action, self)
else
send("action_#{@action}")
@@ -176,13 +176,128 @@ class Chef
end
def self.provides(short_name, opts={}, &block)
- Chef.set_provider_priority_array(short_name, self, opts, &block)
+ Chef.provider_priority_map.set(short_name, self, opts, &block)
end
def self.provides?(node, resource)
Chef::ProviderResolver.new(node, resource, :nothing).provided_by?(self)
end
+ #
+ # Include attributes, public and protected methods from this Resource in
+ # the provider.
+ #
+ # If this is set to true, delegate methods are included in the provider so
+ # that you can call (for example) `attrname` and it will call
+ # `new_resource.attrname`.
+ #
+ # The actual include does not happen until the first time the Provider
+ # is instantiated (so that we don't have to worry about load order issues).
+ #
+ # @param include_resource_dsl [Boolean] Whether to include resource DSL or
+ # not (defaults to `false`).
+ #
+ def self.include_resource_dsl(include_resource_dsl)
+ @include_resource_dsl = include_resource_dsl
+ end
+
+ # Create the resource DSL module that forwards resource methods to new_resource
+ #
+ # @api private
+ def self.include_resource_dsl_module(resource)
+ if @include_resource_dsl && !defined?(@included_resource_dsl_module)
+ provider_class = self
+ @included_resource_dsl_module = Module.new do
+ extend Forwardable
+ define_singleton_method(:to_s) { "#{resource_class} forwarder module" }
+ define_singleton_method(:inspect) { to_s }
+ dsl_methods =
+ resource.class.public_instance_methods +
+ resource.class.protected_instance_methods -
+ provider_class.instance_methods
+ def_delegators(:new_resource, *dsl_methods)
+ end
+ include @included_resource_dsl_module
+ end
+ end
+
+ # Enables inline evaluation of resources in provider actions.
+ #
+ # Without this option, any resources declared inside the Provider 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 Provider, and potentially be notified by resources outside the Provider
+ # (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 Provider 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 Provider cannot interact with
+ # external resources via notifies, though notifications to other
+ # resources within the Provider 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::Provider::InlineResources
+ # Implementation of inline resource convergence for providers. See
+ # Provider.use_inline_resources for a longer explanation.
+ #
+ # This code is restricted to a module so that it can be selectively
+ # applied to providers on an opt-in basis.
+ #
+ # @api private
+ module InlineResources
+
+ # Our run context is a child of the main run context; that gives us a
+ # whole new resource collection and notification set.
+ def initialize(resource, run_context)
+ super(resource, run_context.create_child)
+ end
+
+ # Class methods for InlineResources. Overrides the `action` DSL method
+ # with one that enables inline resource convergence.
+ #
+ # @api private
+ module ClassMethods
+ # Defines an action method on the provider, running the block to
+ # compile the resources, converging them, and then checking if any
+ # were updated (and updating new-resource if so)
+ def action(name, &block)
+ class_eval <<-EOM, __FILE__, __LINE__+1
+ def action_#{name}
+ return_value = compile_action_#{name}
+ Chef::Runner.new(run_context).converge
+ return_value
+ ensure
+ if run_context.resource_collection.any? {|r| r.updated? }
+ new_resource.updated_by_last_action(true)
+ end
+ end
+ EOM
+ # We put the action in its own method so that super() works.
+ define_method("compile_action_#{name}", &block)
+ end
+ end
+
+ require 'chef/dsl/recipe'
+ include Chef::DSL::Recipe::FullDSL
+ end
+
protected
def converge_actions
@@ -200,12 +315,14 @@ class Chef
# manipulating notifies.
converge_by ("evaluate block and run any associated actions") do
- saved_run_context = @run_context
- @run_context = @run_context.dup
- @run_context.resource_collection = Chef::ResourceCollection.new
- instance_eval(&block)
- Chef::Runner.new(@run_context).converge
- @run_context = saved_run_context
+ saved_run_context = run_context
+ begin
+ @run_context = run_context.create_child
+ instance_eval(&block)
+ Chef::Runner.new(run_context).converge
+ ensure
+ @run_context = saved_run_context
+ end
end
end
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
index 19e7c01ab1..6d9b7f4397 100644
--- a/lib/chef/provider/deploy.rb
+++ b/lib/chef/provider/deploy.rb
@@ -373,11 +373,9 @@ class Chef
end
def gem_resource_collection_runner
- gems_collection = Chef::ResourceCollection.new
- gem_packages.each { |rbgem| gems_collection.insert(rbgem) }
- gems_run_context = run_context.dup
- gems_run_context.resource_collection = gems_collection
- Chef::Runner.new(gems_run_context)
+ child_context = run_context.create_child
+ gem_packages.each { |rbgem| child_context.resource_collection.insert(rbgem) }
+ Chef::Runner.new(child_context)
end
def gem_packages
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index b5efbb284d..a96c382a01 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -28,52 +28,10 @@ class Chef
# Base class from which LWRP providers inherit.
class LWRPBase < Provider
- # Chef::Provider::LWRPBase::InlineResources
- # Implementation of inline resource convergence for LWRP providers. See
- # Provider::LWRPBase.use_inline_resources for a longer explanation.
- #
- # This code is restricted to a module so that it can be selectively
- # applied to providers on an opt-in basis.
- module InlineResources
-
- # Class methods for InlineResources. Overrides the `action` DSL method
- # with one that enables inline resource convergence.
- module ClassMethods
- # Defines an action method on the provider, using
- # recipe_eval_with_update_check to execute the given block.
- def action(name, &block)
- define_method("action_#{name}") do
- recipe_eval_with_update_check(&block)
- end
- end
- end
-
- # Executes the given block in a temporary run_context with its own
- # resource collection. After the block is executed, any resources
- # declared inside are converged, and if any are updated, the
- # new_resource will be marked updated.
- def recipe_eval_with_update_check(&block)
- saved_run_context = @run_context
- temp_run_context = @run_context.dup
- @run_context = temp_run_context
- @run_context.resource_collection = Chef::ResourceCollection.new
-
- return_value = instance_eval(&block)
- Chef::Runner.new(@run_context).converge
- return_value
- ensure
- @run_context = saved_run_context
- if temp_run_context.resource_collection.any? {|r| r.updated? }
- new_resource.updated_by_last_action(true)
- end
- end
-
- end
-
include Chef::DSL::Recipe
# These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
- # They are not included by its replacment, Chef::DSL::Recipe, but
+ # They are not included by its replacement, Chef::DSL::Recipe, but
# they may be used in existing LWRPs.
include Chef::DSL::PlatformIntrospection
include Chef::DSL::DataQuery
@@ -122,38 +80,6 @@ class Chef
provider_class
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
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 01505924cb..c67f3f05da 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -25,8 +25,6 @@ class Chef
UPDATE_RC_D_ENABLED_MATCHES = /\/rc[\dS].d\/S|not installed/i
UPDATE_RC_D_PRIORITIES = /\/rc([\dS]).d\/([SK])(\d\d)/i
- provides :service, platform_family: "debian"
-
def self.provides?(node, resource)
super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian)
end
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
index 31965a4bc6..4534e33f32 100644
--- a/lib/chef/provider/service/insserv.rb
+++ b/lib/chef/provider/service/insserv.rb
@@ -24,8 +24,6 @@ class Chef
class Service
class Insserv < Chef::Provider::Service::Init
- provides :service, os: "linux"
-
def self.provides?(node, resource)
super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv)
end
diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb
index 5ff24e0dbb..fdf4cbc256 100644
--- a/lib/chef/provider/service/invokercd.rb
+++ b/lib/chef/provider/service/invokercd.rb
@@ -23,8 +23,6 @@ class Chef
class Service
class Invokercd < Chef::Provider::Service::Init
- provides :service, platform_family: "debian"
-
def self.provides?(node, resource)
super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd)
end
diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb
index 850953125e..a8deb13aec 100644
--- a/lib/chef/provider/service/redhat.rb
+++ b/lib/chef/provider/service/redhat.rb
@@ -26,8 +26,6 @@ class Chef
CHKCONFIG_ON = /\d:on/
CHKCONFIG_MISSING = /No such/
- provides :service, platform_family: [ "rhel", "fedora", "suse" ]
-
def self.provides?(node, resource)
super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
end
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
index 8d4aa41035..d8ce6af649 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -27,8 +27,6 @@ class Chef
class Upstart < Chef::Provider::Service::Simple
UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
- provides :service, os: "linux"
-
def self.provides?(node, resource)
super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
end
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
index ad92a72a0a..244b11db98 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -23,8 +23,6 @@ require 'etc'
class Chef
class Provider
class User < Chef::Provider
- provides :user
-
include Chef::Mixin::Command
attr_accessor :user_exists, :locked
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index b4d37c2d61..262560f754 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -36,14 +36,7 @@ class Chef
# A Recipe object is the context in which Chef recipes are evaluated.
class Recipe
- include Chef::DSL::DataQuery
- include Chef::DSL::PlatformIntrospection
- include Chef::DSL::IncludeRecipe
- include Chef::DSL::Recipe
- include Chef::DSL::RegistryHelper
- include Chef::DSL::RebootPending
- include Chef::DSL::Audit
- include Chef::DSL::Powershell
+ include Chef::DSL::Recipe::FullDSL
include Chef::Mixin::FromFile
include Chef::Mixin::Deprecation
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 7fe8a52d95..8d2532dac4 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -58,8 +58,6 @@ class Chef
include Chef::Mixin::ShellOut
include Chef::Mixin::PowershellOut
- NULL_ARG = Object.new
-
#
# The node the current Chef run is using.
#
@@ -187,8 +185,7 @@ class Chef
end
self.action = arg
else
- # Pull the action from the class if it's not set
- @action || self.class.default_action
+ @action
end
end
@@ -531,9 +528,7 @@ class Chef
#
# Equivalent to #ignore_failure.
#
- def epic_fail(arg=nil)
- ignore_failure(arg)
- end
+ alias :epic_fail :ignore_failure
#
# Make this resource into an exact (shallow) copy of the other resource.
@@ -688,26 +683,133 @@ class Chef
#
# The provider class for this resource.
#
+ # If `action :x do ... end` has been declared on this resource or its
+ # superclasses, this will return the `action_provider_class`.
+ #
# If this is not set, `provider_for_action` will dynamically determine the
# provider.
#
# @param arg [String, Symbol, Class] Sets the provider class for this resource.
# If passed a String or Symbol, e.g. `:file` or `"file"`, looks up the
# provider based on the name.
+ #
# @return The provider class for this resource.
#
+ # @see Chef::Resource.action_provider_class
+ #
def provider(arg=nil)
klass = if arg.kind_of?(String) || arg.kind_of?(Symbol)
lookup_provider_constant(arg)
else
arg
end
- set_or_return(:provider, klass, kind_of: [ Class ])
+ set_or_return(:provider, klass, kind_of: [ Class ]) ||
+ self.class.action_provider_class
end
def provider=(arg)
provider(arg)
end
+ #
+ # Create a property on this resource class.
+ #
+ # If a superclass has this property, or if this property has already been
+ # defined by this resource, this will *override* the previous value.
+ #
+ # @param name [Symbol] The name of the property.
+ # @param type [Object,Array<Object>] The type(s) of this property.
+ # If present, this is prepended to the `is` validation option.
+ # @param options [Hash<Symbol,Object>] Validation options.
+ # @option options [Object,Array] :is An object, or list of
+ # objects, that must match the value using Ruby's `===` operator
+ # (`options[:is].any? { |v| v === value }`).
+ # @option options [Object,Array] :equal_to An object, or list
+ # of objects, that must be equal to the value using Ruby's `==`
+ # operator (`options[:is].any? { |v| v == value }`)
+ # @option options [Regexp,Array<Regexp>] :regex An object, or
+ # list of objects, that must match the value with `regex.match(value)`.
+ # @option options [Class,Array<Class>] :kind_of A class, or
+ # list of classes, that the value must be an instance of.
+ # @option options [Hash<String,Proc>] :callbacks A hash of
+ # messages -> procs, all of which match the value. The proc must
+ # return a truthy or falsey value (true means it matches).
+ # @option options [Symbol,Array<Symbol>] :respond_to A method
+ # name, or list of method names, the value must respond to.
+ # @option options [Symbol,Array<Symbol>] :cannot_be A property,
+ # or a list of properties, that the value cannot have (such as `:nil` or
+ # `:empty`). The method with a questionmark at the end is called on the
+ # value (e.g. `value.empty?`). If the value does not have this method,
+ # it is considered valid (i.e. if you don't respond to `empty?` we
+ # assume you are not empty).
+ # @option options [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # @option options [Boolean] :required `true` if this property
+ # must be present; `false` otherwise. This is checked after the resource
+ # is fully initialized.
+ # @option options [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set.
+ # @option options [Boolean] :name_attribute Same as `name_property`.
+ # @option options [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties).
+ #
+ # @example Bare property
+ # property :x
+ #
+ # @example With just a type
+ # property :x, String
+ #
+ # @example With just options
+ # property :x, default: 'hi'
+ #
+ # @example With type and options
+ # property :x, String, default: 'hi'
+ #
+ def self.property(name, type=NOT_PASSED, **options)
+ name = name.to_sym
+
+ if type != NOT_PASSED
+ if options[:is]
+ options[:is] = ([ type ] + [ options[:is] ]).flatten(1)
+ else
+ options[:is] = type
+ end
+ end
+
+ define_method(name) do |value=NOT_PASSED|
+ set_or_return(name, value, options)
+ end
+ define_method("#{name}=") do |value|
+ set_or_return(name, value, options)
+ end
+ end
+
+ #
+ # Whether this property has been set (or whether it has a default that has
+ # been retrieved).
+ #
+ def property_is_set?(name)
+ name = name.to_sym
+ instance_variable_defined?("@#{name}")
+ end
+
+ #
+ # Create a lazy value for assignment to a default value.
+ #
+ # @param block The block to run when the value is retrieved.
+ #
+ # @return [Chef::DelayedEvaluator] The lazy value
+ #
+ def self.lazy(&block)
+ DelayedEvaluator.new(&block)
+ end
+
# Set or return the list of "state attributes" implemented by the Resource
# subclass. State attributes are attributes that describe the desired state
# of the system, such as file permissions or ownership. In general, state
@@ -773,8 +875,8 @@ class Chef
# have.
#
attr_accessor :allowed_actions
- def allowed_actions(value=NULL_ARG)
- if value != NULL_ARG
+ def allowed_actions(value=NOT_PASSED)
+ if value != NOT_PASSED
self.allowed_actions = value
end
@allowed_actions
@@ -908,9 +1010,9 @@ class Chef
#
# @return [Symbol] The name of this resource type (e.g. `:execute`).
#
- def self.resource_name(name=NULL_ARG)
+ def self.resource_name(name=NOT_PASSED)
# Setter
- if name != NULL_ARG
+ if name != NOT_PASSED
remove_canonical_dsl
# Set the resource_name and call provides
@@ -924,13 +1026,7 @@ class Chef
else
@resource_name = nil
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
end
-
@resource_name
end
def self.resource_name=(name)
@@ -938,6 +1034,19 @@ class Chef
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 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`.
@@ -965,7 +1074,7 @@ class Chef
#
# @param actions [Array<Symbol>] The list of actions to add to allowed_actions.
#
- # @return [Arrau<Symbol>] The list of actions, as symbols.
+ # @return [Array<Symbol>] The list of actions, as symbols.
#
def self.allowed_actions(*actions)
@allowed_actions ||=
@@ -974,10 +1083,10 @@ class Chef
else
[ :nothing ]
end
- @allowed_actions |= actions
+ @allowed_actions |= actions.flatten
end
def self.allowed_actions=(value)
- @allowed_actions = value
+ @allowed_actions = value.uniq
end
#
@@ -993,8 +1102,8 @@ class Chef
#
# @return [Symbol,Array<Symbol>] The default actions for the resource.
#
- def self.default_action(action_name=NULL_ARG)
- unless action_name.equal?(NULL_ARG)
+ def self.default_action(action_name=NOT_PASSED)
+ unless action_name.equal?(NOT_PASSED)
if action_name.is_a?(Array)
@default_action = action_name.map { |arg| arg.to_sym }
else
@@ -1013,6 +1122,91 @@ class Chef
end
end
def self.default_action=(action_name)
+ default_action action_name
+ end
+
+ #
+ # Define an action on this resource.
+ #
+ # The action is defined as a *recipe* block that will be compiled and then
+ # converged when the action is taken (when Resource is converged). The recipe
+ # has access to the resource's attributes and methods, as well as the Chef
+ # recipe DSL.
+ #
+ # Resources in the action recipe may notify and subscribe to other resources
+ # within the action recipe, but cannot notify or subscribe to resources
+ # in the main Chef run.
+ #
+ # Resource actions are *inheritable*: if resource A defines `action :create`
+ # and B is a subclass of A, B gets all of A's actions. Additionally,
+ # resource B can define `action :create` and call `super()` to invoke A's
+ # action code.
+ #
+ # The first action defined (besides `:nothing`) will become the default
+ # action for the resource.
+ #
+ # @param name [Symbol] The action name to define.
+ # @param recipe_block The recipe to run when the action is taken. This block
+ # takes no parameters, and will be evaluated in a new context containing:
+ #
+ # - The resource's public and protected methods (including attributes)
+ # - The Chef Recipe DSL (file, etc.)
+ # - super() referring to the parent version of the action (if any)
+ #
+ # @return The Action class implementing the action
+ #
+ def self.action(action, &recipe_block)
+ action = action.to_sym
+ new_action_provider_class.action(action, &recipe_block)
+ self.allowed_actions += [ action ]
+ default_action action if default_action == :nothing
+ end
+
+ #
+ # The action provider class is an automatic `Provider` created to handle
+ # actions declared by `action :x do ... end`.
+ #
+ # This class will be returned by `resource.provider` if `resource.provider`
+ # is not set. `provider_for_action` will also use this instead of calling
+ # out to `Chef::ProviderResolver`.
+ #
+ # If the user has not declared actions on this class or its superclasses
+ # using `action :x do ... end`, then there is no need for this class and
+ # `action_provider_class` will be `nil`.
+ #
+ # @api private
+ #
+ def self.action_provider_class
+ @action_provider_class ||
+ # If the superclass needed one, then we need one as well.
+ if superclass.respond_to?(:action_provider_class) && superclass.action_provider_class
+ new_action_provider_class
+ end
+ end
+
+ #
+ # Ensure the action provider class actually gets created. This is called
+ # when the user does `action :x do ... end`.
+ #
+ # @api private
+ def self.new_action_provider_class
+ return @action_provider_class if @action_provider_class
+
+ if superclass.respond_to?(:action_provider_class)
+ base_provider = superclass.action_provider_class
+ end
+ base_provider ||= Chef::Provider
+
+ resource_class = self
+ @action_provider_class = Class.new(base_provider) do
+ use_inline_resources
+ include_resource_dsl true
+ define_singleton_method(:to_s) { "#{resource_class} action provider" }
+ define_singleton_method(:inspect) { to_s }
+ define_method(:load_current_resource) {}
+ end
+ end
+ def self.default_action=(action_name)
default_action(action_name)
end
@@ -1087,7 +1281,7 @@ class Chef
class << self
# back-compat
- # NOTE: that we do not support unregistering classes as descendents like
+ # NOTE: that we do not support unregistering classes as descendants like
# we used to for LWRP unloading because that was horrible and removed in
# Chef-12.
# @deprecated
@@ -1111,7 +1305,12 @@ class Chef
def self.inherited(child)
super
@sorted_descendants = nil
- child.resource_name
+ # set resource_name automatically if it's not set
+ if child.name && !child.resource_name
+ if child.name =~ /^Chef::Resource::(\w+)$/
+ child.resource_name(convert_to_snake_case($1))
+ end
+ end
end
@@ -1143,7 +1342,7 @@ class Chef
remove_canonical_dsl
end
- result = Chef.set_resource_priority_array(name, self, options, &block)
+ result = Chef.resource_priority_map.set(name, self, options, &block)
Chef::DSL::Resources.add_resource_dsl(name)
result
end
@@ -1208,7 +1407,8 @@ class Chef
end
def provider_for_action(action)
- provider = Chef::ProviderResolver.new(node, self, action).resolve.new(self, run_context)
+ provider_class = Chef::ProviderResolver.new(node, self, action).resolve
+ provider = provider_class.new(self, run_context)
provider.action = action
provider
end
@@ -1318,23 +1518,22 @@ class Chef
# when Chef::Resource::MyLwrp
# end
#
- resource_subclass = class_eval <<-EOM, __FILE__, __LINE__+1
- class Chef::Resource::#{class_name} < resource_class
- resource_name nil # we do not actually provide anything
- def initialize(*args, &block)
- Chef::Log.deprecation("Using an LWRP by its name (#{class_name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.")
+ resource_subclass = Class.new(resource_class) do
+ resource_name nil # we do not actually provide anything
+ def initialize(*args, &block)
+ Chef::Log.deprecation("Using an LWRP by its name (#{self.class.name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.")
+ super
+ end
+ def self.resource_name(*args)
+ if args.empty?
+ @resource_name ||= superclass.resource_name
+ else
super
end
- def self.resource_name(*args)
- if args.empty?
- @resource_name ||= superclass.resource_name
- else
- super
- end
- end
- self
end
- EOM
+ self
+ end
+ eval("Chef::Resource::#{class_name} = resource_subclass")
# Make case, is_a and kind_of work with the new subclass, for backcompat.
# Any subclass of Chef::Resource::ResourceClass is already a subclass of resource_class
# Any subclass of resource_class is considered a subclass of Chef::Resource::ResourceClass
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index c486233020..443e0ed819 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -74,19 +74,14 @@ class Chef
resource_class
end
- # 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
+ alias :attribute :property
# Adds +action_names+ to the list of valid actions for this resource.
# Does not include superclass's action list when appending.
def actions(*action_names)
+ action_names = action_names.flatten
if !action_names.empty? && !@allowed_actions
- self.allowed_actions = action_names
+ self.allowed_actions = ([ :nothing ] + action_names).uniq
else
allowed_actions(*action_names)
end
diff --git a/lib/chef/resource/macports_package.rb b/lib/chef/resource/macports_package.rb
index 937839b6e1..5843016897 100644
--- a/lib/chef/resource/macports_package.rb
+++ b/lib/chef/resource/macports_package.rb
@@ -16,10 +16,11 @@
# limitations under the License.
#
+require 'chef/resource/package'
+
class Chef
class Resource
class MacportsPackage < Chef::Resource::Package
- provides :package, os: "darwin"
end
end
end
diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb
index 1c6da75678..5be1c34b89 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -100,8 +100,3 @@ class Chef
end
end
end
-
-require 'chef/chef_class'
-require 'chef/resource/homebrew_package'
-
-Chef.set_resource_priority_array :package, Chef::Resource::HomebrewPackage, os: "darwin"
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
index 31b39f7e24..9df627beb2 100644
--- a/lib/chef/resource_resolver.rb
+++ b/lib/chef/resource_resolver.rb
@@ -18,6 +18,7 @@
require 'chef/exceptions'
require 'chef/platform/resource_priority_map'
+require 'chef/mixin/convert_to_class_name'
class Chef
class ResourceResolver
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index 44b05f0cc0..f55d1740b1 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -25,118 +25,223 @@ require 'chef/log'
require 'chef/recipe'
require 'chef/run_context/cookbook_compiler'
require 'chef/event_dispatch/events_output_stream'
+require 'forwardable'
class Chef
# == Chef::RunContext
# Value object that loads and tracks the context of a Chef run
class RunContext
+ #
+ # Global state
+ #
- # Chef::Node object for this run
+ #
+ # The node for this run
+ #
+ # @return [Chef::Node]
+ #
attr_reader :node
- # Chef::CookbookCollection for this run
+ #
+ # The set of cookbooks involved in this run
+ #
+ # @return [Chef::CookbookCollection]
+ #
attr_reader :cookbook_collection
+ #
# Resource Definitions for this run. Populated when the files in
# +definitions/+ are evaluated (this is triggered by #load).
+ #
+ # @return [Array[Chef::ResourceDefinition]]
+ #
attr_reader :definitions
- ###
- # These need to be settable so deploy can run a resource_collection
- # independent of any cookbooks via +recipe_eval+
+ #
+ # Event dispatcher for this run.
+ #
+ # @return [Chef::EventDispatch::Dispatcher]
+ #
+ attr_reader :events
- # The Chef::ResourceCollection for this run. Populated by evaluating
- # recipes, which is triggered by #load. (See also: CookbookCompiler)
- attr_accessor :resource_collection
+ #
+ # Hash of factoids for a reboot request.
+ #
+ # @return [Hash]
+ #
+ attr_accessor :reboot_info
+ #
+ # Scoped state
+ #
+
+ #
+ # The parent run context.
+ #
+ # @return [Chef::RunContext] The parent run context, or `nil` if this is the
+ # root context.
+ #
+ attr_reader :parent_run_context
+
+ #
+ # The collection of resources intended to be converged (and able to be
+ # notified).
+ #
+ # @return [Chef::ResourceCollection]
+ #
+ # @see CookbookCompiler
+ #
+ attr_reader :resource_collection
+
+ #
# The list of control groups to execute during the audit phase
- attr_accessor :audits
+ #
+ attr_reader :audits
+ #
+ # Notification handling
+ #
+
+ #
# A Hash containing the immediate notifications triggered by resources
# during the converge phase of the chef run.
- attr_accessor :immediate_notification_collection
+ #
+ # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+ # <notifying resource name> => <list of notifications it sent>
+ #
+ attr_reader :immediate_notification_collection
+ #
# A Hash containing the delayed (end of run) notifications triggered by
# resources during the converge phase of the chef run.
- attr_accessor :delayed_notification_collection
-
- # Event dispatcher for this run.
- attr_reader :events
-
- # Hash of factoids for a reboot request.
- attr_reader :reboot_info
+ #
+ # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+ # <notifying resource name> => <list of notifications it sent>
+ #
+ attr_reader :delayed_notification_collection
# Creates a new Chef::RunContext object and populates its fields. This object gets
# used by the Chef Server to generate a fully compiled recipe list for a node.
#
- # === Returns
- # object<Chef::RunContext>:: Duh. :)
+ # @param node [Chef::Node] The node to run against.
+ # @param cookbook_collection [Chef::CookbookCollection] The cookbooks
+ # involved in this run.
+ # @param events [EventDispatch::Dispatcher] The event dispatcher for this
+ # run.
+ #
def initialize(node, cookbook_collection, events)
@node = node
@cookbook_collection = cookbook_collection
- @resource_collection = Chef::ResourceCollection.new
- @audits = {}
- @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
- @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
- @definitions = Hash.new
- @loaded_recipes = {}
- @loaded_attributes = {}
@events = events
- @reboot_info = {}
- @node.run_context = self
- @node.set_cookbook_attribute
+ node.run_context = self
+ node.set_cookbook_attribute
+
+ @definitions = Hash.new
+ @loaded_recipes_hash = {}
+ @loaded_attributes_hash = {}
+ @reboot_info = {}
@cookbook_compiler = nil
+
+ initialize_child_state
end
- # Triggers the compile phase of the chef run. Implemented by
- # Chef::RunContext::CookbookCompiler
+ #
+ # Triggers the compile phase of the chef run.
+ #
+ # @param run_list_expansion [Chef::RunList::RunListExpansion] The run list.
+ # @see Chef::RunContext::CookbookCompiler
+ #
def load(run_list_expansion)
@cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
- @cookbook_compiler.compile
+ cookbook_compiler.compile
end
- # Adds an immediate notification to the
- # +immediate_notification_collection+. The notification should be a
- # Chef::Resource::Notification or duck type.
+ #
+ # Initialize state that applies to both Chef::RunContext and Chef::ChildRunContext
+ #
+ def initialize_child_state
+ @audits = {}
+ @resource_collection = Chef::ResourceCollection.new
+ @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
+ @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
+ end
+
+ #
+ # Adds an immediate notification to the +immediate_notification_collection+.
+ #
+ # @param [Chef::Resource::Notification] The notification to add.
+ #
def notifies_immediately(notification)
nr = notification.notifying_resource
if nr.instance_of?(Chef::Resource)
- @immediate_notification_collection[nr.name] << notification
+ immediate_notification_collection[nr.name] << notification
else
- @immediate_notification_collection[nr.declared_key] << notification
+ immediate_notification_collection[nr.declared_key] << notification
end
end
- # Adds a delayed notification to the +delayed_notification_collection+. The
- # notification should be a Chef::Resource::Notification or duck type.
+ #
+ # Adds a delayed notification to the +delayed_notification_collection+.
+ #
+ # @param [Chef::Resource::Notification] The notification to add.
+ #
def notifies_delayed(notification)
nr = notification.notifying_resource
if nr.instance_of?(Chef::Resource)
- @delayed_notification_collection[nr.name] << notification
+ delayed_notification_collection[nr.name] << notification
else
- @delayed_notification_collection[nr.declared_key] << notification
+ delayed_notification_collection[nr.declared_key] << notification
end
end
+ #
+ # Get the list of immediate notifications sent by the given resource.
+ #
+ # TODO seriously, this is actually wrong. resource.name is not unique,
+ # you need the type as well.
+ #
+ # @return [Array[Notification]]
+ #
def immediate_notifications(resource)
if resource.instance_of?(Chef::Resource)
- return @immediate_notification_collection[resource.name]
+ return immediate_notification_collection[resource.name]
else
- return @immediate_notification_collection[resource.declared_key]
+ return immediate_notification_collection[resource.declared_key]
end
end
+ #
+ # Get the list of delayed (end of run) notifications sent by the given
+ # resource.
+ #
+ # TODO seriously, this is actually wrong. resource.name is not unique,
+ # you need the type as well.
+ #
+ # @return [Array[Notification]]
+ #
def delayed_notifications(resource)
if resource.instance_of?(Chef::Resource)
- return @delayed_notification_collection[resource.name]
+ return delayed_notification_collection[resource.name]
else
- return @delayed_notification_collection[resource.declared_key]
+ return delayed_notification_collection[resource.declared_key]
end
end
+ #
+ # Cookbook and recipe loading
+ #
+
+ #
# Evaluates the recipes +recipe_names+. Used by DSL::IncludeRecipe
+ #
+ # @param recipe_names [Array[String]] The list of recipe names (e.g.
+ # 'my_cookbook' or 'my_cookbook::my_resource').
+ # @param current_cookbook The cookbook we are currently running in.
+ #
+ # @see DSL::IncludeRecipe#include_recipe
+ #
def include_recipe(*recipe_names, current_cookbook: nil)
result_recipes = Array.new
recipe_names.flatten.each do |recipe_name|
@@ -147,7 +252,21 @@ class Chef
result_recipes
end
+ #
# Evaluates the recipe +recipe_name+. Used by DSL::IncludeRecipe
+ #
+ # TODO I am sort of confused why we have both this and include_recipe ...
+ # I don't see anything different beyond accepting and returning an
+ # array of recipes.
+ #
+ # @param recipe_names [Array[String]] The recipe name (e.g 'my_cookbook' or
+ # 'my_cookbook::my_resource').
+ # @param current_cookbook The cookbook we are currently running in.
+ #
+ # @return A truthy value if the load occurred; `false` if already loaded.
+ #
+ # @see DSL::IncludeRecipe#load_recipe
+ #
def load_recipe(recipe_name, current_cookbook: nil)
Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe")
@@ -175,6 +294,15 @@ ERROR_MESSAGE
end
end
+ #
+ # Load the given recipe from a filename.
+ #
+ # @param recipe_file [String] The recipe filename.
+ #
+ # @return [Chef::Recipe] The loaded recipe.
+ #
+ # @raise [Chef::Exceptions::RecipeNotFound] If the file does not exist.
+ #
def load_recipe_file(recipe_file)
if !File.exist?(recipe_file)
raise Chef::Exceptions::RecipeNotFound, "could not find recipe file #{recipe_file}"
@@ -186,8 +314,19 @@ ERROR_MESSAGE
recipe
end
- # Looks up an attribute file given the +cookbook_name+ and
- # +attr_file_name+. Used by DSL::IncludeAttribute
+ #
+ # Look up an attribute filename.
+ #
+ # @param cookbook_name [String] The cookbook name of the attribute file.
+ # @param attr_file_name [String] The attribute file's name (not path).
+ #
+ # @return [String] The filename.
+ #
+ # @see DSL::IncludeAttribute#include_attribute
+ #
+ # @raise [Chef::Exceptions::CookbookNotFound] If the cookbook could not be found.
+ # @raise [Chef::Exceptions::AttributeNotFound] If the attribute file could not be found.
+ #
def resolve_attribute(cookbook_name, attr_file_name)
cookbook = cookbook_collection[cookbook_name]
raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{cookbook_name} while loading attribute #{name}" unless cookbook
@@ -198,76 +337,152 @@ ERROR_MESSAGE
attribute_filename
end
- # An Array of all recipes that have been loaded. This is stored internally
- # as a Hash, so ordering is predictable.
#
- # Recipe names are given in fully qualified form, e.g., the recipe "nginx"
- # will be given as "nginx::default"
+ # A list of all recipes that have been loaded.
+ #
+ # This is stored internally as a Hash, so ordering is predictable.
+ #
+ # TODO is the above statement true in a 1.9+ ruby world? Is it relevant?
+ #
+ # @return [Array[String]] A list of recipes in fully qualified form, e.g.
+ # the recipe "nginx" will be given as "nginx::default".
+ #
+ # @see #loaded_recipe? To determine if a particular recipe has been loaded.
#
- # To determine if a particular recipe has been loaded, use #loaded_recipe?
def loaded_recipes
- @loaded_recipes.keys
+ loaded_recipes_hash.keys
end
- # An Array of all attributes files that have been loaded. Stored internally
- # using a Hash, so order is predictable.
#
- # Attribute file names are given in fully qualified form, e.g.,
- # "nginx::default" instead of "nginx".
+ # A list of all attributes files that have been loaded.
+ #
+ # Stored internally using a Hash, so order is predictable.
+ #
+ # TODO is the above statement true in a 1.9+ ruby world? Is it relevant?
+ #
+ # @return [Array[String]] A list of attribute file names in fully qualified
+ # form, e.g. the "nginx" will be given as "nginx::default".
+ #
def loaded_attributes
- @loaded_attributes.keys
+ loaded_attributes_hash.keys
end
+ #
+ # Find out if a given recipe has been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param recipe [String] Recipe name.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_fully_qualified_recipe?(cookbook, recipe)
- @loaded_recipes.has_key?("#{cookbook}::#{recipe}")
+ loaded_recipes_hash.has_key?("#{cookbook}::#{recipe}")
end
- # Returns true if +recipe+ has been loaded, false otherwise. Default recipe
- # names are expanded, so `loaded_recipe?("nginx")` and
- # `loaded_recipe?("nginx::default")` are valid and give identical results.
+ #
+ # Find out if a given recipe has been loaded.
+ #
+ # @param recipe [String] Recipe name. "nginx" and "nginx::default" yield
+ # the same results.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_recipe?(recipe)
cookbook, recipe_name = Chef::Recipe.parse_recipe_name(recipe)
loaded_fully_qualified_recipe?(cookbook, recipe_name)
end
+ #
+ # Mark a given recipe as having been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param recipe [String] Recipe name.
+ #
+ def loaded_recipe(cookbook, recipe)
+ loaded_recipes_hash["#{cookbook}::#{recipe}"] = true
+ end
+
+ #
+ # Find out if a given attribute file has been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param attribute_file [String] Attribute file name.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_fully_qualified_attribute?(cookbook, attribute_file)
- @loaded_attributes.has_key?("#{cookbook}::#{attribute_file}")
+ loaded_attributes_hash.has_key?("#{cookbook}::#{attribute_file}")
end
+ #
+ # Mark a given attribute file as having been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param attribute_file [String] Attribute file name.
+ #
def loaded_attribute(cookbook, attribute_file)
- @loaded_attributes["#{cookbook}::#{attribute_file}"] = true
+ loaded_attributes_hash["#{cookbook}::#{attribute_file}"] = true
end
##
# Cookbook File Introspection
+ #
+ # Find out if the cookbook has the given template.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param template_name [String] Template name.
+ #
+ # @return [Boolean] `true` if the template is in the cookbook, `false`
+ # otherwise.
+ # @see Chef::CookbookVersion#has_template_for_node?
+ #
def has_template_in_cookbook?(cookbook, template_name)
cookbook = cookbook_collection[cookbook]
cookbook.has_template_for_node?(node, template_name)
end
+ #
+ # Find out if the cookbook has the given file.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param cb_file_name [String] File name.
+ #
+ # @return [Boolean] `true` if the file is in the cookbook, `false`
+ # otherwise.
+ # @see Chef::CookbookVersion#has_cookbook_file_for_node?
+ #
def has_cookbook_file_in_cookbook?(cookbook, cb_file_name)
cookbook = cookbook_collection[cookbook]
cookbook.has_cookbook_file_for_node?(node, cb_file_name)
end
- # Delegates to CookbookCompiler#unreachable_cookbook?
- # Used to raise an error when attempting to load a recipe belonging to a
- # cookbook that is not in the dependency graph. See also: CHEF-4367
+ #
+ # Find out whether the given cookbook is in the cookbook dependency graph.
+ #
+ # @param cookbook_name [String] Cookbook name.
+ #
+ # @return [Boolean] `true` if the cookbook is reachable, `false` otherwise.
+ #
+ # @see Chef::CookbookCompiler#unreachable_cookbook?
def unreachable_cookbook?(cookbook_name)
- @cookbook_compiler.unreachable_cookbook?(cookbook_name)
+ cookbook_compiler.unreachable_cookbook?(cookbook_name)
end
+ #
# Open a stream object that can be printed into and will dispatch to events
#
- # == Arguments
- # options is a hash with these possible options:
- # - name: a string that identifies the stream to the user. Preferably short.
+ # @param name [String] The name of the stream.
+ # @param options [Hash] Other options for the stream.
+ #
+ # @return [EventDispatch::EventsOutputStream] The created stream.
#
- # Pass a block and the stream will be yielded to it, and close on its own
- # at the end of the block.
- def open_stream(options = {})
- stream = EventDispatch::EventsOutputStream.new(events, options)
+ # @yield If a block is passed, it will be run and the stream will be closed
+ # afterwards.
+ # @yieldparam stream [EventDispatch::EventsOutputStream] The created stream.
+ #
+ def open_stream(name: nil, **options)
+ stream = EventDispatch::EventsOutputStream.new(events, name: name, **options)
if block_given?
begin
yield stream
@@ -280,31 +495,130 @@ ERROR_MESSAGE
end
# there are options for how to handle multiple calls to these functions:
- # 1. first call always wins (never change @reboot_info once set).
- # 2. last call always wins (happily change @reboot_info whenever).
+ # 1. first call always wins (never change reboot_info once set).
+ # 2. last call always wins (happily change reboot_info whenever).
# 3. raise an exception on the first conflict.
# 4. disable reboot after this run if anyone ever calls :cancel.
# 5. raise an exception on any second call.
# 6. ?
def request_reboot(reboot_info)
- Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to #{reboot_info.inspect}"
+ Chef::Log::info "Changing reboot status from #{self.reboot_info.inspect} to #{reboot_info.inspect}"
@reboot_info = reboot_info
end
def cancel_reboot
- Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to {}"
+ Chef::Log::info "Changing reboot status from #{reboot_info.inspect} to {}"
@reboot_info = {}
end
def reboot_requested?
- @reboot_info.size > 0
+ reboot_info.size > 0
+ end
+
+ #
+ # Create a child RunContext.
+ #
+ def create_child
+ ChildRunContext.new(self)
end
- private
+ protected
- def loaded_recipe(cookbook, recipe)
- @loaded_recipes["#{cookbook}::#{recipe}"] = true
+ attr_reader :cookbook_compiler
+ attr_reader :loaded_attributes_hash
+ attr_reader :loaded_recipes_hash
+
+ module Deprecated
+ ###
+ # These need to be settable so deploy can run a resource_collection
+ # independent of any cookbooks via +recipe_eval+
+
+ def audits=(value)
+ Chef::Log.deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ end
+
+ def immediate_notification_collection=(value)
+ Chef::Log.deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ end
+
+ def delayed_notification_collection=(value)
+ Chef::Log.deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ end
end
+ prepend Deprecated
+
+
+ #
+ # A child run context. Delegates all root context calls to its parent.
+ #
+ # @api private
+ #
+ class ChildRunContext < RunContext
+ extend Forwardable
+ def_delegators :parent_run_context, *%w(
+ cancel_reboot
+ config
+ cookbook_collection
+ cookbook_compiler
+ definitions
+ events
+ has_cookbook_file_in_cookbook?
+ has_template_in_cookbook?
+ load
+ loaded_attribute
+ loaded_attributes
+ loaded_attributes_hash
+ loaded_fully_qualified_attribute?
+ loaded_fully_qualified_recipe?
+ loaded_recipe
+ loaded_recipe?
+ loaded_recipes
+ loaded_recipes_hash
+ node
+ open_stream
+ reboot_info
+ reboot_info=
+ reboot_requested?
+ request_reboot
+ resolve_attribute
+ unreachable_cookbook?
+ )
+
+ def initialize(parent_run_context)
+ @parent_run_context = parent_run_context
+
+ # We don't call super, because we don't bother initializing stuff we're
+ # going to delegate to the parent anyway. Just initialize things that
+ # every instance needs.
+ initialize_child_state
+ end
+ CHILD_STATE = %w(
+ audits
+ audits=
+ create_child
+ delayed_notification_collection
+ delayed_notification_collection=
+ delayed_notifications
+ immediate_notification_collection
+ immediate_notification_collection=
+ immediate_notifications
+ include_recipe
+ initialize_child_state
+ load_recipe
+ load_recipe_file
+ notifies_immediately
+ notifies_delayed
+ parent_run_context
+ resource_collection
+ resource_collection=
+ ).map { |x| x.to_sym }
+
+ # Verify that we didn't miss any methods
+ missing_methods = superclass.instance_methods(false) - instance_methods(false) - CHILD_STATE
+ if !missing_methods.empty?
+ raise "ERROR: not all methods of RunContext accounted for in ChildRunContext! All methods must be marked as child methods with CHILD_STATE or delegated to the parent_run_context. Missing #{missing_methods.join(", ")}."
+ end
+ end
end
end
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index 80fd422c55..743a99824d 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -21,7 +21,7 @@
class Chef
CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
- VERSION = '12.4.0.rc.2'
+ VERSION = '12.5.0.current.0'
end
#
diff --git a/spec/data/run_context/cookbooks/include/recipes/default.rb b/spec/data/run_context/cookbooks/include/recipes/default.rb
new file mode 100644
index 0000000000..8d22994252
--- /dev/null
+++ b/spec/data/run_context/cookbooks/include/recipes/default.rb
@@ -0,0 +1,24 @@
+module ::RanResources
+ def self.resources
+ @resources ||= []
+ end
+end
+class RunContextCustomResource < Chef::Resource
+ action :create do
+ ruby_block '4' do
+ block { RanResources.resources << 4 }
+ end
+ recipe_eval do
+ ruby_block '1' do
+ block { RanResources.resources << 1 }
+ end
+ include_recipe 'include::includee'
+ ruby_block '3' do
+ block { RanResources.resources << 3 }
+ end
+ end
+ ruby_block '5' do
+ block { RanResources.resources << 5 }
+ end
+ end
+end
diff --git a/spec/data/run_context/cookbooks/include/recipes/includee.rb b/spec/data/run_context/cookbooks/include/recipes/includee.rb
new file mode 100644
index 0000000000..87bb7f114e
--- /dev/null
+++ b/spec/data/run_context/cookbooks/include/recipes/includee.rb
@@ -0,0 +1,3 @@
+ruby_block '2' do
+ block { RanResources.resources << 2 }
+end
diff --git a/spec/integration/recipes/lwrp_spec.rb b/spec/integration/recipes/lwrp_spec.rb
index e93763fddc..7ecdfc7c3a 100644
--- a/spec/integration/recipes/lwrp_spec.rb
+++ b/spec/integration/recipes/lwrp_spec.rb
@@ -45,12 +45,8 @@ log_level :warn
EOM
result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" --no-color -F doc -o 'l-w-r-p::default'", :cwd => chef_dir)
- actual = result.stdout.lines.map { |l| l.chomp }.join("\n")
- expected = <<EOM
- * l_w_r_p_foo[me] action create (up to date)
-EOM
- expected = expected.lines.map { |l| l.chomp }.join("\n")
- expect(actual).to include(expected)
+ expect(result.stdout).to match(/\* l_w_r_p_foo\[me\] action create \(up to date\)/)
+ expect(result.stdout).not_to match(/WARN: You are overriding l_w_r_p_foo/)
result.error!
end
end
diff --git a/spec/integration/recipes/recipe_dsl_spec.rb b/spec/integration/recipes/recipe_dsl_spec.rb
index 3f4bf9fd5f..6bbb9a5c4c 100644
--- a/spec/integration/recipes/recipe_dsl_spec.rb
+++ b/spec/integration/recipes/recipe_dsl_spec.rb
@@ -15,13 +15,8 @@ describe "Recipe DSL methods" do
before(:context) {
class BaseThingy < Chef::Resource
- def initialize(*args, &block)
- super
- @allowed_actions = [ :create ]
- @action = :create
- end
-
resource_name 'base_thingy'
+ default_action :create
class<<self
attr_accessor :created_resource
@@ -61,11 +56,7 @@ describe "Recipe DSL methods" do
before(:context) {
class Chef::Resource::BackcompatThingy < Chef::Resource
- def initialize(*args, &block)
- super
- @allowed_actions = [ :create ]
- @action = :create
- end
+ default_action :create
end
class Chef::Provider::BackcompatThingy < Chef::Provider
def load_current_resource
@@ -100,7 +91,7 @@ describe "Recipe DSL methods" do
recipe = converge {
backcompat_thingy 'blah' do; end
}
- expect(recipe.logged_warnings).to eq ''
+ expect(recipe.logged_warnings).to match(/Class Chef::Provider::BackcompatThingy does not declare 'resource_name :backcompat_thingy'./)
expect(BaseThingy.created_resource).not_to be_nil
end
end
@@ -114,19 +105,17 @@ describe "Recipe DSL methods" do
}
- it "bar_thingy works" do
- recipe = converge {
+ it "bar_thingy does not work" do
+ expect_converge {
bar_thingy 'blah' do; end
- }
- expect(recipe.logged_warnings).to eq ''
- expect(BaseThingy.created_resource).to eq(RecipeDSLSpecNamespace::Bar::BarThingy)
+ }.to raise_error(NoMethodError)
end
end
- context "With a resource named NoNameThingy with resource_name nil" do
+ context "With a resource named Chef::Resource::NoNameThingy with resource_name nil" do
before(:context) {
- class NoNameThingy < BaseThingy
+ class Chef::Resource::NoNameThingy < BaseThingy
resource_name nil
end
@@ -134,7 +123,7 @@ describe "Recipe DSL methods" do
it "no_name_thingy does not work" do
expect_converge {
- thingy 'blah' do; end
+ no_name_thingy 'blah' do; end
}.to raise_error(NoMethodError)
end
end
@@ -199,6 +188,7 @@ describe "Recipe DSL methods" do
before(:context) {
class AnotherNoNameThingy3 < BaseThingy
+ resource_name :another_no_name_thingy_3
provides :another_no_name_thingy3, os: 'blarghle'
end
@@ -227,6 +217,7 @@ describe "Recipe DSL methods" do
before(:context) {
class AnotherNoNameThingy4 < BaseThingy
+ resource_name :another_no_name_thingy_4
provides :another_no_name_thingy4, os: 'blarghle'
provides :another_no_name_thingy4, platform_family: 'foo'
end
@@ -418,6 +409,7 @@ describe "Recipe DSL methods" do
before {
eval <<-EOM, nil, __FILE__, __LINE__+1
class #{class_name} < BaseThingy
+ resource_name #{dsl_method.inspect}
end
EOM
}
@@ -426,11 +418,12 @@ describe "Recipe DSL methods" do
eval <<-EOM, nil, __FILE__, __LINE__+1
module BlahModule
class #{class_name} < BaseThingy
+ resource_name #{dsl_method.inspect}
end
end
EOM
}
- it "two_classes_one_dsl resolves to BlahModule::TwoClassesOneDsl (last declared)" do
+ it "two_classes_one_dsl resolves to BlahModule::TwoClassesOneDsl (alphabetical)" do
dsl_method = self.dsl_method
recipe = converge {
instance_eval("#{dsl_method} 'blah' do; end")
@@ -491,6 +484,7 @@ describe "Recipe DSL methods" do
eval <<-EOM, nil, __FILE__, __LINE__+1
module BlahModule
class BlahModule::#{class_name} < BaseThingy
+ resource_name #{dsl_method.inspect}
provides #{dsl_method.inspect}, os: 'blarghle'
end
end
@@ -572,11 +566,11 @@ describe "Recipe DSL methods" do
}
- it "thingy3 works in a recipe and yields Foo::Thingy4 (the explicit one)" do
+ it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
recipe = converge {
thingy3 'blah' do; end
}
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy4
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
end
it "thingy4 does not work in a recipe" do
@@ -586,16 +580,19 @@ describe "Recipe DSL methods" do
end
it "resource_matching_short_name returns Thingy4" do
- expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy4
+ expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
end
end
end
- context "when Thingy5 has resource_name :thingy5" do
+ context "when Thingy5 has resource_name :thingy5 and provides :thingy5reverse, :thingy5_2 and :thingy5_2reverse" do
before(:context) {
class RecipeDSLSpecNamespace::Thingy5 < BaseThingy
resource_name :thingy5
+ provides :thingy5reverse
+ provides :thingy5_2
+ provides :thingy5_2reverse
end
}
@@ -611,6 +608,7 @@ describe "Recipe DSL methods" do
before(:context) {
class RecipeDSLSpecNamespace::Thingy6 < BaseThingy
+ resource_name :thingy6
provides :thingy5
end
@@ -623,23 +621,151 @@ describe "Recipe DSL methods" do
expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy6
end
- it "thingy5 works in a recipe and yields Foo::Thingy6 (the later one)" do
+ it "thingy5 works in a recipe and yields Foo::Thingy5 (the alphabetical one)" do
recipe = converge {
thingy5 'blah' do; end
}
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy6
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
end
it "resource_matching_short_name returns Thingy5" do
expect(Chef::Resource.resource_matching_short_name(:thingy5)).to eq RecipeDSLSpecNamespace::Thingy5
end
+
+ context "and AThingy5 provides :thingy5reverse" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::AThingy5 < BaseThingy
+ resource_name :thingy5reverse
+ end
+
+ }
+
+ it "thingy5reverse works in a recipe and yields AThingy5 (the alphabetical one)" do
+ recipe = converge {
+ thingy5reverse 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::AThingy5
+ end
+ end
+
+ context "and ZRecipeDSLSpecNamespace::Thingy5 provides :thingy5_2" do
+ before(:context) {
+
+ module ZRecipeDSLSpecNamespace
+ class Thingy5 < BaseThingy
+ resource_name :thingy5_2
+ end
+ end
+
+ }
+
+ it "thingy5_2 works in a recipe and yields the RecipeDSLSpaceNamespace one (the alphabetical one)" do
+ recipe = converge {
+ thingy5_2 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+ end
+ end
+
+ context "and ARecipeDSLSpecNamespace::Thingy5 provides :thingy5_2" do
+ before(:context) {
+
+ module ARecipeDSLSpecNamespace
+ class Thingy5 < BaseThingy
+ resource_name :thingy5_2reverse
+ end
+ end
+
+ }
+
+ it "thingy5_2reverse works in a recipe and yields the ARecipeDSLSpaceNamespace one (the alphabetical one)" do
+ recipe = converge {
+ thingy5_2reverse 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq ARecipeDSLSpecNamespace::Thingy5
+ end
+ end
end
+
+ context "when Thingy3 has resource_name :thingy3" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy3 < BaseThingy
+ resource_name :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 RecipeDSLSpecNamespace::Thingy3
+ end
+
+ context "and Thingy4 has resource_name :thingy3" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy4 < BaseThingy
+ resource_name :thingy3
+ end
+
+ }
+
+ it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
+ recipe = converge {
+ thingy3 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+
+ it "thingy4 does not work in a recipe" do
+ expect_converge {
+ thingy4 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+
+ it "resource_matching_short_name returns Thingy4" do
+ expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+ end
+
+ context "and Thingy4 has resource_name :thingy3" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy4 < BaseThingy
+ resource_name :thingy3
+ end
+
+ }
+
+ it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
+ recipe = converge {
+ thingy3 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+
+ it "thingy4 does not work in a recipe" do
+ expect_converge {
+ thingy4 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+
+ it "resource_matching_short_name returns Thingy4" do
+ expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+ end
+ end
+
end
context "when Thingy7 provides :thingy8" do
before(:context) {
class RecipeDSLSpecNamespace::Thingy7 < BaseThingy
+ resource_name :thingy7
provides :thingy8
end
@@ -661,11 +787,11 @@ describe "Recipe DSL methods" do
expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy7
end
- it "thingy8 works in a recipe and yields Thingy8 (the later one)" do
+ it "thingy8 works in a recipe and yields Thingy7 (alphabetical)" do
recipe = converge {
thingy8 'blah' do; end
}
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy8
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy7
end
it "resource_matching_short_name returns Thingy8" do
@@ -674,36 +800,36 @@ describe "Recipe DSL methods" do
end
end
- context "when Thingy5 provides :thingy5, :twizzle and :twizzle2" do
+ context "when Thingy12 provides :thingy12, :twizzle and :twizzle2" do
before(:context) {
- class RecipeDSLSpecNamespace::Thingy5 < BaseThingy
- resource_name :thingy5
+ class RecipeDSLSpecNamespace::Thingy12 < BaseThingy
+ resource_name :thingy12
provides :twizzle
provides :twizzle2
end
}
- it "thingy5 works in a recipe and yields Thingy5" do
+ it "thingy12 works in a recipe and yields Thingy12" do
expect_recipe {
- thingy5 'blah' do; end
+ thingy12 'blah' do; end
}.to emit_no_warnings_or_errors
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
end
- it "twizzle works in a recipe and yields Thingy5" do
+ it "twizzle works in a recipe and yields Thingy12" do
expect_recipe {
twizzle 'blah' do; end
}.to emit_no_warnings_or_errors
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
end
- it "twizzle2 works in a recipe and yields Thingy5" do
+ it "twizzle2 works in a recipe and yields Thingy12" do
expect_recipe {
twizzle2 'blah' do; end
}.to emit_no_warnings_or_errors
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
end
end
@@ -752,6 +878,95 @@ describe "Recipe DSL methods" do
}.to raise_error(Chef::Exceptions::NoSuchResourceType)
end
end
+
+ context "when Thingy9 provides :thingy9" do
+ before(:context) {
+ class RecipeDSLSpecNamespace::Thingy9 < BaseThingy
+ resource_name :thingy9
+ end
+ }
+
+ it "declaring a resource providing the same :thingy9 produces a warning" do
+ expect(Chef::Log).to receive(:warn).with("You declared a new resource RecipeDSLSpecNamespace::Thingy9AlternateProvider for resource thingy9, but it comes alphabetically after RecipeDSLSpecNamespace::Thingy9 and has the same filters ({}), so it will not be used. Use override: true if you want to use it for thingy9.")
+ class RecipeDSLSpecNamespace::Thingy9AlternateProvider < BaseThingy
+ resource_name :thingy9
+ end
+ end
+ end
+
+ context "when Thingy10 provides :thingy10" do
+ before(:context) {
+ class RecipeDSLSpecNamespace::Thingy10 < BaseThingy
+ resource_name :thingy10
+ end
+ }
+
+ it "declaring a resource providing the same :thingy10 with override: true does not produce a warning" do
+ expect(Chef::Log).not_to receive(:warn)
+ class RecipeDSLSpecNamespace::Thingy10AlternateProvider < BaseThingy
+ provides :thingy10, override: true
+ end
+ end
+ end
+
+ context "when Thingy11 provides :thingy11" do
+ before(:context) {
+ class RecipeDSLSpecNamespace::Thingy11 < BaseThingy
+ resource_name :thingy10
+ end
+ }
+
+ it "declaring a resource providing the same :thingy11 with os: 'linux' does not produce a warning" do
+ expect(Chef::Log).not_to receive(:warn)
+ class RecipeDSLSpecNamespace::Thingy11AlternateProvider < BaseThingy
+ provides :thingy11, os: 'linux'
+ end
+ end
+ end
+ end
+ end
+
+ before(:all) { Namer.current_index = 0 }
+ before { Namer.current_index += 1 }
+
+ context "with an LWRP that declares actions" do
+ let(:resource_class) {
+ Class.new(Chef::Resource::LWRPBase) do
+ provides :"recipe_dsl_spec#{Namer.current_index}"
+ actions :create
+ end
+ }
+ let(:resource) {
+ resource_class.new("blah", run_context)
+ }
+ it "The actions are part of actions along with :nothing" do
+ expect(resource_class.actions).to eq [ :nothing, :create ]
+ end
+ it "The actions are part of allowed_actions along with :nothing" do
+ expect(resource.allowed_actions).to eq [ :nothing, :create ]
+ end
+
+ context "and a subclass that declares more actions" do
+ let(:subresource_class) {
+ Class.new(Chef::Resource::LWRPBase) do
+ provides :"recipe_dsl_spec_sub#{Namer.current_index}"
+ actions :delete
+ end
+ }
+ let(:subresource) {
+ subresource_class.new("subblah", run_context)
+ }
+
+ it "The parent class actions are not part of actions" do
+ expect(subresource_class.actions).to eq [ :nothing, :delete ]
+ end
+ it "The parent class actions are not part of allowed_actions" do
+ expect(subresource.allowed_actions).to eq [ :nothing, :delete ]
+ end
+ it "The parent class actions do not change" do
+ expect(resource_class.actions).to eq [ :nothing, :create ]
+ expect(resource.allowed_actions).to eq [ :nothing, :create ]
+ end
end
end
end
diff --git a/spec/integration/recipes/resource_action_spec.rb b/spec/integration/recipes/resource_action_spec.rb
new file mode 100644
index 0000000000..4786294803
--- /dev/null
+++ b/spec/integration/recipes/resource_action_spec.rb
@@ -0,0 +1,343 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Resource.action" do
+ include IntegrationSupport
+
+ def converge(str=nil, file=nil, line=nil, &block)
+ if block
+ super(&block)
+ else
+ super() do
+ eval(str, nil, file, line)
+ end
+ end
+ end
+
+ shared_context "ActionJackson" do
+ it "The default action is the first declared action" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+ expect(ActionJackson.succeeded).to eq true
+ end
+
+ it "The action can access recipe DSL" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_recipe_dsl
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+ expect(ActionJackson.succeeded).to eq true
+ end
+
+ it "The action can access attributes" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_attribute
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_attribute
+ expect(ActionJackson.succeeded).to eq 'foo!'
+ end
+
+ it "The action can access public methods" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_method
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_method
+ expect(ActionJackson.succeeded).to eq 'foo_public!'
+ end
+
+ it "The action can access protected methods" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_protected_method
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_protected_method
+ expect(ActionJackson.succeeded).to eq 'foo_protected!'
+ end
+
+ it "The action cannot access private methods" do
+ expect {
+ converge(<<-EOM, __FILE__, __LINE__+1)
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_private_method
+ end
+ EOM
+ }.to raise_error(NameError)
+ expect(ActionJackson.ran_action).to eq :access_private_method
+ end
+
+ it "The action cannot access resource instance variables" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_instance_variable
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_instance_variable
+ expect(ActionJackson.succeeded).to be_nil
+ end
+
+ it "The action does not compile until the prior resource has converged" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ ruby_block 'wow' do
+ block do
+ ActionJackson.ruby_block_converged = 'ruby_block_converged!'
+ end
+ end
+
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_class_method
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_class_method
+ expect(ActionJackson.succeeded).to eq 'ruby_block_converged!'
+ end
+
+ it "The action's resources converge before the next resource converges" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_attribute
+ end
+
+ ruby_block 'wow' do
+ block do
+ ActionJackson.ruby_block_converged = ActionJackson.succeeded
+ end
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_attribute
+ expect(ActionJackson.succeeded).to eq 'foo!'
+ expect(ActionJackson.ruby_block_converged).to eq 'foo!'
+ end
+ end
+
+ context "With resource 'action_jackson'" do
+ before(:context) {
+ class ActionJackson < Chef::Resource
+ use_automatic_resource_name
+ def foo(value=nil)
+ @foo = value if value
+ @foo
+ end
+ def blarghle(value=nil)
+ @blarghle = value if value
+ @blarghle
+ end
+
+ class <<self
+ attr_accessor :ran_action
+ attr_accessor :succeeded
+ attr_accessor :ruby_block_converged
+ end
+
+ public
+ def foo_public
+ 'foo_public!'
+ end
+ protected
+ def foo_protected
+ 'foo_protected!'
+ end
+ private
+ def foo_private
+ 'foo_private!'
+ end
+
+ public
+ action :access_recipe_dsl do
+ ActionJackson.ran_action = :access_recipe_dsl
+ ruby_block 'hi there' do
+ block do
+ ActionJackson.succeeded = true
+ end
+ end
+ end
+ action :access_attribute do
+ ActionJackson.ran_action = :access_attribute
+ ActionJackson.succeeded = foo
+ ActionJackson.succeeded += " #{blarghle}" if blarghle
+ ActionJackson.succeeded += " #{bar}" if respond_to?(:bar)
+ end
+ action :access_attribute2 do
+ ActionJackson.ran_action = :access_attribute2
+ ActionJackson.succeeded = foo
+ ActionJackson.succeeded += " #{blarghle}" if blarghle
+ ActionJackson.succeeded += " #{bar}" if respond_to?(:bar)
+ end
+ action :access_method do
+ ActionJackson.ran_action = :access_method
+ ActionJackson.succeeded = foo_public
+ end
+ action :access_protected_method do
+ ActionJackson.ran_action = :access_protected_method
+ ActionJackson.succeeded = foo_protected
+ end
+ action :access_private_method do
+ ActionJackson.ran_action = :access_private_method
+ ActionJackson.succeeded = foo_private
+ end
+ action :access_instance_variable do
+ ActionJackson.ran_action = :access_instance_variable
+ ActionJackson.succeeded = @foo
+ end
+ action :access_class_method do
+ ActionJackson.ran_action = :access_class_method
+ ActionJackson.succeeded = ActionJackson.ruby_block_converged
+ end
+ end
+ }
+ before(:each) {
+ ActionJackson.ran_action = :error
+ ActionJackson.succeeded = :error
+ ActionJackson.ruby_block_converged = :error
+ }
+
+ it_behaves_like "ActionJackson" do
+ let(:resource_dsl) { :action_jackson }
+ end
+
+ context "And 'action_jackgrandson' inheriting from ActionJackson and changing nothing" do
+ before(:context) {
+ class ActionJackgrandson < ActionJackson
+ use_automatic_resource_name
+ end
+ }
+
+ it_behaves_like "ActionJackson" do
+ let(:resource_dsl) { :action_jackgrandson }
+ end
+ end
+
+ context "And 'action_jackalope' inheriting from ActionJackson with an extra attribute and action" do
+ before(:context) {
+ class ActionJackalope < ActionJackson
+ use_automatic_resource_name
+
+ def foo(value=nil)
+ @foo = "#{value}alope" if value
+ @foo
+ end
+ def bar(value=nil)
+ @bar = "#{value}alope" if value
+ @bar
+ end
+ class <<self
+ attr_accessor :jackalope_ran
+ end
+ action :access_jackalope do
+ ActionJackalope.jackalope_ran = :access_jackalope
+ ActionJackalope.succeeded = "#{foo} #{blarghle} #{bar}"
+ end
+ action :access_attribute do
+ super()
+ ActionJackalope.jackalope_ran = :access_attribute
+ ActionJackalope.succeeded = ActionJackson.succeeded
+ end
+ end
+ }
+ before do
+ ActionJackalope.jackalope_ran = nil
+ end
+
+ context "action_jackson still behaves the same" do
+ it_behaves_like "ActionJackson" do
+ let(:resource_dsl) { :action_jackson }
+ end
+ end
+
+ it "The default action remains the same even though new actions were specified first" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ end
+ }
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+ expect(ActionJackson.succeeded).to eq true
+ end
+
+ it "new actions run, and can access overridden, new, and overridden attributes" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ blarghle 'blarghle!'
+ action :access_jackalope
+ end
+ }
+ expect(ActionJackalope.jackalope_ran).to eq :access_jackalope
+ expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope"
+ end
+
+ it "overridden actions run, call super, and can access overridden, new, and overridden attributes" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ blarghle 'blarghle!'
+ action :access_attribute
+ end
+ }
+ expect(ActionJackson.ran_action).to eq :access_attribute
+ expect(ActionJackson.succeeded).to eq "foo!alope blarghle! bar!alope"
+ expect(ActionJackalope.jackalope_ran).to eq :access_attribute
+ expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope"
+ end
+
+ it "non-overridden actions run and can access overridden and non-overridden variables (but not necessarily new ones)" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ blarghle 'blarghle!'
+ action :access_attribute2
+ end
+ }
+ expect(ActionJackson.ran_action).to eq :access_attribute2
+ expect(ActionJackson.succeeded).to eq("foo!alope blarghle! bar!alope").or(eq("foo!alope blarghle!"))
+ end
+ end
+ end
+
+ context "With a resource with no actions" do
+ before(:context) {
+ class NoActionJackson < Chef::Resource
+ use_automatic_resource_name
+
+ def foo(value=nil)
+ @foo = value if value
+ @foo
+ end
+
+ class <<self
+ attr_accessor :action_was
+ end
+ end
+ }
+ it "The default action is :nothing" do
+ converge {
+ no_action_jackson 'hi' do
+ foo 'foo!'
+ NoActionJackson.action_was = action
+ end
+ }
+ expect(NoActionJackson.action_was).to eq :nothing
+ end
+ end
+end
diff --git a/spec/support/shared/shared_examples.rb b/spec/support/shared/shared_examples.rb
index b20c65f8b6..550fa2eb68 100644
--- a/spec/support/shared/shared_examples.rb
+++ b/spec/support/shared/shared_examples.rb
@@ -1,7 +1,7 @@
# For storing any examples shared between multiple tests
# Any object which defines a .to_json should import this test
-shared_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+shared_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) {
raise "You must define the subject when including this test"
diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb
index ba0eca3284..bc4b38848b 100644
--- a/spec/unit/api_client_spec.rb
+++ b/spec/unit/api_client_spec.rb
@@ -144,7 +144,7 @@ describe Chef::ApiClient do
expect(@json).not_to include("private_key")
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @client }
end
end
diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb
index 1e4bbb5c56..8146774764 100644
--- a/spec/unit/client_spec.rb
+++ b/spec/unit/client_spec.rb
@@ -238,23 +238,24 @@ describe Chef::Client do
describe "when converge completes successfully" do
include_context "a client run"
include_context "converge completed"
-
- describe "when audit phase errors" do
- include_context "audit phase failed with error"
- include_examples "a completed run with audit failure" do
- let(:run_errors) { [audit_error] }
+ context 'when audit mode is enabled' do
+ describe "when audit phase errors" do
+ include_context "audit phase failed with error"
+ include_examples "a completed run with audit failure" do
+ let(:run_errors) { [audit_error] }
+ end
end
- end
- describe "when audit phase completed" do
- include_context "audit phase completed"
- include_examples "a completed run"
- end
+ describe "when audit phase completed" do
+ include_context "audit phase completed"
+ include_examples "a completed run"
+ end
- describe "when audit phase completed with failed controls" do
- include_context "audit phase completed with failed controls"
- include_examples "a completed run with audit failure" do
- let(:run_errors) { [audit_error] }
+ describe "when audit phase completed with failed controls" do
+ include_context "audit phase completed with failed controls"
+ include_examples "a completed run with audit failure" do
+ let(:run_errors) { [audit_error] }
+ end
end
end
end
@@ -512,11 +513,26 @@ describe Chef::Client do
allow_any_instance_of(Chef::RunLock).to receive(:save_pid).and_raise(NoMethodError)
end
- it "should run exception handlers on early fail" do
- expect(subject).to receive(:run_failed)
- expect { subject.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
- expect(error.wrapped_errors.size).to eq 1
- expect(error.wrapped_errors).to include(NoMethodError)
+ context 'when audit mode is enabled' do
+ before do
+ Chef::Config[:audit_mode] = :enabled
+ end
+ it "should run exception handlers on early fail" do
+ expect(subject).to receive(:run_failed)
+ expect { subject.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
+ expect(error.wrapped_errors.size).to eq 1
+ expect(error.wrapped_errors).to include(NoMethodError)
+ end
+ end
+ end
+
+ context 'when audit mode is disabled' do
+ before do
+ Chef::Config[:audit_mode] = :disabled
+ end
+ it "should run exception handlers on early fail" do
+ expect(subject).to receive(:run_failed)
+ expect { subject.run }.to raise_error(NoMethodError)
end
end
end
diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb
index 4990aef004..2bccddcaec 100644
--- a/spec/unit/cookbook_version_spec.rb
+++ b/spec/unit/cookbook_version_spec.rb
@@ -336,7 +336,7 @@ describe Chef::CookbookVersion do
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { Chef::CookbookVersion.new("tatft", '/tmp/blah') }
end
diff --git a/spec/unit/data_bag_item_spec.rb b/spec/unit/data_bag_item_spec.rb
index 4348252388..497817ecf1 100644
--- a/spec/unit/data_bag_item_spec.rb
+++ b/spec/unit/data_bag_item_spec.rb
@@ -193,7 +193,7 @@ describe Chef::DataBagItem do
expect(deserial["snooze"]).to eq({ "finally" => "world_will" })
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { data_bag_item }
end
end
diff --git a/spec/unit/data_bag_spec.rb b/spec/unit/data_bag_spec.rb
index bd9a99a1de..13b835d120 100644
--- a/spec/unit/data_bag_spec.rb
+++ b/spec/unit/data_bag_spec.rb
@@ -73,7 +73,7 @@ describe Chef::DataBag do
expect(@deserial.send(t.to_sym)).to eq(@data_bag.send(t.to_sym))
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @data_bag }
end
end
diff --git a/spec/unit/environment_spec.rb b/spec/unit/environment_spec.rb
index ee3b8b21e1..64617e0888 100644
--- a/spec/unit/environment_spec.rb
+++ b/spec/unit/environment_spec.rb
@@ -208,7 +208,7 @@ describe Chef::Environment do
expect(@json).to match(/"chef_type":"environment"/)
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @environment }
end
end
diff --git a/spec/unit/event_dispatch/dsl_spec.rb b/spec/unit/event_dispatch/dsl_spec.rb
index e30b1cb40e..f467ea81ea 100644
--- a/spec/unit/event_dispatch/dsl_spec.rb
+++ b/spec/unit/event_dispatch/dsl_spec.rb
@@ -20,8 +20,29 @@ require 'spec_helper'
require 'chef/event_dispatch/dsl'
describe Chef::EventDispatch::DSL do
+ let(:events) do
+ Chef::EventDispatch::Dispatcher.new
+ end
+
+ let(:run_context) do
+ Chef::RunContext.new(Chef::Node.new, nil, events)
+ end
+
+ before do
+ Chef.set_run_context(run_context)
+ end
+
+ after do
+ Chef.reset!
+ end
+
subject{ described_class.new('test') }
+ it 'set handler name' do
+ subject.on(:run_started) {}
+ expect(events.subscribers.first.name).to eq('test')
+ end
+
it 'raise error when invalid event type is supplied' do
expect do
subject.on(:foo_bar) {}
@@ -30,12 +51,10 @@ describe Chef::EventDispatch::DSL do
it 'register user hooks against valid event type' do
subject.on(:run_failed) {'testhook'}
- expect(Chef::Config[:event_handlers].first.run_failed).to eq('testhook')
+ expect(events.subscribers.first.run_failed).to eq('testhook')
end
it 'preserve state across event hooks' do
- Chef::Config.reset!
- node = Chef::Node.new
calls = []
Chef.event_handler do
on :resource_updated do
@@ -45,17 +64,13 @@ describe Chef::EventDispatch::DSL do
calls << :started
end
end
- events = Chef::EventDispatch::Dispatcher.new(*Chef::Config[:event_handlers])
- rc = Chef::RunContext.new(node, nil, events)
- resource = Chef::Resource::RubyBlock.new('foo', rc)
+ resource = Chef::Resource::RubyBlock.new('foo', run_context)
resource.block { }
resource.run_action(:run)
expect(calls).to eq([:started, :updated])
end
it 'preserve instance variables across handler callbacks' do
- Chef::Config.reset!
- node = Chef::Node.new
Chef.event_handler do
on :resource_action_start do
@ivar = [1]
@@ -64,11 +79,9 @@ describe Chef::EventDispatch::DSL do
@ivar << 2
end
end
- events = Chef::EventDispatch::Dispatcher.new(*Chef::Config[:event_handlers])
- rc = Chef::RunContext.new(node, nil, events)
- resource = Chef::Resource::RubyBlock.new('foo', rc)
+ resource = Chef::Resource::RubyBlock.new('foo', run_context)
resource.block { }
resource.run_action(:run)
- expect(Chef::Config[:event_handlers].first.instance_variable_get(:@ivar)).to eq([1, 2])
+ expect(events.subscribers.first.instance_variable_get(:@ivar)).to eq([1, 2])
end
end
diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb
index fd90aeab71..85c54aa693 100644
--- a/spec/unit/exceptions_spec.rb
+++ b/spec/unit/exceptions_spec.rb
@@ -76,7 +76,7 @@ describe Chef::Exceptions do
end
if exception.methods.include?(:to_json)
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { exception }
end
end
diff --git a/spec/unit/json_compat_spec.rb b/spec/unit/json_compat_spec.rb
index 7482ba8a28..aaf0c48806 100644
--- a/spec/unit/json_compat_spec.rb
+++ b/spec/unit/json_compat_spec.rb
@@ -67,7 +67,7 @@ describe Chef::JSONCompat do
expect(Chef::JSONCompat.to_json_pretty(f)).to eql("{\n \"foo\": 1234,\n \"bar\": {\n \"baz\": 5678\n }\n}\n")
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { Foo.new }
end
end
diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb
index 34c6f6f1c5..784ff966cd 100644
--- a/spec/unit/lwrp_spec.rb
+++ b/spec/unit/lwrp_spec.rb
@@ -409,13 +409,13 @@ describe "LWRP" do
end
end
- context "when the child does not defined the methods" do
+ context "when the child does not define the methods" do
let(:child) do
Class.new(parent)
end
it "delegates #actions to the parent" do
- expect(child.actions).to eq([:eat, :sleep])
+ expect(child.actions).to eq([:nothing, :eat, :sleep])
end
it "delegates #default_action to the parent" do
@@ -432,7 +432,7 @@ describe "LWRP" do
end
it "does not delegate #actions to the parent" do
- expect(child.actions).to eq([:dont_eat, :dont_sleep])
+ expect(child.actions).to eq([:nothing, :dont_eat, :dont_sleep])
end
it "does not delegate #default_action to the parent" do
@@ -457,11 +457,50 @@ describe "LWRP" do
it "amends actions when they are already defined" do
raise_if_deprecated!
- expect(child.actions).to eq([:eat, :sleep, :drink])
+ expect(child.actions).to eq([:nothing, :eat, :sleep, :drink])
end
end
end
+ describe "when actions is set to an array" do
+ let(:resource_class) do
+ Class.new(Chef::Resource::LWRPBase) do
+ actions [ :eat, :sleep ]
+ end
+ end
+ let(:resource) do
+ resource_class.new('blah')
+ end
+ it "actions includes those actions" do
+ expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "allowed_actions includes those actions" do
+ expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "resource.allowed_actions includes those actions" do
+ expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ end
+
+ describe "when allowed_actions is set to an array" do
+ let(:resource_class) do
+ Class.new(Chef::Resource::LWRPBase) do
+ allowed_actions [ :eat, :sleep ]
+ end
+ end
+ let(:resource) do
+ resource_class.new('blah')
+ end
+ it "actions includes those actions" do
+ expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "allowed_actions includes those actions" do
+ expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "resource.allowed_actions includes those actions" do
+ expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ end
end
describe "Lightweight Chef::Provider" do
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
index 5939403ce6..032cb1accb 100644
--- a/spec/unit/node_spec.rb
+++ b/spec/unit/node_spec.rb
@@ -1106,7 +1106,7 @@ describe Chef::Node do
expect(serialized_node.run_list).to eq(node.run_list)
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) {
node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA))
node
diff --git a/spec/unit/osc_user_spec.rb b/spec/unit/osc_user_spec.rb
index 678486a16d..16731b47bd 100644
--- a/spec/unit/osc_user_spec.rb
+++ b/spec/unit/osc_user_spec.rb
@@ -160,7 +160,7 @@ describe Chef::OscUser do
expect(@json).not_to include("password")
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @user }
end
end
diff --git a/spec/unit/property/state_spec.rb b/spec/unit/property/state_spec.rb
new file mode 100644
index 0000000000..80ebe01a41
--- /dev/null
+++ b/spec/unit/property/state_spec.rb
@@ -0,0 +1,491 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource#identity and #state" do
+ include IntegrationSupport
+
+ class NewResourceNamer
+ @i = 0
+ def self.next
+ "chef_resource_property_spec_#{@i += 1}"
+ end
+ end
+
+ def self.new_resource_name
+ NewResourceNamer.next
+ end
+
+ let(:resource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(Chef::Resource) do
+ resource_name new_resource_name
+ end
+ end
+
+ let(:resource) do
+ resource_class.new("blah")
+ end
+
+ def self.english_join(values)
+ return '<nothing>' if values.size == 0
+ return values[0].inspect if values.size == 1
+ "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}"
+ end
+
+ def self.with_property(*properties, &block)
+ tags_index = properties.find_index { |p| !p.is_a?(String)}
+ if tags_index
+ properties, tags = properties[0..tags_index-1], properties[tags_index..-1]
+ else
+ tags = []
+ end
+ properties = properties.map { |property| "property #{property}" }
+ context "With properties #{english_join(properties)}", *tags do
+ before do
+ properties.each do |property_str|
+ resource_class.class_eval(property_str, __FILE__, __LINE__)
+ end
+ end
+ instance_eval(&block)
+ end
+ end
+
+ # identity
+ context "Chef::Resource#identity_attr" do
+ with_property ":x" do
+ # it "name is the default identity" do
+ # expect(resource_class.identity_attr).to eq :name
+ # expect(resource_class.properties[:name].identity?).to be_falsey
+ # expect(resource.name).to eq 'blah'
+ # expect(resource.identity).to eq 'blah'
+ # end
+
+ it "identity_attr :x changes the identity" do
+ expect(resource_class.identity_attr :x).to eq :x
+ expect(resource_class.identity_attr).to eq :x
+ # expect(resource_class.properties[:name].identity?).to be_falsey
+ # expect(resource_class.properties[:x].identity?).to be_truthy
+
+ expect(resource.x 'woo').to eq 'woo'
+ expect(resource.x).to eq 'woo'
+
+ expect(resource.name).to eq 'blah'
+ expect(resource.identity).to eq 'woo'
+ end
+
+ # with_property ":y, identity: true" do
+ # context "and identity_attr :x" do
+ # before do
+ # resource_class.class_eval do
+ # identity_attr :x
+ # end
+ # end
+ #
+ # it "only returns :x as identity" do
+ # resource.x 'foo'
+ # resource.y 'bar'
+ # expect(resource_class.identity_attr).to eq :x
+ # expect(resource.identity).to eq 'foo'
+ # end
+ # it "does not flip y.desired_state off" do
+ # resource.x 'foo'
+ # resource.y 'bar'
+ # expect(resource_class.state_attrs).to eq [ :x, :y ]
+ # expect(resource.state).to eq({ x: 'foo', y: 'bar' })
+ # end
+ # end
+ # end
+
+ context "With a subclass" do
+ let(:subresource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(resource_class) do
+ resource_name new_resource_name
+ end
+ end
+ let(:subresource) do
+ subresource_class.new('sub')
+ end
+
+ it "name is the default identity on the subclass" do
+ # expect(subresource_class.identity_attr).to eq :name
+ # expect(subresource_class.properties[:name].identity?).to be_falsey
+ expect(subresource.name).to eq 'sub'
+ expect(subresource.identity).to eq 'sub'
+ end
+
+ context "With identity_attr :x on the superclass" do
+ before do
+ resource_class.class_eval do
+ identity_attr :x
+ end
+ end
+
+ it "The subclass inherits :x as identity" do
+ expect(subresource_class.identity_attr).to eq :x
+ # expect(subresource_class.properties[:name].identity?).to be_falsey
+ # expect(subresource_class.properties[:x].identity?).to be_truthy
+
+ subresource.x 'foo'
+ expect(subresource.identity).to eq 'foo'
+ end
+
+ # context "With property :y, identity: true on the subclass" do
+ # before do
+ # subresource_class.class_eval do
+ # property :y, identity: true
+ # end
+ # end
+ # it "The subclass's identity includes both x and y" do
+ # expect(subresource_class.identity_attr).to eq :x
+ # subresource.x 'foo'
+ # subresource.y 'bar'
+ # expect(subresource.identity).to eq({ x: 'foo', y: 'bar' })
+ # end
+ # end
+
+ with_property ":y, String" do
+ context "With identity_attr :y on the subclass" do
+ before do
+ subresource_class.class_eval do
+ identity_attr :y
+ end
+ end
+ # it "y is part of state" do
+ # subresource.x 'foo'
+ # subresource.y 'bar'
+ # expect(subresource.state).to eq({ x: 'foo', y: 'bar' })
+ # expect(subresource_class.state_attrs).to eq [ :x, :y ]
+ # end
+ it "y is the identity" do
+ expect(subresource_class.identity_attr).to eq :y
+ subresource.x 'foo'
+ subresource.y 'bar'
+ expect(subresource.identity).to eq 'bar'
+ end
+ it "y still has validation" do
+ expect { subresource.y 12 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ end
+ end
+ end
+ end
+
+ # with_property ":string_only, String, identity: true", ":string_only2, String" do
+ # it "identity_attr does not change validation" do
+ # resource_class.identity_attr :string_only
+ # expect { resource.string_only 12 }.to raise_error Chef::Exceptions::ValidationFailed
+ # expect { resource.string_only2 12 }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # end
+ #
+ # with_property ":x, desired_state: false" do
+ # it "identity_attr does not flip on desired_state" do
+ # resource_class.identity_attr :x
+ # resource.x 'hi'
+ # expect(resource.identity).to eq 'hi'
+ # # expect(resource_class.properties[:x].desired_state?).to be_falsey
+ # expect(resource_class.state_attrs).to eq []
+ # expect(resource.state).to eq({})
+ # end
+ # end
+
+ context "With custom property custom_property defined only as methods, using different variables for storage" do
+ before do
+ resource_class.class_eval do
+ def custom_property
+ @blarghle ? @blarghle*3 : nil
+ end
+ def custom_property=(x)
+ @blarghle = x*2
+ end
+ end
+ end
+
+ context "And identity_attr :custom_property" do
+ before do
+ resource_class.class_eval do
+ identity_attr :custom_property
+ end
+ end
+
+ it "identity_attr comes back as :custom_property" do
+ # expect(resource_class.properties[:custom_property].identity?).to be_truthy
+ expect(resource_class.identity_attr).to eq :custom_property
+ end
+ # it "custom_property becomes part of desired_state" do
+ # resource.custom_property = 1
+ # expect(resource.state).to eq({ custom_property: 6 })
+ # expect(resource_class.properties[:custom_property].desired_state?).to be_truthy
+ # expect(resource_class.state_attrs).to eq [ :custom_property ]
+ # end
+ it "identity_attr does not change custom_property's getter or setter" do
+ resource.custom_property = 1
+ expect(resource.custom_property).to eq 6
+ end
+ it "custom_property is returned as the identity" do
+ expect(resource.identity).to be_nil
+ resource.custom_property = 1
+ expect(resource.identity).to eq 6
+ end
+ end
+ end
+ end
+
+ # context "PropertyType#identity" do
+ # with_property ":x, identity: true" do
+ # it "name is only part of the identity if an identity attribute is defined" do
+ # expect(resource_class.identity_attr).to eq :x
+ # resource.x 'woo'
+ # expect(resource.identity).to eq 'woo'
+ # end
+ # end
+ #
+ # with_property ":x, identity: true, default: 'xxx'",
+ # ":y, identity: true, default: 'yyy'",
+ # ":z, identity: true, default: 'zzz'" do
+ # it "identity_attr returns the first identity attribute if multiple are defined" do
+ # expect(resource_class.identity_attr).to eq :x
+ # end
+ # it "identity returns all identity values in a hash if multiple are defined" do
+ # resource.x 'foo'
+ # resource.y 'bar'
+ # resource.z 'baz'
+ # expect(resource.identity).to eq({ x: 'foo', y: 'bar', z: 'baz' })
+ # end
+ # it "identity returns only identity values that are set, and does not include defaults" do
+ # resource.x 'foo'
+ # resource.z 'baz'
+ # expect(resource.identity).to eq({ x: 'foo', z: 'baz' })
+ # end
+ # it "identity returns only set identity values in a hash, if there is only one set identity value" do
+ # resource.x 'foo'
+ # expect(resource.identity).to eq({ x: 'foo' })
+ # end
+ # it "identity returns an empty hash if no identity values are set" do
+ # expect(resource.identity).to eq({})
+ # end
+ # it "identity_attr wipes out any other identity attributes if multiple are defined" do
+ # resource_class.identity_attr :y
+ # resource.x 'foo'
+ # resource.y 'bar'
+ # resource.z 'baz'
+ # expect(resource.identity).to eq 'bar'
+ # end
+ # end
+ #
+ # with_property ":x, identity: true, name_property: true" do
+ # it "identity when x is not defined returns the value of x" do
+ # expect(resource.identity).to eq 'blah'
+ # end
+ # it "state when x is not defined returns the value of x" do
+ # expect(resource.state).to eq({ x: 'blah' })
+ # end
+ # end
+ # end
+
+ # state_attrs
+ context "Chef::Resource#state_attrs" do
+ it "name is not part of state_attrs" do
+ expect(Chef::Resource.state_attrs).to eq []
+ expect(resource_class.state_attrs).to eq []
+ expect(resource.state).to eq({})
+ end
+
+ # with_property ":x", ":y", ":z" do
+ # it "x, y and z are state attributes" do
+ # resource.x 1
+ # resource.y 2
+ # resource.z 3
+ # expect(resource_class.state_attrs).to eq [ :x, :y, :z ]
+ # expect(resource.state).to eq(x: 1, y: 2, z: 3)
+ # end
+ # it "values that are not set are not included in state" do
+ # resource.x 1
+ # expect(resource.state).to eq(x: 1)
+ # end
+ # it "when no values are set, nothing is included in state" do
+ # end
+ # end
+ #
+ # with_property ":x", ":y, desired_state: false", ":z, desired_state: true" do
+ # it "x and z are state attributes, and y is not" do
+ # resource.x 1
+ # resource.y 2
+ # resource.z 3
+ # expect(resource_class.state_attrs).to eq [ :x, :z ]
+ # expect(resource.state).to eq(x: 1, z: 3)
+ # end
+ # end
+
+ # with_property ":x, name_property: true" do
+ # it "Unset values with name_property are included in state" do
+ # expect(resource.state).to eq(x: 'blah')
+ # end
+ # it "Set values with name_property are included in state" do
+ # resource.x 1
+ # expect(resource.state).to eq(x: 1)
+ # end
+ # end
+
+ # with_property ":x, default: 1" do
+ # it "Unset values with defaults are not included in state" do
+ # expect(resource.state).to eq({})
+ # end
+ # it "Set values with defaults are included in state" do
+ # resource.x 1
+ # expect(resource.state).to eq(x: 1)
+ # end
+ # end
+
+ context "With a class with a normal getter and setter" do
+ before do
+ resource_class.class_eval do
+ def x
+ @blah*3
+ end
+ def x=(value)
+ @blah = value*2
+ end
+ end
+ end
+ it "state_attrs(:x) causes the value to be included in properties" do
+ resource_class.state_attrs(:x)
+ resource.x = 1
+
+ expect(resource.x).to eq 6
+ expect(resource.state).to eq(x: 6)
+ end
+ end
+
+ # with_property ":x, Integer, identity: true" do
+ # it "state_attrs(:x) leaves the property in desired_state" do
+ # resource_class.state_attrs(:x)
+ # resource.x 10
+ #
+ # # expect(resource_class.properties[:x].desired_state?).to be_truthy
+ # expect(resource_class.state_attrs).to eq [ :x ]
+ # expect(resource.state).to eq(x: 10)
+ # end
+ # it "state_attrs(:x) does not turn off validation" do
+ # resource_class.state_attrs(:x)
+ # expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # it "state_attrs(:x) does not turn off identity" do
+ # resource_class.state_attrs(:x)
+ # resource.x 10
+ #
+ # expect(resource_class.identity_attr).to eq :x
+ # # expect(resource_class.properties[:x].identity?).to be_truthy
+ # expect(resource.identity).to eq 10
+ # end
+ # end
+
+ # with_property ":x, Integer, identity: true, desired_state: false" do
+ # before do
+ # resource_class.class_eval do
+ # def y
+ # 20
+ # end
+ # end
+ # end
+ # it "state_attrs(:x) sets the property in desired_state" do
+ # resource_class.state_attrs(:x)
+ # resource.x 10
+ #
+ # # expect(resource_class.properties[:x].desired_state?).to be_truthy
+ # expect(resource_class.state_attrs).to eq [ :x ]
+ # expect(resource.state).to eq(x: 10)
+ # end
+ # it "state_attrs(:x) does not turn off validation" do
+ # resource_class.state_attrs(:x)
+ # expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # it "state_attrs(:x) does not turn off identity" do
+ # resource_class.state_attrs(:x)
+ # resource.x 10
+ #
+ # expect(resource_class.identity_attr).to eq :x
+ # # expect(resource_class.properties[:x].identity?).to be_truthy
+ # expect(resource.identity).to eq 10
+ # end
+ # it "state_attrs(:y) adds y and removes x from desired state" do
+ # resource_class.state_attrs(:y)
+ # resource.x 10
+ #
+ # # expect(resource_class.properties[:x].desired_state?).to be_falsey
+ # # expect(resource_class.properties[:y].desired_state?).to be_truthy
+ # expect(resource_class.state_attrs).to eq [ :y ]
+ # expect(resource.state).to eq(y: 20)
+ # end
+ # it "state_attrs(:y) does not turn off validation" do
+ # resource_class.state_attrs(:y)
+ #
+ # expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # it "state_attrs(:y) does not turn off identity" do
+ # resource_class.state_attrs(:y)
+ # resource.x 10
+ #
+ # expect(resource_class.identity_attr).to eq :x
+ # # expect(resource_class.properties[:x].identity?).to be_truthy
+ # expect(resource.identity).to eq 10
+ # end
+ #
+ # context "With a subclassed resource" do
+ # let(:resource_subclass) do
+ # new_resource_name = self.class.new_resource_name
+ # Class.new(resource_class) do
+ # resource_name new_resource_name
+ # end
+ # end
+ # let(:subresource) do
+ # resource_subclass.new('blah')
+ # end
+ # it "state_attrs(:x) sets the property in desired_state" do
+ # resource_subclass.state_attrs(:x)
+ # subresource.x 10
+ #
+ # # expect(resource_subclass.properties[:x].desired_state?).to be_truthy
+ # expect(resource_subclass.state_attrs).to eq [ :x ]
+ # expect(subresource.state).to eq(x: 10)
+ # end
+ # it "state_attrs(:x) does not turn off validation" do
+ # resource_subclass.state_attrs(:x)
+ # expect { subresource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # it "state_attrs(:x) does not turn off identity" do
+ # resource_subclass.state_attrs(:x)
+ # subresource.x 10
+ #
+ # expect(resource_subclass.identity_attr).to eq :x
+ # # expect(resource_subclass.properties[:x].identity?).to be_truthy
+ # expect(subresource.identity).to eq 10
+ # end
+ # it "state_attrs(:y) adds y and removes x from desired state" do
+ # resource_subclass.state_attrs(:y)
+ # subresource.x 10
+ #
+ # # expect(resource_subclass.properties[:x].desired_state?).to be_falsey
+ # # expect(resource_subclass.properties[:y].desired_state?).to be_truthy
+ # expect(resource_subclass.state_attrs).to eq [ :y ]
+ # expect(subresource.state).to eq(y: 20)
+ # end
+ # it "state_attrs(:y) does not turn off validation" do
+ # resource_subclass.state_attrs(:y)
+ #
+ # expect { subresource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # it "state_attrs(:y) does not turn off identity" do
+ # resource_subclass.state_attrs(:y)
+ # subresource.x 10
+ #
+ # expect(resource_subclass.identity_attr).to eq :x
+ # # expect(resource_subclass.properties[:x].identity?).to be_truthy
+ # expect(subresource.identity).to eq 10
+ # end
+ # end
+ # end
+ end
+
+end
diff --git a/spec/unit/property/validation_spec.rb b/spec/unit/property/validation_spec.rb
new file mode 100644
index 0000000000..e32147bd38
--- /dev/null
+++ b/spec/unit/property/validation_spec.rb
@@ -0,0 +1,652 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource.property validation" do
+ include IntegrationSupport
+
+ module Namer
+ @i = 0
+ def self.next_resource_name
+ "chef_resource_property_spec_#{@i += 1}"
+ end
+ def self.reset_index
+ @current_index = 0
+ end
+ def self.current_index
+ @current_index
+ end
+ def self.next_index
+ @current_index += 1
+ end
+ end
+
+ def lazy(&block)
+ Chef::DelayedEvaluator.new(&block)
+ end
+
+ before do
+ Namer.reset_index
+ end
+
+ def self.new_resource_name
+ Namer.next_resource_name
+ end
+
+ let(:resource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(Chef::Resource) do
+ resource_name new_resource_name
+ def blah
+ Namer.next_index
+ end
+ def self.blah
+ "class#{Namer.next_index}"
+ end
+ end
+ end
+
+ let(:resource) do
+ resource_class.new("blah")
+ end
+
+ def self.english_join(values)
+ return '<nothing>' if values.size == 0
+ return values[0].inspect if values.size == 1
+ "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}"
+ end
+
+ def self.with_property(*properties, &block)
+ tags_index = properties.find_index { |p| !p.is_a?(String)}
+ if tags_index
+ properties, tags = properties[0..tags_index-1], properties[tags_index..-1]
+ else
+ tags = []
+ end
+ properties = properties.map { |property| "property #{property}" }
+ context "With properties #{english_join(properties)}", *tags do
+ before do
+ properties.each do |property_str|
+ resource_class.class_eval(property_str, __FILE__, __LINE__)
+ end
+ end
+ instance_eval(&block)
+ end
+ end
+
+ def self.validation_test(validation, success_values, failure_values, getter_values=[], *tags)
+ with_property ":x, #{validation}", *tags do
+ it "gets nil when retrieving the initial (non-set) value" do
+ expect(resource.x).to be_nil
+ end
+ success_values.each do |v|
+ it "value #{v.inspect} is valid" do
+ resource.instance_eval { @x = 'default' }
+ expect(resource.x v).to eq v
+ expect(resource.x).to eq v
+ end
+ end
+ failure_values.each do |v|
+ it "value #{v.inspect} is invalid" do
+ expect { resource.x v }.to raise_error Chef::Exceptions::ValidationFailed
+ resource.instance_eval { @x = 'default' }
+ expect { resource.x v }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ getter_values.each do |v|
+ it "setting value to #{v.inspect} does not change the value" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ resource.instance_eval { @x = 'default' }
+ expect(resource.x v).to eq 'default'
+ expect(resource.x).to eq 'default'
+ end
+ end
+ end
+ end
+
+ context "basic get, set, and nil set" do
+ with_property ":x, kind_of: String" do
+ context "when the variable already has a value" do
+ before do
+ resource.instance_eval { @x = 'default' }
+ end
+ it "get succeeds" do
+ expect(resource.x).to eq 'default'
+ end
+ it "set(nil) = get" do
+ expect(resource.x nil).to eq 'default'
+ expect(resource.x).to eq 'default'
+ end
+ it "set to valid value succeeds" do
+ expect(resource.x 'str').to eq 'str'
+ expect(resource.x).to eq 'str'
+ end
+ it "set to invalid value raises ValidationFailed" do
+ expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ context "when the variable does not have an initial value" do
+ it "get succeeds" do
+ expect(resource.x).to be_nil
+ end
+ it "set(nil) = get" do
+ expect(resource.x nil).to be_nil
+ expect(resource.x).to be_nil
+ end
+ it "set to valid value succeeds" do
+ expect(resource.x 'str').to eq 'str'
+ expect(resource.x).to eq 'str'
+ end
+ it "set to invalid value raises ValidationFailed" do
+ expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ end
+ with_property ":x, [ String, nil ]" do
+ context "when the variable already has a value" do
+ before do
+ resource.instance_eval { @x = 'default' }
+ end
+ it "get succeeds" do
+ expect(resource.x).to eq 'default'
+ end
+ it "set(nil) sets the value" do
+ expect(resource.x nil).to be_nil
+ expect(resource.x).to be_nil
+ end
+ it "set to valid value succeeds" do
+ expect(resource.x 'str').to eq 'str'
+ expect(resource.x).to eq 'str'
+ end
+ it "set to invalid value raises ValidationFailed" do
+ expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ context "when the variable does not have an initial value" do
+ it "get succeeds" do
+ expect(resource.x).to be_nil
+ end
+ it "set(nil) sets the value" do
+ expect(resource.x nil).to be_nil
+ expect(resource.x).to be_nil
+ end
+ it "set to valid value succeeds" do
+ expect(resource.x 'str').to eq 'str'
+ expect(resource.x).to eq 'str'
+ end
+ it "set to invalid value raises ValidationFailed" do
+ expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ end
+ end
+
+ # Bare types
+ context "bare types" do
+ validation_test 'String',
+ [ 'hi' ],
+ [ 10 ],
+ [ nil ]
+
+ validation_test ':a',
+ [ :a ],
+ [ :b ],
+ [ nil ]
+
+ validation_test ':a, is: :b',
+ [ :a, :b ],
+ [ :c ],
+ [ nil ]
+
+ validation_test ':a, is: [ :b, :c ]',
+ [ :a, :b, :c ],
+ [ :d ],
+ [ nil ]
+
+ validation_test '[ :a, :b ], is: :c',
+ [ :a, :b, :c ],
+ [ :d ],
+ [ nil ]
+
+ validation_test '[ :a, :b ], is: [ :c, :d ]',
+ [ :a, :b, :c, :d ],
+ [ :e ],
+ [ nil ]
+
+ validation_test 'nil',
+ [ nil ],
+ [ :a ]
+
+ validation_test '[ nil ]',
+ [ nil ],
+ [ :a ]
+
+ validation_test '[]',
+ [],
+ [ :a ],
+ [ nil ]
+ end
+
+ # is
+ context "is" do
+ # Class
+ validation_test 'is: String',
+ [ 'a', '' ],
+ [ :a, 1 ],
+ [ nil ]
+
+ # Value
+ validation_test 'is: :a',
+ [ :a ],
+ [ :b ],
+ [ nil ]
+
+ validation_test 'is: [ :a, :b ]',
+ [ :a, :b ],
+ [ [ :a, :b ] ],
+ [ nil ]
+
+ validation_test 'is: [ [ :a, :b ] ]',
+ [ [ :a, :b ] ],
+ [ :a, :b ],
+ [ nil ]
+
+ # Regex
+ validation_test 'is: /abc/',
+ [ 'abc', 'wowabcwow' ],
+ [ '', 'abac' ],
+ [ nil ]
+
+ # PropertyType
+ # validation_test 'is: PropertyType.new(is: :a)',
+ # [ :a ],
+ # [ :b, nil ]
+
+ # RSpec Matcher
+ class Globalses
+ extend RSpec::Matchers
+ end
+
+ validation_test "is: Globalses.eq(10)",
+ [ 10 ],
+ [ 1 ],
+ [ nil ]
+
+ # Proc
+ validation_test 'is: proc { |x| x }',
+ [ true, 1 ],
+ [ false ],
+ [ nil ]
+
+ validation_test 'is: proc { |x| x > blah }',
+ [ 10 ],
+ [ -1 ]
+
+ validation_test 'is: nil',
+ [ nil ],
+ [ 'a' ]
+
+ validation_test 'is: [ String, nil ]',
+ [ 'a', nil ],
+ [ :b ]
+
+ validation_test 'is: []',
+ [],
+ [ :a ],
+ [ nil ]
+ end
+
+ # Combination
+ context "combination" do
+ validation_test 'kind_of: String, equal_to: "a"',
+ [ 'a' ],
+ [ 'b' ],
+ [ nil ]
+ end
+
+ # equal_to
+ context "equal_to" do
+ # Value
+ validation_test 'equal_to: :a',
+ [ :a ],
+ [ :b ],
+ [ nil ]
+
+ validation_test 'equal_to: [ :a, :b ]',
+ [ :a, :b ],
+ [ [ :a, :b ] ],
+ [ nil ]
+
+ validation_test 'equal_to: [ [ :a, :b ] ]',
+ [ [ :a, :b ] ],
+ [ :a, :b ],
+ [ nil ]
+
+ validation_test 'equal_to: nil',
+ [ ],
+ [ 'a' ],
+ [ nil ]
+
+ validation_test 'equal_to: [ "a", nil ]',
+ [ 'a' ],
+ [ 'b' ],
+ [ nil ]
+
+ validation_test 'equal_to: [ nil, "a" ]',
+ [ 'a' ],
+ [ 'b' ],
+ [ nil ]
+
+ validation_test 'equal_to: []',
+ [],
+ [ :a ],
+ [ nil ]
+ end
+
+ # kind_of
+ context "kind_of" do
+ validation_test 'kind_of: String',
+ [ 'a' ],
+ [ :b ],
+ [ nil ]
+
+ validation_test 'kind_of: [ String, Symbol ]',
+ [ 'a', :b ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'kind_of: [ Symbol, String ]',
+ [ 'a', :b ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'kind_of: NilClass',
+ [ ],
+ [ 'a' ],
+ [ nil ]
+
+ validation_test 'kind_of: [ NilClass, String ]',
+ [ 'a' ],
+ [ :a ],
+ [ nil ]
+
+ validation_test 'kind_of: []',
+ [],
+ [ :a ],
+ [ nil ]
+
+ validation_test 'kind_of: nil',
+ [],
+ [ :a ],
+ [ nil ]
+ end
+
+ # regex
+ context "regex" do
+ validation_test 'regex: /abc/',
+ [ 'xabcy' ],
+ [ 'gbh', 123 ],
+ [ nil ]
+
+ validation_test 'regex: [ /abc/, /z/ ]',
+ [ 'xabcy', 'aza' ],
+ [ 'gbh', 123 ],
+ [ nil ]
+
+ validation_test 'regex: [ /z/, /abc/ ]',
+ [ 'xabcy', 'aza' ],
+ [ 'gbh', 123 ],
+ [ nil ]
+
+ validation_test 'regex: []',
+ [],
+ [ :a ],
+ [ nil ]
+
+ validation_test 'regex: nil',
+ [],
+ [ :a ],
+ [ nil ]
+ end
+
+ # callbacks
+ context "callbacks" do
+ validation_test 'callbacks: { "a" => proc { |x| x > 10 }, "b" => proc { |x| x%2 == 0 } }',
+ [ 12 ],
+ [ 11, 4 ]
+
+ validation_test 'callbacks: { "a" => proc { |x| x%2 == 0 }, "b" => proc { |x| x > 10 } }',
+ [ 12 ],
+ [ 11, 4 ]
+
+ validation_test 'callbacks: { "a" => proc { |x| x.nil? } }',
+ [ ],
+ [ 'a' ],
+ [ nil ]
+
+ validation_test 'callbacks: {}',
+ [ :a ],
+ [],
+ [ nil ]
+ end
+
+ # respond_to
+ context "respond_to" do
+ validation_test 'respond_to: :split',
+ [ 'hi' ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'respond_to: "split"',
+ [ 'hi' ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'respond_to: :to_s',
+ [ :a ],
+ [],
+ [ nil ]
+
+ validation_test 'respond_to: [ :split, :to_s ]',
+ [ 'hi' ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'respond_to: %w(split to_s)',
+ [ 'hi' ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'respond_to: [ :to_s, :split ]',
+ [ 'hi' ],
+ [ 1, ],
+ [ nil ]
+
+ validation_test 'respond_to: []',
+ [ :a ],
+ [],
+ [ nil ]
+
+ validation_test 'respond_to: nil',
+ [ :a ],
+ [],
+ [ nil ]
+ end
+
+ context "cannot_be" do
+ validation_test 'cannot_be: :empty',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: "empty"',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: [ :empty, :nil ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: [ "empty", "nil" ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: [ :nil, :empty ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: [ :empty, :nil, :blahblah ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: []',
+ [ :a ],
+ [],
+ [ nil ]
+
+ validation_test 'cannot_be: nil',
+ [ :a ],
+ [],
+ [ nil ]
+
+ end
+
+ context "required" do
+ with_property ':x, required: true' do
+ it "if x is not specified, retrieval fails" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+ it "value nil does a get" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ resource.x 1
+ resource.x nil
+ expect(resource.x).to eq 1
+ end
+ end
+
+ with_property ':x, [String, nil], required: true' do
+ it "if x is not specified, retrieval fails" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ it "value nil is valid" do
+ expect(resource.x nil).to be_nil
+ expect(resource.x).to be_nil
+ end
+ it "value '1' is valid" do
+ expect(resource.x '1').to eq '1'
+ expect(resource.x).to eq '1'
+ end
+ it "value 1 is invalid" do
+ expect { resource.x 1 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+
+ with_property ':x, name_property: true, required: true' do
+ it "if x is not specified, retrieval fails" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+ it "value nil does a get" do
+ resource.x 1
+ resource.x nil
+ expect(resource.x).to eq 1
+ end
+ end
+
+ with_property ':x, default: 10, required: true' do
+ it "if x is not specified, retrieval fails" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+ it "value nil does a get" do
+ resource.x 1
+ resource.x nil
+ expect(resource.x).to eq 1
+ end
+ end
+ end
+
+ context "custom validators (def _pv_blarghle)" do
+ before do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ end
+
+ with_property ':x, blarghle: 1' do
+ context "and a class that implements _pv_blarghle" do
+ before do
+ resource_class.class_eval do
+ def _pv_blarghle(opts, key, value)
+ if _pv_opts_lookup(opts, key) != value
+ raise Chef::Exceptions::ValidationFailed, "ouch"
+ end
+ end
+ end
+ end
+
+ # it "getting the value causes a deprecation warning" do
+ # Chef::Config[:treat_deprecation_warnings_as_errors] = true
+ # expect { resource.x }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+ # end
+
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+
+ it "value '1' is invalid" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ expect { resource.x '1' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+
+ it "value nil does a get" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ resource.x 1
+ resource.x nil
+ expect(resource.x).to eq 1
+ end
+ end
+ end
+
+ with_property ':x, blarghle: 1' do
+ context "and a class that implements _pv_blarghle" do
+ before do
+ resource_class.class_eval do
+ def _pv_blarghle(opts, key, value)
+ if _pv_opts_lookup(opts, key) != value
+ raise Chef::Exceptions::ValidationFailed, "ouch"
+ end
+ end
+ end
+ end
+
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+
+ it "value '1' is invalid" do
+ expect { resource.x '1' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+
+ it "value nil does a get" do
+ resource.x 1
+ resource.x nil
+ expect(resource.x).to eq 1
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb
new file mode 100644
index 0000000000..ce0552c564
--- /dev/null
+++ b/spec/unit/property_spec.rb
@@ -0,0 +1,802 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource.property" do
+ include IntegrationSupport
+
+ module Namer
+ @i = 0
+ def self.next_resource_name
+ "chef_resource_property_spec_#{@i += 1}"
+ end
+ def self.reset_index
+ @current_index = 0
+ end
+ def self.current_index
+ @current_index
+ end
+ def self.next_index
+ @current_index += 1
+ end
+ end
+
+ def lazy(&block)
+ Chef::DelayedEvaluator.new(&block)
+ end
+
+ before do
+ Namer.reset_index
+ end
+
+ def self.new_resource_name
+ Namer.next_resource_name
+ end
+
+ let(:resource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(Chef::Resource) do
+ resource_name new_resource_name
+ def next_index
+ Namer.next_index
+ end
+ end
+ end
+
+ let(:resource) do
+ resource_class.new("blah")
+ end
+
+ def self.english_join(values)
+ return '<nothing>' if values.size == 0
+ return values[0].inspect if values.size == 1
+ "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}"
+ end
+
+ def self.with_property(*properties, &block)
+ tags_index = properties.find_index { |p| !p.is_a?(String)}
+ if tags_index
+ properties, tags = properties[0..tags_index-1], properties[tags_index..-1]
+ else
+ tags = []
+ end
+ properties = properties.map { |property| "property #{property}" }
+ context "With properties #{english_join(properties)}", *tags do
+ before do
+ properties.each do |property_str|
+ resource_class.class_eval(property_str, __FILE__, __LINE__)
+ end
+ end
+ instance_eval(&block)
+ end
+ end
+
+ # Basic properties
+ with_property ':bare_property' do
+ it "can be set" do
+ expect(resource.bare_property 10).to eq 10
+ expect(resource.bare_property).to eq 10
+ end
+ # it "emits a deprecation warning and does a get, if set to nil" do
+ it "emits a deprecation warning and does a get, if set to nil" do
+ expect(resource.bare_property 10).to eq 10
+ # expect { resource.bare_property nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+ # Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ expect(resource.bare_property nil).to eq 10
+ expect(resource.bare_property).to eq 10
+ end
+ it "can be updated" do
+ expect(resource.bare_property 10).to eq 10
+ expect(resource.bare_property 20).to eq 20
+ expect(resource.bare_property).to eq 20
+ end
+ it "can be set with =" do
+ expect(resource.bare_property 10).to eq 10
+ expect(resource.bare_property).to eq 10
+ end
+ # it "can be set to nil with =" do
+ # expect(resource.bare_property 10).to eq 10
+ # expect(resource.bare_property = nil).to be_nil
+ # expect(resource.bare_property).to be_nil
+ # end
+ it "can be updated with =" do
+ expect(resource.bare_property 10).to eq 10
+ expect(resource.bare_property = 20).to eq 20
+ expect(resource.bare_property).to eq 20
+ end
+ end
+
+ with_property ":x, Integer" do
+ context "and subclass" do
+ let(:subresource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(resource_class) do
+ resource_name new_resource_name
+ end
+ end
+ let(:subresource) do
+ subresource_class.new('blah')
+ end
+
+ it "x is inherited" do
+ expect(subresource.x 10).to eq 10
+ expect(subresource.x).to eq 10
+ expect(subresource.x = 20).to eq 20
+ expect(subresource.x).to eq 20
+ # expect(subresource_class.properties[:x]).not_to be_nil
+ end
+
+ it "x's validation is inherited" do
+ expect { subresource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+
+ context "with property :y on the subclass" do
+ before do
+ subresource_class.class_eval do
+ property :y
+ end
+ end
+
+ it "x is still there" do
+ expect(subresource.x 10).to eq 10
+ expect(subresource.x).to eq 10
+ expect(subresource.x = 20).to eq 20
+ expect(subresource.x).to eq 20
+ # expect(subresource_class.properties[:x]).not_to be_nil
+ end
+ it "y is there" do
+ expect(subresource.y 10).to eq 10
+ expect(subresource.y).to eq 10
+ expect(subresource.y = 20).to eq 20
+ expect(subresource.y).to eq 20
+ # expect(subresource_class.properties[:y]).not_to be_nil
+ end
+ it "y is not on the superclass" do
+ expect { resource_class.y 10 }.to raise_error
+ # expect(resource_class.properties[:y]).to be_nil
+ end
+ end
+
+ context "with property :x on the subclass" do
+ before do
+ subresource_class.class_eval do
+ property :x
+ end
+ end
+
+ it "x is still there" do
+ expect(subresource.x 10).to eq 10
+ expect(subresource.x).to eq 10
+ expect(subresource.x = 20).to eq 20
+ expect(subresource.x).to eq 20
+ # expect(subresource_class.properties[:x]).not_to be_nil
+ # expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x]
+ end
+
+ it "x's validation is overwritten" do
+ expect(subresource.x 'ohno').to eq 'ohno'
+ expect(subresource.x).to eq 'ohno'
+ end
+
+ it "the superclass's validation for x is still there" do
+ expect { resource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+
+ context "with property :x, String on the subclass" do
+ before do
+ subresource_class.class_eval do
+ property :x, String
+ end
+ end
+
+ it "x is still there" do
+ expect(subresource.x "10").to eq "10"
+ expect(subresource.x).to eq "10"
+ expect(subresource.x = "20").to eq "20"
+ expect(subresource.x).to eq "20"
+ # expect(subresource_class.properties[:x]).not_to be_nil
+ # expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x]
+ end
+
+ it "x's validation is overwritten" do
+ expect { subresource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+ expect(subresource.x 'ohno').to eq 'ohno'
+ expect(subresource.x).to eq 'ohno'
+ end
+
+ it "the superclass's validation for x is still there" do
+ expect { resource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed
+ expect(resource.x 10).to eq 10
+ expect(resource.x).to eq 10
+ end
+ end
+ end
+ end
+
+ context "Chef::Resource::PropertyType#property_is_set?" do
+ it "when a resource is newly created, property_is_set?(:name) is true" do
+ expect(resource.property_is_set?(:name)).to be_truthy
+ end
+
+ # it "when referencing an undefined property, property_is_set?(:x) raises an error" do
+ # expect { resource.property_is_set?(:x) }.to raise_error(ArgumentError)
+ # end
+
+ with_property ':x' do
+ it "when the resource is newly created, property_is_set?(:x) is false" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ it "when x is set, property_is_set?(:x) is true" do
+ resource.x 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set with =, property_is_set?(:x) is true" do
+ resource.x = 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set to a lazy value, property_is_set?(:x) is true" do
+ resource.x lazy { 10 }
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is retrieved, property_is_set?(:x) is false" do
+ resource.x
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ end
+
+ with_property ':x, default: 10' do
+ it "when the resource is newly created, property_is_set?(:x) is false" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ it "when x is set, property_is_set?(:x) is true" do
+ resource.x 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set with =, property_is_set?(:x) is true" do
+ resource.x = 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set to a lazy value, property_is_set?(:x) is true" do
+ resource.x lazy { 10 }
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is retrieved, property_is_set?(:x) is true" do
+ resource.x
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ end
+
+ with_property ':x, default: nil' do
+ it "when the resource is newly created, property_is_set?(:x) is false" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ it "when x is set, property_is_set?(:x) is true" do
+ resource.x 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set with =, property_is_set?(:x) is true" do
+ resource.x = 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set to a lazy value, property_is_set?(:x) is true" do
+ resource.x lazy { 10 }
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is retrieved, property_is_set?(:x) is true" do
+ resource.x
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ end
+
+ with_property ':x, default: lazy { 10 }' do
+ it "when the resource is newly created, property_is_set?(:x) is false" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ it "when x is set, property_is_set?(:x) is true" do
+ resource.x 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set with =, property_is_set?(:x) is true" do
+ resource.x = 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is retrieved, property_is_set?(:x) is true" do
+ resource.x
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ end
+ end
+
+ context "Chef::Resource::PropertyType#default" do
+ with_property ':x, default: 10' do
+ it "when x is set, it returns its value" do
+ expect(resource.x 20).to eq 20
+ expect(resource.property_is_set?(:x)).to be_truthy
+ expect(resource.x).to eq 20
+ end
+ it "when x is not set, it returns 10" do
+ expect(resource.x).to eq 10
+ end
+ it "when x is not set, it is not included in state" do
+ expect(resource.state).to eq({})
+ end
+ it "when x is set to nil, it returns nil" do
+ resource.instance_eval { @x = nil }
+ expect(resource.x).to be_nil
+ end
+
+ context "With a subclass" do
+ let(:subresource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(resource_class) do
+ resource_name new_resource_name
+ end
+ end
+ let(:subresource) { subresource_class.new('blah') }
+ it "The default is inherited" do
+ expect(subresource.x).to eq 10
+ end
+ end
+ end
+
+ with_property ':x, default: 10, identity: true' do
+ it "when x is not set, it is not included in identity" do
+ expect(resource.state).to eq({})
+ end
+ end
+
+ with_property ':x, default: nil' do
+ it "when x is not set, it returns nil" do
+ expect(resource.x).to be_nil
+ end
+ end
+
+ with_property ':x' do
+ it "when x is not set, it returns nil" do
+ expect(resource.x).to be_nil
+ end
+ end
+
+ context "hash default" do
+ with_property ':x, default: {}' do
+ it "when x is not set, it returns {}" do
+ expect(resource.x).to eq({})
+ end
+ it "The same exact value is returned multiple times in a row" do
+ value = resource.x
+ expect(value).to eq({})
+ expect(resource.x.object_id).to eq(value.object_id)
+ end
+ it "Multiple instances of x receive the exact same value" do
+ expect(resource.x.object_id).to eq(resource_class.new('blah2').x.object_id)
+ end
+ it "The default value is frozen" do
+ expect(resource.x).to be_frozen
+ end
+ it "The default value cannot be written to" do
+ expect { resource.x[:a] = 1 }.to raise_error RuntimeError, /frozen/
+ end
+ end
+
+ with_property ':x, default: lazy { {} }' do
+ it "when x is not set, it returns {}" do
+ expect(resource.x).to eq({})
+ end
+ # it "The value is different each time it is called" do
+ # value = resource.x
+ # expect(value).to eq({})
+ # expect(resource.x.object_id).not_to eq(value.object_id)
+ # end
+ it "Multiple instances of x receive different values" do
+ expect(resource.x.object_id).not_to eq(resource_class.new('blah2').x.object_id)
+ end
+ end
+ end
+
+ context "with a class with 'blah' as both class and instance methods" do
+ before do
+ resource_class.class_eval do
+ def self.blah
+ 'class'
+ end
+ def blah
+ "#{name}#{next_index}"
+ end
+ end
+ end
+
+ with_property ':x, default: lazy { blah }' do
+ it "x is run in context of the instance" do
+ expect(resource.x).to eq "blah1"
+ end
+ it "x is run in the context of each instance it is run in" do
+ expect(resource.x).to eq "blah1"
+ expect(resource_class.new('another').x).to eq "another2"
+ # expect(resource.x).to eq "blah3"
+ end
+ end
+
+ with_property ':x, default: lazy { |x| "#{blah}#{x.blah}" }' do
+ it "x is run in context of the class (where it was defined) and passed the instance" do
+ expect(resource.x).to eq "classblah1"
+ end
+ it "x is passed the value of each instance it is run in" do
+ expect(resource.x).to eq "classblah1"
+ expect(resource_class.new('another').x).to eq "classanother2"
+ # expect(resource.x).to eq "classblah3"
+ end
+ end
+ end
+
+ context "validation of defaults" do
+ with_property ':x, String, default: 10' do
+ it "when the resource is created, no error is raised" do
+ resource
+ end
+ it "when x is set, no error is raised" do
+ expect(resource.x 'hi').to eq 'hi'
+ expect(resource.x).to eq 'hi'
+ end
+ it "when x is retrieved, no validation error is raised" do
+ expect(resource.x).to eq 10
+ end
+ # it "when x is retrieved, a validation error is raised" do
+ # expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ end
+
+ with_property ":x, String, default: lazy { Namer.next_index }" do
+ it "when the resource is created, no error is raised" do
+ resource
+ end
+ it "when x is set, no error is raised" do
+ expect(resource.x 'hi').to eq 'hi'
+ expect(resource.x).to eq 'hi'
+ end
+ it "when x is retrieved, no validation error is raised" do
+ expect(resource.x).to eq 1
+ expect(Namer.current_index).to eq 1
+ end
+ # it "when x is retrieved, a validation error is raised" do
+ # expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ # expect(Namer.current_index).to eq 1
+ # end
+ end
+
+ with_property ":x, default: lazy { Namer.next_index }, is: proc { |v| Namer.next_index; true }" do
+ it "validation is not run at all on the default value" do
+ expect(resource.x).to eq 1
+ expect(Namer.current_index).to eq 1
+ end
+ # it "validation is only run the first time" do
+ # expect(resource.x).to eq 1
+ # expect(Namer.current_index).to eq 2
+ # expect(resource.x).to eq 1
+ # expect(Namer.current_index).to eq 2
+ # end
+ end
+ end
+
+ context "coercion of defaults" do
+ with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do
+ it "when the resource is created, the proc is not yet run" do
+ resource
+ expect(Namer.current_index).to eq 0
+ end
+ it "when x is set, coercion is run" do
+ expect(resource.x 'hi').to eq 'hi1'
+ expect(resource.x).to eq 'hi1'
+ expect(Namer.current_index).to eq 1
+ end
+ it "when x is retrieved, coercion is run, no more than once" do
+ expect(resource.x).to eq '101'
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 1
+ end
+ end
+
+ with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+ it "when the resource is created, the proc is not yet run" do
+ resource
+ expect(Namer.current_index).to eq 0
+ end
+ it "when x is set, coercion is run" do
+ expect(resource.x 'hi').to eq 'hi1'
+ expect(resource.x).to eq 'hi1'
+ expect(Namer.current_index).to eq 1
+ end
+ end
+
+ with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+ it "coercion is only run the first time x is retrieved, and validation is not run" do
+ expect(Namer.current_index).to eq 0
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 1
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 1
+ end
+ end
+
+ context "validation and coercion of defaults" do
+ with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do
+ it "when x is retrieved, it is coerced before validating and passes" do
+ expect(resource.x).to eq '101'
+ end
+ end
+ with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do
+ it "when x is retrieved, it is coerced and not validated" do
+ expect(resource.x).to eq '101'
+ end
+ # it "when x is retrieved, it is coerced before validating and fails" do
+ # expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ end
+ with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+ it "when x is retrieved, it is coerced before validating and passes" do
+ expect(resource.x).to eq '101'
+ end
+ end
+ with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+ it "when x is retrieved, it is coerced and not validated" do
+ expect(resource.x).to eq '101'
+ end
+ # it "when x is retrieved, it is coerced before validating and fails" do
+ # expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ end
+ with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+ it "coercion is only run the first time x is retrieved, and validation is not run" do
+ expect(Namer.current_index).to eq 0
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 1
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 1
+ end
+ end
+ end
+ end
+ end
+
+ context "Chef::Resource#lazy" do
+ with_property ':x' do
+ it "setting x to a lazy value does not run it immediately" do
+ resource.x lazy { Namer.next_index }
+ expect(Namer.current_index).to eq 0
+ end
+ it "you can set x to a lazy value in the instance" do
+ resource.instance_eval do
+ x lazy { Namer.next_index }
+ end
+ expect(resource.x).to eq 1
+ expect(Namer.current_index).to eq 1
+ end
+ it "retrieving a lazy value pops it open" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq 1
+ expect(Namer.current_index).to eq 1
+ end
+ it "retrieving a lazy value twice evaluates it twice" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq 1
+ expect(resource.x).to eq 2
+ expect(Namer.current_index).to eq 2
+ end
+ it "setting the same lazy value on two different instances runs it on each instancee" do
+ resource2 = resource_class.new("blah2")
+ l = lazy { Namer.next_index }
+ resource.x l
+ resource2.x l
+ expect(resource2.x).to eq 1
+ expect(resource.x).to eq 2
+ expect(resource2.x).to eq 3
+ end
+
+ context "when the class has a class and instance method named blah" do
+ before do
+ resource_class.class_eval do
+ def self.blah
+ "class"
+ end
+ def blah
+ "#{name}#{Namer.next_index}"
+ end
+ end
+ end
+ def blah
+ "example"
+ end
+ # it "retrieving lazy { blah } gets the instance variable" do
+ # resource.x lazy { blah }
+ # expect(resource.x).to eq "blah1"
+ # end
+ # it "retrieving lazy { blah } from two different instances gets two different instance variables" do
+ # resource2 = resource_class.new("another")
+ # l = lazy { blah }
+ # resource2.x l
+ # resource.x l
+ # expect(resource2.x).to eq "another1"
+ # expect(resource.x).to eq "blah2"
+ # expect(resource2.x).to eq "another3"
+ # end
+ it 'retrieving lazy { |x| "#{blah}#{x.blah}" } gets the example and instance variables' do
+ resource.x lazy { |x| "#{blah}#{x.blah}" }
+ expect(resource.x).to eq "exampleblah1"
+ end
+ it 'retrieving lazy { |x| "#{blah}#{x.blah}" } from two different instances gets two different instance variables' do
+ resource2 = resource_class.new("another")
+ l = lazy { |x| "#{blah}#{x.blah}" }
+ resource2.x l
+ resource.x l
+ expect(resource2.x).to eq "exampleanother1"
+ expect(resource.x).to eq "exampleblah2"
+ expect(resource2.x).to eq "exampleanother3"
+ end
+ end
+ end
+
+ with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+ it "lazy values are not coerced on set" do
+ resource.x lazy { Namer.next_index }
+ expect(Namer.current_index).to eq 0
+ end
+ it "lazy values are coerced on get" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq "12"
+ expect(Namer.current_index).to eq 2
+ end
+ it "lazy values are coerced on each access" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq "12"
+ expect(Namer.current_index).to eq 2
+ expect(resource.x).to eq "34"
+ expect(Namer.current_index).to eq 4
+ end
+ end
+
+ with_property ':x, String' do
+ it "lazy values are not validated on set" do
+ resource.x lazy { Namer.next_index }
+ expect(Namer.current_index).to eq 0
+ end
+ it "lazy values are validated on get" do
+ resource.x lazy { Namer.next_index }
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ expect(Namer.current_index).to eq 1
+ end
+ end
+
+ with_property ':x, is: proc { |v| Namer.next_index; true }' do
+ it "lazy values are validated on each access" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq 1
+ expect(Namer.current_index).to eq 2
+ expect(resource.x).to eq 3
+ expect(Namer.current_index).to eq 4
+ end
+ end
+
+ with_property ':x, Integer, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+ it "lazy values are not validated or coerced on set" do
+ resource.x lazy { Namer.next_index }
+ expect(Namer.current_index).to eq 0
+ end
+ it "lazy values are coerced before being validated, which fails" do
+ resource.x lazy { Namer.next_index }
+ expect(Namer.current_index).to eq 0
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ expect(Namer.current_index).to eq 2
+ end
+ end
+
+ with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }, is: proc { |v| Namer.next_index; true }' do
+ it "lazy values are coerced and validated exactly once" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq "12"
+ expect(Namer.current_index).to eq 3
+ expect(resource.x).to eq "45"
+ expect(Namer.current_index).to eq 6
+ end
+ end
+
+ with_property ':x, String, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+ it "lazy values are coerced before being validated, which succeeds" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq "12"
+ expect(Namer.current_index).to eq 2
+ end
+ end
+ end
+
+ context "Chef::Resource::PropertyType#coerce" do
+ with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+ it "coercion runs on set" do
+ expect(resource.x 10).to eq "101"
+ expect(Namer.current_index).to eq 1
+ end
+ it "coercion sets the value (and coercion does not run on get)" do
+ expect(resource.x 10).to eq "101"
+ expect(resource.x).to eq "101"
+ expect(Namer.current_index).to eq 1
+ end
+ it "coercion runs each time set happens" do
+ expect(resource.x 10).to eq "101"
+ expect(Namer.current_index).to eq 1
+ expect(resource.x 10).to eq "102"
+ expect(Namer.current_index).to eq 2
+ end
+ end
+ with_property ':x, coerce: proc { |x| Namer.next_index; raise "hi" if x == 10; x }, is: proc { |x| Namer.next_index; x != 10 }' do
+ it "failed coercion fails to set the value" do
+ resource.x 20
+ expect(resource.x).to eq 20
+ expect(Namer.current_index).to eq 2
+ expect { resource.x 10 }.to raise_error 'hi'
+ expect(resource.x).to eq 20
+ expect(Namer.current_index).to eq 3
+ end
+ it "validation does not run if coercion fails" do
+ expect { resource.x 10 }.to raise_error 'hi'
+ expect(Namer.current_index).to eq 1
+ end
+ end
+ end
+
+ context "Chef::Resource::PropertyType validation" do
+ with_property ':x, is: proc { |v| Namer.next_index; v.is_a?(Integer) }' do
+ it "validation runs on set" do
+ expect(resource.x 10).to eq 10
+ expect(Namer.current_index).to eq 1
+ end
+ it "validation sets the value (and validation does not run on get)" do
+ expect(resource.x 10).to eq 10
+ expect(resource.x).to eq 10
+ expect(Namer.current_index).to eq 1
+ end
+ it "validation runs each time set happens" do
+ expect(resource.x 10).to eq 10
+ expect(Namer.current_index).to eq 1
+ expect(resource.x 10).to eq 10
+ expect(Namer.current_index).to eq 2
+ end
+ it "failed validation fails to set the value" do
+ expect(resource.x 10).to eq 10
+ expect(Namer.current_index).to eq 1
+ expect { resource.x 'blah' }.to raise_error Chef::Exceptions::ValidationFailed
+ expect(resource.x).to eq 10
+ expect(Namer.current_index).to eq 2
+ end
+ end
+ end
+
+ [ 'name_attribute', 'name_property' ].each do |name|
+ context "Chef::Resource::PropertyType##{name}" do
+ with_property ":x, #{name}: true" do
+ it "defaults x to resource.name" do
+ expect(resource.x).to eq 'blah'
+ end
+ it "does not pick up resource.name if set" do
+ expect(resource.x 10).to eq 10
+ expect(resource.x).to eq 10
+ end
+ it "binds to the latest resource.name when run" do
+ resource.name 'foo'
+ expect(resource.x).to eq 'foo'
+ end
+ it "caches resource.name" do
+ expect(resource.x).to eq 'blah'
+ resource.name 'foo'
+ expect(resource.x).to eq 'blah'
+ end
+ end
+ with_property ":x, default: 10, #{name}: true" do
+ it "chooses default over #{name}" do
+ expect(resource.x).to eq 10
+ end
+ end
+ with_property ":x, #{name}: true, default: 10" do
+ it "chooses default over #{name}" do
+ expect(resource.x).to eq 10
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb
index e18d69bc19..cd3d7713a7 100644
--- a/spec/unit/provider_resolver_spec.rb
+++ b/spec/unit/provider_resolver_spec.rb
@@ -482,7 +482,6 @@ describe Chef::ProviderResolver do
python: Chef::Provider::Script,
remote_directory: Chef::Provider::RemoteDirectory,
route: Chef::Provider::Route,
- rpm_package: Chef::Provider::Package::Rpm,
ruby: Chef::Provider::Script,
ruby_block: Chef::Provider::RubyBlock,
script: Chef::Provider::Script,
diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb
index d7a34bc21b..97b88b1732 100644
--- a/spec/unit/provider_spec.rb
+++ b/spec/unit/provider_spec.rb
@@ -114,9 +114,7 @@ describe Chef::Provider do
end
it "does not re-load recipes when creating the temporary run context" do
- # we actually want to test that RunContext#load is never called, but we
- # can't stub all instances of an object with rspec's mocks. :/
- allow(Chef::RunContext).to receive(:new).and_raise("not supposed to happen")
+ expect_any_instance_of(Chef::RunContext).not_to receive(:load)
snitch = Proc.new {temporary_collection = @run_context.resource_collection}
@provider.send(:recipe_eval, &snitch)
end
diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb
index ee98e63c1f..17ea498fe3 100644
--- a/spec/unit/recipe_spec.rb
+++ b/spec/unit/recipe_spec.rb
@@ -121,6 +121,7 @@ describe Chef::Recipe do
it "locate resource for particular platform" do
ShaunTheSheep = Class.new(Chef::Resource)
+ ShaunTheSheep.resource_name :shaun_the_sheep
ShaunTheSheep.provides :laughter, :platform => ["television"]
node.automatic[:platform] = "television"
node.automatic[:platform_version] = "123"
@@ -131,6 +132,7 @@ describe Chef::Recipe do
it "locate a resource for all platforms" do
YourMom = Class.new(Chef::Resource)
+ YourMom.resource_name :your_mom
YourMom.provides :love_and_caring
res = recipe.love_and_caring "mommy"
expect(res.name).to eql("mommy")
@@ -141,7 +143,9 @@ describe Chef::Recipe do
before do
node.automatic[:platform] = "nbc_sports"
Sounders = Class.new(Chef::Resource)
+ Sounders.resource_name :sounders
TottenhamHotspur = Class.new(Chef::Resource)
+ TottenhamHotspur.resource_name :tottenham_hotspur
end
after do
@@ -149,24 +153,24 @@ describe Chef::Recipe do
Object.send(:remove_const, :TottenhamHotspur)
end
- it "selects one if it is the last declared" do
- expect(Chef::Log).not_to receive(:warn)
+ it "selects the first one alphabetically" do
+ expect(Chef::Log).to receive(:warn).with("You declared a new resource TottenhamHotspur for resource football, but it comes alphabetically after Sounders and has the same filters ({:platform=>\"nbc_sports\"}), so it will not be used. Use override: true if you want to use it for football.")
Sounders.provides :football, platform: "nbc_sports"
TottenhamHotspur.provides :football, platform: "nbc_sports"
res1 = recipe.football "club world cup"
expect(res1.name).to eql("club world cup")
- expect(res1).to be_a_kind_of(TottenhamHotspur)
+ expect(res1).to be_a_kind_of(Sounders)
end
- it "selects the other one if it is given priority" do
- expect(Chef::Log).not_to receive(:warn)
+ it "selects the first one alphabetically even if the declaration order is reversed" do
+ expect(Chef::Log).to receive(:warn).with("You are overriding football2 on {:platform=>\"nbc_sports\"} with Sounders: used to be TottenhamHotspur. Use override: true if this is what you intended.")
- TottenhamHotspur.provides :football, platform: "nbc_sports"
- Sounders.provides :football, platform: "nbc_sports"
+ TottenhamHotspur.provides :football2, platform: "nbc_sports"
+ Sounders.provides :football2, platform: "nbc_sports"
- res1 = recipe.football "club world cup"
+ res1 = recipe.football2 "club world cup"
expect(res1.name).to eql("club world cup")
expect(res1).to be_a_kind_of(Sounders)
end
diff --git a/spec/unit/resource_collection_spec.rb b/spec/unit/resource_collection_spec.rb
index b43b012dfc..d52e7e2c26 100644
--- a/spec/unit/resource_collection_spec.rb
+++ b/spec/unit/resource_collection_spec.rb
@@ -252,7 +252,7 @@ describe Chef::ResourceCollection do
expect(json).to match(/instance_vars/)
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { rc }
end
end
diff --git a/spec/unit/resource_resolver_spec.rb b/spec/unit/resource_resolver_spec.rb
new file mode 100644
index 0000000000..09ff026575
--- /dev/null
+++ b/spec/unit/resource_resolver_spec.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Ranjib Dey
+# Copyright:: Copyright (c) 2015 Ranjib Dey <ranjib@linux.com>.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/resource_resolver'
+
+
+describe Chef::ResourceResolver do
+ it '#resolve' do
+ expect(described_class.resolve(:execute)).to eq(Chef::Resource::Execute)
+ end
+
+ it '#list' do
+ expect(described_class.list(:package)).to_not be_empty
+ end
+
+ context 'instance methods' do
+ let(:resolver) do
+ described_class.new(Chef::Node.new, 'execute[echo]')
+ end
+
+ it '#resolve' do
+ expect(resolver.resolve).to be_nil
+ end
+
+ it '#list' do
+ expect(resolver.list).to be_empty
+ end
+
+ it '#provided_by?' do
+ expect(resolver.provided_by?(Chef::Resource::Execute)).to be_truthy
+ end
+ end
+end
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index 8ba45d9350..8b0baff921 100644
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -344,6 +344,7 @@ describe Chef::Resource do
expect(r.resource_name).to be_nil
expect(r.declared_type).to eq :d
end
+
it "and there are no provides lines, @resource_name is used" do
c = Class.new(Chef::Resource) do
def initialize(*args, &block)
@@ -358,6 +359,20 @@ describe Chef::Resource do
expect(r.resource_name).to eq :blah
expect(r.declared_type).to eq :d
end
+
+ it "and the resource class gets a late-bound name, resource_name is nil" do
+ c = Class.new(Chef::Resource) do
+ def self.name
+ "ResourceSpecNameTest"
+ end
+ end
+
+ r = c.new('hi')
+ r.declared_type = :d
+ expect(c.resource_name).to be_nil
+ expect(r.resource_name).to be_nil
+ expect(r.declared_type).to eq :d
+ end
end
it "resource_name without provides is honored" do
@@ -416,7 +431,7 @@ describe Chef::Resource do
expect(json).to match(/instance_vars/)
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @resource }
end
end
@@ -795,21 +810,21 @@ describe Chef::Resource do
end
it 'adds mappings for a single platform' do
- expect(Chef).to receive(:set_resource_priority_array).with(
+ expect(Chef.resource_priority_map).to receive(:set).with(
:dinobot, Chef::Resource::Klz, { platform: ['autobots'] }
)
klz.provides :dinobot, platform: ['autobots']
end
it 'adds mappings for multiple platforms' do
- expect(Chef).to receive(:set_resource_priority_array).with(
+ expect(Chef.resource_priority_map).to receive(:set).with(
:energy, Chef::Resource::Klz, { platform: ['autobots', 'decepticons']}
)
klz.provides :energy, platform: ['autobots', 'decepticons']
end
it 'adds mappings for all platforms' do
- expect(Chef).to receive(:set_resource_priority_array).with(
+ expect(Chef.resource_priority_map).to receive(:set).with(
:tape_deck, Chef::Resource::Klz, {}
)
klz.provides :tape_deck
diff --git a/spec/unit/role_spec.rb b/spec/unit/role_spec.rb
index f120ca6da6..ecc7945a08 100644
--- a/spec/unit/role_spec.rb
+++ b/spec/unit/role_spec.rb
@@ -217,7 +217,7 @@ describe Chef::Role do
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @role }
end
end
diff --git a/spec/unit/run_context/child_run_context_spec.rb b/spec/unit/run_context/child_run_context_spec.rb
new file mode 100644
index 0000000000..63586e459c
--- /dev/null
+++ b/spec/unit/run_context/child_run_context_spec.rb
@@ -0,0 +1,133 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Tim Hinderliter (<tim@opscode.com>)
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'support/lib/library_load_order'
+
+describe Chef::RunContext::ChildRunContext do
+ context "with a run context with stuff in it" do
+ let(:chef_repo_path) { File.expand_path(File.join(CHEF_SPEC_DATA, "run_context", "cookbooks")) }
+ let(:cookbook_collection) {
+ cl = Chef::CookbookLoader.new(chef_repo_path)
+ cl.load_cookbooks
+ Chef::CookbookCollection.new(cl)
+ }
+ let(:node) {
+ node = Chef::Node.new
+ node.run_list << "test" << "test::one" << "test::two"
+ node
+ }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) }
+
+ context "and a child run context" do
+ let(:child) { run_context.create_child }
+
+ it "parent_run_context is set to the parent" do
+ expect(child.parent_run_context).to eq run_context
+ end
+
+ it "audits is not the same as the parent" do
+ expect(child.audits.object_id).not_to eq run_context.audits.object_id
+ child.audits['hi'] = 'lo'
+ expect(child.audits['hi']).to eq('lo')
+ expect(run_context.audits['hi']).not_to eq('lo')
+ end
+
+ it "resource_collection is not the same as the parent" do
+ expect(child.resource_collection.object_id).not_to eq run_context.resource_collection.object_id
+ f = Chef::Resource::File.new('hi', child)
+ child.resource_collection.insert(f)
+ expect(child.resource_collection).to include f
+ expect(run_context.resource_collection).not_to include f
+ end
+
+ it "immediate_notification_collection is not the same as the parent" do
+ expect(child.immediate_notification_collection.object_id).not_to eq run_context.immediate_notification_collection.object_id
+ src = Chef::Resource::File.new('hi', child)
+ dest = Chef::Resource::File.new('argh', child)
+ notification = Chef::Resource::Notification.new(dest, :create, src)
+ child.notifies_immediately(notification)
+ expect(child.immediate_notification_collection['file[hi]']).to eq([notification])
+ expect(run_context.immediate_notification_collection['file[hi]']).not_to eq([notification])
+ end
+
+ it "immediate_notifications is not the same as the parent" do
+ src = Chef::Resource::File.new('hi', child)
+ dest = Chef::Resource::File.new('argh', child)
+ notification = Chef::Resource::Notification.new(dest, :create, src)
+ child.notifies_immediately(notification)
+ expect(child.immediate_notifications(src)).to eq([notification])
+ expect(run_context.immediate_notifications(src)).not_to eq([notification])
+ end
+
+ it "delayed_notification_collection is not the same as the parent" do
+ expect(child.delayed_notification_collection.object_id).not_to eq run_context.delayed_notification_collection.object_id
+ src = Chef::Resource::File.new('hi', child)
+ dest = Chef::Resource::File.new('argh', child)
+ notification = Chef::Resource::Notification.new(dest, :create, src)
+ child.notifies_delayed(notification)
+ expect(child.delayed_notification_collection['file[hi]']).to eq([notification])
+ expect(run_context.delayed_notification_collection['file[hi]']).not_to eq([notification])
+ end
+
+ it "delayed_notifications is not the same as the parent" do
+ src = Chef::Resource::File.new('hi', child)
+ dest = Chef::Resource::File.new('argh', child)
+ notification = Chef::Resource::Notification.new(dest, :create, src)
+ child.notifies_delayed(notification)
+ expect(child.delayed_notifications(src)).to eq([notification])
+ expect(run_context.delayed_notifications(src)).not_to eq([notification])
+ end
+
+ it "create_child creates a child-of-child" do
+ c = child.create_child
+ expect(c.parent_run_context).to eq child
+ end
+
+ context "after load('include::default')" do
+ before do
+ run_list = Chef::RunList.new('include::default').expand('_default')
+ # TODO not sure why we had to do this to get everything to work ...
+ node.automatic_attrs[:recipes] = []
+ child.load(run_list)
+ end
+
+ it "load_recipe loads into the child" do
+ expect(child.resource_collection).to be_empty
+ child.load_recipe("include::includee")
+ expect(child.resource_collection).not_to be_empty
+ end
+
+ it "include_recipe loads into the child" do
+ expect(child.resource_collection).to be_empty
+ child.include_recipe("include::includee")
+ expect(child.resource_collection).not_to be_empty
+ end
+
+ it "load_recipe_file loads into the child" do
+ expect(child.resource_collection).to be_empty
+ child.load_recipe_file(File.expand_path("include/recipes/includee.rb", chef_repo_path))
+ expect(child.resource_collection).not_to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/run_context_spec.rb b/spec/unit/run_context_spec.rb
index e20ba63b72..99801575ef 100644
--- a/spec/unit/run_context_spec.rb
+++ b/spec/unit/run_context_spec.rb
@@ -68,6 +68,9 @@ describe Chef::RunContext do
"dependency2" => {
"version" => "0.0.0",
},
+ "include" => {
+ "version" => "0.0.0",
+ },
"no-default-attr" => {
"version" => "0.0.0",
},
@@ -84,6 +87,10 @@ describe Chef::RunContext do
)
end
+ it "has a nil parent_run_context" do
+ expect(run_context.parent_run_context).to be_nil
+ end
+
describe "loading cookbooks for a run list" do
before do
diff --git a/spec/unit/run_list_spec.rb b/spec/unit/run_list_spec.rb
index bf996de8c1..e150579431 100644
--- a/spec/unit/run_list_spec.rb
+++ b/spec/unit/run_list_spec.rb
@@ -307,7 +307,7 @@ describe Chef::RunList do
expect(Chef::JSONCompat.to_json(@run_list)).to eq(Chef::JSONCompat.to_json(["recipe[nagios::client]", "role[production]", "recipe[apache2]"]))
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @run_list }
end
diff --git a/spec/unit/runner_spec.rb b/spec/unit/runner_spec.rb
index 82e57e068c..b30f818da1 100644
--- a/spec/unit/runner_spec.rb
+++ b/spec/unit/runner_spec.rb
@@ -273,8 +273,8 @@ describe Chef::Runner do
expected_message =<<-E
Multiple failures occurred:
-* FailureProvider::ChefClientFail occurred in delayed notification: failure_resource[explode] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort
-* FailureProvider::ChefClientFail occurred in delayed notification: failure_resource[explode again] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort
+* FailureProvider::ChefClientFail occurred in delayed notification: [explode] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort
+* FailureProvider::ChefClientFail occurred in delayed notification: [explode again] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort
E
expect(exception.message).to eq(expected_message)
diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb
index 57822df7e3..5222c951b3 100644
--- a/spec/unit/user_spec.rb
+++ b/spec/unit/user_spec.rb
@@ -244,7 +244,7 @@ describe Chef::User do
expect(@json).not_to include("password")
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @user }
end
end