diff options
author | Adam Edwards <adamed@opscode.com> | 2014-03-30 00:29:24 -0700 |
---|---|---|
committer | Adam Edwards <adamed@opscode.com> | 2014-03-30 00:29:24 -0700 |
commit | daa0f2af387df26bf254d5d287c4a27934540b97 (patch) | |
tree | 2624da050d3c0d843d6afa1ce8e00e3d38359454 | |
parent | 852cadd9040c71759bbeb55311ebc7a8cff27150 (diff) | |
parent | d8d8ae0e97a7d8a2f7b6a3217407e5fe19fe632b (diff) | |
download | chef-daa0f2af387df26bf254d5d287c4a27934540b97.tar.gz |
Merge pull request #1316 from opscode/adamed/guard-interpreter
CHEF-4553: Guard interpreter and powershell boolean awareness
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | DOC_CHANGES.md | 108 | ||||
-rw-r--r-- | RELEASE_NOTES.md | 26 | ||||
-rw-r--r-- | lib/chef/guard_interpreter/default_guard_interpreter.rb | 42 | ||||
-rw-r--r-- | lib/chef/guard_interpreter/resource_guard_interpreter.rb | 122 | ||||
-rw-r--r-- | lib/chef/platform/provider_mapping.rb | 12 | ||||
-rw-r--r-- | lib/chef/provider/powershell_script.rb | 25 | ||||
-rw-r--r-- | lib/chef/resource.rb | 15 | ||||
-rw-r--r-- | lib/chef/resource/conditional.rb | 27 | ||||
-rw-r--r-- | lib/chef/resource/execute.rb | 2 | ||||
-rw-r--r-- | lib/chef/resource/powershell_script.rb | 24 | ||||
-rw-r--r-- | lib/chef/resource/script.rb | 25 | ||||
-rw-r--r-- | lib/chef/resource/windows_script.rb | 5 | ||||
-rw-r--r-- | spec/functional/resource/powershell_spec.rb | 263 | ||||
-rw-r--r-- | spec/support/shared/functional/windows_script.rb | 2 | ||||
-rw-r--r-- | spec/support/shared/unit/script_resource.rb | 38 | ||||
-rw-r--r-- | spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb | 56 | ||||
-rw-r--r-- | spec/unit/resource/conditional_spec.rb | 25 | ||||
-rw-r--r-- | spec/unit/resource/powershell_spec.rb | 87 | ||||
-rw-r--r-- | spec/unit/resource_spec.rb | 24 |
20 files changed, 885 insertions, 44 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eacaa9e40..657e1f6adf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ * Increase bootstrap log_level when knife -V -V is set (CHEF-3610) * Knife cookbook test should honor chefignore (CHEF-4203) * Fix ImmutableMash and ImmutableArray to_hash and to_a methods (CHEF-5132) +* guard_interpreter attribute: use powershell\_script, other script resources in guards (CHEF-4553) ## Last Release: 11.10.0 (02/06/2014) diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md index dff930ac09..f29d6ab132 100644 --- a/DOC_CHANGES.md +++ b/DOC_CHANGES.md @@ -177,3 +177,111 @@ end * installer_type - The type of package being installed. Can be auto-detected. Currently only :msi is supported. * timeout - The time in seconds allowed for the package to successfully be installed. Defaults to 600 seconds. * returns - Return codes that signal a successful installation. Defaults to 0. + +### New resource attribute: `guard_interpreter` +All resources have a new attribute, `guard_interpreter`, which specifies a +Chef script resource that should be used to evaluate a string command +passed to a guard. Any attributes of the evaluating resource may be specified in +the options that normally follow the guard's command string argument. For example: + + # Tell Chef to use bash to interpret the guard string. + # Then we can use a guard command that is valid for bash + # but not for csh for instance + bash 'backupsettings' do + guard_interpreter :bash + code 'cp ~/appsettings.json ~/backup/appsettings.json' + not_if '[[ -e ./appsettings.json ]]', :cwd => '~/backup' + end + +The argument for `guard_interpreter` may be set to any of the following values: +* The symbol name for a Chef Resource derived from the Chef `script` resource + such as `:bash`, `:powershell_script`, etc. +* The symbol `:default` which means that a resource is not used to evaluate + the guard command argument, it is simply executed by the default shell as in + previous releases of Chef. + +By default, `guard_interpreter` is set to `:default` in this release. + +#### Attribute inheritance with `guard_interpreter` + +When `guard_interpreter` is not set to `:default`, the resource that evaluates the command will +also inherit certain attribute values from the resource that contains the +guard. + +Inherited attributes for all `script` resources are: + +* `:cwd` +* `:environment` +* `:group` +* `:path` +* `:user` +* `:umask` + +For the `powershell_script` resource, the following attribute is inherited: +* `:architecture` + +Inherited attributes may be overridden by specifying the same attribute as an +argument to the guard itself. + +#### Guard inheritance example + +In the following example, the `:environment` hash only needs to be set once +since the `bash` resource that execute the guard will inherit the same value: + + script "javatooling" do + environment {"JAVA_HOME" => '/usr/lib/java/jdk1.7/home'} + code 'java-based-daemon-ctl.sh -start' + not_if 'java-based-daemon-ctl.sh -test-started' # No need to specify environment again + end + +### New `powershell_script` resource attribute: `convert_boolean_return` + +The `powershell_script` resource has a new attribute, `convert_boolean_return` +that causes the script interpreter to return 0 if the last line of the command +evaluted by PowerShell results in a boolean PowerShell data type that is true, or 1 if +it results in a boolean PowerShell data type that is false. For example, the +following two fragments will run successfully without error: + + powershell_script 'false' do + code '$false' + end + + powershell_script 'true' do + code '$true' + end + +But when `convert_boolean_return` is set to `true`, the "true" case above will +still succeed, but the false case will raise an exception: + + # Raises an exception + powershell_script 'false' do + convert_boolean_return true + code '$false' + end + +When used at recipe scope, the default value of `convert_boolean_return` is +`false` in this release. However, if `guard_interpreter` is set to +`:powershell_script`, the guard expression will be evaluted with a +`powershell_script` resource that has the `convert_boolean_return` attribute +set to `true`. + +#### Guard command example + +The behavior of `convert_boolean_return` is similar to the "$?" +expression's value after use of the `test` command in Unix-flavored shells and +its translation to an exit code for the shell. Since this attribute is set to +`true` when `powershell_script` is used via the `guard_interpreter` to +evaluate the guard expression, the behavior of `powershell_script` is very +similar to guards executed with Unix shell interpreters as seen below: + + bash 'make_safe_backup' do + code 'cp ~/data/nodes.json ~/data/nodes.bak' + not_if 'test -e ~/data/nodes.bak' + end + + # convert_boolean_return is true by default in guards + powershell_script 'make_safe_backup' do + guard_interpreter :powershell_script + code 'cp ~/data/nodes.json ~/data/nodes.bak' + not_if 'test-path ~/data/nodes.bak' + end diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 355733af38..a4796c3e5a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -133,6 +133,32 @@ If you're an advanced user of attribute precedence, you may find some attributes The weekday attribute now accepts the weekday as a symbol, e.g. :monday or :thursday. There is a new attribute named ```time``` that takes special cron time values as a symbol, such as :reboot or :monthly. +#### `guard_interpreter` attribute + +All Chef resources now support the `guard_interpreter` attribute, which +enables you to use a Chef `script` such as `bash`, `powershell_script`, +`perl`, etc., to evaluate the string command passed to a +guard (i.e. `not_if` or `only_if` attribute). This addresses the related ticket +[CHEF-4553](https://tickets.opscode.com/browse/CHEF-4453) which is concerned +with the usability of the `powershell_script` resource, but also benefits +users of resources like `python`, `bash`, etc: + + # See CHEF-4553 -- let powershell_script execute the guard + powershell_script 'make_logshare' do + guard_interpreter :powershell_script + code 'new-smbshare logshare $env:systemdrive\\logs' + not_if 'get-smbshare logshare' + end + +#### `convert_boolean_return` attribute for `powershell_script` + +When set to `true`, the `convert_boolean_return` attribute will allow any script executed by +`powershell_script` that exits with a PowerShell boolean data type to convert +PowerShell boolean `$true` to exit status 0 and `$false` to exit status 1. + +The new attribute defaults to `false` except when the `powershell_script` resource is executing script passed to a guard attribute +via the `guard_interpreter` attribute in which case it is `true` by default. + #### knife bootstrap log_level Running ```knife bootstrap -V -V``` will run the initial chef-client with a log level of debug. diff --git a/lib/chef/guard_interpreter/default_guard_interpreter.rb b/lib/chef/guard_interpreter/default_guard_interpreter.rb new file mode 100644 index 0000000000..df91c2b1ad --- /dev/null +++ b/lib/chef/guard_interpreter/default_guard_interpreter.rb @@ -0,0 +1,42 @@ +# +# Author:: Adam Edwards (<adamed@getchef.com>) +# Copyright:: Copyright (c) 2014 Chef Software, 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. +# + +class Chef + class GuardInterpreter + class DefaultGuardInterpreter + include Chef::Mixin::ShellOut + + protected + + def initialize(command, opts) + @command = command + @command_opts = opts + end + + public + + def evaluate + shell_out(@command, @command_opts).status.success? + rescue Chef::Exceptions::CommandTimeout + Chef::Log.warn "Command '#{@command}' timed out" + false + end + end + end +end + diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb new file mode 100644 index 0000000000..229a8502c7 --- /dev/null +++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb @@ -0,0 +1,122 @@ +# +# Author:: Adam Edwards (<adamed@getchef.com>) +# Copyright:: Copyright (c) 2014 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/guard_interpreter/default_guard_interpreter' + +class Chef + class GuardInterpreter + class ResourceGuardInterpreter < DefaultGuardInterpreter + + def initialize(parent_resource, command, opts, &block) + super(command, opts) + @parent_resource = parent_resource + @resource = get_interpreter_resource(parent_resource) + end + + def evaluate + # Add attributes inherited from the parent class + # to the resource + merge_inherited_attributes + + # Script resources have a code attribute, which is + # what is used to execute the command, so include + # that with attributes specified by caller in opts + block_attributes = @command_opts.merge({:code => @command}) + + # Handles cases like powershell_script where default + # attributes are different when used in a guard vs. not. For + # powershell_script in particular, this will go away when + # the one attribue that causes this changes its default to be + # the same after some period to prepare for deprecation + if @resource.class.respond_to?(:get_default_attributes) + block_attributes = @resource.class.send(:get_default_attributes, @command_opts).merge(block_attributes) + end + + resource_block = block_from_attributes(block_attributes) + evaluate_action(nil, &resource_block) + end + + protected + + def evaluate_action(action=nil, &block) + @resource.instance_eval(&block) + + run_action = action || @resource.action + + begin + @resource.run_action(run_action) + resource_updated = @resource.updated + rescue Mixlib::ShellOut::ShellCommandFailed + resource_updated = nil + end + + resource_updated + end + + def get_interpreter_resource(parent_resource) + if parent_resource.nil? || parent_resource.node.nil? + raise ArgumentError, "Node for guard resource parent must not be nil" + end + + resource_class = Chef::Resource.resource_for_node(parent_resource.guard_interpreter, parent_resource.node) + + if resource_class.nil? + raise ArgumentError, "Specified guard_interpreter resource #{parent_resource.guard_interpreter.to_s} unknown for this platform" + end + + if ! resource_class.ancestors.include?(Chef::Resource::Script) + raise ArgumentError, "Specified guard interpreter class #{resource_class} must be a kind of Chef::Resource::Script resource" + end + + empty_events = Chef::EventDispatch::Dispatcher.new + anonymous_run_context = Chef::RunContext.new(parent_resource.node, {}, empty_events) + interpreter_resource = resource_class.new('Guard resource', anonymous_run_context) + + interpreter_resource + end + + def block_from_attributes(attributes) + Proc.new do + attributes.keys.each do |attribute_name| + send(attribute_name, attributes[attribute_name]) if respond_to?(attribute_name) + end + end + end + + def merge_inherited_attributes + inherited_attributes = [] + + if @parent_resource.class.respond_to?(:guard_inherited_attributes) + inherited_attributes = @parent_resource.class.send(:guard_inherited_attributes) + end + + if inherited_attributes && !inherited_attributes.empty? + inherited_attributes.each do |attribute| + if @parent_resource.respond_to?(attribute) && @resource.respond_to?(attribute) + parent_value = @parent_resource.send(attribute) + child_value = @resource.send(attribute) + if parent_value || child_value + @resource.send(attribute, parent_value) + end + end + end + end + end + end + end +end diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index 2f934faa3e..a773da550e 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -241,7 +241,9 @@ class Chef :service => Chef::Provider::Service::Windows, :user => Chef::Provider::User::Windows, :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows + :mount => Chef::Provider::Mount::Windows, + :batch => Chef::Provider::Batch, + :powershell_script => Chef::Provider::PowershellScript } }, :mingw32 => { @@ -250,7 +252,9 @@ class Chef :service => Chef::Provider::Service::Windows, :user => Chef::Provider::User::Windows, :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows + :mount => Chef::Provider::Mount::Windows, + :batch => Chef::Provider::Batch, + :powershell_script => Chef::Provider::PowershellScript } }, :windows => { @@ -259,7 +263,9 @@ class Chef :service => Chef::Provider::Service::Windows, :user => Chef::Provider::User::Windows, :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows + :mount => Chef::Provider::Mount::Windows, + :batch => Chef::Provider::Batch, + :powershell_script => Chef::Provider::PowershellScript } }, :solaris => {}, diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb index c459cdf678..967b2d822b 100644 --- a/lib/chef/provider/powershell_script.rb +++ b/lib/chef/provider/powershell_script.rb @@ -23,9 +23,9 @@ class Chef class PowershellScript < Chef::Provider::WindowsScript protected - - EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -eq $true) {exit 0} elseif ( $LASTEXITCODE -ne 0) {exit $LASTEXITCODE} else { exit 1 }" - EXIT_STATUS_RESET_SCRIPT = "$LASTEXITCODE=0\n" + EXIT_STATUS_EXCEPTION_HANDLER = "\ntrap [Exception] {write-error -exception ($_.Exception.Message);exit 1}".freeze + EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -ne $true) { if ( $LASTEXITCODE -ne 0) {exit $LASTEXITCODE} else { exit 1 }}".freeze + EXIT_STATUS_RESET_SCRIPT = "\n$LASTEXITCODE=0".freeze # Process exit codes are strange with PowerShell. Unless you # explicitly call exit in Powershell, the powershell.exe @@ -36,15 +36,28 @@ class Chef # last process run in the script if it is the last command # executed, otherwise 0 or 1 based on whether $? is set to true # (success, where we return 0) or false (where we return 1). - def NormalizeScriptExitStatus( code ) - @code = (! code.nil?) ? ( EXIT_STATUS_RESET_SCRIPT + code + EXIT_STATUS_NORMALIZATION_SCRIPT ) : nil + def normalize_script_exit_status( code ) + target_code = ( EXIT_STATUS_EXCEPTION_HANDLER + + EXIT_STATUS_RESET_SCRIPT + + "\n" + + code.to_s + + EXIT_STATUS_NORMALIZATION_SCRIPT ) + convert_boolean_return = @new_resource.convert_boolean_return + @code = <<EOH +new-variable -name interpolatedexitcode -visibility private -value $#{convert_boolean_return} +new-variable -name chefscriptresult -visibility private +$chefscriptresult = { +#{target_code} +}.invokereturnasis() +if ($interpolatedexitcode -and $chefscriptresult.gettype().name -eq 'boolean') { exit [int32](!$chefscriptresult) } else { exit 0 } +EOH end public def initialize (new_resource, run_context) super(new_resource, run_context, '.ps1') - NormalizeScriptExitStatus(new_resource.code) + normalize_script_exit_status(new_resource.code) end def flags diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index bcee8f1235..7d96b26b4b 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -23,6 +23,7 @@ require 'chef/dsl/data_query' require 'chef/dsl/registry_helper' require 'chef/dsl/reboot_pending' require 'chef/mixin/convert_to_class_name' +require 'chef//guard_interpreter/resource_guard_interpreter' require 'chef/resource/conditional' require 'chef/resource/conditional_action_not_nothing' require 'chef/resource_collection' @@ -249,6 +250,7 @@ F @not_if = [] @only_if = [] @source_line = nil + @guard_interpreter = :default @elapsed_time = 0 @node = run_context ? deprecated_ivar(run_context.node, :node, :warn) : nil @@ -401,6 +403,14 @@ F ignore_failure(arg) end + def guard_interpreter(arg=nil) + set_or_return( + :guard_interpreter, + arg, + :kind_of => Symbol + ) + end + # Sets up a notification from this resource to the resource specified by +resource_spec+. def notifies(action, resource_spec, timing=:delayed) # when using old-style resources(:template => "/foo.txt") style, you @@ -552,7 +562,7 @@ F # * evaluates to false if the block is false, or if the command returns a non-zero exit code. def only_if(command=nil, opts={}, &block) if command || block_given? - @only_if << Conditional.only_if(command, opts, &block) + @only_if << Conditional.only_if(self, command, opts, &block) end @only_if end @@ -573,7 +583,7 @@ F # * evaluates to false if the block is true, or if the command returns a 0 exit status. def not_if(command=nil, opts={}, &block) if command || block_given? - @not_if << Conditional.not_if(command, opts, &block) + @not_if << Conditional.not_if(self, command, opts, &block) end @not_if end @@ -819,6 +829,5 @@ F end end end - end end diff --git a/lib/chef/resource/conditional.rb b/lib/chef/resource/conditional.rb index 60f65e14e2..e6623be5dd 100644 --- a/lib/chef/resource/conditional.rb +++ b/lib/chef/resource/conditional.rb @@ -17,6 +17,7 @@ # require 'chef/mixin/shell_out' +require 'chef/guard_interpreter/resource_guard_interpreter' class Chef class Resource @@ -29,12 +30,12 @@ class Chef private :new end - def self.not_if(command=nil, command_opts={}, &block) - new(:not_if, command, command_opts, &block) + def self.not_if(parent_resource, command=nil, command_opts={}, &block) + new(:not_if, parent_resource, command, command_opts, &block) end - def self.only_if(command=nil, command_opts={}, &block) - new(:only_if, command, command_opts, &block) + def self.only_if(parent_resource, command=nil, command_opts={}, &block) + new(:only_if, parent_resource, command, command_opts, &block) end attr_reader :positivity @@ -42,14 +43,16 @@ class Chef attr_reader :command_opts attr_reader :block - def initialize(positivity, command=nil, command_opts={}, &block) + def initialize(positivity, parent_resource, command=nil, command_opts={}, &block) @positivity = positivity case command when String + @guard_interpreter = new_guard_interpreter(parent_resource, command, command_opts, &block) @command, @command_opts = command, command_opts @block = nil when nil raise ArgumentError, "only_if/not_if requires either a command or a block" unless block_given? + @guard_interpreter = nil @command, @command_opts = nil, nil @block = block else @@ -69,11 +72,11 @@ class Chef end def evaluate - @command ? evaluate_command : evaluate_block + @guard_interpreter ? evaluate_command : evaluate_block end def evaluate_command - shell_out(@command, @command_opts).status.success? + @guard_interpreter.evaluate rescue Chef::Exceptions::CommandTimeout Chef::Log.warn "Command '#{@command}' timed out" false @@ -100,6 +103,16 @@ class Chef end end + private + + def new_guard_interpreter(parent_resource, command, opts) + if parent_resource.guard_interpreter == :default + guard_interpreter = Chef::GuardInterpreter::DefaultGuardInterpreter.new(command, opts) + else + guard_interpreter = Chef::GuardInterpreter::ResourceGuardInterpreter.new(parent_resource, command, opts) + end + end + end end end diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb index 6c07bf9352..7c4fa48c0a 100644 --- a/lib/chef/resource/execute.rb +++ b/lib/chef/resource/execute.rb @@ -125,8 +125,6 @@ class Chef ) end - - end end end diff --git a/lib/chef/resource/powershell_script.rb b/lib/chef/resource/powershell_script.rb index cbd81b1259..1b47e7411a 100644 --- a/lib/chef/resource/powershell_script.rb +++ b/lib/chef/resource/powershell_script.rb @@ -15,17 +15,39 @@ # See the License for the specific language governing permissions and # limitations under the License. # - require 'chef/resource/windows_script' class Chef class Resource class PowershellScript < Chef::Resource::WindowsScript + set_guard_inherited_attributes(:architecture) + def initialize(name, run_context=nil) super(name, run_context, :powershell_script, "powershell.exe") + @convert_boolean_return = false + end + + def convert_boolean_return(arg=nil) + set_or_return( + :convert_boolean_return, + arg, + :kind_of => [ FalseClass, TrueClass ] + ) end + protected + + # Allow callers evaluating guards to request default + # attribute values. This is needed to allow + # convert_boolean_return to be true in guard context by default, + # and false by default otherwise. When this mode becomes the + # default for this resource, this method can be removed since + # guard context and recipe resource context will have the + # same behavior. + def self.get_default_attributes(opts) + {:convert_boolean_return => true} + end end end end diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb index 8cc9c6f0c5..6f66fb9094 100644 --- a/lib/chef/resource/script.rb +++ b/lib/chef/resource/script.rb @@ -58,6 +58,31 @@ class Chef ) end + def self.set_guard_inherited_attributes(*inherited_attributes) + @class_inherited_attributes = inherited_attributes + end + + def self.guard_inherited_attributes(*inherited_attributes) + # Similar to patterns elsewhere, return attributes from this + # class and superclasses as a form of inheritance + ancestor_attributes = [] + + if superclass.respond_to?(:guard_inherited_attributes) + ancestor_attributes = superclass.guard_inherited_attributes + end + + ancestor_attributes.concat(@class_inherited_attributes ? @class_inherited_attributes : []).uniq + end + + set_guard_inherited_attributes( + :cwd, + :environment, + :group, + :path, + :user, + :umask + ) + end end end diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb index 2b563f5bec..108891e9ba 100644 --- a/lib/chef/resource/windows_script.rb +++ b/lib/chef/resource/windows_script.rb @@ -52,11 +52,6 @@ class Chef "cannot execute script with requested architecture '#{desired_architecture.to_s}' on a system with architecture '#{node_windows_architecture(node)}'" end end - - def node - run_context && run_context.node - end - end end end diff --git a/spec/functional/resource/powershell_spec.rb b/spec/functional/resource/powershell_spec.rb index 6bd3b3c1e5..5001e870a9 100644 --- a/spec/functional/resource/powershell_spec.rb +++ b/spec/functional/resource/powershell_spec.rb @@ -84,7 +84,7 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do # last line executed -- in this case, we return the status of the # second to last line. This happens because Powershell gives no # way for us to determine whether the last operation was a cmdlet - # or Windows process. Because the latter gives more specified + # or Windows process. Because the latter gives more specific # errors than 0 or 1, we return that instead, which is acceptable # since callers can test for nonzero rather than testing for 1. it "returns 1 if the last command was a cmdlet that failed and was preceded by an unsuccessfully executed non-cmdlet Windows binary" do @@ -111,6 +111,32 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do resource.run_action(:run) end + it "returns 0 for $false as the last line of the script when convert_boolean_return is false" do + resource.code "$false" + resource.returns(0) + resource.run_action(:run) + end + + it "returns 0 for $true as the last line of the script when convert_boolean_return is false" do + resource.code "$true" + resource.returns(0) + resource.run_action(:run) + end + + it "returns 1 for $false as the last line of the script when convert_boolean_return is true" do + resource.convert_boolean_return true + resource.code "$false" + resource.returns(1) + resource.run_action(:run) + end + + it "returns 0 for $true as the last line of the script when convert_boolean_return is true" do + resource.convert_boolean_return true + resource.code "$true" + resource.returns(0) + resource.run_action(:run) + end + it "executes a script with a 64-bit process on a 64-bit OS, otherwise a 32-bit process" do resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.returns(0) @@ -177,6 +203,241 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end end + describe "when executing guards" do + + before(:each) do + resource.not_if.clear + resource.only_if.clear + resource.guard_interpreter :powershell_script + end + + it "evaluates a succeeding not_if block using cmd.exe as false by default" do + resource.guard_interpreter :default + resource.not_if "exit /b 0" + resource.should_skip?(:run).should be_true + end + + it "evaluates a failing not_if block using cmd.exe as true by default" do + resource.guard_interpreter :default + resource.not_if "exit /b 2" + resource.should_skip?(:run).should be_false + end + + it "evaluates an succeeding only_if block using cmd.exe as true by default" do + resource.guard_interpreter :default + resource.only_if "exit /b 0" + resource.should_skip?(:run).should be_false + end + + it "evaluates a failing only_if block using cmd.exe as false by default" do + resource.guard_interpreter :default + resource.only_if "exit /b 2" + resource.should_skip?(:run).should be_true + end + + it "evaluates a powershell $false for a not_if block as true" do + resource.not_if "$false" + resource.should_skip?(:run).should be_false + end + + it "evaluates a powershell $true for a not_if block as false" do + resource.not_if "$true" + resource.should_skip?(:run).should be_true + end + + it "evaluates a powershell $false for an only_if block as false" do + resource.only_if "$false" + resource.should_skip?(:run).should be_true + end + + it "evaluates a powershell $true for a only_if block as true" do + resource.only_if "$true" + resource.should_skip?(:run).should be_false + end + + it "evaluates a not_if block using powershell.exe" do + resource.not_if "exit([int32](![System.Environment]::CommandLine.Contains('powershell.exe')))" + resource.should_skip?(:run).should be_true + end + + it "evaluates an only_if block using powershell.exe" do + resource.only_if "exit([int32](![System.Environment]::CommandLine.Contains('powershell.exe')))" + resource.should_skip?(:run).should be_false + end + + it "evaluates a not_if block as false" do + resource.not_if { false } + resource.should_skip?(:run).should be_false + end + + it "evaluates a not_if block as true" do + resource.not_if { true } + resource.should_skip?(:run).should be_true + end + + it "evaluates an only_if block as false" do + resource.only_if { false } + resource.should_skip?(:run).should be_true + end + + it "evaluates an only_if block as true" do + resource.only_if { true } + resource.should_skip?(:run).should be_false + end + + it "evaluates a non-zero powershell exit status for not_if as true" do + resource.not_if "exit 37" + resource.should_skip?(:run).should be_false + end + + it "evaluates a zero powershell exit status for not_if as false" do + resource.not_if "exit 0" + resource.should_skip?(:run).should be_true + end + + it "evaluates a failed executable exit status for not_if as false" do + resource.not_if windows_process_exit_code_not_found_content + resource.should_skip?(:run).should be_false + end + + it "evaluates a successful executable exit status for not_if as true" do + resource.not_if windows_process_exit_code_success_content + resource.should_skip?(:run).should be_true + end + + it "evaluates a failed executable exit status for only_if as false" do + resource.only_if windows_process_exit_code_not_found_content + resource.should_skip?(:run).should be_true + end + + it "evaluates a successful executable exit status for only_if as true" do + resource.only_if windows_process_exit_code_success_content + resource.should_skip?(:run).should be_false + end + + it "evaluates a failed cmdlet exit status for not_if as true" do + resource.not_if "throw 'up'" + resource.should_skip?(:run).should be_false + end + + it "evaluates a successful cmdlet exit status for not_if as true" do + resource.not_if "cd ." + resource.should_skip?(:run).should be_true + end + + it "evaluates a failed cmdlet exit status for only_if as false" do + resource.only_if "throw 'up'" + resource.should_skip?(:run).should be_true + end + + it "evaluates a successful cmdlet exit status for only_if as true" do + resource.only_if "cd ." + resource.should_skip?(:run).should be_false + end + + it "evaluates a not_if block using the cwd guard parameter" do + custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" + resource.not_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')", :cwd => custom_cwd + resource.should_skip?(:run).should be_true + end + + it "evaluates an only_if block using the cwd guard parameter" do + custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" + resource.only_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')", :cwd => custom_cwd + resource.should_skip?(:run).should be_false + end + + it "inherits cwd from the parent resource for only_if" do + custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" + resource.cwd custom_cwd + resource.only_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')" + resource.should_skip?(:run).should be_false + end + + it "inherits cwd from the parent resource for not_if" do + custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" + resource.cwd custom_cwd + resource.not_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')" + resource.should_skip?(:run).should be_true + end + + it "evaluates a 64-bit resource with a 64-bit guard and interprets boolean false as zero status code", :windows64_only do + resource.architecture :x86_64 + resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -ne 'AMD64')" + resource.should_skip?(:run).should be_false + end + + it "evaluates a 64-bit resource with a 64-bit guard and interprets boolean true as nonzero status code", :windows64_only do + resource.architecture :x86_64 + resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'AMD64')" + resource.should_skip?(:run).should be_true + end + + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code" do + resource.architecture :i386 + resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -ne 'X86')" + resource.should_skip?(:run).should be_false + end + + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code" do + resource.architecture :i386 + resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'X86')" + resource.should_skip?(:run).should be_true + end + + it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for only_if" do + resource.convert_boolean_return true + resource.only_if "$false" + resource.should_skip?(:run).should be_true + end + + it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for not_if" do + resource.convert_boolean_return true + resource.not_if "$false" + resource.should_skip?(:run).should be_false + end + + it "evaluates a simple boolean true as 0 status code when convert_boolean_return is true for only_if" do + resource.convert_boolean_return true + resource.only_if "$true" + resource.should_skip?(:run).should be_false + end + + it "evaluates a simple boolean true as 0 status code when convert_boolean_return is true for not_if" do + resource.convert_boolean_return true + resource.not_if "$true" + resource.should_skip?(:run).should be_true + end + + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for only_if" do + resource.convert_boolean_return true + resource.architecture :i386 + resource.only_if "$env:PROCESSOR_ARCHITECTURE -eq 'X86'" + resource.should_skip?(:run).should be_false + end + + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for not_if" do + resource.convert_boolean_return true + resource.architecture :i386 + resource.not_if "$env:PROCESSOR_ARCHITECTURE -ne 'X86'" + resource.should_skip?(:run).should be_false + end + + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for only_if" do + resource.convert_boolean_return true + resource.architecture :i386 + resource.only_if "$env:PROCESSOR_ARCHITECTURE -ne 'X86'" + resource.should_skip?(:run).should be_true + end + + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for not_if" do + resource.convert_boolean_return true + resource.architecture :i386 + resource.not_if "$env:PROCESSOR_ARCHITECTURE -eq 'X86'" + resource.should_skip?(:run).should be_true + end + end + def get_script_output script_output = File.read(script_output_path) end diff --git a/spec/support/shared/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb index db2882e1a1..fc06fb55d0 100644 --- a/spec/support/shared/functional/windows_script.rb +++ b/spec/support/shared/functional/windows_script.rb @@ -38,7 +38,7 @@ shared_context Chef::Resource::WindowsScript do end before(:each) do -k File.delete(script_output_path) if File.exists?(script_output_path) + File.delete(script_output_path) if File.exists?(script_output_path) end after(:each) do diff --git a/spec/support/shared/unit/script_resource.rb b/spec/support/shared/unit/script_resource.rb index 5f37506df6..1137958420 100644 --- a/spec/support/shared/unit/script_resource.rb +++ b/spec/support/shared/unit/script_resource.rb @@ -48,5 +48,43 @@ shared_examples_for "a script resource" do @resource.flags.should eql("-f") end + describe "when executing guards" do + let(:resource) { @resource } + + before(:each) do + node = Chef::Node.new + + node.automatic[:platform] = "debian" + node.automatic[:platform_version] = "6.0" + + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, {}, events) + resource.run_context = run_context + resource.code 'echo hi' + end + + it "inherits exactly the :cwd, :environment, :group, :path, :user, and :umask attributes from a parent resource class" do + inherited_difference = Chef::Resource::Script.guard_inherited_attributes - + [:cwd, :environment, :group, :path, :user, :umask ] + + inherited_difference.should == [] + end + + it "when guard_interpreter is set to the default value, the guard command string should be evaluated by command execution and not through a resource" do + Chef::Resource::Conditional.any_instance.should_not_receive(:evaluate_block) + Chef::Resource::Conditional.any_instance.should_receive(:evaluate_command).and_return(true) + Chef::GuardInterpreter::ResourceGuardInterpreter.any_instance.should_not_receive(:evaluate_action) + resource.only_if 'echo hi' + resource.should_skip?(:run).should == nil + end + + it "when a valid guard_interpreter resource is specified, a block should be used to evaluate the guard" do + Chef::GuardInterpreter::DefaultGuardInterpreter.any_instance.should_not_receive(:evaluate) + Chef::GuardInterpreter::ResourceGuardInterpreter.any_instance.should_receive(:evaluate_action).and_return(true) + resource.guard_interpreter :script + resource.only_if 'echo hi' + resource.should_skip?(:run).should == nil + end + end end diff --git a/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb new file mode 100644 index 0000000000..a016cbfeb8 --- /dev/null +++ b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb @@ -0,0 +1,56 @@ +# +# Author:: Adam Edwards (<adamed@getchef.com>) +# Copyright:: Copyright (c) 2014 Chef Software, 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' + +describe Chef::GuardInterpreter::ResourceGuardInterpreter do + before(:each) do + node = Chef::Node.new + + node.default["kernel"] = Hash.new + node.default["kernel"][:machine] = :x86_64.to_s + + run_context = Chef::RunContext.new(node, nil, nil) + + @resource = Chef::Resource.new("powershell_unit_test", run_context) + @resource.stub(:run_action) + @resource.stub(:updated).and_return(true) + end + + describe "when evaluating a guard resource" do + let(:resource) { @resource } + + it "should allow guard interpreter to be set to Chef::Resource::Script" do + resource.guard_interpreter(:script) + allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false) + resource.only_if("echo hi") + end + + it "should allow guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do + resource.guard_interpreter(:powershell_script) + allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false) + resource.only_if("echo hi") + end + + it "should raise an exception if guard_interpreter is set to a resource not derived from Chef::Resource::Script" do + resource.guard_interpreter(:file) + expect { resource.only_if("echo hi") }.to raise_error ArgumentError + end + end +end + diff --git a/spec/unit/resource/conditional_spec.rb b/spec/unit/resource/conditional_spec.rb index 1be7bcea71..4df185bcd6 100644 --- a/spec/unit/resource/conditional_spec.rb +++ b/spec/unit/resource/conditional_spec.rb @@ -24,12 +24,13 @@ describe Chef::Resource::Conditional do Mixlib::ShellOut.any_instance.stub(:run_command).and_return(nil) @status = OpenStruct.new(:success? => true) Mixlib::ShellOut.any_instance.stub(:status).and_return(@status) + @parent_resource = Chef::Resource.new(nil, Chef::Node.new) end describe "when created as an `only_if`" do describe "after running a successful command" do before do - @conditional = Chef::Resource::Conditional.only_if("true") + @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "true") end it "indicates that resource convergence should continue" do @@ -40,7 +41,7 @@ describe Chef::Resource::Conditional do describe "after running a negative/false command" do before do @status.send("success?=", false) - @conditional = Chef::Resource::Conditional.only_if("false") + @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "false") end it "indicates that resource convergence should not continue" do @@ -50,8 +51,8 @@ describe Chef::Resource::Conditional do describe 'after running a command which timed out' do before do - @conditional = Chef::Resource::Conditional.only_if("false") - @conditional.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout) + @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "false") + Chef::GuardInterpreter::DefaultGuardInterpreter.any_instance.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout) end it 'indicates that resource convergence should not continue' do @@ -66,7 +67,7 @@ describe Chef::Resource::Conditional do describe "after running a block that returns a truthy value" do before do - @conditional = Chef::Resource::Conditional.only_if { Object.new } + @conditional = Chef::Resource::Conditional.only_if(@parent_resource) { Object.new } end it "indicates that resource convergence should continue" do @@ -76,7 +77,7 @@ describe Chef::Resource::Conditional do describe "after running a block that returns a falsey value" do before do - @conditional = Chef::Resource::Conditional.only_if { nil } + @conditional = Chef::Resource::Conditional.only_if(@parent_resource) { nil } end it "indicates that resource convergence should not continue" do @@ -88,7 +89,7 @@ describe Chef::Resource::Conditional do describe "when created as a `not_if`" do describe "after running a successful/true command" do before do - @conditional = Chef::Resource::Conditional.not_if("true") + @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "true") end it "indicates that resource convergence should not continue" do @@ -99,7 +100,7 @@ describe Chef::Resource::Conditional do describe "after running a failed/false command" do before do @status.send("success?=", false) - @conditional = Chef::Resource::Conditional.not_if("false") + @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "false") end it "indicates that resource convergence should continue" do @@ -109,8 +110,8 @@ describe Chef::Resource::Conditional do describe 'after running a command which timed out' do before do - @conditional = Chef::Resource::Conditional.not_if("false") - @conditional.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout) + @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "false") + Chef::GuardInterpreter::DefaultGuardInterpreter.any_instance.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout) end it 'indicates that resource convergence should continue' do @@ -125,7 +126,7 @@ describe Chef::Resource::Conditional do describe "after running a block that returns a truthy value" do before do - @conditional = Chef::Resource::Conditional.not_if { Object.new } + @conditional = Chef::Resource::Conditional.not_if(@parent_resource) { Object.new } end it "indicates that resource convergence should not continue" do @@ -135,7 +136,7 @@ describe Chef::Resource::Conditional do describe "after running a block that returns a falsey value" do before do - @conditional = Chef::Resource::Conditional.not_if { nil } + @conditional = Chef::Resource::Conditional.not_if(@parent_resource) { nil } end it "indicates that resource convergence should continue" do diff --git a/spec/unit/resource/powershell_spec.rb b/spec/unit/resource/powershell_spec.rb index a35e37c696..da20c4f0bf 100644 --- a/spec/unit/resource/powershell_spec.rb +++ b/spec/unit/resource/powershell_spec.rb @@ -36,7 +36,91 @@ describe Chef::Resource::PowershellScript do @resource.should be_a_kind_of(Chef::Resource::PowershellScript) end - context "windowsscript" do + it "should set convert_boolean_return to false by default" do + @resource.convert_boolean_return.should == false + end + + it "should return the value for convert_boolean_return that was set" do + @resource.convert_boolean_return true + @resource.convert_boolean_return.should == true + @resource.convert_boolean_return false + @resource.convert_boolean_return.should == false + end + + context "when using guards" do + let(:resource) { @resource } + before(:each) do + resource.stub(:run_action) + resource.stub(:updated).and_return(true) + end + + it "inherits exactly the :cwd, :environment, :group, :path, :user, :umask, and :architecture attributes from a parent resource class" do + inherited_difference = Chef::Resource::PowershellScript.guard_inherited_attributes - + [:cwd, :environment, :group, :path, :user, :umask, :architecture ] + + inherited_difference.should == [] + end + + it "should allow guard interpreter to be set to Chef::Resource::Script" do + resource.guard_interpreter(:script) + allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false) + resource.only_if("echo hi") + end + + it "should allow guard interpreter to be set to Chef::Resource::Bash derived from Chef::Resource::Script" do + resource.guard_interpreter(:bash) + allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false) + resource.only_if("echo hi") + end + + it "should allow guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do + resource.guard_interpreter(:powershell_script) + allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false) + resource.only_if("echo hi") + end + + it "should enable convert_boolean_return by default for guards in the context of powershell_script when no guard params are specified" do + allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(true) + allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( + {:convert_boolean_return => true, :code => "$true"}).and_return(Proc.new {}) + resource.only_if("$true") + end + + it "should enable convert_boolean_return by default for guards in non-Chef::Resource::Script derived resources when no guard params are specified" do + node = Chef::Node.new + run_context = Chef::RunContext.new(node, nil, nil) + file_resource = Chef::Resource::File.new('idontexist', run_context) + file_resource.guard_interpreter :powershell_script + + allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( + {:convert_boolean_return => true, :code => "$true"}).and_return(Proc.new {}) + resource.only_if("$true") + end + + it "should enable convert_boolean_return by default for guards in the context of powershell_script when guard params are specified" do + guard_parameters = {:cwd => '/etc/chef', :architecture => :x86_64} + allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( + {:convert_boolean_return => true, :code => "$true"}.merge(guard_parameters)).and_return(Proc.new {}) + resource.only_if("$true", guard_parameters) + end + + it "should pass convert_boolean_return as true if it was specified as true in a guard parameter" do + guard_parameters = {:cwd => '/etc/chef', :convert_boolean_return => true, :architecture => :x86_64} + allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( + {:convert_boolean_return => true, :code => "$true"}.merge(guard_parameters)).and_return(Proc.new {}) + resource.only_if("$true", guard_parameters) + end + + it "should pass convert_boolean_return as false if it was specified as true in a guard parameter" do + other_guard_parameters = {:cwd => '/etc/chef', :architecture => :x86_64} + parameters_with_boolean_disabled = other_guard_parameters.merge({:convert_boolean_return => false, :code => "$true"}) + allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( + parameters_with_boolean_disabled).and_return(Proc.new {}) + resource.only_if("$true", parameters_with_boolean_disabled) + end + end + + context "as a script running in Windows-based scripting language" do let(:resource_instance) { @resource } let(:resource_instance_name ) { @resource.command } let(:resource_name) { :powershell_script } @@ -44,5 +128,4 @@ describe Chef::Resource::PowershellScript do it_should_behave_like "a Windows script resource" end - end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index e9a60c9861..60f3bdb8ea 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -344,7 +344,7 @@ describe Chef::Resource do expected_keys = [ :allowed_actions, :params, :provider, :updated, :updated_by_last_action, :before, :supports, :noop, :ignore_failure, :name, :source_line, - :action, :retries, :retry_delay, :elapsed_time] + :action, :retries, :retry_delay, :elapsed_time, :guard_interpreter] (hash.keys - expected_keys).should == [] (expected_keys - hash.keys).should == [] hash[:name].should eql("funk") @@ -526,6 +526,28 @@ describe Chef::Resource do snitch_var2.should be_false end + describe "guard_interpreter attribute" do + let(:resource) { @resource } + + it "should be set to :default by default" do + resource.guard_interpreter.should == :default + end + + it "if set to :default should return :default when read" do + resource.guard_interpreter(:default) + resource.guard_interpreter.should == :default + end + + it "should raise Chef::Exceptions::ValidationFailed on an attempt to set the guard_interpreter attribute to something other than a Symbol" do + expect { resource.guard_interpreter('command_dot_com') }.to raise_error(Chef::Exceptions::ValidationFailed) + end + + it "should not raise an exception when setting the guard interpreter attribute to a Symbol" do + Chef::GuardInterpreter::ResourceGuardInterpreter.stub(:new).and_return(nil) + expect { resource.guard_interpreter(:command_dot_com) }.not_to raise_error + end + end + end describe "should_skip?" do |