diff options
269 files changed, 6693 insertions, 2374 deletions
diff --git a/.travis.yml b/.travis.yml index ec05a9f002..65d28d0f68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,10 @@ branches: script: bundle exec rspec --color --format progress +env: + global: + - FORCE_FFI_YAJL=ext + matrix: include: - rvm: 1.9.3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2daf841499..e22cb2f150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,11 +74,74 @@ Improve the regex for /etc/rc.conf for the FreeBSD service provider * [**Stanislav Bogatyrev**](https://github.com/realloc): Fetch recipe_url before loading json_attribs in chef-solo (CHEF-5075) -* [**Mal Graty**](https://github.com/mal): Workaround for a breaking change in git's shallow-clone behavior. (Issue 1563) -* [**Dave Eddy**](https://github.com/bahamas10): Fix version detection in FreeBSD pkgng provider. (PR 1980) +* [**Mal Graty**](https://github.com/mal): + Workaround for a breaking change in git's shallow-clone behavior. (Issue 1563) +* [**Dave Eddy**](https://github.com/bahamas10): + Fix version detection in FreeBSD pkgng provider. (PR 1980) +* [**Dan Rathbone**](https://github.com/rathers): + Fixed gem_package resource to be able to upgrade gems when version is not set. +* [**Jean Mertz**](https://github.com/JeanMertz): + Made Chef Client load library folder recursively. +* [**Eric Saxby**](https://github.com/sax): + Made Chef Client read the non-root crontab entries as the user specified in the resource. +* [**sawanoboly**](https://github.com/sawanoboly): + Added `--dry-run` option to `knife cookbook site share` which displays the files that are to be uploaded to Supermarket. +* [**Sander van Harmelen**](https://github.com/svanharmelen): + Fixed `Chef::HTTP` to be able to follow relative redirects. +* [**Cory Stephenson**](https://github.com/Aevin1387): + Fixed FreeBSD port package provider to interpret FreeBSD version 10 correctly. +* [**Brett Chalupa**](https://github.com/brettchalupa): + Added `source_url` and `issues_url` options to metadata to be used by Supermarket. +* [**Anshul Sharma**](https://github.com/justanshulsharma): + Fixed Chef Client to use the `:client_name` instead of `:node_name` during initial client registration. +* [**tbe**](https://github.com/tbe): + Fixed Paludis package provider to be able to interpret the package category. +* [**David Workman**](https://github.com/workmad3): + Added a more clear error message to chef-apply when no recipe is given. +* [**Joe Nuspl**](https://github.com/nvwls): + Added support for `sensitive` property to the execute resource. +* [**Nolan Davidson**](https://github.com/nsdavidson): + Added an error message to prevent unintentional running of `exec()` in recipes. +* [**wacky612**](https://github.com/wacky612): + Fixed a bug in pacman package provider that was preventing the installation of `bind` package. +* [**Ionuț Arțăriși**](https://github.com/mapleoin): + Changed the default service provider to systemd on SLES versions 12 and higher. +* [**Ionuț Arțăriși**](https://github.com/mapleoin): + Changed the default group provider to gpasswd on SLES versions 12 and higher. +* [**Noah Kantrowitz**](https://github.com/coderanger): + Implemented [RFC017 - File Specificity Overhaul](https://github.com/opscode/chef-rfc/blob/master/rfc017-file-specificity.md). +* [**James Bence**](https://github.com/jbence): + Improved the reliability of Git provider by making it to be more specific when selecting tags. +* [**Jean Mertz**](https://github.com/JeanMertz): + Changed knife upload not to validate the ruby files under files & templates directories. +* [**Alex Pop**](https://github.com/alexpop): + Made `knife cookbook create` to display the directory of the cookbook that is being created. +* [**Alex Pop**](https://github.com/alexpop): + Fixed the information debug output for the configuration file being used when running knife. +* [**Martin Smith**](https://github.com/martinb3): + Changed `knife cookbook site share` to make category an optional parameter when uploading cookbooks. + It is still required when the cookbook is being uploaded for the first time but on the consequent + uploads existing category of the cookbook will be used. +* [**Nicolas DUPEUX**](https://github.com/vaxvms): + Added JSON output to `knife status` command. `--medium` and `--long` output formatting parameters are now supported in knife status. +* [**Trevor North**](https://github.com/trvrnrth): + Removed dead code from `knife ssh`. +* [**Nicolas Szalay**](https://github.com/rottenbytes): + Fixed a bug preventing mounting of cgroup type devices in the mount provider. +* [**Anshul Sharma**](https://github.com/justanshulsharma): + Fixed inconsistent globbing in `knife from file` command. +* [**Nicolas Szalay**](https://github.com/rottenbytes): + Made user prompts in knife more beautiful by adding a space after Y/N prompts. +* [**Ivan Larionov**](https://github.com/xeron): + Made empty run_list to produce an empty array when using node.to_hash. + ### Chef Contributions +* Default `guard_interpreter` for `powershell_script` resource set to `:powershell_script`, for `batch` to `:batch` +* Recipe definition now returns the retval of the definition +* Add support for Windows 10 to version helper. +* `dsc_script` resource should honor configuration parameters when `configuration_data_script` is not set (Issue #2209) * Ruby has been updated to 2.1.3 along with rubygems update to 2.4.2 * Removed shelling out to erubis/ruby for syntax checks (>= 1.9 has been able to do this in the ruby vm itself for awhile now and we've dropped 1.8.7 which @@ -147,6 +210,16 @@ * Verify x509 properties of certificates in the :trusted_certs_dir during knife ssl check. * Disable unforked interval chef-client runs. * Removed dependencies on the 'json' gem, replaced with ffi-yajl. Use Chef::JSONCompat library for parsing and printing. +* Restore the deprecation logic of #valid_actions in LWRPs until Chef 13. +* Now that we don't allow unforked chef-client interval runs, remove the reloading of previously defined LWRPs. +* Use shell_out to determine Chef::Config[:internal_locale], fix CentOS locale detection bug. +* `only_if` and `not_if` attributes of `execute` resource now inherits the parent resource's + attributes when set to a `String`. +* Retain the original value of `retries` for resources and display the original value when the run fails. +* Added service provider for AIX. +* The Windows env provider will delete elements even if they are only in ENV (and not in the registry) +* Allow events to be logged to Windows Event Log +* Fixed bug in env resource where a value containing the delimiter could never correctly match the existing values ## Last Release: 11.14.2 diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md index 08c343809a..7eb85eb894 100644 --- a/DOC_CHANGES.md +++ b/DOC_CHANGES.md @@ -521,3 +521,25 @@ end Chef will then execute the Homebrew command as that user. The `homebrew_user` attribute can only be provided to the `homebrew_package` resource, not the `package` resource. + +### Default `guard_interpreter` attribute for `powershell_script` resource + +For the `powershell_script` resource, the `guard_interpreter` attribute is set to `:powershell_script` by default. This means +that if a string is supplied to an `only_if` or `not_if` attribute of a `powersell_script` resource, the PowerShell command +interpreter (the 64-bit version) will be used to evaluate the guard. It also means that other features available to the guard +when `guard_interpreter` is set to something other than `:default`, such as inheritance of attributes and the specification of +process architectur of the guard process (i.e. 32-bit or 64-bit process) are available by default. + +In versions of Chef prior to Chef 12, the value of the attribute was `:default` by default, which uses the 32-bit version of the +`cmd.exe` (batch script language) shell to evaluate strings supplied to guards. + +### Default `guard_interpreter` attribute for `batch` resource + +For the`batch` resource, the `guard_interpreter` attribute it is set to `:batch` by default. This means +that if a string is supplied to an `only_if` or `not_if` attribute of a `batch` resource, the 64-bit version of the Windows +default command interpreter, `cmd.exe`, will be used to evaluate the guard. It also means that other features available to the guard +when `guard_interpreter` is set to something other than `:default`, such as inheritance of attributes and the specification of +process architectur of the guard process (i.e. 32-bit or 64-bit process) are available by default. + +In versions of Chef prior to Chef 12, the value of the attribute was `:default` by default, which means the 32-bit version of the +`cmd.exe` (batch script language) shell would be used to evaluate strings supplied to guards. @@ -2,8 +2,6 @@ source "https://rubygems.org" gemspec :name => "chef" gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby" -# TODO remove this when next version of ffi-yajl is released including this change -gem "ffi-yajl", :github => 'tyler-ball/ffi-yajl', :branch => 'tball/remove_to_json' group(:docgen) do gem "yard" @@ -33,7 +33,7 @@ emerge, etc.): * git * C compiler, header files, etc. On Ubuntu/debian, use the `build-essential` package. -* ruby 1.8.7 or later (1.9.3+ recommended) +* ruby 1.9.3 or later * rubygems * bundler diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a6d1a65f51..ca1f504ba5 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,26 @@ # Chef Client Release Notes 12.0.0: +# Internal API Changes in this Release + +These changes do not impact any cookbook code, but may impact tools that +use the code base as a library. Authors of tools that rely on Chef +internals should review these changes carefully and update their +applications. + +## Changes to CookbookUpload + +`Chef::CookbookUpload.new` previously took a path as the second +argument, but due to internal changes, this parameter was not used, and +it has been removed. See: https://github.com/opscode/chef/commit/12c9bed3a5a7ab86ff78cb660d96f8b77ad6395d + +## Changes to FileVendor + +`Chef::Cookbook::FileVendor` was previously configured by passing a +block to the `on_create` method; it is now configured by calling either +`fetch_from_remote` or `fetch_from_disk`. See: https://github.com/opscode/chef/commit/3b2b4de8e7f0d55524f2a0ccaf3e1aa9f2d371eb + +# End-User Changes + ## Knife Prefers `config.rb` to `knife.rb`. Knife will now look for `config.rb` in preference to `knife.rb` for its @@ -118,7 +139,7 @@ homebrew_package 'vim' do end ``` -Chef will then execute the Homebrew command as that user. The `homebrew_user` attribute can only be provided to the +Chef will then execute the Homebrew command as that user. The `homebrew_user` attribute can only be provided to the `homebrew_package` resource, not the `package` resource. ## DSCL user provider now supports Mac OS X 10.7 and above. @@ -249,25 +270,6 @@ Informational messages from knife are now sent to stderr, allowing you to pipe t The `data_bag_item` dsl method can be used to load encrypted data bag items when an additional `secret` String parameter is included. If no `secret` is provided but the data bag item is encrypted, `Chef::Config[:encrypted_data_bag_secret]` will be checked. -# Internal API Changes in this Release - -These changes do not impact any cookbook code, but may impact tools that -use the code base as a library. Authors of tools that rely on Chef -internals should review these changes carefully and update their -applications. - -## Changes to CookbookUpload - -`Chef::CookbookUpload.new` previously took a path as the second -argument, but due to internal changes, this parameter was not used, and -it has been removed. See: https://github.com/opscode/chef/commit/12c9bed3a5a7ab86ff78cb660d96f8b77ad6395d - -## Changes to FileVendor - -`Chef::Cookbook::FileVendor` was previously configured by passing a -block to the `on_create` method; it is now configured by calling either -`fetch_from_remote` or `fetch_from_disk`. See: https://github.com/opscode/chef/commit/3b2b4de8e7f0d55524f2a0ccaf3e1aa9f2d371eb - ## 'group' provider on OSX properly uses 'dscl' to determine existing groups On OSX, the 'group' provider would use 'etc' to determine existing groups, @@ -315,3 +317,111 @@ error when `client_fork false` is set. ## Interval sleep occurs before converge When running chef-client or chef-solo at intervals, the application will perform splay and interval sleep before converging chef. (In previous releases, splay sleep occurred first, then convergance, then interval sleep). + +## `--dry-run` option for knife cookbook site share +"knife cookbook site share" command now accepts a new command line option `--dry-run`. When this option is specified, command + will display the files that are about to be uploaded to the Supermarket. + +## New cookbook metadata attributes for Supermarket +Cookbook metadata now accepts `source_url` and `issues_url` that should point to the source code of the cookbook and + the issue tracker of the cookbook. These attributes are being used by Supermarket. + +## CHEF RFC-017 - File Specificity Overhaul +RFC-017 has two great advantages: +1. It makes it easy to create cookbooks by removing the need for `default/` folder when adding templates and cookbook files. +2. It enables the configuring a custom lookup logic when Chef is attempting to find cookbook files. + +You can read more about this RFC [here](https://github.com/opscode/chef-rfc/blob/master/rfc017-file-specificity.md). + +## JSON output for `knife status` +`knife status` command now supports two additional output formats: + +1. `--medium`: Includes normal attributes in the output and presents the output as JSON. +1. `--long`: Includes all attributes in the output and presents the output as JSON. + +## AIX Service Provider Support + +Chef 12 now supports managing services on AIX, using both the SRC (Subsystem Resource Controller) as well as the BSD-style init system. SRC is the default; the BSD-style provider can be selected using `Chef::Provider::Service::AixInit`. + +The SRC service provider will manage services as well as service groups. However, because SRC has no standard mechanism for starting services on system boot, `action :enable` and `action :disable` are not supported for SRC services. You may use the `execute` resource to invoke `mkitab`, for example, to add lines to `/etc/inittab` with the right parameters. + +## `guard_interpreter` attribute for `powershell_script` defaults to `:powershell_script` +The default `guard_interpreter` attribute for the `powershell_script` resource is `:powershell_script`. This means that the +64-bit version of the PowerShell shell will be used to evaluate strings supplied to the `not_if` or `only_if` attributes of the +resource. Prior to this release, the default value was `:default`, which used the 32-bit version of the `cmd.exe` shell to evaluate the guard. + +If you are using guard expressions with the `powershell_script` resource in your recipes, you should override the +`guard_interpreter` attribute to restore the behavior of guards for this resource in Chef 11: + +```ruby +# The not_if will be evaluated with 64-bit PowerShell by default, +# So override it to :default if your guard assumes 32-bit cmd.exe +powershell_script 'make_safe_backup' do + guard_interpreter :default # Chef 11 behavior + code 'cp ~/data/nodes.json $env:systemroot/system32/data/nodes.bak' + + # cmd.exe (batch) guard below behaves differently in 32-bit vs. 64-bit processes + not_if 'if NOT EXIST %SYSTEMROOT%\\system32\\data\\nodes.bak exit /b 1' +end +``` + +If the code in your guard expression does not rely on the `cmd.exe` interpreter, e.g. it simply executes a process that should +return an exit code such as `findstr datafile sentinelvalue`, and does not rely on being executed from a 32-bit process, then it +should function identically when executed from the PowerShell shell and it is not necessary to override the attribute +to`:default` to restore Chef 11 behavior. + +Note that with this change guards for the `powershell_script` resource will also inherit some attributes like `:architecture`, `:cwd`, +`:environment`, and `:path`. + +## `guard_interpreter` attribute for `batch` resource defaults to `:batch` + +The default `guard_interpreter` attribute for the `batch` resource is now `:batch`. This means that the +64-bit version of the `cmd.exe` shell will be used to evaluate strings supplied to the `not_if` or `only_if` attributes of the +resource. Prior to this release, the default value was `:default`, which used the 32-bit version of the `cmd.exe` shell to evaluate the guard. + +Note that with this change guards for the `batch` resource will also inherit some attributes like `:architecture`, `:cwd`, +`:environment`, and `:path`. + +Unless the code you supply to guard attributes (`only_if` and `not_if`) has logic that requires that the 32-bit version of +`cmd.exe` be used to evaluate the guard or you need to avoid the inheritance behavior of guard options, that code should function identically in this release of Chef and Chef 11 releases. + +If an assumption of a 32-bit process for guard evaluation exists in your code, you can obtain the equivalent of Chef 11's 32-bit +process behavior by supplying an architecture attribute to the guard as follows: + +```ruby +# The not_if will be evaluated with 64-bit cmd.exe by default, +# so you can verride it with the :architecture guard option to +# make it 32-bit as it is in Chef 11 +batch 'make_safe_backup' do + code 'copy %USERPROFILE%\\data\\nodes.json %SYSTEMROOT%\\system32\\data\\nodes.bak' + + # cmd.exe (batch) guard code below behaves differently in 32-bit vs. 64-bit processes + not_if 'if NOT EXIST %SYSTEMROOT%\\system32\\data\\nodes.bak exit /b 1', :architecture => :i386 +end +``` + +If in addition to the 32-bit process assumption you also need to avoid the inheritance behavior, you can revert completely to +the Chef 11's 32-bit process, no inheritance behavior by supplying `:default` for the `guard_interpreter` as follows: + +```ruby +# The not_if will be evaluated with 64-bit cmd.exe by default, +# so override it to :default if your guard assumes 32-bit cmd.exe +batch 'make_safe_backup' do + guard_interpreter :default # Revert to Chef 11 behavior + code 'copy %USERPROFILE%\\data\\nodes.json %SYSTEMROOT%\\system32\\data\\nodes.bak' + + # cmd.exe (batch) guard code below behaves differently in 32-bit vs. 64-bit processes + not_if 'if NOT EXIST %SYSTEMROOT%\\system32\\data\\nodes.bak exit /b 1' +end +``` + +## Chef Client logs events to Windows Event Log on Windows + +Chef 12 will log a small set of events to Windows Event Log. This feature is enabled by default, and can be disabled by the new config option `disable_event_logger`. + +Events by default will be logged to the "Application" event log on Windows. Chef will log event when: +* Run starts +* Run completes +* Run fails + +Information about these events can be found in `Chef::EventDispatch::Base`. @@ -52,6 +52,18 @@ task :pedant do require File.expand_path('spec/support/pedant/run_pedant') end +task :build_eventlog do + Dir.chdir 'ext/win32-eventlog/' do + system 'rake build' + end +end + +task :register_eventlog do + Dir.chdir 'ext/win32-eventlog/' do + system 'rake register' + end +end + begin require 'yard' DOC_FILES = [ "README.rdoc", "LICENSE", "spec/tiny_server.rb", "lib/**/*.rb" ] diff --git a/bin/chef-service-manager b/bin/chef-service-manager index aaf91c7568..7cef10f506 100755 --- a/bin/chef-service-manager +++ b/bin/chef-service-manager @@ -28,7 +28,7 @@ if Chef::Platform.windows? :service_name => "chef-client", :service_display_name => "Chef Client Service", :service_description => "Runs Chef Client on regular, configurable intervals.", - :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../lib/chef/application/windows_service.rb')) + :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../../../bin/chef-windows-service')) } Chef::Application::WindowsServiceManager.new(chef_client_service).run else diff --git a/bin/chef-windows-service b/bin/chef-windows-service new file mode 100644 index 0000000000..292d5651fc --- /dev/null +++ b/bin/chef-windows-service @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby +# +# Author:: Jay Mundrawala (<jdm@getchef.com>) +# +# Copyright:: 2014, Chef Software, Inc. +# +# 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. +# + +# Note: +# The file is used by appbundler to generate a ruby file that +# we can execute using the correct gems. The batch file we +# generate will call that file, and will be registered as +# a windows service. + +require 'rubygems' +$:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) +require 'chef' +require 'chef/application/windows_service' + +if Chef::Platform.windows? + Chef::Application::WindowsService.mainloop +else + puts "chef-windows-service is only available on Windows platforms." +end diff --git a/chef-x86-mingw32.gemspec b/chef-x86-mingw32.gemspec index 6d69c4e7e6..a35ec5d63c 100644 --- a/chef-x86-mingw32.gemspec +++ b/chef-x86-mingw32.gemspec @@ -14,5 +14,8 @@ gemspec.add_dependency "win32-process", "0.7.3" gemspec.add_dependency "win32-service", "0.8.2" gemspec.add_dependency "win32-mmap", "0.4.0" gemspec.add_dependency "wmi-lite", "~> 1.0" +gemspec.add_dependency "win32-eventlog", "0.6.1" +gemspec.extensions << "ext/win32-eventlog/Rakefile" +gemspec.files += %w(ext/win32-eventlog/Rakefile ext/win32-eventlog/chef-log.man) gemspec diff --git a/chef.gemspec b/chef.gemspec index f970bb32f9..427657f678 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.add_dependency "mixlib-shellout", ">= 2.0.0.rc.0", "< 3.0" s.add_dependency "ohai", ">= 7.6.0.rc.0" - s.add_dependency "ffi-yajl", "~> 1.1" + s.add_dependency "ffi-yajl", "~> 1.2" s.add_dependency "net-ssh", "~> 2.6" s.add_dependency "net-ssh-multi", "~> 1.1" # CHEF-3027: The knife-cloud plugins require newer features from highline, core chef should not. @@ -49,7 +49,7 @@ Gem::Specification.new do |s| # chef-service-manager is a windows only executable. # However gemspec doesn't give us a way to have this executable only # on windows. So we're including this in all platforms. - s.executables = %w( chef-client chef-solo knife chef-shell shef chef-apply chef-service-manager ) + s.executables = %w( chef-client chef-solo knife chef-shell shef chef-apply chef-service-manager chef-windows-service ) s.require_path = 'lib' s.files = %w(Rakefile LICENSE README.md CONTRIBUTING.md) + Dir.glob("{distro,lib,tasks,spec}/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } diff --git a/ext/win32-eventlog/Rakefile b/ext/win32-eventlog/Rakefile new file mode 100644 index 0000000000..3112c27e8e --- /dev/null +++ b/ext/win32-eventlog/Rakefile @@ -0,0 +1,50 @@ +require 'rubygems' +require 'rake' +require 'mkmf' + +desc "Building event log dll" + +def ensure_present(commands) + commands.each do |c| + unless find_executable c + warn "Could not find #{c}. Windows Event Logging will not correctly function." + end + end +end + + +EVT_MC_FILE = 'chef-log.man' +EVT_RC_FILE = 'chef-log.rc' +EVT_RESOURCE_OBJECT = 'resource.o' +EVT_SHARED_OBJECT = 'chef-log.dll' +MC = 'windmc' +RC = 'windres' +CC = 'gcc' + +ensure_present [MC, RC, CC] + +task :build => [EVT_RESOURCE_OBJECT, EVT_SHARED_OBJECT] +task :default => [:build, :register] + +file EVT_RC_FILE=> EVT_MC_FILE do + sh "#{MC} #{EVT_MC_FILE}" +end + +file EVT_RESOURCE_OBJECT => EVT_RC_FILE do + sh "#{RC} -i #{EVT_RC_FILE} -o #{EVT_RESOURCE_OBJECT}" +end + +file EVT_SHARED_OBJECT => EVT_RESOURCE_OBJECT do + sh "#{CC} -o #{EVT_SHARED_OBJECT} -shared #{EVT_RESOURCE_OBJECT}" +end + +task :register => EVT_SHARED_OBJECT do + require 'win32/eventlog' + dll_file = File.expand_path(EVT_SHARED_OBJECT) + Win32::EventLog.add_event_source( + :source => "Application", + :key_name => "Chef", + :event_message_file => dll_file, + :category_message_file => dll_file + ) +end diff --git a/ext/win32-eventlog/chef-log.man b/ext/win32-eventlog/chef-log.man new file mode 100644 index 0000000000..4b4a022d7f --- /dev/null +++ b/ext/win32-eventlog/chef-log.man @@ -0,0 +1,26 @@ +MessageId=10000 +SymbolicName=RUN_START +Language=English +Starting Chef Client run v%1 +. + +MessageId=10001 +SymbolicName=RUN_STARTED +Language=English +Started Chef Client run %1 +. + +MessageId=10002 +SymbolicName=RUN_COMPLETED +Language=English +Completed Chef Client run %1 in %2 seconds +. + +MessageId=10003 +SymbolicName=RUN_FAILED +Language=English +Failed Chef Client run %1 in %2 seconds.%n +Exception type: %3%n +Exception message: %4%n +Exception backtrace: %5%n +. diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb index ea9154c6f2..22d835e876 100644 --- a/lib/chef/application/apply.rb +++ b/lib/chef/application/apply.rb @@ -31,7 +31,6 @@ class Chef::Application::Apply < Chef::Application banner "Usage: chef-apply [RECIPE_FILE] [-e RECIPE_TEXT] [-s]" - option :execute, :short => "-e RECIPE_TEXT", :long => "--execute RECIPE_TEXT", @@ -92,14 +91,17 @@ class Chef::Application::Apply < Chef::Application end def read_recipe_file(file_name) - recipe_path = file_name - unless File.exist?(recipe_path) - Chef::Application.fatal!("No file exists at #{recipe_path}", 1) + if file_name.nil? + Chef::Application.fatal!("No recipe file was provided", 1) + else + recipe_path = File.expand_path(file_name) + unless File.exist?(recipe_path) + Chef::Application.fatal!("No file exists at #{recipe_path}", 1) + end + recipe_fh = open(recipe_path) + recipe_text = recipe_fh.read + [recipe_text, recipe_fh] end - recipe_path = File.expand_path(recipe_path) - recipe_fh = open(recipe_path) - recipe_text = recipe_fh.read - [recipe_text, recipe_fh] end def get_recipe_and_run_context diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb index 7f0a39782a..5463f504bc 100644 --- a/lib/chef/application/client.rb +++ b/lib/chef/application/client.rb @@ -238,6 +238,11 @@ class Chef::Application::Client < Chef::Application :boolean => true end + option :audit_mode, + :long => "--[no-]audit-mode", + :description => "If not specified, run converge and audit phase. If true, run only audit phase. If false, run only converge phase.", + :boolean => true + IMMEDIATE_RUN_SIGNAL = "1".freeze attr_reader :chef_client_json diff --git a/lib/chef/client.rb b/lib/chef/client.rb index e531da5768..4f37bd0ee3 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -36,6 +36,8 @@ require 'chef/cookbook/file_vendor' require 'chef/cookbook/file_system_file_vendor' require 'chef/cookbook/remote_file_vendor' require 'chef/event_dispatch/dispatcher' +require 'chef/event_loggers/base' +require 'chef/event_loggers/windows_eventlog' require 'chef/formatters/base' require 'chef/formatters/doc' require 'chef/formatters/minimal' @@ -151,7 +153,7 @@ class Chef @runner = nil @ohai = Ohai::System.new - event_handlers = configure_formatters + event_handlers = configure_formatters + configure_event_loggers event_handlers += Array(Chef::Config[:event_handlers]) @events = EventDispatch::Dispatcher.new(*event_handlers) @@ -191,6 +193,22 @@ class Chef end end + def configure_event_loggers + if Chef::Config.disable_event_logger + [] + else + Chef::Config.event_loggers.map do |evt_logger| + case evt_logger + when Symbol + Chef::EventLoggers.new(evt_logger) + when Class + evt_logger.new + else + end + end + end + end + # Instantiates a Chef::Node object, possibly loading the node's prior state # when using chef-client. Delegates to policy_builder # @@ -282,7 +300,7 @@ class Chef rescue Exception => e # TODO: munge exception so a semantic failure message can be given to the # user - @events.registration_failed(node_name, e, config) + @events.registration_failed(client_name, e, config) raise end diff --git a/lib/chef/config.rb b/lib/chef/config.rb index 107b50ee85..d033a2981b 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -25,11 +25,13 @@ require 'mixlib/config' require 'chef/util/selinux' require 'chef/util/path_helper' require 'pathname' +require 'chef/mixin/shell_out' class Chef class Config extend Mixlib::Config + extend Chef::Mixin::ShellOut PathHelper = Chef::Util::PathHelper @@ -76,6 +78,10 @@ class Chef formatters << [name, file_path] end + def self.add_event_logger(logger) + event_handlers << logger + end + # Config file to load (client.rb, knife.rb, etc. defaults set differently in knife, chef-client, etc.) configurable(:config_file) @@ -449,6 +455,15 @@ class Chef # Event Handlers default :event_handlers, [] + default :disable_event_loggers, false + default :event_loggers do + evt_loggers = [] + if Chef::Platform::windows? + evt_loggers << :win_evt + end + evt_loggers + end + # Exception Handlers default :exception_handlers, [] @@ -604,25 +619,37 @@ class Chef # available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'. default :internal_locale do begin - locales = `locale -a`.split + # https://github.com/opscode/chef/issues/2181 + # Some systems have the `locale -a` command, but the result has + # invalid characters for the default encoding. + # + # For example, on CentOS 6 with ENV['LANG'] = "en_US.UTF-8", + # `locale -a`.split fails with ArgumentError invalid UTF-8 encoding. + locales = shell_out_with_systems_locale("locale -a").stdout.split case when locales.include?('C.UTF-8') 'C.UTF-8' - when locales.include?('en_US.UTF-8') + when locales.include?('en_US.UTF-8'), locales.include?('en_US.utf8') 'en_US.UTF-8' when locales.include?('en.UTF-8') 'en.UTF-8' - when guesses = locales.select { |l| l =~ /^en_.*UTF-8$'/ } - guesses.first else - Chef::Log.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support." - 'C' + # Will match en_ZZ.UTF-8, en_ZZ.utf-8, en_ZZ.UTF8, en_ZZ.utf8 + guesses = locales.select { |l| l =~ /^en_.*UTF-?8$/i } + unless guesses.empty? + guessed_locale = guesses.first + # Transform into the form en_ZZ.UTF-8 + guessed_locale.gsub(/UTF-?8$/i, "UTF-8") + else + Chef::Log.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support." + 'C' + end end rescue if Chef::Platform.windows? Chef::Log.debug "Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else." else - Chef::Log.warn "No usable locale -a command found, assuming you have en_US.UTF-8 installed." + Chef::Log.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed." end 'en_US.UTF-8' end diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb index 5481ba7ddc..bcbfcbeec8 100644 --- a/lib/chef/cookbook/cookbook_version_loader.rb +++ b/lib/chef/cookbook/cookbook_version_loader.rb @@ -81,7 +81,7 @@ class Chef load_as(:attribute_filenames, 'attributes', '*.rb') load_as(:definition_filenames, 'definitions', '*.rb') load_as(:recipe_filenames, 'recipes', '*.rb') - load_as(:library_filenames, 'libraries', '*.rb') + load_recursively_as(:library_filenames, 'libraries', '*.rb') load_recursively_as(:template_filenames, "templates", "*") load_recursively_as(:file_filenames, "files", "*") load_recursively_as(:resource_filenames, "resources", "*.rb") diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb index 3964354d50..54e930135d 100644 --- a/lib/chef/cookbook/metadata.rb +++ b/lib/chef/cookbook/metadata.rb @@ -35,28 +35,31 @@ class Chef # about Chef Cookbooks. class Metadata - NAME = 'name'.freeze - DESCRIPTION = 'description'.freeze - LONG_DESCRIPTION = 'long_description'.freeze - MAINTAINER = 'maintainer'.freeze - MAINTAINER_EMAIL = 'maintainer_email'.freeze - LICENSE = 'license'.freeze - PLATFORMS = 'platforms'.freeze - DEPENDENCIES = 'dependencies'.freeze - RECOMMENDATIONS = 'recommendations'.freeze - SUGGESTIONS = 'suggestions'.freeze - CONFLICTING = 'conflicting'.freeze - PROVIDING = 'providing'.freeze - REPLACING = 'replacing'.freeze - ATTRIBUTES = 'attributes'.freeze - GROUPINGS = 'groupings'.freeze - RECIPES = 'recipes'.freeze - VERSION = 'version'.freeze + NAME = 'name'.freeze + DESCRIPTION = 'description'.freeze + LONG_DESCRIPTION = 'long_description'.freeze + MAINTAINER = 'maintainer'.freeze + MAINTAINER_EMAIL = 'maintainer_email'.freeze + LICENSE = 'license'.freeze + PLATFORMS = 'platforms'.freeze + DEPENDENCIES = 'dependencies'.freeze + RECOMMENDATIONS = 'recommendations'.freeze + SUGGESTIONS = 'suggestions'.freeze + CONFLICTING = 'conflicting'.freeze + PROVIDING = 'providing'.freeze + REPLACING = 'replacing'.freeze + ATTRIBUTES = 'attributes'.freeze + GROUPINGS = 'groupings'.freeze + RECIPES = 'recipes'.freeze + VERSION = 'version'.freeze + SOURCE_URL = 'source_url'.freeze + ISSUES_URL = 'issues_url'.freeze COMPARISON_FIELDS = [ :name, :description, :long_description, :maintainer, :maintainer_email, :license, :platforms, :dependencies, :recommendations, :suggestions, :conflicting, :providing, - :replacing, :attributes, :groupings, :recipes, :version] + :replacing, :attributes, :groupings, :recipes, :version, + :source_url, :issues_url ] VERSION_CONSTRAINTS = {:depends => DEPENDENCIES, :recommends => RECOMMENDATIONS, @@ -111,6 +114,8 @@ class Chef @groupings = Mash.new @recipes = Mash.new @version = Version.new("0.0.0") + @source_url = '' + @issues_url = '' @errors = [] end @@ -413,7 +418,7 @@ class Chef end end - # Adds an attribute )hat a user needs to configure for this cookbook. Takes + # Adds an attribute that a user needs to configure for this cookbook. Takes # a name (with the / notation for a nested attribute), followed by any of # these options # @@ -443,7 +448,9 @@ class Chef :type => { :equal_to => [ "string", "array", "hash", "symbol", "boolean", "numeric" ], :default => "string" }, :required => { :equal_to => [ "required", "recommended", "optional", true, false ], :default => "optional" }, :recipes => { :kind_of => [ Array ], :default => [] }, - :default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] } + :default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] }, + :source_url => { :kind_of => String }, + :issues_url => { :kind_of => String } } ) options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil? @@ -469,23 +476,25 @@ class Chef def to_hash { - NAME => self.name, - DESCRIPTION => self.description, - LONG_DESCRIPTION => self.long_description, - MAINTAINER => self.maintainer, - MAINTAINER_EMAIL => self.maintainer_email, - LICENSE => self.license, - PLATFORMS => self.platforms, - DEPENDENCIES => self.dependencies, - RECOMMENDATIONS => self.recommendations, - SUGGESTIONS => self.suggestions, - CONFLICTING => self.conflicting, - PROVIDING => self.providing, - REPLACING => self.replacing, - ATTRIBUTES => self.attributes, - GROUPINGS => self.groupings, - RECIPES => self.recipes, - VERSION => self.version + NAME => self.name, + DESCRIPTION => self.description, + LONG_DESCRIPTION => self.long_description, + MAINTAINER => self.maintainer, + MAINTAINER_EMAIL => self.maintainer_email, + LICENSE => self.license, + PLATFORMS => self.platforms, + DEPENDENCIES => self.dependencies, + RECOMMENDATIONS => self.recommendations, + SUGGESTIONS => self.suggestions, + CONFLICTING => self.conflicting, + PROVIDING => self.providing, + REPLACING => self.replacing, + ATTRIBUTES => self.attributes, + GROUPINGS => self.groupings, + RECIPES => self.recipes, + VERSION => self.version, + SOURCE_URL => self.source_url, + ISSUES_URL => self.issues_url } end @@ -517,6 +526,8 @@ class Chef @groupings = o[GROUPINGS] if o.has_key?(GROUPINGS) @recipes = o[RECIPES] if o.has_key?(RECIPES) @version = o[VERSION] if o.has_key?(VERSION) + @source_url = o[SOURCE_URL] if o.has_key?(SOURCE_URL) + @issues_url = o[ISSUES_URL] if o.has_key?(ISSUES_URL) self end @@ -531,7 +542,9 @@ class Chef VERSION_CONSTRAINTS.each do |dependency_type, hash_key| if dependency_group = o[hash_key] dependency_group.each do |cb_name, constraints| - metadata.send(method_name, cb_name, *Array(constraints)) + if metadata.respond_to?(method_name) + metadata.public_send(method_name, cb_name, *Array(constraints)) + end end end end @@ -543,6 +556,36 @@ class Chef from_hash(o) end + # Sets the cookbook's source URL, or returns it. + # + # === Parameters + # maintainer<String>:: The source URL + # + # === Returns + # source_url<String>:: Returns the current source URL. + def source_url(arg=nil) + set_or_return( + :source_url, + arg, + :kind_of => [ String ] + ) + end + + # Sets the cookbook's issues URL, or returns it. + # + # === Parameters + # issues_url<String>:: The issues URL + # + # === Returns + # issues_url<String>:: Returns the current issues URL. + def issues_url(arg=nil) + set_or_return( + :issues_url, + arg, + :kind_of => [ String ] + ) + end + private def run_validation diff --git a/lib/chef/cookbook/syntax_check.rb b/lib/chef/cookbook/syntax_check.rb index 1437785259..96fc7e7b84 100644 --- a/lib/chef/cookbook/syntax_check.rb +++ b/lib/chef/cookbook/syntax_check.rb @@ -101,15 +101,24 @@ class Chef end def remove_ignored_files(file_list) - return file_list unless chefignore.ignores.length > 0 + return file_list if chefignore.ignores.empty? + file_list.reject do |full_path| relative_pn = Chef::Util::PathHelper.relative_path_from(cookbook_path, full_path) - chefignore.ignored? relative_pn.to_s + chefignore.ignored?(relative_pn.to_s) end end + def remove_uninteresting_ruby_files(file_list) + file_list.reject { |f| f =~ %r{#{cookbook_path}/(files|templates)/} } + end + def ruby_files - remove_ignored_files Dir[File.join(Chef::Util::PathHelper.escape_glob(cookbook_path), '**', '*.rb')] + path = Chef::Util::PathHelper.escape_glob(cookbook_path) + files = Dir[File.join(path, '**', '*.rb')] + files = remove_ignored_files(files) + files = remove_uninteresting_ruby_files(files) + files end def untested_ruby_files diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 95af94bdf7..505b403e65 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -315,13 +315,20 @@ class Chef else if segment == :files || segment == :templates error_message = "Cookbook '#{name}' (#{version}) does not contain a file at any of these locations:\n" - error_locations = [ - " #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}", - " #{segment}/#{node[:platform]}/#{filename}", - " #{segment}/default/#{filename}", - ] + error_locations = if filename.is_a?(Array) + filename.map{|name| " #{File.join(segment.to_s, name)}"} + else + [ + " #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}", + " #{segment}/#{node[:platform]}/#{filename}", + " #{segment}/default/#{filename}", + " #{segment}/#{filename}", + ] + end error_message << error_locations.join("\n") existing_files = segment_filenames(segment) + # Strip the root_dir prefix off all files for readability + existing_files.map!{|path| path[root_dir.length+1..-1]} if root_dir # Show the files that the cookbook does have. If the user made a typo, # hopefully they'll see it here. unless existing_files.empty? @@ -421,38 +428,44 @@ class Chef def preferences_for_path(node, segment, path) # only files and templates can be platform-specific if segment.to_sym == :files || segment.to_sym == :templates - begin - platform, version = Chef::Platform.find_platform_and_version(node) - rescue ArgumentError => e - # Skip platform/version if they were not found by find_platform_and_version - if e.message =~ /Cannot find a (?:platform|version)/ - platform = "/unknown_platform/" - version = "/unknown_platform_version/" - else - raise + relative_search_path = if path.is_a?(Array) + path + else + begin + platform, version = Chef::Platform.find_platform_and_version(node) + rescue ArgumentError => e + # Skip platform/version if they were not found by find_platform_and_version + if e.message =~ /Cannot find a (?:platform|version)/ + platform = "/unknown_platform/" + version = "/unknown_platform_version/" + else + raise + end end - end - fqdn = node[:fqdn] + fqdn = node[:fqdn] - # Break version into components, eg: "5.7.1" => [ "5.7.1", "5.7", "5" ] - search_versions = [] - parts = version.to_s.split('.') + # Break version into components, eg: "5.7.1" => [ "5.7.1", "5.7", "5" ] + search_versions = [] + parts = version.to_s.split('.') - parts.size.times do - search_versions << parts.join('.') - parts.pop - end + parts.size.times do + search_versions << parts.join('.') + parts.pop + end - # Most specific to least specific places to find the path - search_path = [ File.join(segment.to_s, "host-#{fqdn}", path) ] - search_versions.each do |v| - search_path << File.join(segment.to_s, "#{platform}-#{v}", path) - end - search_path << File.join(segment.to_s, platform.to_s, path) - search_path << File.join(segment.to_s, "default", path) + # Most specific to least specific places to find the path + search_path = [ File.join("host-#{fqdn}", path) ] + search_versions.each do |v| + search_path << File.join("#{platform}-#{v}", path) + end + search_path << File.join(platform.to_s, path) + search_path << File.join("default", path) + search_path << path - search_path + search_path + end + relative_search_path.map {|relative_path| File.join(segment.to_s, relative_path)} else [File.join(segment, path)] end @@ -666,7 +679,13 @@ class Chef # Check if path is actually under root_path next if parts[0] == '..' if segment == :templates || segment == :files - return [ pathname.to_s, parts[1] ] + # Check if pathname looks like files/foo or templates/foo (unscoped) + if pathname.each_filename.to_a.length == 2 + # Use root_default in case the same path exists at root_default and default + return [ pathname.to_s, 'root_default' ] + else + return [ pathname.to_s, parts[1] ] + end else return [ pathname.to_s, 'default' ] end diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb index 3282320b8c..120497d56e 100644 --- a/lib/chef/dsl/recipe.rb +++ b/lib/chef/dsl/recipe.rb @@ -17,8 +17,8 @@ # limitations under the License. # -require 'chef/resource_platform_map' require 'chef/mixin/convert_to_class_name' +require 'chef/exceptions' class Chef module DSL @@ -52,9 +52,7 @@ class Chef end def has_resource_definition?(name) - yes_or_no = run_context.definitions.has_key?(name) - - yes_or_no + run_context.definitions.has_key?(name) end # Processes the arguments and block as a resource definition. @@ -68,12 +66,10 @@ class Chef # This sets up the parameter overrides new_def.instance_eval(&block) if block - new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context) new_recipe.params = new_def.params new_recipe.params[:name] = args[0] new_recipe.instance_eval(&new_def.recipe) - new_recipe end # Instantiates a resource (via #build_resource), then adds it to the @@ -85,21 +81,7 @@ class Chef resource = build_resource(type, name, created_at, &resource_attrs_block) - # Some resources (freebsd_package) can be invoked with multiple names - # (package || freebsd_package). - # https://github.com/opscode/chef/issues/1773 - # For these resources we want to make sure - # their key in resource collection is same as the name they are declared - # as. Since this might be a breaking change for resources that define - # customer to_s methods, we are working around the issue by letting - # resources know of their created_as_type until this issue is fixed in - # Chef 12: - # https://github.com/opscode/chef/issues/1817 - if resource.respond_to?(:created_as_type=) - resource.created_as_type = type - end - - run_context.resource_collection.insert(resource) + run_context.resource_collection.insert(resource, resource_type:type, instance_name:name) resource end @@ -123,7 +105,7 @@ class Chef # This behavior is very counter-intuitive and should be removed. # See CHEF-3694, https://tickets.opscode.com/browse/CHEF-3694 # Moved to this location to resolve CHEF-5052, https://tickets.opscode.com/browse/CHEF-5052 - resource.load_prior_resource + resource.load_prior_resource(type, name) resource.cookbook_name = cookbook_name resource.recipe_name = recipe_name # Determine whether this resource is being created in the context of an enclosing Provider @@ -162,6 +144,10 @@ class Chef end end + def exec(args) + raise Chef::Exceptions::ResourceNotFound, "exec was called, but you probably meant to use an execute resource. If not, please call Kernel#exec explicitly. The exec block called was \"#{args}\"" + end + end end end diff --git a/lib/chef/encrypted_data_bag_item/assertions.rb b/lib/chef/encrypted_data_bag_item/assertions.rb index e9acebbd29..0f9416e7b6 100644 --- a/lib/chef/encrypted_data_bag_item/assertions.rb +++ b/lib/chef/encrypted_data_bag_item/assertions.rb @@ -45,7 +45,7 @@ class Chef::EncryptedDataBagItem def assert_aead_requirements_met!(algorithm) unless OpenSSL::Cipher.method_defined?(:auth_data=) - raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires Ruby >= 1.9" + raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires Ruby >= 2.0" end unless OpenSSL::Cipher.ciphers.include?(algorithm) raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires an OpenSSL version with \"#{algorithm}\" algorithm support" diff --git a/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb b/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb index 3567925844..9a54396174 100644 --- a/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb +++ b/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb @@ -25,7 +25,7 @@ class Chef::EncryptedDataBagItem def assert_requirements_met! unless OpenSSL::Cipher.method_defined?(:auth_data=) - raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires Ruby >= 1.9" + raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires Ruby >= 2.0" end unless OpenSSL::Cipher.ciphers.include?(algorithm) raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires an OpenSSL version with \"#{algorithm}\" algorithm support" diff --git a/lib/chef/event_loggers/base.rb b/lib/chef/event_loggers/base.rb new file mode 100644 index 0000000000..1f676dd516 --- /dev/null +++ b/lib/chef/event_loggers/base.rb @@ -0,0 +1,62 @@ +# +# Author:: Jay Mundrawala (<jdm@getchef.com>) +# +# Copyright:: 2014, Chef Software, Inc. +# +# 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/event_dispatch/base' + +class Chef + module EventLoggers + class UnknownEventLogger < StandardError; end + class UnavailableEventLogger < StandardError; end + + def self.event_loggers_by_name + @event_loggers_by_name ||= {} + end + + def self.register(name, logger) + event_loggers_by_name[name.to_s] = logger + end + + def self.by_name(name) + event_loggers_by_name[name] + end + + def self.available_event_loggers + event_loggers_by_name.select do |key, val| + val.available? + end.keys + end + + def self.new(name) + event_logger_class = by_name(name.to_s) or + raise UnknownEventLogger, "No event logger found for #{name} (available: #{available_event_loggers.join(', ')})" + raise UnavailableEventLogger unless available_event_loggers.include? name.to_s + event_logger_class.new + end + + class Base < EventDispatch::Base + def self.short_name(name) + Chef::EventLoggers.register(name, self) + end + + # Returns true if this implementation of EventLoggers can be used + def self.available? + false + end + end + end +end diff --git a/lib/chef/event_loggers/windows_eventlog.rb b/lib/chef/event_loggers/windows_eventlog.rb new file mode 100644 index 0000000000..e3bbbfa1e6 --- /dev/null +++ b/lib/chef/event_loggers/windows_eventlog.rb @@ -0,0 +1,104 @@ +# +# Author:: Jay Mundrawala (<jdm@getchef.com>) +# +# Copyright:: 2014, Chef Software, Inc. +# +# 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/event_loggers/base' +require 'chef/platform/query_helpers' + +if Chef::Platform::windows? + [:INFINITE, :WAIT_FAILED, :FORMAT_MESSAGE_IGNORE_INSERTS, :ERROR_INSUFFICIENT_BUFFER].each do |c| + # These are redefined in 'win32/eventlog' + Windows::Constants.send(:remove_const, c) + end + + require 'win32/eventlog' + include Win32 +end + +class Chef + module EventLoggers + class WindowsEventLogger < EventLoggers::Base + short_name(:win_evt) + + # These must match those that are defined in the manifest file + RUN_START_EVENT_ID = 10000 + RUN_STARTED_EVENT_ID = 10001 + RUN_COMPLETED_EVENT_ID = 10002 + RUN_FAILED_EVENT_ID = 10003 + + EVENT_CATEGORY_ID = 11000 + LOG_CATEGORY_ID = 11001 + + # Since we must install the event logger, this is not really configurable + SOURCE = 'Chef' + + def self.available? + return Chef::Platform::windows? + end + + def initialize + @eventlog = EventLog::open('Application') + end + + def run_start(version) + @eventlog.report_event( + :event_type => EventLog::INFO_TYPE, + :source => SOURCE, + :event_id => RUN_START_EVENT_ID, + :data => [version] + ) + end + + def run_started(run_status) + @run_status = run_status + @eventlog.report_event( + :event_type => EventLog::INFO_TYPE, + :source => SOURCE, + :event_id => RUN_STARTED_EVENT_ID, + :data => [run_status.run_id] + ) + end + + def run_completed(node) + @eventlog.report_event( + :event_type => EventLog::INFO_TYPE, + :source => SOURCE, + :event_id => RUN_COMPLETED_EVENT_ID, + :data => [@run_status.run_id, @run_status.elapsed_time.to_s] + ) + end + + #Failed chef-client run %1 in %2 seconds. + #Exception type: %3 + #Exception message: %4 + #Exception backtrace: %5 + def run_failed(e) + @eventlog.report_event( + :event_type => EventLog::ERROR_TYPE, + :source => SOURCE, + :event_id => RUN_FAILED_EVENT_ID, + :data => [@run_status.run_id, + @run_status.elapsed_time.to_s, + e.class.name, + e.message, + e.backtrace.join("\n")] + ) + end + + end + end +end diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 67429ac5a2..25f08455fc 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -91,7 +91,11 @@ class Chef class ResourceNotFound < RuntimeError; end # Can't find a Resource of this type that is valid on this platform. - class NoSuchResourceType < NameError; end + class NoSuchResourceType < NameError + def initialize(short_name, node) + super "Cannot find a resource for #{short_name} on #{node[:platform]} version #{node[:platform_version]}" + end + end class InvalidResourceSpecification < ArgumentError; end class SolrConnectionError < RuntimeError; end @@ -355,5 +359,12 @@ class Chef end class InvalidSearchQuery < ArgumentError; end + + # Raised by Chef::ProviderResolver + class AmbiguousProviderResolution < RuntimeError + def initialize(resource, classes) + super "Found more than one provider for #{resource.resource_name} resource: #{classes}" + end + end end end diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb index 229a8502c7..346b585d8c 100644 --- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb +++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb @@ -33,10 +33,19 @@ class Chef # 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}) + # Only execute and script resources and use guard attributes. + # The command to be executed on them are passed via different attributes. + # Script resources use code attribute and execute resources use + # command attribute. Moreover script resources are also execute + # resources. Here we make sure @command is assigned to the right + # attribute by checking the type of the resources. + # We need to make sure we check for Script first because any resource + # that can get to here is an Execute resource. + if @parent_resource.is_a? Chef::Resource::Script + block_attributes = @command_opts.merge({:code => @command}) + else + block_attributes = @command_opts.merge({:command => @command}) + end # Handles cases like powershell_script where default # attributes are different when used in a guard vs. not. For @@ -79,8 +88,8 @@ class Chef 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" + if ! resource_class.ancestors.include?(Chef::Resource::Execute) + raise ArgumentError, "Specified guard interpreter class #{resource_class} must be a kind of Chef::Resource::Execute resource" end empty_events = Chef::EventDispatch::Dispatcher.new diff --git a/lib/chef/http.rb b/lib/chef/http.rb index 7f2d00157b..ee951bd675 100644 --- a/lib/chef/http.rb +++ b/lib/chef/http.rb @@ -271,7 +271,7 @@ class Chef elsif redirect_location = redirected_to(response) if [:GET, :HEAD].include?(method) follow_redirect do - send_http_request(method, create_url(redirect_location), headers, body, &response_handler) + send_http_request(method, url+redirect_location, headers, body, &response_handler) end else raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects." diff --git a/lib/chef/json_compat.rb b/lib/chef/json_compat.rb index 3350da0c13..d0b3b4c7f8 100644 --- a/lib/chef/json_compat.rb +++ b/lib/chef/json_compat.rb @@ -39,6 +39,8 @@ class Chef CHEF_SANDBOX = "Chef::Sandbox".freeze CHEF_RESOURCE = "Chef::Resource".freeze CHEF_RESOURCECOLLECTION = "Chef::ResourceCollection".freeze + CHEF_RESOURCESET = "Chef::ResourceCollection::ResourceSet".freeze + CHEF_RESOURCELIST = "Chef::ResourceCollection::ResourceList".freeze class <<self @@ -145,8 +147,12 @@ class Chef Chef::Resource when CHEF_RESOURCECOLLECTION Chef::ResourceCollection + when CHEF_RESOURCESET + Chef::ResourceCollection::ResourceSet + when CHEF_RESOURCELIST + Chef::ResourceCollection::ResourceList when /^Chef::Resource/ - Chef::Resource.find_subclass_by_name(json_class) + Chef::Resource.find_descendants_by_name(json_class) else raise Chef::Exceptions::JSON::ParseError, "Unsupported `json_class` type '#{json_class}'" end diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb index 6421384f01..0c079792a4 100644 --- a/lib/chef/knife.rb +++ b/lib/chef/knife.rb @@ -164,7 +164,6 @@ class Chef def self.load_config(explicit_config_file) config_loader = WorkstationConfigLoader.new(explicit_config_file, Chef::Log) - Chef::Log.debug("Using configuration from #{config_loader.config_location}") config_loader.load ui.warn("No knife configuration file found") if config_loader.no_config_found? @@ -393,6 +392,8 @@ class Chef merge_configs apply_computed_config + # This has to be after apply_computed_config so that Mixlib::Log is configured + Chef::Log.info("Using configuration from #{config[:config_file]}") if config[:config_file] end def show_usage diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb index f4183a7245..e17a54079f 100644 --- a/lib/chef/knife/cookbook_create.rb +++ b/lib/chef/knife/cookbook_create.rb @@ -80,7 +80,7 @@ class Chef end def create_cookbook(dir, cookbook_name, copyright, license) - msg("** Creating cookbook #{cookbook_name}") + msg("** Creating cookbook #{cookbook_name} in #{dir}") FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "attributes")}" FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "recipes")}" FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "definitions")}" diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb index 7204ccdb1c..b4a7873c71 100644 --- a/lib/chef/knife/cookbook_site_share.rb +++ b/lib/chef/knife/cookbook_site_share.rb @@ -31,7 +31,7 @@ class Chef require 'chef/cookbook_site_streaming_uploader' end - banner "knife cookbook site share COOKBOOK CATEGORY (options)" + banner "knife cookbook site share COOKBOOK [CATEGORY] (options)" category "cookbook site" option :cookbook_path, @@ -40,17 +40,28 @@ class Chef :description => "A colon-separated path to look for cookbooks in", :proc => lambda { |o| Chef::Config.cookbook_path = o.split(":") } + option :dry_run, + :long => '--dry-run', + :short => '-n', + :boolean => true, + :default => false, + :description => "Don't take action, only print what files will be upload to SuperMarket." + def run - if @name_args.length < 2 + config[:cookbook_path] ||= Chef::Config[:cookbook_path] + + if @name_args.length < 1 show_usage - ui.fatal("You must specify the cookbook name and the category you want to share this cookbook to.") - exit 1 + ui.fatal("You must specify the cookbook name.") + exit(1) + elsif @name_args.length < 2 + cookbook_name = @name_args[0] + category = get_category(cookbook_name) + else + cookbook_name = @name_args[0] + category = @name_args[1] end - config[:cookbook_path] ||= Chef::Config[:cookbook_path] - - cookbook_name = @name_args[0] - category = @name_args[1] cl = Chef::CookbookLoader.new(config[:cookbook_path]) if cl.cookbook_exists?(cookbook_name) cookbook = cl[cookbook_name] @@ -66,6 +77,14 @@ class Chef exit(1) end + if config[:dry_run] + ui.info("Not uploading #{cookbook_name}.tgz due to --dry-run flag.") + result = shell_out!("tar -tzf #{cookbook_name}.tgz", :cwd => tmp_cookbook_dir) + ui.info(result.stdout) + FileUtils.rm_rf tmp_cookbook_dir + return + end + begin do_upload("#{tmp_cookbook_dir}/#{cookbook_name}.tgz", category, Chef::Config[:node_name], Chef::Config[:client_key]) ui.info("Upload complete!") @@ -84,6 +103,22 @@ class Chef end + def get_category(cookbook_name) + begin + data = noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}") + if !data["category"] && data["error_code"] + ui.fatal("Received an error from the Opscode Cookbook site: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.") + exit(1) + else + data['category'] + end + rescue => e + ui.fatal("Unable to reach Opscode Cookbook Site: #{e.message}. Increase log verbosity (-VV) for more information.") + Chef::Log.debug("\n#{e.backtrace.join("\n")}") + exit(1) + end + end + def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename) uri = "https://supermarket.getchef.com/api/v1/cookbooks" diff --git a/lib/chef/knife/core/status_presenter.rb b/lib/chef/knife/core/status_presenter.rb new file mode 100644 index 0000000000..3298d5e4ac --- /dev/null +++ b/lib/chef/knife/core/status_presenter.rb @@ -0,0 +1,156 @@ +# +# Author:: Nicolas DUPEUX (<nicolas.dupeux@arkea.com>) +# Copyright:: Copyright (c) 2011 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/knife/core/text_formatter' +require 'chef/knife/core/generic_presenter' + +class Chef + class Knife + module Core + + # This module may be included into a knife subcommand class to automatically + # add configuration options used by the StatusPresenter + module StatusFormattingOptions + # :nodoc: + # Would prefer to do this in a rational way, but can't be done b/c of + # Mixlib::CLI's design :( + def self.included(includer) + includer.class_eval do + option :medium_output, + :short => '-m', + :long => '--medium', + :boolean => true, + :default => false, + :description => 'Include normal attributes in the output' + + option :long_output, + :short => '-l', + :long => '--long', + :boolean => true, + :default => false, + :description => 'Include all attributes in the output' + end + end + end + + #==Chef::Knife::Core::StatusPresenter + # A customized presenter for Chef::Node objects. Supports variable-length + # output formats for displaying node data + class StatusPresenter < GenericPresenter + + def format(data) + if parse_format_option == :json + summarize_json(data) + else + super + end + end + + def summarize_json(list) + result_list = [] + list.each do |node| + result = {} + + result["name"] = node.name + result["chef_environment"] = node.chef_environment + ip = (node[:ec2] && node[:ec2][:public_ipv4]) || node[:ipaddress] + fqdn = (node[:ec2] && node[:ec2][:public_hostname]) || node[:fqdn] + result["ip"] = ip if ip + result["fqdn"] = fqdn if fqdn + result["run_list"] = node.run_list if config[:run_list] + result["ohai_time"] = node[:ohai_time] + result["platform"] = node[:platform] if node[:platform] + result["platform_version"] = node[:platform_version] if node[:platform_version] + + if config[:long_output] + result["default"] = node.default_attrs + result["override"] = node.override_attrs + result["automatic"] = node.automatic_attrs + end + result_list << result + end + + Chef::JSONCompat.to_json_pretty(result_list) + end + + # Converts a Chef::Node object to a string suitable for output to a + # terminal. If config[:medium_output] or config[:long_output] are set + # the volume of output is adjusted accordingly. Uses colors if enabled + # in the ui object. + def summarize(list) + summarized='' + list.each do |data| + node = data + # special case ec2 with their split horizon whatsis. + ip = (node[:ec2] && node[:ec2][:public_ipv4]) || node[:ipaddress] + fqdn = (node[:ec2] && node[:ec2][:public_hostname]) || node[:fqdn] + + hours, minutes, seconds = time_difference_in_hms(node["ohai_time"]) + hours_text = "#{hours} hour#{hours == 1 ? ' ' : 's'}" + minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}" + run_list = "#{node.run_list}" if config[:run_list] + if hours > 24 + color = :red + text = hours_text + elsif hours >= 1 + color = :yellow + text = hours_text + else + color = :green + text = minutes_text + end + + line_parts = Array.new + line_parts << @ui.color(text, color) + ' ago' << node.name + line_parts << fqdn if fqdn + line_parts << ip if ip + line_parts << run_list if run_list + + if node['platform'] + platform = node['platform'] + if node['platform_version'] + platform << " #{node['platform_version']}" + end + line_parts << platform + end + + summarized=summarized + line_parts.join(', ') + ".\n" + end + summarized + end + + def key(key_text) + ui.color(key_text, :cyan) + end + + # :nodoc: + # TODO: this is duplicated from StatusHelper in the Webui. dedup. + def time_difference_in_hms(unix_time) + now = Time.now.to_i + difference = now - unix_time.to_i + hours = (difference / 3600).to_i + difference = difference % 3600 + minutes = (difference / 60).to_i + seconds = (difference % 60) + return [hours, minutes, seconds] + end + + end + end + end +end diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb index 0007480ea2..00a4834638 100644 --- a/lib/chef/knife/core/ui.rb +++ b/lib/chef/knife/core/ui.rb @@ -113,7 +113,7 @@ class Chef # determined by the value of `config[:color]`. When output is not to a # terminal, colored output is never used def color? - Chef::Config[:color] && stdout.tty? && !Chef::Platform.windows? + Chef::Config[:color] && stdout.tty? end def ask(*args, &block) @@ -209,11 +209,11 @@ class Chef def confirmation_instructions(default_choice) case default_choice when true - '? (Y/n)' + '? (Y/n) ' when false - '? (y/N)' + '? (y/N) ' else - '? (Y/N)' + '? (Y/N) ' end end diff --git a/lib/chef/knife/node_from_file.rb b/lib/chef/knife/node_from_file.rb index d69392a8db..66f2a466fd 100644 --- a/lib/chef/knife/node_from_file.rb +++ b/lib/chef/knife/node_from_file.rb @@ -35,16 +35,17 @@ class Chef end def run - updated = loader.load_from('nodes', @name_args[0]) - - updated.save - - output(format_for_display(updated)) if config[:print_after] - - ui.info("Updated Node #{updated.name}!") + @name_args.each do |arg| + updated = loader.load_from('nodes', arg) + + updated.save + + output(format_for_display(updated)) if config[:print_after] + + ui.info("Updated Node #{updated.name}!") + end end end end end - diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index accca39aa2..de4c60d998 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -106,13 +106,6 @@ class Chef def session config[:on_error] ||= :skip ssh_error_handler = Proc.new do |server| - if config[:manual] - node_name = server.host - else - @action_nodes.each do |n| - node_name = n if format_for_display(n)[config[:attribute]] == server.host - end - end case config[:on_error] when :skip ui.warn "Failed to connect to #{server.host} -- #{$!.class.name}: #{$!.message}" diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb index 5906a4a624..93e81f8f03 100644 --- a/lib/chef/knife/status.rb +++ b/lib/chef/knife/status.rb @@ -17,13 +17,13 @@ # require 'chef/knife' +require 'chef/knife/core/status_presenter' class Chef class Knife class Status < Knife deps do - require 'highline' require 'chef/search/query' end @@ -44,74 +44,27 @@ class Chef :long => "--hide-healthy", :description => "Hide nodes that have run chef in the last hour" - def highline - @h ||= HighLine.new - end - def run + ui.use_presenter Knife::Core::StatusPresenter all_nodes = [] q = Chef::Search::Query.new - query = @name_args[0] || "*:*" + query = @name_args[0] ? @name_args[0].dup : '*:*' + if config[:hide_healthy] + time = Time.now.to_i + query_unhealthy = "NOT ohai_time:[" << (time - 60*60).to_s << " TO " << time.to_s << "]" + query << ' AND ' << query_unhealthy << @name_args[0] if @name_args[0] + query = query_unhealthy unless @name_args[0] + end q.search(:node, query) do |node| all_nodes << node end - all_nodes.sort { |n1, n2| + output(all_nodes.sort { |n1, n2| if (config[:sort_reverse] || Chef::Config[:knife][:sort_status_reverse]) (n2["ohai_time"] or 0) <=> (n1["ohai_time"] or 0) else (n1["ohai_time"] or 0) <=> (n2["ohai_time"] or 0) end - }.each do |node| - if node.has_key?("ec2") - fqdn = node['ec2']['public_hostname'] - ipaddress = node['ec2']['public_ipv4'] - else - fqdn = node['fqdn'] - ipaddress = node['ipaddress'] - end - hours, minutes, seconds = time_difference_in_hms(node["ohai_time"]) - hours_text = "#{hours} hour#{hours == 1 ? ' ' : 's'}" - minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}" - run_list = "#{node.run_list}" if config[:run_list] - if hours > 24 - color = :red - text = hours_text - elsif hours >= 1 - color = :yellow - text = hours_text - else - color = :green - text = minutes_text - end - - line_parts = Array.new - line_parts << @ui.color(text, color) + " ago" << node.name - line_parts << fqdn if fqdn - line_parts << ipaddress if ipaddress - line_parts << run_list if run_list - - if node['platform'] - platform = node['platform'] - if node['platform_version'] - platform << " #{node['platform_version']}" - end - line_parts << platform - end - highline.say(line_parts.join(', ') + '.') unless (config[:hide_healthy] && hours < 1) - end - - end - - # :nodoc: - # TODO: this is duplicated from StatusHelper in the Webui. dedup. - def time_difference_in_hms(unix_time) - now = Time.now.to_i - difference = now - unix_time.to_i - hours = (difference / 3600).to_i - difference = difference % 3600 - minutes = (difference / 60).to_i - seconds = (difference % 60) - return [hours, minutes, seconds] + }) end end diff --git a/lib/chef/mixin/command/unix.rb b/lib/chef/mixin/command/unix.rb index b63a02663b..2bad4e6bcf 100644 --- a/lib/chef/mixin/command/unix.rb +++ b/lib/chef/mixin/command/unix.rb @@ -100,9 +100,9 @@ class Chef begin if cmd.kind_of?(Array) - exec(*cmd) + Kernel.exec(*cmd) else - exec(cmd) + Kernel.exec(cmd) end raise 'forty-two' rescue Exception => e diff --git a/lib/chef/mixin/convert_to_class_name.rb b/lib/chef/mixin/convert_to_class_name.rb index f849b8de6a..19f229fdd3 100644 --- a/lib/chef/mixin/convert_to_class_name.rb +++ b/lib/chef/mixin/convert_to_class_name.rb @@ -61,6 +61,60 @@ class Chef base.to_s + (file_base == 'default' ? '' : "_#{file_base}") end + # Copied from rails activesupport. In ruby >= 2.0 const_get will just do this, so this can + # be deprecated and removed. + # + # MIT LICENSE is here: https://github.com/rails/rails/blob/master/activesupport/MIT-LICENSE + + # Tries to find a constant with the name specified in the argument string. + # + # 'Module'.constantize # => Module + # 'Test::Unit'.constantize # => Test::Unit + # + # The name is assumed to be the one of a top-level constant, no matter + # whether it starts with "::" or not. No lexical context is taken into + # account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # 'C'.constantize # => 'outside', same as ::C + # end + # + # NameError is raised when the name is not in CamelCase or the constant is + # unknown. + def constantize(camel_cased_word) + names = camel_cased_word.split('::') + + # Trigger a built-in NameError exception including the ill-formed constant in the message. + Object.const_get(camel_cased_word) if names.empty? + + # Remove the first blank element in case of '::ClassName' notation. + names.shift if names.size > 1 && names.first.empty? + + names.inject(Object) do |constant, name| + if constant == Object + constant.const_get(name) + else + candidate = constant.const_get(name) + next candidate if constant.const_defined?(name, false) + next candidate unless Object.const_defined?(name) + + # Go down the ancestors to check if it is owned directly. The check + # stops when we reach Object or the end of ancestors tree. + constant = constant.ancestors.inject do |const, ancestor| + break const if ancestor == Object + break ancestor if ancestor.const_defined?(name, false) + const + end + + # owner is in Object, so raise + constant.const_get(name, false) + end + end + end + end end end diff --git a/lib/chef/mixin/descendants_tracker.rb b/lib/chef/mixin/descendants_tracker.rb new file mode 100644 index 0000000000..75d1f620d4 --- /dev/null +++ b/lib/chef/mixin/descendants_tracker.rb @@ -0,0 +1,82 @@ +# +# Copyright (c) 2005-2012 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + + +# This is lifted from rails activesupport (note the copyright above): +# https://github.com/rails/rails/blob/9f84e60ac9d7bf07d6ae1bc94f3941f5b8f1a228/activesupport/lib/active_support/descendants_tracker.rb + +class Chef + module Mixin + module DescendantsTracker + @@direct_descendants = {} + + class << self + def direct_descendants(klass) + @@direct_descendants[klass] || [] + end + + def descendants(klass) + arr = [] + accumulate_descendants(klass, arr) + arr + end + + def find_descendants_by_name(klass, name) + descendants(klass).first {|c| c.name == name } + end + + # This is the only method that is not thread safe, but is only ever called + # during the eager loading phase. + def store_inherited(klass, descendant) + (@@direct_descendants[klass] ||= []) << descendant + end + + private + + def accumulate_descendants(klass, acc) + if direct_descendants = @@direct_descendants[klass] + acc.concat(direct_descendants) + direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) } + end + end + end + + def inherited(base) + DescendantsTracker.store_inherited(self, base) + super + end + + def direct_descendants + DescendantsTracker.direct_descendants(self) + end + + def find_descendants_by_name(name) + DescendantsTracker.find_descendants_by_name(self, name) + end + + def descendants + DescendantsTracker.descendants(self) + end + end + end +end diff --git a/lib/chef/mixin/homebrew_user.rb b/lib/chef/mixin/homebrew_user.rb index 854a954a90..ab6fb19563 100644 --- a/lib/chef/mixin/homebrew_user.rb +++ b/lib/chef/mixin/homebrew_user.rb @@ -36,7 +36,7 @@ class Chef # the brew executable. def find_homebrew_uid(provided_user = nil) # They could provide us a user name or a UID - unless provided_user.nil? + if provided_user return provided_user if provided_user.is_a? Integer return Etc.getpwnam(provided_user).uid end diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb index 82772b584a..5b05e788db 100644 --- a/lib/chef/mixin/shell_out.rb +++ b/lib/chef/mixin/shell_out.rb @@ -15,10 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# chef/shell_out has been deprecated in favor of mixlib/shellout -# chef/shell_out is still required here to ensure backward compatibility -require 'chef/shell_out' - require 'mixlib/shellout' class Chef diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 327da67b1c..5f788af4d4 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -397,7 +397,7 @@ class Chef end index_hash["recipe"] = run_list.recipe_names if run_list.recipe_names.length > 0 index_hash["role"] = run_list.role_names if run_list.role_names.length > 0 - index_hash["run_list"] = run_list.run_list if run_list.run_list.length > 0 + index_hash["run_list"] = run_list.run_list_items index_hash end @@ -409,7 +409,7 @@ class Chef display["normal"] = normal_attrs display["default"] = attributes.combined_default display["override"] = attributes.combined_override - display["run_list"] = run_list.run_list + display["run_list"] = run_list.run_list_items display end diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb new file mode 100644 index 0000000000..2ca6d9ba17 --- /dev/null +++ b/lib/chef/node_map.rb @@ -0,0 +1,146 @@ +# +# Author:: Lamont Granquist (<lamont@chef.io>) +# 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 NodeMap + + VALID_OPTS = [ + :on_platform, + :on_platforms, + :platform, + :os, + :platform_family, + ] + + DEPRECATED_OPTS = [ + :on_platform, + :on_platforms, + ] + + # Create a new NodeMap + # + def initialize + @map = {} + end + + # Set a key/value pair on the map with a filter. The filter must be true + # when applied to the node in order to retrieve the value. + # + # @param key [Object] Key to store + # @param value [Object] Value associated with the key + # @param filters [Hash] Node filter options to apply to key retrieval + # @yield [node] Arbitrary node filter as a block which takes a node argument + # @return [NodeMap] Returns self for possible chaining + # + def set(key, value, filters = {}, &block) + validate_filter!(filters) + deprecate_filter!(filters) + @map[key] ||= [] + # we match on the first value we find, so we want to unshift so that the + # last setter wins + # FIXME: need a test for this behavior + @map[key].unshift({ filters: filters, block: block, value: value }) + self + end + + # Get a value from the NodeMap via applying the node to the filters that + # were set on the key. + # + # @param node [Chef::Node] The Chef::Node object for the run + # @param key [Object] Key to look up + # @return [Object] Value + # + def get(node, key) + # FIXME: real exception + raise "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) + return nil unless @map.has_key?(key) + @map[key].each do |matcher| + if filters_match?(node, matcher[:filters]) && + block_matches?(node, matcher[:block]) + return matcher[:value] + end + end + nil + end + + private + + # only allow valid filter options + def validate_filter!(filters) + filters.each_key do |key| + # FIXME: real exception + raise "Bad key #{key} in Chef::NodeMap filter expression" unless VALID_OPTS.include?(key) + end + end + + # warn on deprecated filter options + def deprecate_filter!(filters) + filters.each_key do |key| + Chef::Log.warn "The #{key} option to node_map has been deprecated" if DEPRECATED_OPTS.include?(key) + end + end + + # @todo: this works fine, but is probably hard to understand + def negative_match(filter, param) + # We support strings prefaced by '!' to mean 'not'. In particular, this is most useful + # for os matching on '!windows'. + negative_matches = filter.select { |f| f[0] == '!' } + return true if !negative_matches.empty? && negative_matches.include?('!' + param) + + # We support the symbol :all to match everything, for backcompat, but this can and should + # simply be ommitted. + positive_matches = filter.reject { |f| f[0] == '!' || f == :all } + return true if !positive_matches.empty? && !positive_matches.include?(param) + + # sorry double-negative: this means we pass this filter. + false + end + + def filters_match?(node, filters) + return true if filters.empty? + + # each filter is applied in turn. if any fail, then it shortcuts and returns false. + # if it passes or does not exist it succeeds and continues on. so multiple filters are + # effectively joined by 'and'. all filters can be single strings, or arrays which are + # effectively joined by 'or'. + + os_filter = [ filters[:os] ].flatten.compact + unless os_filter.empty? + return false if negative_match(os_filter, node[:os]) + end + + platform_family_filter = [ filters[:platform_family] ].flatten.compact + unless platform_family_filter.empty? + return false if negative_match(platform_family_filter, node[:platform_family]) + end + + # :on_platform and :on_platforms here are synonyms which are deprecated + platform_filter = [ filters[:platform] || filters[:on_platform] || filters[:on_platforms] ].flatten.compact + unless platform_filter.empty? + return false if negative_match(platform_filter, node[:platform]) + end + + return true + end + + def block_matches?(node, block) + return true if block.nil? + block.call node + end + end +end diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index 0766ccffa7..382df342f5 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -41,7 +41,6 @@ class Chef :mac_os_x => { :default => { :package => Chef::Provider::Package::Homebrew, - :service => Chef::Provider::Service::Macosx, :user => Chef::Provider::User::Dscl, :group => Chef::Provider::Group::Dscl } @@ -49,7 +48,6 @@ class Chef :mac_os_x_server => { :default => { :package => Chef::Provider::Package::Homebrew, - :service => Chef::Provider::Service::Macosx, :user => Chef::Provider::User::Dscl, :group => Chef::Provider::Group::Dscl } @@ -57,7 +55,6 @@ class Chef :freebsd => { :default => { :group => Chef::Provider::Group::Pw, - :service => Chef::Provider::Service::Freebsd, :user => Chef::Provider::User::Pw, :cron => Chef::Provider::Cron } @@ -197,10 +194,14 @@ class Chef }, :suse => { :default => { - :service => Chef::Provider::Service::Redhat, + :service => Chef::Provider::Service::Systemd, :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Zypper, - :group => Chef::Provider::Group::Suse + :group => Chef::Provider::Group::Gpasswd + }, + "< 12.0" => { + :group => Chef::Provider::Group::Suse, + :service => Chef::Provider::Service::Redhat } }, :oracle => { @@ -272,7 +273,6 @@ class Chef :mswin => { :default => { :env => Chef::Provider::Env::Windows, - :service => Chef::Provider::Service::Windows, :user => Chef::Provider::User::Windows, :group => Chef::Provider::Group::Windows, :mount => Chef::Provider::Mount::Windows, @@ -283,7 +283,6 @@ class Chef :mingw32 => { :default => { :env => Chef::Provider::Env::Windows, - :service => Chef::Provider::Service::Windows, :user => Chef::Provider::User::Windows, :group => Chef::Provider::Group::Windows, :mount => Chef::Provider::Mount::Windows, @@ -294,7 +293,6 @@ class Chef :windows => { :default => { :env => Chef::Provider::Env::Windows, - :service => Chef::Provider::Service::Windows, :user => Chef::Provider::User::Windows, :group => Chef::Provider::Group::Windows, :mount => Chef::Provider::Mount::Windows, @@ -306,7 +304,6 @@ class Chef :openindiana => { :default => { :mount => Chef::Provider::Mount::Solaris, - :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Ips, :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod @@ -315,7 +312,6 @@ class Chef :opensolaris => { :default => { :mount => Chef::Provider::Mount::Solaris, - :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Ips, :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod @@ -324,7 +320,6 @@ class Chef :nexentacore => { :default => { :mount => Chef::Provider::Mount::Solaris, - :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Solaris, :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod @@ -333,7 +328,6 @@ class Chef :omnios => { :default => { :mount => Chef::Provider::Mount::Solaris, - :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Ips, :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod, @@ -343,7 +337,6 @@ class Chef :solaris2 => { :default => { :mount => Chef::Provider::Mount::Solaris, - :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Ips, :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod, @@ -351,7 +344,6 @@ class Chef }, "< 5.11" => { :mount => Chef::Provider::Mount::Solaris, - :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Solaris, :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod, @@ -361,7 +353,6 @@ class Chef :smartos => { :default => { :mount => Chef::Provider::Mount::Solaris, - :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::SmartOS, :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod @@ -369,7 +360,6 @@ class Chef }, :netbsd => { :default => { - :service => Chef::Provider::Service::Freebsd, :group => Chef::Provider::Group::Groupmod } }, @@ -390,7 +380,8 @@ class Chef :ifconfig => Chef::Provider::Ifconfig::Aix, :cron => Chef::Provider::Cron::Aix, :package => Chef::Provider::Package::Aix, - :user => Chef::Provider::User::Aix + :user => Chef::Provider::User::Aix, + :service => Chef::Provider::Service::Aix } }, :exherbo => { @@ -402,29 +393,10 @@ class Chef } }, :default => { - :file => Chef::Provider::File, - :directory => Chef::Provider::Directory, - :link => Chef::Provider::Link, - :template => Chef::Provider::Template, - :remote_directory => Chef::Provider::RemoteDirectory, - :execute => Chef::Provider::Execute, :mount => Chef::Provider::Mount::Mount, - :script => Chef::Provider::Script, - :service => Chef::Provider::Service::Init, - :perl => Chef::Provider::Script, - :python => Chef::Provider::Script, - :ruby => Chef::Provider::Script, - :bash => Chef::Provider::Script, - :csh => Chef::Provider::Script, :user => Chef::Provider::User::Useradd, :group => Chef::Provider::Group::Gpasswd, - :http_request => Chef::Provider::HttpRequest, - :route => Chef::Provider::Route, :ifconfig => Chef::Provider::Ifconfig, - :ruby_block => Chef::Provider::RubyBlock, - :whyrun_safe_ruby_block => Chef::Provider::WhyrunSafeRubyBlock, - :erl_call => Chef::Provider::ErlCall, - :log => Chef::Provider::Log::ChefLog } } end diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb new file mode 100644 index 0000000000..ccf6ef0bbe --- /dev/null +++ b/lib/chef/platform/provider_priority_map.rb @@ -0,0 +1,80 @@ + +require 'chef/providers' + +class Chef + class Platform + class ProviderPriorityMap + include Singleton + + def initialize + load_default_map + end + + def load_default_map + + # + # Linux + # + + # default block for linux O/Sen must come before platform_family exceptions + priority :service, [ + Chef::Provider::Service::Systemd, + Chef::Provider::Service::Insserv, + Chef::Provider::Service::Redhat, + ], os: "linux" + + priority :service, [ + Chef::Provider::Service::Systemd, + Chef::Provider::Service::Arch, + ], platform_family: "arch" + + priority :service, [ + Chef::Provider::Service::Systemd, + Chef::Provider::Service::Gentoo, + ], platform_family: "gentoo" + + priority :service, [ + # on debian-ish system if an upstart script exists that always wins + Chef::Provider::Service::Upstart, + Chef::Provider::Service::Systemd, + Chef::Provider::Service::Insserv, + Chef::Provider::Service::Debian, + Chef::Provider::Service::Invokercd, + ], platform_family: "debian" + + priority :service, [ + Chef::Provider::Service::Systemd, + Chef::Provider::Service::Insserv, + Chef::Provider::Service::Redhat, + ], platform_family: [ "rhel", "fedora", "suse" ] + + # + # BSDen + # + + priority :service, Chef::Provider::Service::Freebsd, os: [ "freebsd", "netbsd" ] + + # + # Solaris-en + # + + priority :service, Chef::Provider::Service::Solaris, os: "solaris2" + + # + # Mac + # + + priority :service, Chef::Provider::Service::Macosx, os: "darwin" + end + + def priority_map + @priority_map ||= Chef::NodeMap.new + end + + def priority(*args) + priority_map.set(*args) + end + + end + end +end diff --git a/lib/chef/platform/service_helpers.rb b/lib/chef/platform/service_helpers.rb new file mode 100644 index 0000000000..440391843e --- /dev/null +++ b/lib/chef/platform/service_helpers.rb @@ -0,0 +1,113 @@ +# +# Author:: Lamont Granquist (<lamont@chef.io>) +# 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. +# + +# XXX: mixing shellout into a mixin into classes has to be code smell +require 'chef/mixin/shell_out' + +class Chef + class Platform + class ServiceHelpers + class << self + + include Chef::Mixin::ShellOut + + # This helper is mostly used to sort out the mess of different + # linux mechanisms that can be used to start services. It does + # not necessarily need to linux-specific, but currently all our + # other service providers are narrowly platform-specific with no + # alternatives. + def service_resource_providers + service_resource_providers = [] + + if ::File.exist?("/usr/sbin/update-rc.d") + service_resource_providers << :debian + end + + if ::File.exist?("/usr/sbin/invoke-rc.d") + service_resource_providers << :invokercd + end + + if ::File.exist?("/sbin/insserv") + service_resource_providers << :insserv + end + + # debian >= 6.0 has /etc/init but does not have upstart + if ::File.exist?("/etc/init") && ::File.exist?("/sbin/start") + service_resource_providers << :upstart + end + + if ::File.exist?("/sbin/chkconfig") + service_resource_providers << :redhat + end + + if ::File.exist?("/bin/systemctl") + # FIXME: look for systemd as init provider + service_resource_providers << :systemd + end + + service_resource_providers + end + + def config_for_service(service_name) + configs = [] + + if ::File.exist?("/etc/init.d/#{service_name}") + configs << :initd + end + + if ::File.exist?("/etc/init/#{service_name}.conf") + configs << :upstart + end + + if ::File.exist?("/etc/xinetd.d/#{service_name}") + configs << :xinetd + end + + if ::File.exist?("/etc/rc.d/#{service_name}") + configs << :etc_rcd + end + + if ::File.exist?("/usr/local/etc/rc.d/#{service_name}") + configs << :usr_local_etc_rcd + end + + if ::File.exist?("/bin/systemctl") && platform_has_systemd_unit?(service_name) + configs << :systemd + end + + configs + end + + private + + def extract_systemd_services(output) + # first line finds e.g. "sshd.service" + services = output.lines.split.map { |l| l.split[0] } + # this splits off the suffix after the last dot to return "sshd" + services += services.map { |s| s.sub(/(.*)\..*/, '\1') } + end + + def platform_has_systemd_unit?(service_name) + services = extract_systemd_services(shell_out!("systemctl --all").stdout) + + extract_systemd_services(shell_out!("systemctl --list-unit-files").stdout) + services.include?(service_name) + end + end + end + end +end diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index bdfe826944..680fe9782f 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -22,11 +22,35 @@ require 'chef/mixin/convert_to_class_name' require 'chef/mixin/enforce_ownership_and_permissions' require 'chef/mixin/why_run' require 'chef/mixin/shell_out' +require 'chef/mixin/descendants_tracker' +require 'chef/platform/service_helpers' +require 'chef/node_map' class Chef class Provider include Chef::Mixin::WhyRun include Chef::Mixin::ShellOut + extend Chef::Mixin::DescendantsTracker + + class << self + def node_map + @node_map ||= Chef::NodeMap.new + end + + def provides(resource_name, opts={}, &block) + node_map.set(resource_name.to_sym, true, opts, &block) + end + + # provides a node on the resource (early binding) + def provides?(node, resource) + node_map.get(node, resource.resource_name) + end + + # supports the given resource and action (late binding) + def supports?(resource, action) + true + end + end attr_accessor :new_resource attr_accessor :current_resource diff --git a/lib/chef/provider/breakpoint.rb b/lib/chef/provider/breakpoint.rb index 224e2758eb..663d558f66 100644 --- a/lib/chef/provider/breakpoint.rb +++ b/lib/chef/provider/breakpoint.rb @@ -20,6 +20,8 @@ class Chef class Provider class Breakpoint < Chef::Provider + provides :breakpoint + def load_current_resource end diff --git a/lib/chef/provider/cookbook_file.rb b/lib/chef/provider/cookbook_file.rb index 26d6ebf1d9..b501a9b41d 100644 --- a/lib/chef/provider/cookbook_file.rb +++ b/lib/chef/provider/cookbook_file.rb @@ -24,6 +24,8 @@ class Chef class Provider class CookbookFile < Chef::Provider::File + provides :cookbook_file + extend Chef::Deprecation::Warnings include Chef::Deprecation::Provider::CookbookFile add_deprecation_warnings_for(Chef::Deprecation::Provider::CookbookFile.instance_methods) diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb index c3be9746df..1590c624f6 100644 --- a/lib/chef/provider/cron.rb +++ b/lib/chef/provider/cron.rb @@ -28,7 +28,7 @@ class Chef SPECIAL_TIME_VALUES = [:reboot, :yearly, :annually, :monthly, :weekly, :daily, :midnight, :hourly] CRON_ATTRIBUTES = [:minute, :hour, :day, :month, :weekday, :time, :command, :mailto, :path, :shell, :home, :environment] WEEKDAY_SYMBOLS = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday] - + CRON_PATTERN = /\A([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+|[a-zA-Z]{3})\s([-0-9*,\/]+|[a-zA-Z]{3})\s(.*)/ SPECIAL_PATTERN = /\A(@(#{SPECIAL_TIME_VALUES.join('|')}))\s(.*)/ ENV_PATTERN = /\A(\S+)=(\S*)/ diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb index 5cb1bcda41..350f8bda18 100644 --- a/lib/chef/provider/cron/unix.rb +++ b/lib/chef/provider/cron/unix.rb @@ -25,18 +25,21 @@ class Chef class Provider class Cron class Unix < Chef::Provider::Cron + include Chef::Mixin::ShellOut private def read_crontab - crontab = nil - status = popen4("crontab -l #{@new_resource.user}") do |pid, stdin, stdout, stderr| - crontab = stdout.read - end - if status.exitstatus > 1 - raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status.exitstatus}" + crontab = shell_out('/usr/bin/crontab -l', :user => @new_resource.user) + status = crontab.status.exitstatus + + Chef::Log.debug crontab.format_for_exception if status > 0 + + if status > 1 + raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status}" end - crontab + return nil if status > 0 + crontab.stdout.chomp << "\n" end def write_crontab(crontab) @@ -47,8 +50,9 @@ class Chef exit_status = 0 error_message = "" begin - status, stdout, stderr = run_command_and_return_stdout_stderr(:command => "/usr/bin/crontab #{tempcron.path}",:user => @new_resource.user) - exit_status = status.exitstatus + crontab_write = shell_out("/usr/bin/crontab #{tempcron.path}", :user => @new_resource.user) + stderr = crontab_write.stderr + exit_status = crontab_write.status.exitstatus # solaris9, 10 on some failures for example invalid 'mins' in crontab fails with exit code of zero :( if stderr && stderr.include?("errors detected in input, no crontab file generated") error_message = stderr diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb index db147278c2..b30f7ed17e 100644 --- a/lib/chef/provider/deploy.rb +++ b/lib/chef/provider/deploy.rb @@ -375,7 +375,7 @@ class Chef def gem_resource_collection_runner gems_collection = Chef::ResourceCollection.new - gem_packages.each { |rbgem| gems_collection << rbgem } + 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) diff --git a/lib/chef/provider/deploy/revision.rb b/lib/chef/provider/deploy/revision.rb index c98c1e5c75..62aa0e87f6 100644 --- a/lib/chef/provider/deploy/revision.rb +++ b/lib/chef/provider/deploy/revision.rb @@ -27,6 +27,8 @@ class Chef class Provider class Deploy class Revision < Chef::Provider::Deploy + provides :deploy_revision + provides :deploy_branch def all_releases sorted_releases diff --git a/lib/chef/provider/deploy/timestamped.rb b/lib/chef/provider/deploy/timestamped.rb index ce921161e0..ba3f6683f0 100644 --- a/lib/chef/provider/deploy/timestamped.rb +++ b/lib/chef/provider/deploy/timestamped.rb @@ -20,6 +20,8 @@ class Chef class Provider class Deploy class Timestamped < Chef::Provider::Deploy + provides :timestamped_deploy + provides :deploy protected diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb index 067737b9d4..c9c3d466b9 100644 --- a/lib/chef/provider/directory.rb +++ b/lib/chef/provider/directory.rb @@ -27,6 +27,8 @@ class Chef class Provider class Directory < Chef::Provider::File + provides :directory + def whyrun_supported? true end diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb index b8ca54f1b8..5db50e74b3 100644 --- a/lib/chef/provider/dsc_script.rb +++ b/lib/chef/provider/dsc_script.rb @@ -24,6 +24,9 @@ require 'chef/util/path_helper' class Chef class Provider class DscScript < Chef::Provider + + provides :dsc_script, os: "windows" + def initialize(dsc_resource, run_context) super(dsc_resource, run_context) @dsc_resource = dsc_resource @@ -62,7 +65,7 @@ class Chef def define_resource_requirements requirements.assert(:run) do |a| err = [ - 'Could not find Dsc on the system', + 'Could not find PowerShell DSC support on the system', powershell_info_str, "Powershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource.", ] @@ -98,9 +101,8 @@ class Chef end def get_augmented_configuration_flags(configuration_data_path) - updated_flags = nil + updated_flags = @dsc_resource.flags.nil? ? {} : @dsc_resource.flags.dup if configuration_data_path - updated_flags = @dsc_resource.flags.nil? ? {} : @dsc_resource.flags.dup Chef::Util::PathHelper.validate_path(configuration_data_path) updated_flags[:configurationdata] = configuration_data_path end diff --git a/lib/chef/provider/env.rb b/lib/chef/provider/env.rb index e91b8bbb97..600cac1e6b 100644 --- a/lib/chef/provider/env.rb +++ b/lib/chef/provider/env.rb @@ -58,20 +58,22 @@ class Chef # ==== Returns # <true>:: If a change is required # <false>:: If a change is not required - def compare_value + def requires_modify_or_create? if @new_resource.delim #e.g. check for existing value within PATH - not @current_resource.value.split(@new_resource.delim).any? do |val| - val == @new_resource.value + not new_values.all? do |val| + current_values.include? val end else @new_resource.value != @current_resource.value end end + alias_method :compare_value, :requires_modify_or_create? + def action_create if @key_exists - if compare_value + if requires_modify_or_create? modify_env Chef::Log.info("#{@new_resource} altered") @new_resource.updated_by_last_action(true) @@ -91,13 +93,14 @@ class Chef # after we removed the element. def delete_element return false unless @new_resource.delim #no delim: delete the key - if compare_value + needs_delete = new_values.any? { |v| current_values.include?(v) } + if !needs_delete Chef::Log.debug("#{@new_resource} element '#{@new_resource.value}' does not exist") return true #do not delete the key else new_value = - @current_resource.value.split(@new_resource.delim).select { |item| - item != @new_resource.value + current_values.select { |item| + not new_values.include?(item) }.join(@new_resource.delim) if new_value.empty? @@ -122,7 +125,7 @@ class Chef def action_modify if @key_exists - if compare_value + if requires_modify_or_create? modify_env Chef::Log.info("#{@new_resource} modified") @new_resource.updated_by_last_action(true) @@ -142,11 +145,23 @@ class Chef def modify_env if @new_resource.delim - #e.g. add to PATH - @new_resource.value(@new_resource.value + @new_resource.delim + @current_resource.value) + values = new_values.reject do |v| + current_values.include?(v) + end + @new_resource.value((values + [@current_resource.value]).join(@new_resource.delim)) end create_env end + + # Returns the current values to split by delimiter + def current_values + @current_values ||= @current_resource.value.split(@new_resource.delim) + end + + # Returns the new values to split by delimiter + def new_values + @new_values ||= @new_resource.value.split(@new_resource.delim) + end end end end diff --git a/lib/chef/provider/env/windows.rb b/lib/chef/provider/env/windows.rb index 572ec5c633..dd7cb1bc46 100644 --- a/lib/chef/provider/env/windows.rb +++ b/lib/chef/provider/env/windows.rb @@ -43,14 +43,16 @@ class Chef obj = env_obj(@new_resource.key_name) if obj obj.delete_ - ENV.delete(@new_resource.key_name) broadcast_env_change end + if ENV[@new_resource.key_name] + ENV.delete(@new_resource.key_name) + end end def env_value(key_name) obj = env_obj(key_name) - return obj ? obj.variablevalue : nil + return obj ? obj.variablevalue : ENV[key_name] end def env_obj(key_name) diff --git a/lib/chef/provider/erl_call.rb b/lib/chef/provider/erl_call.rb index cdd494a243..f5855bcce6 100644 --- a/lib/chef/provider/erl_call.rb +++ b/lib/chef/provider/erl_call.rb @@ -25,6 +25,8 @@ class Chef class ErlCall < Chef::Provider include Chef::Mixin::Command + provides :erl_call + def initialize(node, new_resource) super(node, new_resource) end diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb index d469bea769..48b2a344d1 100644 --- a/lib/chef/provider/execute.rb +++ b/lib/chef/provider/execute.rb @@ -23,6 +23,8 @@ class Chef class Provider class Execute < Chef::Provider + provides :execute + def load_current_resource true end @@ -50,10 +52,11 @@ class Chef opts[:umask] = @new_resource.umask if @new_resource.umask opts[:log_level] = :info opts[:log_tag] = @new_resource.to_s - if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info? + if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info? && !@new_resource.sensitive opts[:live_stream] = STDOUT end - converge_by("execute #{@new_resource.command}") do + description = @new_resource.sensitive ? "sensitive resource" : @new_resource.command + converge_by("execute #{description}") do result = shell_out!(@new_resource.command, opts) Chef::Log.info("#{@new_resource} ran successfully") end diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb index 256248f240..a9390cc45c 100644 --- a/lib/chef/provider/file.rb +++ b/lib/chef/provider/file.rb @@ -54,6 +54,8 @@ class Chef include Chef::Deprecation::Provider::File add_deprecation_warnings_for(Chef::Deprecation::Provider::File.instance_methods) + provides :file + attr_reader :deployment_strategy attr_accessor :needs_creating diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb index c8e615c1f9..8418f22933 100644 --- a/lib/chef/provider/git.rb +++ b/lib/chef/provider/git.rb @@ -25,6 +25,8 @@ class Chef class Provider class Git < Chef::Provider + provides :git + def whyrun_supported? true end @@ -64,7 +66,7 @@ class Chef a.failure_message Chef::Exceptions::UnresolvableGitReference, "Unable to parse SHA reference for '#{@new_resource.revision}' in repository '#{@new_resource.repository}'. " + "Verify your (case-sensitive) repository URL and revision.\n" + - "`git ls-remote` output: #{@resolved_reference}" + "`git ls-remote '#{@new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}" end end @@ -240,35 +242,55 @@ class Chef # annotated tags, we have to search for "revision*" and # post-process. Special handling for 'HEAD' to ignore a tag # named 'HEAD'. - rev_pattern = case @new_resource.revision - when '', 'HEAD' - 'HEAD' - else - @new_resource.revision + '*' - end - command = git("ls-remote \"#{@new_resource.repository}\" \"#{rev_pattern}\"") - @resolved_reference = shell_out!(command, run_options).stdout - ref_lines = @resolved_reference.split("\n") - refs = ref_lines.map { |line| line.split("\t") } - # first try for ^{} indicating the commit pointed to by an - # annotated tag - tagged_commit = refs.find { |m| m[1].end_with?("#{@new_resource.revision}^{}") } + @resolved_reference = git_ls_remote(rev_search_pattern) + refs = @resolved_reference.split("\n").map { |line| line.split("\t") } + # First try for ^{} indicating the commit pointed to by an + # annotated tag. # It is possible for a user to create a tag named 'HEAD'. # Using such a degenerate annotated tag would be very # confusing. We avoid the issue by disallowing the use of # annotated tags named 'HEAD'. - if tagged_commit && rev_pattern != 'HEAD' - tagged_commit[0] + if rev_search_pattern != 'HEAD' + found = find_revision(refs, @new_resource.revision, '^{}') else - found = refs.find { |m| m[1].end_with?(@new_resource.revision) } - if found - found[0] - else - nil - end + found = refs_search(refs, 'HEAD') + end + found = find_revision(refs, @new_resource.revision) if found.empty? + found.size == 1 ? found.first[0] : nil + end + + def find_revision(refs, revision, suffix="") + found = refs_search(refs, rev_match_pattern('refs/tags/', revision) + suffix) + found = refs_search(refs, rev_match_pattern('refs/heads/', revision) + suffix) if found.empty? + found = refs_search(refs, revision + suffix) if found.empty? + found + end + + def rev_match_pattern(prefix, revision) + if revision.start_with?(prefix) + revision + else + prefix + revision end end + def rev_search_pattern + if ['', 'HEAD'].include? @new_resource.revision + 'HEAD' + else + @new_resource.revision + '*' + end + end + + def git_ls_remote(rev_pattern) + command = git(%Q(ls-remote "#{@new_resource.repository}" "#{rev_pattern}")) + shell_out!(command, run_options).stdout + end + + def refs_search(refs, pattern) + refs.find_all { |m| m[1] == pattern } + end + private def run_options(run_opts={}) diff --git a/lib/chef/provider/http_request.rb b/lib/chef/provider/http_request.rb index ba54b10195..61aff434ed 100644 --- a/lib/chef/provider/http_request.rb +++ b/lib/chef/provider/http_request.rb @@ -23,6 +23,8 @@ class Chef class Provider class HttpRequest < Chef::Provider + provides :http_request + attr_accessor :http def whyrun_supported? diff --git a/lib/chef/provider/link.rb b/lib/chef/provider/link.rb index 639dc4f3ff..417d6a21b0 100644 --- a/lib/chef/provider/link.rb +++ b/lib/chef/provider/link.rb @@ -28,6 +28,8 @@ class Chef class Provider class Link < Chef::Provider + provides :link + include Chef::Mixin::EnforceOwnershipAndPermissions include Chef::Mixin::FileClass diff --git a/lib/chef/provider/log.rb b/lib/chef/provider/log.rb index 9379ceeefa..40eaf0aa28 100644 --- a/lib/chef/provider/log.rb +++ b/lib/chef/provider/log.rb @@ -25,6 +25,8 @@ class Chef # Chef log provider, allows logging to chef's logs from recipes class ChefLog < Chef::Provider + provides :log + def whyrun_supported? true end diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb index 90ce70ae61..135a3f6b7c 100644 --- a/lib/chef/provider/lwrp_base.rb +++ b/lib/chef/provider/lwrp_base.rb @@ -81,22 +81,24 @@ class Chef include Chef::DSL::DataQuery def self.build_from_file(cookbook_name, filename, run_context) + provider_class = nil provider_name = filename_to_qualified_string(cookbook_name, filename) - # Add log entry if we override an existing light-weight provider. class_name = convert_to_class_name(provider_name) if Chef::Provider.const_defined?(class_name) - Chef::Log.info("#{class_name} light-weight provider already initialized -- overriding!") + Chef::Log.info("#{class_name} light-weight provider is already initialized -- Skipping loading #{filename}!") + Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.") + provider_class = Chef::Provider.const_get(class_name) + else + provider_class = Class.new(self) + provider_class.class_from_file(filename) + + class_name = convert_to_class_name(provider_name) + Chef::Provider.const_set(class_name, provider_class) + Chef::Log.debug("Loaded contents of #{filename} into a provider named #{provider_name} defined in Chef::Provider::#{class_name}") end - provider_class = Class.new(self) - provider_class.class_from_file(filename) - - class_name = convert_to_class_name(provider_name) - Chef::Provider.const_set(class_name, provider_class) - Chef::Log.debug("Loaded contents of #{filename} into a provider named #{provider_name} defined in Chef::Provider::#{class_name}") - provider_class end diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb index 2d4a5aadef..c1d4fb2223 100644 --- a/lib/chef/provider/mount/mount.rb +++ b/lib/chef/provider/mount/mount.rb @@ -191,7 +191,7 @@ class Chef def device_should_exist? ( @new_resource.device != "none" ) && ( not network_device? ) && - ( not %w[ tmpfs fuse ].include? @new_resource.fstype ) + ( not %w[ cgroup tmpfs fuse ].include? @new_resource.fstype ) end private diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb index da3e6d1684..88de4679ba 100644 --- a/lib/chef/provider/package/aix.rb +++ b/lib/chef/provider/package/aix.rb @@ -26,6 +26,8 @@ class Chef class Package class Aix < Chef::Provider::Package + provides :bff_package, os: "aix" + include Chef::Mixin::GetSourceFromPackage def define_resource_requirements diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb index 0d91d0d1f0..eb2c038eaa 100644 --- a/lib/chef/provider/package/apt.rb +++ b/lib/chef/provider/package/apt.rb @@ -25,6 +25,8 @@ class Chef class Package class Apt < Chef::Provider::Package + provides :apt_package, os: "linux" + attr_accessor :is_virtual_package def load_current_resource diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb index a1f1c797b1..3a9cecc660 100644 --- a/lib/chef/provider/package/dpkg.rb +++ b/lib/chef/provider/package/dpkg.rb @@ -30,6 +30,8 @@ class Chef DPKG_INSTALLED = /^Status: install ok installed/ DPKG_VERSION = /^Version: (.+)$/ + provides :dpkg_package, os: "linux" + include Chef::Mixin::GetSourceFromPackage def define_resource_requirements diff --git a/lib/chef/provider/package/easy_install.rb b/lib/chef/provider/package/easy_install.rb index 2af8a72e61..90727b738d 100644 --- a/lib/chef/provider/package/easy_install.rb +++ b/lib/chef/provider/package/easy_install.rb @@ -25,6 +25,8 @@ class Chef class Package class EasyInstall < Chef::Provider::Package + provides :easy_install_package + def install_check(name) check = false diff --git a/lib/chef/provider/package/freebsd/port.rb b/lib/chef/provider/package/freebsd/port.rb index 4b3510f0e9..8b191179f0 100644 --- a/lib/chef/provider/package/freebsd/port.rb +++ b/lib/chef/provider/package/freebsd/port.rb @@ -34,7 +34,7 @@ class Chef end def current_installed_version - pkg_info = if supports_pkgng? + pkg_info = if @new_resource.supports_pkgng? shell_out!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70]) else shell_out!("pkg_info -E \"#{@new_resource.package_name}*\"", :env => nil, :returns => [0,1]) @@ -53,14 +53,6 @@ class Chef def port_dir super(@new_resource.package_name) end - - private - - def supports_pkgng? - with_pkgng = makefile_variable_value('WITH_PKGNG') - with_pkgng && with_pkgng =~ /yes/i - end - end end end diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb index 202e4d2533..822f4c8a42 100644 --- a/lib/chef/provider/package/homebrew.rb +++ b/lib/chef/provider/package/homebrew.rb @@ -25,7 +25,11 @@ class Chef class Provider class Package class Homebrew < Chef::Provider::Package + + provides :homebrew_package, os: "mac_os_x" + include Chef::Mixin::HomebrewUser + def load_current_resource self.current_resource = Chef::Resource::Package.new(new_resource.name) current_resource.package_name(new_resource.package_name) @@ -109,7 +113,7 @@ class Chef private def get_response_from_command(command) - homebrew_uid = find_homebrew_uid(new_resource.homebrew_user) + homebrew_uid = find_homebrew_uid(new_resource.respond_to?(:homebrew_user) && new_resource.homebrew_user) homebrew_user = Etc.getpwuid(homebrew_uid) Chef::Log.debug "Executing '#{command}' as user '#{homebrew_user.name}'" diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb index 4090507303..87022d770a 100644 --- a/lib/chef/provider/package/ips.rb +++ b/lib/chef/provider/package/ips.rb @@ -27,6 +27,8 @@ class Chef class Package class Ips < Chef::Provider::Package + provides :ips_package, os: "solaris2" + attr_accessor :virtual def define_resource_requirements diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb index 05247e6d31..cd142eca42 100644 --- a/lib/chef/provider/package/macports.rb +++ b/lib/chef/provider/package/macports.rb @@ -2,6 +2,9 @@ class Chef class Provider class Package class Macports < Chef::Provider::Package + + provides :macports_package, os: "mac_os_x" + def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb index 1014ebcaa5..45edda5c5d 100644 --- a/lib/chef/provider/package/pacman.rb +++ b/lib/chef/provider/package/pacman.rb @@ -25,6 +25,8 @@ class Chef class Package class Pacman < Chef::Provider::Package + provides :pacman_package, os: "linux" + def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) @@ -34,7 +36,6 @@ class Chef Chef::Log.debug("#{@new_resource} checking pacman for #{@new_resource.package_name}") status = popen4("pacman -Qi #{@new_resource.package_name}") do |pid, stdin, stdout, stderr| stdout.each do |line| - line.force_encoding(Encoding::UTF_8) if line.respond_to?(:force_encoding) case line when /^Version(\s?)*: (.+)$/ Chef::Log.debug("#{@new_resource} current version is #{$2}") @@ -62,11 +63,11 @@ class Chef package_repos = repos.map {|r| Regexp.escape(r) }.join('|') - status = popen4("pacman -Ss #{@new_resource.package_name}") do |pid, stdin, stdout, stderr| + status = popen4("pacman -Sl") do |pid, stdin, stdout, stderr| stdout.each do |line| case line - when /^(#{package_repos})\/#{Regexp.escape(@new_resource.package_name)} (.+)$/ - # $2 contains a string like "4.4.0-1 (kde kdenetwork)" or "3.10-4 (base)" + when /^(#{package_repos}) #{Regexp.escape(@new_resource.package_name)} (.+)$/ + # $2 contains a string like "4.4.0-1" or "3.10-4 [installed]" # simply split by space and use first token @candidate_version = $2.split(" ").first end diff --git a/lib/chef/provider/package/paludis.rb b/lib/chef/provider/package/paludis.rb index 7c5245fc97..407e0d0110 100644 --- a/lib/chef/provider/package/paludis.rb +++ b/lib/chef/provider/package/paludis.rb @@ -24,6 +24,8 @@ class Chef class Package class Paludis < Chef::Provider::Package + provides :paludis_package, os: "linux" + def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.package_name) @current_resource.package_name(@new_resource.package_name) @@ -34,7 +36,7 @@ class Chef installed = false re = Regexp.new('(.*)[[:blank:]](.*)[[:blank:]](.*)$') - shell_out!("cave -L warning print-ids -M none -m \"*/#{@new_resource.package_name.split('/').last}\" -f \"%c/%p %v %r\n\"").stdout.each_line do |line| + shell_out!("cave -L warning print-ids -M none -m \"#{@new_resource.package_name}\" -f \"%c/%p %v %r\n\"").stdout.each_line do |line| res = re.match(line) unless res.nil? case res[3] @@ -45,7 +47,7 @@ class Chef @current_resource.version(res[2]) else @candidate_version = res[2] - @current_resource.version(nil) + @current_resource.version(nil) end end end diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb index c0a6444252..131587e066 100644 --- a/lib/chef/provider/package/rpm.rb +++ b/lib/chef/provider/package/rpm.rb @@ -25,6 +25,8 @@ class Chef class Package class Rpm < Chef::Provider::Package + provides :rpm_package, os: [ "linux", "aix" ] + include Chef::Mixin::GetSourceFromPackage def define_resource_requirements diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb index be0022f4aa..3c0ca40693 100644 --- a/lib/chef/provider/package/rubygems.rb +++ b/lib/chef/provider/package/rubygems.rb @@ -359,6 +359,9 @@ class Chef Chef::Log.logger end + provides :chef_gem + provides :gem_package + include Chef::Mixin::GetSourceFromPackage def initialize(new_resource, run_context=nil) @@ -493,6 +496,7 @@ class Chef def target_version_already_installed? return false unless @current_resource && @current_resource.version return false if @current_resource.version.nil? + return false if @new_resource.version.nil? Gem::Requirement.new(@new_resource.version).satisfied_by?(Gem::Version.new(@current_resource.version)) end diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb index 19a6b9efef..7cef91953a 100644 --- a/lib/chef/provider/package/smartos.rb +++ b/lib/chef/provider/package/smartos.rb @@ -29,6 +29,8 @@ class Chef class SmartOS < Chef::Provider::Package attr_accessor :is_virtual_package + provides :smartos_package, os: "solaris2", platform_family: "smartos" + def load_current_resource Chef::Log.debug("#{@new_resource} loading current resource") @current_resource = Chef::Resource::Package.new(@new_resource.name) diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb index 19f844b66a..53dd00dd07 100644 --- a/lib/chef/provider/package/solaris.rb +++ b/lib/chef/provider/package/solaris.rb @@ -27,6 +27,8 @@ class Chef include Chef::Mixin::GetSourceFromPackage + provides :solaris_package, os: "solaris2" + # def initialize(*args) # super # @current_resource = Chef::Resource::Package.new(@new_resource.name) diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb index e77319c254..505f5fd6a3 100644 --- a/lib/chef/provider/package/yum.rb +++ b/lib/chef/provider/package/yum.rb @@ -29,6 +29,8 @@ class Chef class Package class Yum < Chef::Provider::Package + provides :yum_package, os: "linux" + class RPMUtils class << self diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb index 967b2d822b..0e76cd1656 100644 --- a/lib/chef/provider/powershell_script.rb +++ b/lib/chef/provider/powershell_script.rb @@ -51,6 +51,8 @@ $chefscriptresult = { }.invokereturnasis() if ($interpolatedexitcode -and $chefscriptresult.gettype().name -eq 'boolean') { exit [int32](!$chefscriptresult) } else { exit 0 } EOH + Chef::Log.debug("powershell_script provider called with script code:\n\n#{code}\n") + Chef::Log.debug("powershell_script provider will execute transformed code:\n\n#{@code}\n") end public @@ -65,7 +67,7 @@ EOH "-NoLogo", "-NonInteractive", "-NoProfile", - "-ExecutionPolicy RemoteSigned", + "-ExecutionPolicy Unrestricted", # Powershell will hang if STDIN is redirected # http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected "-InputFormat None", diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb index 5bd1cb5493..9a7416e318 100644 --- a/lib/chef/provider/remote_directory.rb +++ b/lib/chef/provider/remote_directory.rb @@ -32,6 +32,8 @@ class Chef class Provider class RemoteDirectory < Chef::Provider::Directory + provides :remote_directory + include Chef::Mixin::FileClass def action_create @@ -63,7 +65,7 @@ class Chef def ls(path) files = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), '**', '*'), ::File::FNM_DOTMATCH) - + # Remove current directory and previous directory files.reject! do |name| basename = Pathname.new(name).basename().to_s diff --git a/lib/chef/provider/route.rb b/lib/chef/provider/route.rb index 208a4f4139..72a5029a94 100644 --- a/lib/chef/provider/route.rb +++ b/lib/chef/provider/route.rb @@ -24,6 +24,8 @@ require 'ipaddr' class Chef::Provider::Route < Chef::Provider include Chef::Mixin::Command + provides :route + attr_accessor :is_running MASK = {'0.0.0.0' => '0', diff --git a/lib/chef/provider/ruby_block.rb b/lib/chef/provider/ruby_block.rb index b0d94a3f8d..eb93fd5708 100644 --- a/lib/chef/provider/ruby_block.rb +++ b/lib/chef/provider/ruby_block.rb @@ -20,6 +20,8 @@ class Chef class Provider class RubyBlock < Chef::Provider + provides :ruby_block + def whyrun_supported? true end diff --git a/lib/chef/provider/script.rb b/lib/chef/provider/script.rb index 4aacf4f524..1615517553 100644 --- a/lib/chef/provider/script.rb +++ b/lib/chef/provider/script.rb @@ -22,6 +22,12 @@ require 'chef/provider/execute' class Chef class Provider class Script < Chef::Provider::Execute + provides :bash + provides :csh + provides :perl + provides :python + provides :ruby + provides :script def initialize(new_resource, run_context) super diff --git a/lib/chef/provider/service/aix.rb b/lib/chef/provider/service/aix.rb new file mode 100644 index 0000000000..6f70f797b9 --- /dev/null +++ b/lib/chef/provider/service/aix.rb @@ -0,0 +1,126 @@ +# +# Author:: kaustubh (<kaustubh@clogeny.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 'chef/provider/service' + +class Chef + class Provider + class Service + class Aix < Chef::Provider::Service + attr_reader :status_load_success + + def initialize(new_resource, run_context) + super + end + + def load_current_resource + @current_resource = Chef::Resource::Service.new(@new_resource.name) + @current_resource.service_name(@new_resource.service_name) + + @status_load_success = true + @priority_success = true + @is_resource_group = false + + determine_current_status! + + @current_resource + end + + def whyrun_supported? + true + end + + def start_service + if @is_resource_group + shell_out!("startsrc -g #{@new_resource.service_name}") + else + shell_out!("startsrc -s #{@new_resource.service_name}") + end + end + + def stop_service + if @is_resource_group + shell_out!("stopsrc -g #{@new_resource.service_name}") + else + shell_out!("stopsrc -s #{@new_resource.service_name}") + end + end + + def restart_service + stop_service + start_service + end + + def reload_service + if @is_resource_group + shell_out!("refresh -g #{@new_resource.service_name}") + else + shell_out!("refresh -s #{@new_resource.service_name}") + end + end + + def shared_resource_requirements + super + requirements.assert(:all_actions) do |a| + a.assertion { @status_load_success } + a.whyrun ["Service status not available. Assuming a prior action would have installed the service.", "Assuming status of not running."] + end + end + + def define_resource_requirements + # FIXME? need reload from service.rb + shared_resource_requirements + end + + protected + def determine_current_status! + Chef::Log.debug "#{@new_resource} using lssrc to check the status " + begin + services = shell_out!("lssrc -a | grep -w #{@new_resource.service_name}").stdout.split("\n") + is_resource_group?(services) + + if services.length == 1 && services[0].split(' ').last == "active" + @current_resource.running true + else + @current_resource.running false + end + Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}" + # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed. + # Temporarily catching different types of exceptions here until we get Shellout fixed. + # TODO: Remove the line before one we get the ShellOut fix. + rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError + @status_load_success = false + @current_resource.running false + nil + end + end + + def is_resource_group? (services) + if services.length > 1 + Chef::Log.debug("#{@new_resource.service_name} is a group") + @is_resource_group = true + elsif services[0].split(' ')[1] == @new_resource.service_name + Chef::Log.debug("#{@new_resource.service_name} is a group") + @is_resource_group = true + end + end + end + end + end +end + diff --git a/lib/chef/provider/service/aixinit.rb b/lib/chef/provider/service/aixinit.rb new file mode 100644 index 0000000000..ab4b8e5406 --- /dev/null +++ b/lib/chef/provider/service/aixinit.rb @@ -0,0 +1,117 @@ +# +# Author:: kaustubh (<kaustubh@clogeny.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 'chef/provider/service/init' + +class Chef + class Provider + class Service + class AixInit < Chef::Provider::Service::Init + RC_D_SCRIPT_NAME = /\/etc\/rc.d\/rc2.d\/([SK])(\d\d|)/i + + def initialize(new_resource, run_context) + super + @init_command = "/etc/rc.d/init.d/#{@new_resource.service_name}" + end + + def load_current_resource + super + @priority_success = true + @rcd_status = nil + + set_current_resource_attributes + @current_resource + end + + def action_enable + if @new_resource.priority.nil? + priority_ok = true + else + priority_ok = @current_resource.priority == @new_resource.priority + end + if @current_resource.enabled and priority_ok + Chef::Log.debug("#{@new_resource} already enabled - nothing to do") + else + converge_by("enable service #{@new_resource}") do + enable_service + Chef::Log.info("#{@new_resource} enabled") + end + end + load_new_resource_state + @new_resource.enabled(true) + end + + def enable_service + Dir.glob(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).each { |f| ::File.delete(f)} + + if @new_resource.priority.is_a? Integer + create_symlink(2, 'S', @new_resource.priority) + + elsif @new_resource.priority.is_a? Hash + @new_resource.priority.each do |level,o| + create_symlink(level,(o[0] == :start ? 'S' : 'K'),o[1]) + end + else + create_symlink(2, 'S', '') + end + end + + def disable_service + Dir.glob(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).each { |f| ::File.delete(f) } + + if @new_resource.priority.is_a? Integer + create_symlink(2, 'K',100 - @new_resource.priority) + elsif @new_resource.priority.is_a? Hash + @new_resource.priority.each do |level,o| + create_symlink(level, 'K', 100 - o[1]) if o[0] == :stop + end + else + create_symlink(2, 'K', '') + end + end + + def create_symlink(run_level, status, priority) + ::File.symlink("/etc/rc.d/init.d/#{@new_resource.service_name}", "/etc/rc.d/rc#{run_level}.d/#{status}#{priority}#{@new_resource.service_name}") + end + + def set_current_resource_attributes + # assuming run level 2 for aix + is_enabled = false + files = Dir.glob(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]) + + priority = {} + + files.each do |file| + if (RC_D_SCRIPT_NAME =~ file) + priority[2] = [($1 == "S" ? :start : :stop), ($2.empty? ? '' : $2.to_i)] + if $1 == "S" + is_enabled = true + end + end + end + + if is_enabled && files.length == 1 + priority = priority[2][1] + end + @current_resource.enabled(is_enabled) + @current_resource.priority(priority) + end + end + end + end +end
\ No newline at end of file diff --git a/lib/chef/provider/service/arch.rb b/lib/chef/provider/service/arch.rb index 9be5fb6fe3..888fb3fdf5 100644 --- a/lib/chef/provider/service/arch.rb +++ b/lib/chef/provider/service/arch.rb @@ -20,6 +20,12 @@ require 'chef/provider/service/init' class Chef::Provider::Service::Arch < Chef::Provider::Service::Init + provides :service, platform_family: "arch" + + def self.supports?(resource, action) + ::File.exist?("/etc/rc.d/#{resource.service_name}") + end + def initialize(new_resource, run_context) super @init_command = "/etc/rc.d/#{@new_resource.service_name}" diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb index 1ebef90349..25b1960b26 100644 --- a/lib/chef/provider/service/debian.rb +++ b/lib/chef/provider/service/debian.rb @@ -25,13 +25,19 @@ 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.supports?(resource, action) + Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian) + end + def load_current_resource super @priority_success = true @rcd_status = nil - @current_resource.priority(get_priority) - @current_resource.enabled(service_currently_enabled?(@current_resource.priority)) - @current_resource + current_resource.priority(get_priority) + current_resource.enabled(service_currently_enabled?(current_resource.priority)) + current_resource end def define_resource_requirements @@ -47,7 +53,7 @@ class Chef requirements.assert(:all_actions) do |a| a.assertion { @priority_success } - a.failure_message Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{@current_resource.service_name} failed - #{@rcd_status.inspect}" + a.failure_message Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{current_resource.service_name} failed - #{@rcd_status.inspect}" # This can happen if the service is not yet installed,so we'll fake it. a.whyrun ["Unable to determine priority of service, assuming service would have been correctly installed earlier in the run.", "Assigning temporary priorities to continue.", @@ -59,7 +65,7 @@ class Chef "3"=>[:start, "20"], "4"=>[:start, "20"], "5"=>[:start, "20"]} - @current_resource.priority(temp_priorities) + current_resource.priority(temp_priorities) end end end @@ -67,7 +73,7 @@ class Chef def get_priority priority = {} - @rcd_status = popen4("/usr/sbin/update-rc.d -n -f #{@current_resource.service_name} remove") do |pid, stdin, stdout, stderr| + @rcd_status = popen4("/usr/sbin/update-rc.d -n -f #{current_resource.service_name} remove") do |pid, stdin, stdout, stderr| [stdout, stderr].each do |iop| iop.each_line do |line| @@ -99,7 +105,7 @@ class Chef def service_currently_enabled?(priority) enabled = false priority.each { |runlevel, arguments| - Chef::Log.debug("#{@new_resource} runlevel #{runlevel}, action #{arguments[0]}, priority #{arguments[1]}") + Chef::Log.debug("#{new_resource} runlevel #{runlevel}, action #{arguments[0]}, priority #{arguments[1]}") # if we are in a update-rc.d default startup runlevel && we start in this runlevel if %w[ 1 2 3 4 5 S ].include?(runlevel) && arguments[0] == :start enabled = true @@ -111,63 +117,63 @@ class Chef # Override method from parent to ensure priority is up-to-date def action_enable - if @new_resource.priority.nil? + if new_resource.priority.nil? priority_ok = true else - priority_ok = @current_resource.priority == @new_resource.priority + priority_ok = @current_resource.priority == new_resource.priority end - if @current_resource.enabled and priority_ok - Chef::Log.debug("#{@new_resource} already enabled - nothing to do") + if current_resource.enabled and priority_ok + Chef::Log.debug("#{new_resource} already enabled - nothing to do") else - converge_by("enable service #{@new_resource}") do + converge_by("enable service #{new_resource}") do enable_service - Chef::Log.info("#{@new_resource} enabled") + Chef::Log.info("#{new_resource} enabled") end end load_new_resource_state - @new_resource.enabled(true) + new_resource.enabled(true) end def enable_service - if @new_resource.priority.is_a? Integer - shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") - shell_out!("/usr/sbin/update-rc.d #{@new_resource.service_name} defaults #{@new_resource.priority} #{100 - @new_resource.priority}") - elsif @new_resource.priority.is_a? Hash + if new_resource.priority.is_a? Integer + shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") + shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults #{new_resource.priority} #{100 - new_resource.priority}") + elsif new_resource.priority.is_a? Hash # we call the same command regardless of we're enabling or disabling # users passing a Hash are responsible for setting their own start priorities set_priority else # No priority, go with update-rc.d defaults - shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") - shell_out!("/usr/sbin/update-rc.d #{@new_resource.service_name} defaults") + shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") + shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults") end end def disable_service - if @new_resource.priority.is_a? Integer + if new_resource.priority.is_a? Integer # Stop processes in reverse order of start using '100 - start_priority' - shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") - shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop #{100 - @new_resource.priority} 2 3 4 5 .") - elsif @new_resource.priority.is_a? Hash + shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") + shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} stop #{100 - new_resource.priority} 2 3 4 5 .") + elsif new_resource.priority.is_a? Hash # we call the same command regardless of we're enabling or disabling # users passing a Hash are responsible for setting their own stop priorities set_priority else # no priority, using '100 - 20 (update-rc.d default)' to stop in reverse order of start - shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") - shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop 80 2 3 4 5 .") + shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") + shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} stop 80 2 3 4 5 .") end end def set_priority args = "" - @new_resource.priority.each do |level, o| + new_resource.priority.each do |level, o| action = o[0] priority = o[1] args += "#{action} #{priority} #{level} . " end - shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") - shell_out!("/usr/sbin/update-rc.d #{@new_resource.service_name} #{args}") + shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") + shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} #{args}") end end end diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb index 08d58232e1..e4a456ac25 100644 --- a/lib/chef/provider/service/freebsd.rb +++ b/lib/chef/provider/service/freebsd.rb @@ -27,6 +27,10 @@ class Chef attr_reader :enabled_state_found + provides :service, os: [ "freebsd", "netbsd" ] + + include Chef::Mixin::ShellOut + def initialize(new_resource, run_context) super @enabled_state_found = false diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb index e2dff10994..3dab920f06 100644 --- a/lib/chef/provider/service/gentoo.rb +++ b/lib/chef/provider/service/gentoo.rb @@ -22,6 +22,9 @@ require 'chef/mixin/command' require 'chef/util/path_helper' class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init + + provides :service, platform_family: "gentoo" + def load_current_resource @new_resource.supports[:status] = true diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb index 5d8bb5bb38..ab40a720f6 100644 --- a/lib/chef/provider/service/init.rb +++ b/lib/chef/provider/service/init.rb @@ -26,6 +26,8 @@ class Chef attr_accessor :init_command + provides :service, os: "!windows" + def initialize(new_resource, run_context) super @init_command = "/etc/init.d/#{@new_resource.service_name}" diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb index 1ee817707a..df5a162a45 100644 --- a/lib/chef/provider/service/insserv.rb +++ b/lib/chef/provider/service/insserv.rb @@ -24,26 +24,32 @@ class Chef class Service class Insserv < Chef::Provider::Service::Init + provides :service, os: "linux" + + def self.supports?(resource, action) + Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv) + end + def load_current_resource super - # Look for a /etc/rc.*/SnnSERVICE link to signifiy that the service would be started in a runlevel - if Dir.glob("/etc/rc**/S*#{Chef::Util::PathHelper.escape_glob(@current_resource.service_name)}").empty? - @current_resource.enabled false + # Look for a /etc/rc.*/SnnSERVICE link to signify that the service would be started in a runlevel + if Dir.glob("/etc/rc**/S*#{Chef::Util::PathHelper.escape_glob(current_resource.service_name)}").empty? + current_resource.enabled false else - @current_resource.enabled true + current_resource.enabled true end - @current_resource + current_resource end def enable_service() - shell_out!("/sbin/insserv -r -f #{@new_resource.service_name}") - shell_out!("/sbin/insserv -d -f #{@new_resource.service_name}") + shell_out!("/sbin/insserv -r -f #{new_resource.service_name}") + shell_out!("/sbin/insserv -d -f #{new_resource.service_name}") end def disable_service() - shell_out!("/sbin/insserv -r -f #{@new_resource.service_name}") + shell_out!("/sbin/insserv -r -f #{new_resource.service_name}") end end end diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb index e6afa7272a..c7472211bc 100644 --- a/lib/chef/provider/service/invokercd.rb +++ b/lib/chef/provider/service/invokercd.rb @@ -23,6 +23,12 @@ class Chef class Service class Invokercd < Chef::Provider::Service::Init + provides :service, platform_family: "debian" + + def self.supports?(resource, action) + Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokerc) + end + def initialize(new_resource, run_context) super @init_command = "/usr/sbin/invoke-rc.d #{@new_resource.service_name}" diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb index ad1535327b..10ad1aa29d 100644 --- a/lib/chef/provider/service/macosx.rb +++ b/lib/chef/provider/service/macosx.rb @@ -26,6 +26,8 @@ class Chef class Service class Macosx < Chef::Provider::Service::Simple + provides :service, os: "darwin" + def self.gather_plist_dirs locations = %w{/Library/LaunchAgents /Library/LaunchDaemons diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb index 7a7b2a1c40..90744ae268 100644 --- a/lib/chef/provider/service/redhat.rb +++ b/lib/chef/provider/service/redhat.rb @@ -26,11 +26,17 @@ class Chef CHKCONFIG_ON = /\d:on/ CHKCONFIG_MISSING = /No such/ + provides :service, platform_family: [ "rhel", "fedora", "suse" ] + + def self.supports?(resource, action) + Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat) + end + def initialize(new_resource, run_context) super - @init_command = "/sbin/service #{@new_resource.service_name}" - @new_resource.supports[:status] = true - @service_missing = false + @init_command = "/sbin/service #{@new_resource.service_name}" + @new_resource.supports[:status] = true + @service_missing = false end def define_resource_requirements diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb index bd51d15f84..ee403ee163 100644 --- a/lib/chef/provider/service/simple.rb +++ b/lib/chef/provider/service/simple.rb @@ -25,6 +25,8 @@ class Chef class Service class Simple < Chef::Provider::Service + # this must be subclassed to be useful so does not directly implement :service + attr_reader :status_load_success def load_current_resource diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb index f0584dcf6d..eaea6bb1ab 100644 --- a/lib/chef/provider/service/solaris.rb +++ b/lib/chef/provider/service/solaris.rb @@ -26,6 +26,8 @@ class Chef class Solaris < Chef::Provider::Service attr_reader :maintenance + provides :service, os: "solaris2" + def initialize(new_resource, run_context=nil) super @init_command = "/usr/sbin/svcadm" diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb index 31feee65d4..311751ab9a 100644 --- a/lib/chef/provider/service/systemd.rb +++ b/lib/chef/provider/service/systemd.rb @@ -20,6 +20,13 @@ require 'chef/resource/service' require 'chef/provider/service/simple' class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple + + provides :service, os: "linux" + + def self.supports?(resource, action) + Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd) + end + def load_current_resource @current_resource = Chef::Resource::Service.new(@new_resource.name) @current_resource.service_name(@new_resource.service_name) diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb index 670bf9e5f8..41bd850d6a 100644 --- a/lib/chef/provider/service/upstart.rb +++ b/lib/chef/provider/service/upstart.rb @@ -27,6 +27,13 @@ class Chef class Upstart < Chef::Provider::Service::Simple UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/ + provides :service, os: "linux" + + def self.supports?(resource, action) + Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart) && + Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:upstart) + end + # Upstart does more than start or stop a service, creating multiple 'states' [1] that a service can be in. # In chef, when we ask a service to start, we expect it to have started before performing the next step # since we have top down dependencies. Which is to say we may follow witha resource next that requires diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb index d31aad4c9d..4b1d2079ec 100644 --- a/lib/chef/provider/service/windows.rb +++ b/lib/chef/provider/service/windows.rb @@ -25,6 +25,10 @@ end class Chef::Provider::Service::Windows < Chef::Provider::Service + provides :service, os: "windows" + + include Chef::Mixin::ShellOut + #Win32::Service.get_start_type AUTO_START = 'auto start' MANUAL = 'demand start' diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb index 6cf31c8ec8..f4a0e6fc13 100644 --- a/lib/chef/provider/subversion.rb +++ b/lib/chef/provider/subversion.rb @@ -27,6 +27,8 @@ class Chef class Provider class Subversion < Chef::Provider + provides :subversion + SVN_INFO_PATTERN = /^([\w\s]+): (.+)$/ include Chef::Mixin::Command diff --git a/lib/chef/provider/template.rb b/lib/chef/provider/template.rb index 48cc45f3a8..1e759074b9 100644 --- a/lib/chef/provider/template.rb +++ b/lib/chef/provider/template.rb @@ -25,6 +25,7 @@ require 'chef/deprecation/warnings' class Chef class Provider class Template < Chef::Provider::File + provides :template extend Chef::Deprecation::Warnings include Chef::Deprecation::Provider::Template diff --git a/lib/chef/provider/whyrun_safe_ruby_block.rb b/lib/chef/provider/whyrun_safe_ruby_block.rb index e5f35debd7..3b95752cc4 100644 --- a/lib/chef/provider/whyrun_safe_ruby_block.rb +++ b/lib/chef/provider/whyrun_safe_ruby_block.rb @@ -19,6 +19,8 @@ class Chef class Provider class WhyrunSafeRubyBlock < Chef::Provider::RubyBlock + provides :whyrun_safe_ruby_block + def action_run @new_resource.block.call @new_resource.updated_by_last_action(true) diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb new file mode 100644 index 0000000000..c819b0c87f --- /dev/null +++ b/lib/chef/provider_resolver.rb @@ -0,0 +1,103 @@ +# +# Author:: Richard Manyanza (<liseki@nyikacraftsmen.com>) +# Copyright:: Copyright (c) 2014 Richard Manyanza. +# 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/exceptions' +require 'chef/platform/provider_priority_map' + +class Chef + class ProviderResolver + + attr_reader :node + + def initialize(node) + @node = node + end + + # return a deterministically sorted list of Chef::Provider subclasses + def providers + Chef::Provider.descendants.sort {|a,b| a.to_s <=> b.to_s } + end + + def resolve(resource, action) + maybe_explicit_provider(resource) || + maybe_dynamic_provider_resolution(resource, action) || + maybe_chef_platform_lookup(resource) + end + + private + + # if resource.provider is set, just return one of those objects + def maybe_explicit_provider(resource) + return nil unless resource.provider + resource.provider + end + + # try dynamically finding a provider based on querying the providers to see what they support + def maybe_dynamic_provider_resolution(resource, action) + # this cut only depends on the node value and is going to be static for all nodes + # will contain all providers that could possibly support a resource on a node + enabled_handlers = providers.select do |klass| + klass.provides?(node, resource) + end + + # log this so we know what providers will work for the generic resource on the node (early cut) + Chef::Log.debug "providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}" + + # ask all the enabled providers if they can actually support the resource + supported_handlers = enabled_handlers.select do |klass| + klass.supports?(resource, action) + end + + # what providers were excluded by machine state (late cut) + Chef::Log.debug "providers that refused resource #{resource} were: #{enabled_handlers - supported_handlers}" + Chef::Log.debug "providers that support resource #{resource} include: #{supported_handlers}" + + handlers = supported_handlers.empty? ? enabled_handlers : supported_handlers + + if handlers.count >= 2 + priority_list = [ get_provider_priority_map(resource.resource_name, node) ].flatten.compact + + handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i } + + handlers = [ handlers.first ] + end + + Chef::Log.debug "providers that survived replacement include: #{handlers}" + + raise Chef::Exceptions::AmbiguousProviderResolution.new(resource, handlers) if handlers.count >= 2 + + return nil if handlers.empty? + + handlers[0] + end + + # try the old static lookup of providers by platform + def maybe_chef_platform_lookup(resource) + Chef::Platform.find_provider_for_node(node, resource) + end + + # dep injection hooks + def get_provider_priority_map(resource_name, node) + provider_priority_map.get(node, resource_name) + end + + def provider_priority_map + Chef::Platform::ProviderPriorityMap.instance.priority_map + end + end +end diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index fec00d0e63..c4f1ce769d 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -75,19 +75,21 @@ require 'chef/provider/package/smartos' require 'chef/provider/package/aix' require 'chef/provider/service/arch' -require 'chef/provider/service/debian' require 'chef/provider/service/freebsd' require 'chef/provider/service/gentoo' require 'chef/provider/service/init' -require 'chef/provider/service/insserv' require 'chef/provider/service/invokercd' +require 'chef/provider/service/debian' require 'chef/provider/service/redhat' +require 'chef/provider/service/insserv' require 'chef/provider/service/simple' require 'chef/provider/service/systemd' require 'chef/provider/service/upstart' require 'chef/provider/service/windows' require 'chef/provider/service/solaris' require 'chef/provider/service/macosx' +require 'chef/provider/service/aixinit' +require 'chef/provider/service/aix' require 'chef/provider/user/dscl' require 'chef/provider/user/pw' diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb index 32578da5ab..de72a8d0c4 100644 --- a/lib/chef/recipe.rb +++ b/lib/chef/recipe.rb @@ -96,6 +96,8 @@ class Chef # true<TrueClass>:: If all the parameters are present # false<FalseClass>:: If any of the parameters are missing def tagged?(*tags) + return false if run_context.node[:tags].nil? + tags.each do |tag| return false unless run_context.node[:tags].include?(tag) end diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 70abfbcdb0..c38f36aa72 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -27,11 +27,12 @@ require 'chef/guard_interpreter/resource_guard_interpreter' require 'chef/resource/conditional' require 'chef/resource/conditional_action_not_nothing' require 'chef/resource_collection' -require 'chef/resource_platform_map' +require 'chef/node_map' require 'chef/node' require 'chef/platform' require 'chef/mixin/deprecation' +require 'chef/mixin/descendants_tracker' class Chef class Resource @@ -133,6 +134,7 @@ F include Chef::Mixin::Deprecation extend Chef::Mixin::ConvertToClassName + extend Chef::Mixin::DescendantsTracker if Module.method(:const_defined?).arity == 1 def self.strict_const_defined?(const) @@ -144,24 +146,13 @@ F end end - # Track all subclasses of Resource. This is used so names can be looked up - # when attempting to deserialize from JSON. (See: json_compat) - def self.resource_classes - # Using a class variable here ensures we have one variable to track - # subclasses shared by the entire class hierarchy; without this, each - # subclass would have its own list of subclasses. - @@resource_classes ||= [] - end - - # Callback when subclass is defined. Adds subclass to list of subclasses. - def self.inherited(subclass) - resource_classes << subclass - end - - # Look up a subclass by +class_name+ which should be a string that matches - # `Subclass.name` - def self.find_subclass_by_name(class_name) - resource_classes.first {|c| c.name == class_name } + class << self + # back-compat + # NOTE: that we do not support unregistering classes as descendents like + # we used to for LWRP unloading because that was horrible and removed in + # Chef-12. + alias :resource_classes :descendants + alias :find_subclass_by_name :find_descendants_by_name end # Set or return the list of "state attributes" implemented by the Resource @@ -229,6 +220,8 @@ F attr_reader :elapsed_time + attr_reader :default_guard_interpreter + # Each notify entry is a resource/action pair, modeled as an # Struct with a #resource and #action member @@ -250,7 +243,13 @@ F @not_if = [] @only_if = [] @source_line = nil - @guard_interpreter = :default + # We would like to raise an error when the user gives us a guard + # interpreter and a ruby_block to the guard. In order to achieve this + # we need to understand when the user overrides the default guard + # interpreter. Therefore we store the default separately in a different + # attribute. + @guard_interpreter = nil + @default_guard_interpreter = :default @elapsed_time = 0 @sensitive = false end @@ -297,12 +296,13 @@ F end end - def load_prior_resource + def load_prior_resource(resource_type, instance_name) begin - prior_resource = run_context.resource_collection.lookup(self.to_s) + key = ::Chef::ResourceCollection::ResourceSet.create_key(resource_type, instance_name) + prior_resource = run_context.resource_collection.lookup(key) # if we get here, there is a prior resource (otherwise we'd have jumped # to the rescue clause). - Chef::Log.warn("Cloning resource attributes for #{self.to_s} from prior resource (CHEF-3694)") + Chef::Log.warn("Cloning resource attributes for #{key} from prior resource (CHEF-3694)") Chef::Log.warn("Previous #{prior_resource}: #{prior_resource.source_line}") if prior_resource.source_line Chef::Log.warn("Current #{self}: #{self.source_line}") if self.source_line prior_resource.instance_variables.each do |iv| @@ -410,11 +410,15 @@ F end def guard_interpreter(arg=nil) - set_or_return( - :guard_interpreter, - arg, - :kind_of => Symbol - ) + if arg.nil? + @guard_interpreter || @default_guard_interpreter + else + set_or_return( + :guard_interpreter, + arg, + :kind_of => Symbol + ) + end end # Sets up a notification from this resource to the resource specified by +resource_spec+. @@ -639,6 +643,11 @@ F # on accident updated_by_last_action(false) + # Don't modify @retries directly and keep it intact, so that the + # recipe_snippet from ResourceFailureInspector can print the value + # that was set in the resource block initially. + remaining_retries = retries + begin return if should_skip?(action) provider_for_action(action).run_action @@ -646,10 +655,10 @@ F if ignore_failure Chef::Log.error("#{custom_exception_message(e)}; ignore_failure is set, continuing") events.resource_failed(self, action, e) - elsif retries > 0 - events.resource_failed_retriable(self, action, retries, e) - @retries -= 1 - Chef::Log.info("Retrying execution of #{self}, #{retries} attempt(s) left") + elsif remaining_retries > 0 + events.resource_failed_retriable(self, action, remaining_retries, e) + remaining_retries -= 1 + Chef::Log.info("Retrying execution of #{self}, #{remaining_retries} attempt(s) left") sleep retry_delay retry else @@ -670,16 +679,9 @@ F end def provider_for_action(action) - # leverage new platform => short_name => resource - # which requires explicitly setting provider in - # resource class - if self.provider - provider = self.provider.new(self, self.run_context) - provider.action = action - provider - else # fall back to old provider resolution - Chef::Platform.provider_for_resource(self, action) - end + provider = run_context.provider_resolver.resolve(self, action).new(self, run_context) + provider.action = action + provider end def custom_exception_message(e) @@ -754,8 +756,8 @@ F @provider_base ||= Chef::Provider end - def self.platform_map - @@platform_map ||= PlatformMap.new + def self.node_map + @@node_map ||= NodeMap.new end # Maps a short_name (and optionally a platform and version) to a @@ -764,66 +766,55 @@ F # (I'm looking at you Chef::Resource::Package) # Ex: # class WindowsFile < Chef::Resource - # provides :file, :on_platforms => ["windows"] + # provides :file, os: "linux", platform_family: "rhel", platform: "redhat" + # provides :file, os: "!windows + # provides :file, os: [ "linux", "aix" ] + # provides :file, os: "solaris2" do |node| + # node['platform_version'].to_f <= 5.11 + # end # # ...other stuff # end # - # TODO: 2011-11-02 schisamo - platform_version support - def self.provides(short_name, opts={}) + def self.provides(short_name, opts={}, &block) short_name_sym = short_name if short_name.kind_of?(String) + # YAGNI: this is probably completely unnecessary and can be removed? + Chef::Log.warn "[DEPRECATION] Passing a String to Chef::Resource#provides will be removed" short_name.downcase! short_name.gsub!(/\s/, "_") short_name_sym = short_name.to_sym end - if opts.has_key?(:on_platforms) - platforms = [opts[:on_platforms]].flatten - platforms.each do |p| - p = :default if :all == p.to_sym - platform_map.set( - :platform => p.to_sym, - :short_name => short_name_sym, - :resource => self - ) - end - else - platform_map.set( - :short_name => short_name_sym, - :resource => self - ) - end + node_map.set(short_name_sym, constantize(self.name), opts, &block) end - # Returns a resource based on a short_name anda platform and version. - # + # Returns a resource based on a short_name and node # # ==== Parameters # short_name<Symbol>:: short_name of the resource (ie :directory) - # platform<Symbol,String>:: platform name - # version<String>:: platform version + # node<Chef::Node>:: Node object to look up platform and version in # # === Returns # <Chef::Resource>:: returns the proper Chef::Resource class - def self.resource_for_platform(short_name, platform=nil, version=nil) - platform_map.get(short_name, platform, version) + def self.resource_for_node(short_name, node) + klass = node_map.get(node, short_name) || + resource_matching_short_name(short_name) + raise Chef::Exceptions::NoSuchResourceType.new(short_name, node) if klass.nil? + klass end - # Returns a resource based on a short_name and a node's - # platform and version. - # + # Returns the class of a Chef::Resource based on the short name # ==== Parameters # short_name<Symbol>:: short_name of the resource (ie :directory) - # node<Chef::Node>:: Node object to look up platform and version in # # === Returns # <Chef::Resource>:: returns the proper Chef::Resource class - def self.resource_for_node(short_name, node) + def self.resource_matching_short_name(short_name) begin - platform, version = Chef::Platform.find_platform_and_version(node) - rescue ArgumentError + rname = convert_to_class_name(short_name.to_s) + Chef::Resource.const_get(rname) + rescue NameError + nil end - resource = resource_for_platform(short_name, platform, version) - resource end private diff --git a/lib/chef/resource/apt_package.rb b/lib/chef/resource/apt_package.rb index 050cf838ae..f944825ac3 100644 --- a/lib/chef/resource/apt_package.rb +++ b/lib/chef/resource/apt_package.rb @@ -23,10 +23,12 @@ class Chef class Resource class AptPackage < Chef::Resource::Package + provides :apt_package + provides :package, os: "linux", platform_family: [ "debian" ] + def initialize(name, run_context=nil) super @resource_name = :apt_package - @provider = Chef::Provider::Package::Apt @default_release = nil end diff --git a/lib/chef/resource/bash.rb b/lib/chef/resource/bash.rb index c56de5fe20..0add0ce501 100644 --- a/lib/chef/resource/bash.rb +++ b/lib/chef/resource/bash.rb @@ -17,6 +17,7 @@ # require 'chef/resource/script' +require 'chef/provider/script' class Chef class Resource diff --git a/lib/chef/resource/bff_package.rb b/lib/chef/resource/bff_package.rb index 2d78483e4b..917f0d1d50 100644 --- a/lib/chef/resource/bff_package.rb +++ b/lib/chef/resource/bff_package.rb @@ -26,7 +26,6 @@ class Chef def initialize(name, run_context=nil) super @resource_name = :bff_package - @provider = Chef::Provider::Package::Aix end end diff --git a/lib/chef/resource/breakpoint.rb b/lib/chef/resource/breakpoint.rb index 83c397bd5b..b2210262d2 100644 --- a/lib/chef/resource/breakpoint.rb +++ b/lib/chef/resource/breakpoint.rb @@ -28,7 +28,7 @@ class Chef super(@name, *args) @action = "break" @allowed_actions << :break - @provider = Chef::Provider::Breakpoint + @resource_name = :breakpoint end end end diff --git a/lib/chef/resource/chef_gem.rb b/lib/chef/resource/chef_gem.rb index af45cb93e6..b4ead11f1d 100644 --- a/lib/chef/resource/chef_gem.rb +++ b/lib/chef/resource/chef_gem.rb @@ -23,13 +23,12 @@ class Chef class Resource class ChefGem < Chef::Resource::Package::GemPackage - provides :chef_gem, :on_platforms => :all + provides :chef_gem def initialize(name, run_context=nil) super @resource_name = :chef_gem @gem_binary = RbConfig::CONFIG['bindir'] + "/gem" - @provider = Chef::Provider::Package::Rubygems end # The chef_gem resources is for installing gems to the current gem environment only for use by Chef cookbooks. diff --git a/lib/chef/resource/conditional.rb b/lib/chef/resource/conditional.rb index 324c5a4676..8960a4d57f 100644 --- a/lib/chef/resource/conditional.rb +++ b/lib/chef/resource/conditional.rb @@ -59,8 +59,10 @@ class Chef @guard_interpreter = new_guard_interpreter(@parent_resource, @command, @command_opts, &@block) @block = nil when nil - # we should have a block if we get here - if @parent_resource.guard_interpreter != :default + # We should have a block if we get here + # Check to see if the user set the guard_interpreter on the parent resource. Note that + # this error will not be raised when using the default_guard_interpreter + if @parent_resource.guard_interpreter != @parent_resource.default_guard_interpreter msg = "#{@parent_resource.name} was given a guard_interpreter of #{@parent_resource.guard_interpreter}, " msg << "but not given a command as a string. guard_interpreter does not support blocks (because they just contain ruby)." raise ArgumentError, msg diff --git a/lib/chef/resource/cookbook_file.rb b/lib/chef/resource/cookbook_file.rb index de758aef71..7be353b648 100644 --- a/lib/chef/resource/cookbook_file.rb +++ b/lib/chef/resource/cookbook_file.rb @@ -27,7 +27,7 @@ class Chef class CookbookFile < Chef::Resource::File include Chef::Mixin::Securable - provides :cookbook_file, :on_platforms => :all + provides :cookbook_file def initialize(name, run_context=nil) super @@ -36,11 +36,10 @@ class Chef @action = "create" @source = ::File.basename(name) @cookbook = nil - @provider = Chef::Provider::CookbookFile end def source(source_filename=nil) - set_or_return(:source, source_filename, :kind_of => String) + set_or_return(:source, source_filename, :kind_of => [ String, Array ]) end def cookbook(cookbook_name=nil) diff --git a/lib/chef/resource/csh.rb b/lib/chef/resource/csh.rb index 95aa8afd7a..36659c349b 100644 --- a/lib/chef/resource/csh.rb +++ b/lib/chef/resource/csh.rb @@ -17,6 +17,7 @@ # require 'chef/resource/script' +require 'chef/provider/script' class Chef class Resource diff --git a/lib/chef/resource/deploy.rb b/lib/chef/resource/deploy.rb index a08e8aeeea..4252aa230f 100644 --- a/lib/chef/resource/deploy.rb +++ b/lib/chef/resource/deploy.rb @@ -77,7 +77,6 @@ class Chef @shallow_clone = false @scm_provider = Chef::Provider::Git @svn_force_export = false - @provider = Chef::Provider::Deploy::Timestamped @allowed_actions.push(:force_deploy, :deploy, :rollback) @additional_remotes = Hash[] @keep_releases = 5 diff --git a/lib/chef/resource/deploy_revision.rb b/lib/chef/resource/deploy_revision.rb index ceac26e91a..e144ce2162 100644 --- a/lib/chef/resource/deploy_revision.rb +++ b/lib/chef/resource/deploy_revision.rb @@ -22,14 +22,19 @@ class Chef # Convenience class for using the deploy resource with the revision # deployment strategy (provider) class DeployRevision < Chef::Resource::Deploy + + provides :deploy_revision + def initialize(*args, &block) super @resource_name = :deploy_revision - @provider = Chef::Provider::Deploy::Revision end end class DeployBranch < Chef::Resource::DeployRevision + + provides :deploy_branch + def initialize(*args, &block) super @resource_name = :deploy_branch diff --git a/lib/chef/resource/directory.rb b/lib/chef/resource/directory.rb index 423c0bbe27..1ab7f0d16d 100644 --- a/lib/chef/resource/directory.rb +++ b/lib/chef/resource/directory.rb @@ -32,7 +32,7 @@ class Chef include Chef::Mixin::Securable - provides :directory, :on_platforms => :all + provides :directory def initialize(name, run_context=nil) super @@ -41,7 +41,6 @@ class Chef @action = :create @recursive = false @allowed_actions.push(:create, :delete) - @provider = Chef::Provider::Directory end def recursive(arg=nil) diff --git a/lib/chef/resource/dpkg_package.rb b/lib/chef/resource/dpkg_package.rb index 2fb5b5c249..35a47e8a82 100644 --- a/lib/chef/resource/dpkg_package.rb +++ b/lib/chef/resource/dpkg_package.rb @@ -23,10 +23,11 @@ class Chef class Resource class DpkgPackage < Chef::Resource::Package + provides :dpkg_package, os: "linux" + def initialize(name, run_context=nil) super @resource_name = :dpkg_package - @provider = Chef::Provider::Package::Dpkg end end diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb index 76ac6659d6..82907c25db 100644 --- a/lib/chef/resource/dsc_script.rb +++ b/lib/chef/resource/dsc_script.rb @@ -22,13 +22,12 @@ class Chef class Resource class DscScript < Chef::Resource - provides :dsc_script, :on_platforms => ["windows"] + provides :dsc_script, platform: "windows" def initialize(name, run_context=nil) super @allowed_actions.push(:run) @action = :run - @provider = Chef::Provider::DscScript @resource_name = :dsc_script end diff --git a/lib/chef/resource/easy_install_package.rb b/lib/chef/resource/easy_install_package.rb index f25e1ac22f..5286e9a289 100644 --- a/lib/chef/resource/easy_install_package.rb +++ b/lib/chef/resource/easy_install_package.rb @@ -22,10 +22,11 @@ class Chef class Resource class EasyInstallPackage < Chef::Resource::Package + provides :easy_install_package + def initialize(name, run_context=nil) super @resource_name = :easy_install_package - @provider = Chef::Provider::Package::EasyInstall end def easy_install_binary(arg=nil) diff --git a/lib/chef/resource/erl_call.rb b/lib/chef/resource/erl_call.rb index 959856af66..24009d51c7 100644 --- a/lib/chef/resource/erl_call.rb +++ b/lib/chef/resource/erl_call.rb @@ -18,6 +18,7 @@ # require 'chef/resource' +require 'chef/provider/erl_call' class Chef class Resource diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb index 7c4fa48c0a..980035b079 100644 --- a/lib/chef/resource/execute.rb +++ b/lib/chef/resource/execute.rb @@ -18,6 +18,7 @@ # require 'chef/resource' +require 'chef/provider/execute' class Chef class Resource @@ -35,12 +36,12 @@ class Chef @cwd = nil @environment = nil @group = nil - @path = nil @returns = 0 @timeout = nil @user = nil @allowed_actions.push(:run) @umask = nil + @default_guard_interpreter = :execute end def umask(arg=nil) @@ -93,14 +94,6 @@ class Chef ) end - def path(arg=nil) - set_or_return( - :path, - arg, - :kind_of => [ Array ] - ) - end - def returns(arg=nil) set_or_return( :returns, @@ -125,6 +118,30 @@ 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, + :user, + :umask + ) + end end end diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb index 3c1f4785ef..16491f9bc8 100644 --- a/lib/chef/resource/file.rb +++ b/lib/chef/resource/file.rb @@ -19,7 +19,6 @@ require 'chef/resource' require 'chef/platform/query_helpers' -require 'chef/provider/file' require 'chef/mixin/securable' class Chef @@ -38,7 +37,7 @@ class Chef attr_writer :checksum - provides :file, :on_platforms => :all + provides :file def initialize(name, run_context=nil) super @@ -47,7 +46,6 @@ class Chef @backup = 5 @action = "create" @allowed_actions.push(:create, :delete, :touch, :create_if_missing) - @provider = Chef::Provider::File @atomic_update = Chef::Config[:file_atomic_update] @force_unlink = false @manage_symlink_source = nil diff --git a/lib/chef/resource/freebsd_package.rb b/lib/chef/resource/freebsd_package.rb index 70ef62ae8a..9c8db506f8 100644 --- a/lib/chef/resource/freebsd_package.rb +++ b/lib/chef/resource/freebsd_package.rb @@ -29,51 +29,38 @@ class Chef class FreebsdPackage < Chef::Resource::Package include Chef::Mixin::ShellOut - provides :package, :on_platforms => ["freebsd"] - - attr_accessor :created_as_type + provides :package, platform: "freebsd" def initialize(name, run_context=nil) super @resource_name = :freebsd_package - @created_as_type = "freebsd_package" end def after_created assign_provider end - # This resource can be invoked with multiple names package & freebsd_package. - # We override the to_s method to ensure the key in resource collection - # matches the type resource is declared as using created_as_type. This - # logic can be removed once Chef does this for all resource in Chef 12: - # https://github.com/opscode/chef/issues/1817 - def to_s - "#{created_as_type}[#{name}]" + def supports_pkgng? + ships_with_pkgng? || !!shell_out!("make -V WITH_PKGNG", :env => nil).stdout.match(/yes/i) end private + def ships_with_pkgng? + # It was not until __FreeBSD_version 1000017 that pkgng became + # the default binary package manager. See '/usr/ports/Mk/bsd.port.mk'. + node.automatic[:os_version].to_i >= 1000017 + end + def assign_provider @provider = if @source.to_s =~ /^ports$/i Chef::Provider::Package::Freebsd::Port - elsif ships_with_pkgng? || supports_pkgng? + elsif supports_pkgng? Chef::Provider::Package::Freebsd::Pkgng else Chef::Provider::Package::Freebsd::Pkg end end - - def ships_with_pkgng? - # It was not until __FreeBSD_version 1000017 that pkgng became - # the default binary package manager. See '/usr/ports/Mk/bsd.port.mk'. - node[:os_version].to_i >= 1000017 - end - - def supports_pkgng? - !!shell_out!("make -V WITH_PKGNG", :env => nil).stdout.match(/yes/i) - end - end end end diff --git a/lib/chef/resource/gem_package.rb b/lib/chef/resource/gem_package.rb index 6def7b6653..631aa13f56 100644 --- a/lib/chef/resource/gem_package.rb +++ b/lib/chef/resource/gem_package.rb @@ -22,10 +22,11 @@ class Chef class Resource class GemPackage < Chef::Resource::Package + provides :gem_package + def initialize(name, run_context=nil) super @resource_name = :gem_package - @provider = Chef::Provider::Package::Rubygems end def source(arg=nil) diff --git a/lib/chef/resource/git.rb b/lib/chef/resource/git.rb index 774bb24f24..7156873315 100644 --- a/lib/chef/resource/git.rb +++ b/lib/chef/resource/git.rb @@ -22,10 +22,11 @@ class Chef class Resource class Git < Chef::Resource::Scm + provides :git + def initialize(name, run_context=nil) super @resource_name = :git - @provider = Chef::Provider::Git @additional_remotes = Hash[] end diff --git a/lib/chef/resource/homebrew_package.rb b/lib/chef/resource/homebrew_package.rb index e1d50c1739..952552e3a8 100644 --- a/lib/chef/resource/homebrew_package.rb +++ b/lib/chef/resource/homebrew_package.rb @@ -25,10 +25,11 @@ class Chef class Resource class HomebrewPackage < Chef::Resource::Package + provides :homebrew_package, os: "mac_os_x" + def initialize(name, run_context=nil) super @resource_name = :homebrew_package - @provider = Chef::Provider::Package::Homebrew @homebrew_user = nil end diff --git a/lib/chef/resource/http_request.rb b/lib/chef/resource/http_request.rb index 47f6286fb4..ccb0a26629 100644 --- a/lib/chef/resource/http_request.rb +++ b/lib/chef/resource/http_request.rb @@ -18,6 +18,7 @@ # require 'chef/resource' +require 'chef/provider/http_request' class Chef class Resource diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb index 88c6e9a538..77b3387946 100644 --- a/lib/chef/resource/ips_package.rb +++ b/lib/chef/resource/ips_package.rb @@ -22,10 +22,12 @@ require 'chef/provider/package/ips' class Chef class Resource class IpsPackage < ::Chef::Resource::Package + + provides :ips_package, os: "solaris2" + def initialize(name, run_context = nil) super(name, run_context) @resource_name = :ips_package - @provider = Chef::Provider::Package::Ips @allowed_actions = [ :install, :remove, :upgrade ] @accept_license = false end diff --git a/lib/chef/resource/link.rb b/lib/chef/resource/link.rb index e53b386a74..8726eded1d 100644 --- a/lib/chef/resource/link.rb +++ b/lib/chef/resource/link.rb @@ -25,7 +25,7 @@ class Chef class Link < Chef::Resource include Chef::Mixin::Securable - provides :link, :on_platform => :all + provides :link identity_attr :target_file @@ -40,7 +40,6 @@ class Chef @link_type = :symbolic @target_file = name @allowed_actions.push(:create, :delete) - @provider = Chef::Provider::Link end def to(arg=nil) diff --git a/lib/chef/resource/log.rb b/lib/chef/resource/log.rb index 391c3b5393..7f970a87a4 100644 --- a/lib/chef/resource/log.rb +++ b/lib/chef/resource/log.rb @@ -16,6 +16,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # + +require 'chef/resource' +require 'chef/provider/log' + class Chef class Resource class Log < Chef::Resource diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb index 5b67941a8b..a4606be842 100644 --- a/lib/chef/resource/lwrp_base.rb +++ b/lib/chef/resource/lwrp_base.rb @@ -35,26 +35,24 @@ class Chef # Evaluates the LWRP resource file and instantiates a new Resource class. def self.build_from_file(cookbook_name, filename, run_context) + resource_class = nil rname = filename_to_qualified_string(cookbook_name, filename) - # Add log entry if we override an existing lightweight resource. class_name = convert_to_class_name(rname) if Resource.strict_const_defined?(class_name) - old_class = Resource.send(:remove_const, class_name) - # CHEF-3432 -- Chef::Resource keeps a list of subclasses; need to - # remove old ones from the list when replacing. - resource_classes.delete(old_class) - Chef::Log.info("#{class_name} lightweight resource already initialized -- overriding!") - end - - resource_class = Class.new(self) + Chef::Log.info("#{class_name} light-weight resource is already initialized -- Skipping loading #{filename}!") + Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.") + resource_class = Resource.const_get(class_name) + else + resource_class = Class.new(self) - resource_class.resource_name = rname - resource_class.run_context = run_context - resource_class.class_from_file(filename) + resource_class.resource_name = rname + resource_class.run_context = run_context + resource_class.class_from_file(filename) - Chef::Resource.const_set(class_name, resource_class) - Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}") + Chef::Resource.const_set(class_name, resource_class) + Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}") + end resource_class end @@ -112,10 +110,21 @@ class Chef if action_names.empty? defined?(@actions) ? @actions : from_superclass(:actions, []).dup else - @actions = action_names + # BC-compat way for checking if actions have already been defined + if defined?(@actions) + @actions.push(*action_names) + else + @actions = action_names + end end end + # @deprecated + def self.valid_actions(*args) + Chef::Log.warn("`valid_actions' is deprecated, please use actions `instead'!") + actions(*args) + end + # Set the run context on the class. Used to provide access to the node # during class definition. def self.run_context=(run_context) diff --git a/lib/chef/resource/macports_package.rb b/lib/chef/resource/macports_package.rb index c9434c9e69..bdc8698155 100644 --- a/lib/chef/resource/macports_package.rb +++ b/lib/chef/resource/macports_package.rb @@ -19,10 +19,12 @@ class Chef class Resource class MacportsPackage < Chef::Resource::Package + + provides :macports_package, os: "mac_os_x" + def initialize(name, run_context=nil) super @resource_name = :macports_package - @provider = Chef::Provider::Package::Macports end end end diff --git a/lib/chef/resource/pacman_package.rb b/lib/chef/resource/pacman_package.rb index 2894e415ac..4c45dd004f 100644 --- a/lib/chef/resource/pacman_package.rb +++ b/lib/chef/resource/pacman_package.rb @@ -22,10 +22,11 @@ class Chef class Resource class PacmanPackage < Chef::Resource::Package + provides :pacman_package, os: "linux" + def initialize(name, run_context=nil) super @resource_name = :pacman_package - @provider = Chef::Provider::Package::Pacman end end diff --git a/lib/chef/resource/paludis_package.rb b/lib/chef/resource/paludis_package.rb index fde25e69b3..7eddf8690b 100644 --- a/lib/chef/resource/paludis_package.rb +++ b/lib/chef/resource/paludis_package.rb @@ -22,10 +22,12 @@ require 'chef/provider/package/paludis' class Chef class Resource class PaludisPackage < Chef::Resource::Package + + provides :paludis_package, os: "linux" + def initialize(name, run_context=nil) super(name, run_context) @resource_name = :paludis_package - @provider = Chef::Provider::Package::Paludis @allowed_actions = [ :install, :remove, :upgrade ] @timeout = 3600 end diff --git a/lib/chef/resource/perl.rb b/lib/chef/resource/perl.rb index 546f639e1f..c4bdb6e130 100644 --- a/lib/chef/resource/perl.rb +++ b/lib/chef/resource/perl.rb @@ -17,6 +17,7 @@ # require 'chef/resource/script' +require 'chef/provider/script' class Chef class Resource diff --git a/lib/chef/resource/powershell_script.rb b/lib/chef/resource/powershell_script.rb index 1b47e7411a..a88fb5701b 100644 --- a/lib/chef/resource/powershell_script.rb +++ b/lib/chef/resource/powershell_script.rb @@ -21,8 +21,6 @@ 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 diff --git a/lib/chef/resource/python.rb b/lib/chef/resource/python.rb index f340afdb39..b1f23d13ce 100644 --- a/lib/chef/resource/python.rb +++ b/lib/chef/resource/python.rb @@ -1,4 +1,3 @@ -# # Author:: Adam Jacob (<adam@opscode.com>) # Copyright:: Copyright (c) 2008 Opscode, Inc. # License:: Apache License, Version 2.0 @@ -17,6 +16,7 @@ # require 'chef/resource/script' +require 'chef/provider/script' class Chef class Resource diff --git a/lib/chef/resource/remote_directory.rb b/lib/chef/resource/remote_directory.rb index 0f7e0eb5de..d4108da47a 100644 --- a/lib/chef/resource/remote_directory.rb +++ b/lib/chef/resource/remote_directory.rb @@ -26,7 +26,7 @@ class Chef class RemoteDirectory < Chef::Resource::Directory include Chef::Mixin::Securable - provides :remote_directory, :on_platforms => :all + provides :remote_directory identity_attr :path @@ -48,7 +48,6 @@ class Chef @overwrite = true @allowed_actions.push(:create, :create_if_missing, :delete) @cookbook = nil - @provider = Chef::Provider::RemoteDirectory end if Chef::Platform.windows? diff --git a/lib/chef/resource/remote_file.rb b/lib/chef/resource/remote_file.rb index 6334b1bf44..46516fd3fb 100644 --- a/lib/chef/resource/remote_file.rb +++ b/lib/chef/resource/remote_file.rb @@ -26,7 +26,7 @@ class Chef class RemoteFile < Chef::Resource::File include Chef::Mixin::Securable - provides :remote_file, :on_platforms => :all + provides :remote_file def initialize(name, run_context=nil) super diff --git a/lib/chef/resource/rpm_package.rb b/lib/chef/resource/rpm_package.rb index 200a9633ce..8634f1e25d 100644 --- a/lib/chef/resource/rpm_package.rb +++ b/lib/chef/resource/rpm_package.rb @@ -23,10 +23,11 @@ class Chef class Resource class RpmPackage < Chef::Resource::Package + provides :rpm_package, os: [ "linux", "aix" ] + def initialize(name, run_context=nil) super @resource_name = :rpm_package - @provider = Chef::Provider::Package::Rpm end end diff --git a/lib/chef/resource/ruby.rb b/lib/chef/resource/ruby.rb index 605d27b00d..2b2aa0249d 100644 --- a/lib/chef/resource/ruby.rb +++ b/lib/chef/resource/ruby.rb @@ -17,6 +17,7 @@ # require 'chef/resource/script' +require 'chef/provider/script' class Chef class Resource diff --git a/lib/chef/resource/ruby_block.rb b/lib/chef/resource/ruby_block.rb index d9b8954a90..a9cbf234cf 100644 --- a/lib/chef/resource/ruby_block.rb +++ b/lib/chef/resource/ruby_block.rb @@ -17,6 +17,9 @@ # limitations under the License. # +require 'chef/resource' +require 'chef/provider/ruby_block' + class Chef class Resource class RubyBlock < Chef::Resource diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb index 6f66fb9094..479295922c 100644 --- a/lib/chef/resource/script.rb +++ b/lib/chef/resource/script.rb @@ -18,6 +18,7 @@ # require 'chef/resource/execute' +require 'chef/provider/script' class Chef class Resource @@ -32,6 +33,7 @@ class Chef @code = nil @interpreter = nil @flags = nil + @default_guard_interpreter = :default end def code(arg=nil) @@ -58,31 +60,6 @@ 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/service.rb b/lib/chef/resource/service.rb index 4d64c3e3f4..36df7c859a 100644 --- a/lib/chef/resource/service.rb +++ b/lib/chef/resource/service.rb @@ -46,10 +46,6 @@ class Chef @action = "nothing" @supports = { :restart => false, :reload => false, :status => false } @allowed_actions.push(:enable, :disable, :start, :stop, :restart, :reload) - - if(run_context && run_context.node[:init_package] == "systemd") - @provider = Chef::Provider::Service::Systemd - end end def service_name(arg=nil) diff --git a/lib/chef/resource/smartos_package.rb b/lib/chef/resource/smartos_package.rb index 0f4f6d8b0a..99b3b953b0 100644 --- a/lib/chef/resource/smartos_package.rb +++ b/lib/chef/resource/smartos_package.rb @@ -23,16 +23,15 @@ class Chef class Resource class SmartosPackage < Chef::Resource::Package + provides :smartos_package + provides :package, os: "solaris2", platform_family: "smartos" + def initialize(name, run_context=nil) super @resource_name = :smartos_package - @provider = Chef::Provider::Package::SmartOS end end end end -# Backwards compatability -# @todo remove in Chef 12 -Chef::Resource::SmartOSPackage = Chef::Resource::SmartosPackage diff --git a/lib/chef/resource/solaris_package.rb b/lib/chef/resource/solaris_package.rb index 3513703076..94be4693b6 100644 --- a/lib/chef/resource/solaris_package.rb +++ b/lib/chef/resource/solaris_package.rb @@ -24,10 +24,16 @@ class Chef class Resource class SolarisPackage < Chef::Resource::Package + provides :solaris_package + provides :package, os: "solaris2", platform_family: "nexentacore" + provides :package, os: "solaris2", platform_family: "solaris2" do |node| + # on >= Solaris 11 we default to IPS packages instead + node[:platform_version].to_f <= 5.10 + end + def initialize(name, run_context=nil) super @resource_name = :solaris_package - @provider = Chef::Provider::Package::Solaris end end diff --git a/lib/chef/resource/subversion.rb b/lib/chef/resource/subversion.rb index 44158cb080..3afbe0baaf 100644 --- a/lib/chef/resource/subversion.rb +++ b/lib/chef/resource/subversion.rb @@ -28,7 +28,6 @@ class Chef @svn_arguments = '--no-auth-cache' @svn_info_args = '--no-auth-cache' @resource_name = :subversion - @provider = Chef::Provider::Subversion allowed_actions << :force_export end diff --git a/lib/chef/resource/template.rb b/lib/chef/resource/template.rb index 9cba6f1c38..8c9607ee07 100644 --- a/lib/chef/resource/template.rb +++ b/lib/chef/resource/template.rb @@ -27,7 +27,7 @@ class Chef class Template < Chef::Resource::File include Chef::Mixin::Securable - provides :template, :on_platforms => :all + provides :template attr_reader :inline_helper_blocks attr_reader :inline_helper_modules @@ -40,7 +40,6 @@ class Chef @cookbook = nil @local = false @variables = Hash.new - @provider = Chef::Provider::Template @inline_helper_blocks = {} @inline_helper_modules = [] @helper_modules = [] @@ -50,7 +49,7 @@ class Chef set_or_return( :source, file, - :kind_of => [ String ] + :kind_of => [ String, Array ] ) end diff --git a/lib/chef/resource/timestamped_deploy.rb b/lib/chef/resource/timestamped_deploy.rb index 4032ae9854..b2109db85c 100644 --- a/lib/chef/resource/timestamped_deploy.rb +++ b/lib/chef/resource/timestamped_deploy.rb @@ -18,13 +18,12 @@ class Chef class Resource - # Convenience class for using the deploy resource with the timestamped # deployment strategy (provider) class TimestampedDeploy < Chef::Resource::Deploy + provides :timestamped_deploy def initialize(*args, &block) super(*args, &block) - @provider = Chef::Provider::Deploy::Timestamped end end end diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/resource/whyrun_safe_ruby_block.rb index ddb9d91dc3..6fa5383f5d 100644 --- a/lib/chef/resource/whyrun_safe_ruby_block.rb +++ b/lib/chef/resource/whyrun_safe_ruby_block.rb @@ -23,7 +23,6 @@ class Chef def initialize(name, run_context=nil) super @resource_name = :whyrun_safe_ruby_block - @provider = Chef::Provider::WhyrunSafeRubyBlock end end diff --git a/lib/chef/resource/windows_package.rb b/lib/chef/resource/windows_package.rb index 8bd41e0cb7..c563ba5fdc 100644 --- a/lib/chef/resource/windows_package.rb +++ b/lib/chef/resource/windows_package.rb @@ -24,12 +24,11 @@ class Chef class Resource class WindowsPackage < Chef::Resource::Package - provides :package, :on_platforms => ["windows"] + provides :package, platform: "windows" def initialize(name, run_context=nil) super @allowed_actions = [ :install, :remove ] - @provider = Chef::Provider::Package::Windows @resource_name = :windows_package @source ||= source(@package_name) @@ -76,4 +75,3 @@ class Chef end end end - diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb index 108891e9ba..6b0827b77c 100644 --- a/lib/chef/resource/windows_script.rb +++ b/lib/chef/resource/windows_script.rb @@ -23,12 +23,15 @@ class Chef class Resource class WindowsScript < Chef::Resource::Script + set_guard_inherited_attributes(:architecture) + protected def initialize(name, run_context, resource_name, interpreter_command) super(name, run_context) @interpreter = interpreter_command @resource_name = resource_name + @default_guard_interpreter = resource_name end include Chef::Mixin::WindowsArchitectureHelper diff --git a/lib/chef/resource/windows_service.rb b/lib/chef/resource/windows_service.rb index 5ed8e76cbd..49495117ee 100644 --- a/lib/chef/resource/windows_service.rb +++ b/lib/chef/resource/windows_service.rb @@ -25,7 +25,7 @@ class Chef # Until #1773 is resolved, you need to manually specify the windows_service resource # to use action :configure_startup and attribute startup_type - # provides :service, :on_platforms => ["windows"] + provides :service, platform: "windows" identity_attr :service_name @@ -34,7 +34,6 @@ class Chef def initialize(name, run_context=nil) super @resource_name = :windows_service - @provider = Chef::Provider::Service::Windows @allowed_actions.push(:configure_startup) @startup_type = :automatic end diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb index dff70bcf62..8fbca9b097 100644 --- a/lib/chef/resource/yum_package.rb +++ b/lib/chef/resource/yum_package.rb @@ -23,10 +23,12 @@ class Chef class Resource class YumPackage < Chef::Resource::Package + provides :yum_package + provides :package, os: "linux", platform_family: [ "rhel", "fedora" ] + def initialize(name, run_context=nil) super @resource_name = :yum_package - @provider = Chef::Provider::Package::Yum @flush_cache = { :before => false, :after => false } @allow_downgrade = false end diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb index cc14a03962..30520cff7e 100644 --- a/lib/chef/resource_collection.rb +++ b/lib/chef/resource_collection.rb @@ -17,250 +17,75 @@ # limitations under the License. # -require 'chef/resource' -require 'chef/resource_collection/stepable_iterator' - +require 'chef/resource_collection/resource_set' +require 'chef/resource_collection/resource_list' +require 'chef/resource_collection/resource_collection_serialization' +require 'chef/log' +require 'forwardable' + +## +# ResourceCollection currently handles two tasks: +# 1) Keeps an ordered list of resources to use when converging the node +# 2) Keeps a unique list of resources (keyed as `type[name]`) used for notifications class Chef class ResourceCollection - include Enumerable - - # Matches a multiple resource lookup specification, - # e.g., "service[nginx,unicorn]" - MULTIPLE_RESOURCE_MATCH = /^(.+)\[(.+?),(.+)\]$/ + include ResourceCollectionSerialization + extend Forwardable - # Matches a single resource lookup specification, - # e.g., "service[nginx]" - SINGLE_RESOURCE_MATCH = /^(.+)\[(.+)\]$/ - - attr_reader :iterator + attr_reader :resource_set, :resource_list + private :resource_set, :resource_list def initialize - @resources = Array.new - @resources_by_name = Hash.new - @insert_after_idx = nil - end - - def all_resources - @resources - end - - def [](index) - @resources[index] - end - - def []=(index, arg) - is_chef_resource(arg) - @resources[index] = arg - @resources_by_name[arg.to_s] = index - end - - def <<(*args) - args.flatten.each do |a| - is_chef_resource(a) - @resources << a - @resources_by_name[a.to_s] = @resources.length - 1 - end - self - end - - # 'push' is an alias method to << - alias_method :push, :<< - - def insert(resource) - if @insert_after_idx - # in the middle of executing a run, so any resources inserted now should - # be placed after the most recent addition done by the currently executing - # resource - insert_at(@insert_after_idx + 1, resource) - @insert_after_idx += 1 + @resource_set = ResourceSet.new + @resource_list = ResourceList.new + end + + # @param resource [Chef::Resource] The resource to insert + # @param resource_type [String,Symbol] If known, the resource type used in the recipe, Eg `package`, `execute` + # @param instance_name [String] If known, the recource name as used in the recipe, IE `vim` in `package 'vim'` + # @param at_location [Integer] If know, a location in the @resource_list to insert resource + # If you know the at_location but not the resource_type or instance_name, pass them in as nil + # This method is meant to be the 1 insert method necessary in the future. It should support all known use cases + # for writing into the ResourceCollection. + def insert(resource, opts={}) + resource_type ||= opts[:resource_type] # Would rather use Ruby 2.x syntax, but oh well + instance_name ||= opts[:instance_name] + resource_list.insert(resource) + if !(resource_type.nil? && instance_name.nil?) + resource_set.insert_as(resource, resource_type, instance_name) else - is_chef_resource(resource) - @resources << resource - @resources_by_name[resource.to_s] = @resources.length - 1 + resource_set.insert_as(resource) end end - def insert_at(insert_at_index, *resources) - resources.each do |resource| - is_chef_resource(resource) - end - @resources.insert(insert_at_index, *resources) - # update name -> location mappings and register new resource - @resources_by_name.each_key do |key| - @resources_by_name[key] += resources.size if @resources_by_name[key] >= insert_at_index - end - resources.each_with_index do |resource, i| - @resources_by_name[resource.to_s] = insert_at_index + i - end + # @deprecated + def []=(index, resource) + Chef::Log.warn("`[]=` is deprecated, use `insert` with the `at_location` parameter") + resource_list[index] = resource + resource_set.insert_as(resource) end - def each - @resources.each do |resource| - yield resource + # @deprecated + def push(*resources) + Chef::Log.warn("`push` is deprecated, use `insert`") + resources.flatten.each do |res| + insert(res) end + self end - def execute_each_resource(&resource_exec_block) - @iterator = StepableIterator.for_collection(@resources) - @iterator.each_with_index do |resource, idx| - @insert_after_idx = idx - yield resource - end - end - - def each_index - @resources.each_index do |i| - yield i - end - end - - def empty? - @resources.empty? - end - - def lookup(resource) - lookup_by = nil - if resource.kind_of?(Chef::Resource) - lookup_by = resource.to_s - elsif resource.kind_of?(String) - lookup_by = resource - else - raise ArgumentError, "Must pass a Chef::Resource or String to lookup" - end - res = @resources_by_name[lookup_by] - unless res - raise Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{lookup_by} (did you define it first?)" - end - @resources[res] - end - - # Find existing resources by searching the list of existing resources. Possible - # forms are: - # - # find(:file => "foobar") - # find(:file => [ "foobar", "baz" ]) - # find("file[foobar]", "file[baz]") - # find("file[foobar,baz]") - # - # Returns the matching resource, or an Array of matching resources. - # - # Raises an ArgumentError if you feed it bad lookup information - # Raises a Runtime Error if it can't find the resources you are looking for. - def find(*args) - results = Array.new - args.each do |arg| - case arg - when Hash - results << find_resource_by_hash(arg) - when String - results << find_resource_by_string(arg) - else - msg = "arguments to #{self.class.name}#find should be of the form :resource => 'name' or resource[name]" - raise Chef::Exceptions::InvalidResourceSpecification, msg - end - end - flat_results = results.flatten - flat_results.length == 1 ? flat_results[0] : flat_results - end - - # resources is a poorly named, but we have to maintain it for back - # compat. - alias_method :resources, :find - - # Returns true if +query_object+ is a valid string for looking up a - # resource, or raises InvalidResourceSpecification if not. - # === Arguments - # * query_object should be a string of the form - # "resource_type[resource_name]", a single element Hash (e.g., :service => - # "apache2"), or a Chef::Resource (this is the happy path). Other arguments - # will raise an exception. - # === Returns - # * true returns true for all valid input. - # === Raises - # * Chef::Exceptions::InvalidResourceSpecification for all invalid input. - def validate_lookup_spec!(query_object) - case query_object - when Chef::Resource - true - when SINGLE_RESOURCE_MATCH, MULTIPLE_RESOURCE_MATCH - true - when Hash - true - when String - raise Chef::Exceptions::InvalidResourceSpecification, - "The string `#{query_object}' is not valid for resource collection lookup. Correct syntax is `resource_type[resource_name]'" - else - raise Chef::Exceptions::InvalidResourceSpecification, - "The object `#{query_object.inspect}' is not valid for resource collection lookup. " + - "Use a String like `resource_type[resource_name]' or a Chef::Resource object" - end - end - - # Serialize this object as a hash - def to_hash - instance_vars = Hash.new - self.instance_variables.each do |iv| - instance_vars[iv] = self.instance_variable_get(iv) - end - { - 'json_class' => self.class.name, - 'instance_vars' => instance_vars - } - end - - def to_json(*a) - Chef::JSONCompat.to_json(to_hash, *a) - end - - def self.json_create(o) - collection = self.new() - o["instance_vars"].each do |k,v| - collection.instance_variable_set(k.to_sym, v) - end - collection - end + # @deprecated + alias_method :<<, :insert - private + # Read-only methods are simple to delegate - doing that below - def find_resource_by_hash(arg) - results = Array.new - arg.each do |resource_name, name_list| - names = name_list.kind_of?(Array) ? name_list : [ name_list ] - names.each do |name| - res_name = "#{resource_name.to_s}[#{name}]" - results << lookup(res_name) - end - end - return results - end + resource_list_methods = Enumerable.instance_methods + + [:iterator, :all_resources, :[], :each, :execute_each_resource, :each_index, :empty?] - + [:find] # find needs to run on the set + resource_set_methods = [:lookup, :find, :resources, :keys, :validate_lookup_spec!] - def find_resource_by_string(arg) - results = Array.new - case arg - when MULTIPLE_RESOURCE_MATCH - resource_type = $1 - arg =~ /^.+\[(.+)\]$/ - resource_list = $1 - resource_list.split(",").each do |name| - resource_name = "#{resource_type}[#{name}]" - results << lookup(resource_name) - end - when SINGLE_RESOURCE_MATCH - resource_type = $1 - name = $2 - resource_name = "#{resource_type}[#{name}]" - results << lookup(resource_name) - else - raise ArgumentError, "Bad string format #{arg}, you must have a string like resource_type[name]!" - end - return results - end + def_delegators :resource_list, *resource_list_methods + def_delegators :resource_set, *resource_set_methods - def is_chef_resource(arg) - unless arg.kind_of?(Chef::Resource) - raise ArgumentError, "Cannot insert a #{arg.class} into a resource collection: must be a subclass of Chef::Resource" - end - true - end end end diff --git a/lib/chef/resource_collection/resource_collection_serialization.rb b/lib/chef/resource_collection/resource_collection_serialization.rb new file mode 100644 index 0000000000..3651fb2a2a --- /dev/null +++ b/lib/chef/resource_collection/resource_collection_serialization.rb @@ -0,0 +1,59 @@ +# +# Author:: Tyler Ball (<tball@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 ResourceCollection + module ResourceCollectionSerialization + # Serialize this object as a hash + def to_hash + instance_vars = Hash.new + self.instance_variables.each do |iv| + instance_vars[iv] = self.instance_variable_get(iv) + end + { + 'json_class' => self.class.name, + 'instance_vars' => instance_vars + } + end + + def to_json(*a) + Chef::JSONCompat.to_json(to_hash, *a) + end + + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def json_create(o) + collection = self.new() + o["instance_vars"].each do |k,v| + collection.instance_variable_set(k.to_sym, v) + end + collection + end + end + + def is_chef_resource!(arg) + unless arg.kind_of?(Chef::Resource) + raise ArgumentError, "Cannot insert a #{arg.class} into a resource collection: must be a subclass of Chef::Resource" + end + true + end + end + end +end diff --git a/lib/chef/resource_collection/resource_list.rb b/lib/chef/resource_collection/resource_list.rb new file mode 100644 index 0000000000..a26bd347aa --- /dev/null +++ b/lib/chef/resource_collection/resource_list.rb @@ -0,0 +1,89 @@ +# +# Author:: Tyler Ball (<tball@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 'chef/resource' +require 'chef/resource_collection/stepable_iterator' +require 'chef/resource_collection/resource_collection_serialization' +require 'forwardable' + +# This class keeps the list of all known Resources in the order they are to be executed in. It also keeps a pointer +# to the most recently executed resource so we can add resources-to-execute after this point. +class Chef + class ResourceCollection + class ResourceList + include ResourceCollection::ResourceCollectionSerialization + include Enumerable + extend Forwardable + + attr_reader :iterator + + attr_reader :resources + private :resources + # Delegate direct access methods to the @resources array + # 4 extra methods here are not included in the Enumerable's instance methods + direct_access_methods = Enumerable.instance_methods + [ :[], :each, :each_index, :empty? ] + def_delegators :resources, *(direct_access_methods) + + def initialize + @resources = Array.new + @insert_after_idx = nil + end + + # @param resource [Chef::Resource] The resource to insert + # If @insert_after_idx is nil, we are not currently executing a converge so the Resource is appended to the + # end of the list. If @insert_after_idx is NOT nil, we ARE currently executing a converge so the resource + # is inserted into the middle of the list after the last resource that was converged. If it is called multiple + # times (when an LWRP contains multiple resources) it keeps track of that. See this example ResourceList: + # [File1, LWRP1, File2] # The iterator starts and points to File1. It is executed and @insert_after_idx=0 + # [File1, LWRP1, File2] # The iterator moves to LWRP1. It is executed and @insert_after_idx=1 + # [File1, LWRP1, Service1, File2] # The LWRP execution inserts Service1 and @insert_after_idx=2 + # [File1, LWRP1, Service1, Service2, File2] # The LWRP inserts Service2 and @insert_after_idx=3. The LWRP + # finishes executing + # [File1, LWRP1, Service1, Service2, File2] # The iterator moves to Service1 since it is the next non-executed + # resource. The execute_each_resource call below resets @insert_after_idx=2 + # If Service1 was another LWRP, it would insert its resources between Service1 and Service2. The iterator keeps + # track of executed resources and @insert_after_idx keeps track of where the next resource to insert should be. + def insert(resource) + is_chef_resource!(resource) + if @insert_after_idx + @resources.insert(@insert_after_idx += 1, resource) + else + @resources << resource + end + end + + # @deprecated - can be removed when it is removed from resource_collection.rb + def []=(index, resource) + @resources[index] = resource + end + + def all_resources + @resources + end + + def execute_each_resource(&resource_exec_block) + @iterator = ResourceCollection::StepableIterator.for_collection(@resources) + @iterator.each_with_index do |resource, idx| + @insert_after_idx = idx + yield resource + end + end + + end + end +end diff --git a/lib/chef/resource_collection/resource_set.rb b/lib/chef/resource_collection/resource_set.rb new file mode 100644 index 0000000000..6425c2ab08 --- /dev/null +++ b/lib/chef/resource_collection/resource_set.rb @@ -0,0 +1,170 @@ +# +# Author:: Tyler Ball (<tball@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 'chef/resource' +require 'chef/resource_collection/resource_collection_serialization' + +class Chef + class ResourceCollection + class ResourceSet + include ResourceCollection::ResourceCollectionSerialization + + # Matches a multiple resource lookup specification, + # e.g., "service[nginx,unicorn]" + MULTIPLE_RESOURCE_MATCH = /^(.+)\[(.+?),(.+)\]$/ + + # Matches a single resource lookup specification, + # e.g., "service[nginx]" + SINGLE_RESOURCE_MATCH = /^(.+)\[(.+)\]$/ + + def initialize + @resources_by_key = Hash.new + end + + def keys + @resources_by_key.keys + end + + def insert_as(resource, resource_type=nil, instance_name=nil) + is_chef_resource!(resource) + resource_type ||= resource.resource_name + instance_name ||= resource.name + key = ResourceSet.create_key(resource_type, instance_name) + @resources_by_key[key] = resource + end + + def lookup(key) + case + when key.kind_of?(String) + lookup_by = key + when key.kind_of?(Chef::Resource) + lookup_by = ResourceSet.create_key(key.resource_name, key.name) + else + raise ArgumentError, "Must pass a Chef::Resource or String to lookup" + end + + res = @resources_by_key[lookup_by] + unless res + raise Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{lookup_by} (did you define it first?)" + end + res + end + + # Find existing resources by searching the list of existing resources. Possible + # forms are: + # + # find(:file => "foobar") + # find(:file => [ "foobar", "baz" ]) + # find("file[foobar]", "file[baz]") + # find("file[foobar,baz]") + # + # Returns the matching resource, or an Array of matching resources. + # + # Raises an ArgumentError if you feed it bad lookup information + # Raises a Runtime Error if it can't find the resources you are looking for. + def find(*args) + results = Array.new + args.each do |arg| + case arg + when Hash + results << find_resource_by_hash(arg) + when String + results << find_resource_by_string(arg) + else + msg = "arguments to #{self.class.name}#find should be of the form :resource => 'name' or 'resource[name]'" + raise Chef::Exceptions::InvalidResourceSpecification, msg + end + end + flat_results = results.flatten + flat_results.length == 1 ? flat_results[0] : flat_results + end + + # @deprecated + # resources is a poorly named, but we have to maintain it for back + # compat. + alias_method :resources, :find + + # Returns true if +query_object+ is a valid string for looking up a + # resource, or raises InvalidResourceSpecification if not. + # === Arguments + # * query_object should be a string of the form + # "resource_type[resource_name]", a single element Hash (e.g., :service => + # "apache2"), or a Chef::Resource (this is the happy path). Other arguments + # will raise an exception. + # === Returns + # * true returns true for all valid input. + # === Raises + # * Chef::Exceptions::InvalidResourceSpecification for all invalid input. + def validate_lookup_spec!(query_object) + case query_object + when Chef::Resource + true + when SINGLE_RESOURCE_MATCH, MULTIPLE_RESOURCE_MATCH + true + when Hash + true + when String + raise Chef::Exceptions::InvalidResourceSpecification, + "The string `#{query_object}' is not valid for resource collection lookup. Correct syntax is `resource_type[resource_name]'" + else + raise Chef::Exceptions::InvalidResourceSpecification, + "The object `#{query_object.inspect}' is not valid for resource collection lookup. " + + "Use a String like `resource_type[resource_name]' or a Chef::Resource object" + end + end + + def self.create_key(resource_type, instance_name) + "#{resource_type}[#{instance_name}]" + end + + private + + def find_resource_by_hash(arg) + results = Array.new + arg.each do |resource_type, name_list| + instance_names = name_list.kind_of?(Array) ? name_list : [ name_list ] + instance_names.each do |instance_name| + results << lookup(ResourceSet.create_key(resource_type, instance_name)) + end + end + return results + end + + def find_resource_by_string(arg) + results = Array.new + case arg + when MULTIPLE_RESOURCE_MATCH + resource_type = $1 + arg =~ /^.+\[(.+)\]$/ + resource_list = $1 + resource_list.split(",").each do |instance_name| + results << lookup(ResourceSet.create_key(resource_type, instance_name)) + end + when SINGLE_RESOURCE_MATCH + resource_type = $1 + name = $2 + results << lookup(ResourceSet.create_key(resource_type, name)) + else + raise ArgumentError, "Bad string format #{arg}, you must have a string like resource_type[name]!" + end + return results + end + + end + end +end diff --git a/lib/chef/resource_platform_map.rb b/lib/chef/resource_platform_map.rb deleted file mode 100644 index a678f5be4b..0000000000 --- a/lib/chef/resource_platform_map.rb +++ /dev/null @@ -1,151 +0,0 @@ -# -# Author:: Seth Chisamore (<schisamo@opscode.com>) -# Copyright:: Copyright (c) 2011 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/mixin/params_validate' -require 'chef/mixin/convert_to_class_name' - -class Chef - class Resource - class PlatformMap - - include Chef::Mixin::ParamsValidate - include Chef::Mixin::ConvertToClassName - - attr_reader :map - - def initialize(map={:default => {}}) - @map = map - end - - def filter(platform, version) - resource_map = map[:default].clone - platform_sym = platform - if platform.kind_of?(String) - platform.downcase! - platform.gsub!(/\s/, "_") - platform_sym = platform.to_sym - end - - if map.has_key?(platform_sym) - if map[platform_sym].has_key?(version) - if map[platform_sym].has_key?(:default) - resource_map.merge!(map[platform_sym][:default]) - end - resource_map.merge!(map[platform_sym][version]) - elsif map[platform_sym].has_key?(:default) - resource_map.merge!(map[platform_sym][:default]) - end - end - resource_map - end - - def set(args) - validate( - args, - { - :platform => { - :kind_of => Symbol, - :required => false - }, - :version => { - :kind_of => String, - :required => false - }, - :short_name => { - :kind_of => Symbol, - :required => true - }, - :resource => { - :kind_of => [ String, Symbol, Class ], - :required => true - } - } - ) - if args.has_key?(:platform) - if args.has_key?(:version) - if map.has_key?(args[:platform]) - if map[args[:platform]].has_key?(args[:version]) - map[args[:platform]][args[:version]][args[:short_name].to_sym] = args[:resource] - else - map[args[:platform]][args[:version]] = { - args[:short_name].to_sym => args[:resource] - } - end - else - map[args[:platform]] = { - args[:version] => { - args[:short_name].to_sym => args[:resource] - } - } - end - else - if map.has_key?(args[:platform]) - if map[args[:platform]].has_key?(:default) - map[args[:platform]][:default][args[:short_name].to_sym] = args[:resource] - else - map[args[:platform]] = { :default => { args[:short_name].to_sym => args[:resource] } } - end - else - map[args[:platform]] = { - :default => { - args[:short_name].to_sym => args[:resource] - } - } - end - end - else - if map.has_key?(:default) - map[:default][args[:short_name].to_sym] = args[:resource] - else - map[:default] = { - args[:short_name].to_sym => args[:resource] - } - end - end - end - - def get(short_name, platform=nil, version=nil) - resource_klass = platform_resource(short_name, platform, version) || - resource_matching_short_name(short_name) - - raise Exceptions::NoSuchResourceType, "Cannot find a resource for #{short_name} on #{platform} version #{version}" if resource_klass.nil? - - resource_klass - end - - private - - def platform_resource(short_name, platform, version) - pmap = filter(platform, version) - rtkey = short_name.kind_of?(Chef::Resource) ? short_name.resource_name.to_sym : short_name - - pmap.has_key?(rtkey) ? pmap[rtkey] : nil - end - - def resource_matching_short_name(short_name) - begin - rname = convert_to_class_name(short_name.to_s) - Chef::Resource.const_get(rname) - rescue NameError - nil - end - end - - end - end -end diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index bbe2f9eba0..18d353ac61 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -18,6 +18,7 @@ # limitations under the License. require 'chef/resource_collection' +require 'chef/provider_resolver' require 'chef/cookbook_version' require 'chef/node' require 'chef/role' @@ -50,6 +51,9 @@ class Chef # recipes, which is triggered by #load. (See also: CookbookCompiler) attr_accessor :resource_collection + # Chef::ProviderResolver for this run + attr_accessor :provider_resolver + # A Hash containing the immediate notifications triggered by resources # during the converge phase of the chef run. attr_accessor :immediate_notification_collection @@ -82,8 +86,8 @@ class Chef @reboot_info = {} @node.run_context = self - @cookbook_compiler = nil + @provider_resolver = Chef::ProviderResolver.new(@node) end # Triggers the compile phase of the chef run. Implemented by diff --git a/lib/chef/shell/ext.rb b/lib/chef/shell/ext.rb index bc4e955169..fd785e2f79 100644 --- a/lib/chef/shell/ext.rb +++ b/lib/chef/shell/ext.rb @@ -547,7 +547,7 @@ E desc "list all the resources on the current recipe" def resources(*args) if args.empty? - pp run_context.resource_collection.instance_variable_get(:@resources_by_name).keys + pp run_context.resource_collection.keys else pp resources = original_resources(*args) resources diff --git a/lib/chef/util/dsc/local_configuration_manager.rb b/lib/chef/util/dsc/local_configuration_manager.rb index 4a56b6a397..f498a2bfea 100644 --- a/lib/chef/util/dsc/local_configuration_manager.rb +++ b/lib/chef/util/dsc/local_configuration_manager.rb @@ -29,7 +29,7 @@ class Chef::Util::DSC def test_configuration(configuration_document) status = run_configuration_cmdlet(configuration_document) - handle_what_if_exception!(status.stderr) unless status.succeeded? + log_what_if_exception(status.stderr) unless status.succeeded? configuration_update_required?(status.return_value) end @@ -78,18 +78,22 @@ $ProgressPreference = 'SilentlyContinue';start-dscconfiguration -path #{@configu EOH end - def handle_what_if_exception!(what_if_exception_output) - if what_if_exception_output.gsub(/\s+/, ' ') =~ /A parameter cannot be found that matches parameter name 'Whatif'/i - # LCM returns an error if any of the resources do not support the opptional What-If - Chef::Log::warn("Received error while testing configuration due to resource not supporting 'WhatIf'") - elsif output_has_dsc_module_failure?(what_if_exception_output) - Chef::Log::warn("Received error while testing configuration due to a module for an imported resource possibly not being fully installed:\n#{what_if_exception_output.gsub(/\s+/, ' ')}") - else - raise Chef::Exceptions::PowershellCmdletException, "Powershell Cmdlet failed: #{what_if_exception_output.gsub(/\s+/, ' ')}" - end + def log_what_if_exception(what_if_exception_output) + if whatif_not_supported?(what_if_exception_output) + # LCM returns an error if any of the resources do not support the opptional What-If + Chef::Log::warn("Received error while testing configuration due to resource not supporting 'WhatIf'") + elsif dsc_module_import_failure?(what_if_exception_output) + Chef::Log::warn("Received error while testing configuration due to a module for an imported resource possibly not being fully installed:\n#{what_if_exception_output.gsub(/\s+/, ' ')}") + else + Chef::Log::warn("Received error while testing configuration:\n#{what_if_exception_output.gsub(/\s+/, ' ')}") + end + end + + def whatif_not_supported?(what_if_exception_output) + !! (what_if_exception_output.gsub(/[\r\n]+/, '').gsub(/\s+/, ' ') =~ /A parameter cannot be found that matches parameter name 'Whatif'/i) end - def output_has_dsc_module_failure?(what_if_output) + def dsc_module_import_failure?(what_if_output) !! (what_if_output =~ /\sCimException/ && what_if_output =~ /ProviderOperationExecutionFailure/ && what_if_output =~ /\smodule\s+is\s+installed/) diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb index d2138289f5..d16bd8c12f 100644 --- a/lib/chef/win32/version.rb +++ b/lib/chef/win32/version.rb @@ -48,6 +48,8 @@ class Chef public WIN_VERSIONS = { + "Windows 10" => {:major => 6, :minor => 4, :callable => lambda{ |product_type, suite_mask| product_type == VER_NT_WORKSTATION }}, + "Windows Server 10" => {:major => 6, :minor => 4, :callable => lambda {|product_type, suite_mask| product_type != VER_NT_WORKSTATION }}, "Windows 8.1" => {:major => 6, :minor => 3, :callable => lambda{ |product_type, suite_mask| product_type == VER_NT_WORKSTATION }}, "Windows Server 2012 R2" => {:major => 6, :minor => 3, :callable => lambda {|product_type, suite_mask| product_type != VER_NT_WORKSTATION }}, "Windows 8" => {:major => 6, :minor => 2, :callable => lambda{ |product_type, suite_mask| product_type == VER_NT_WORKSTATION }}, diff --git a/spec/data/cb_version_cookbooks/cookbook2/files/test.txt b/spec/data/cb_version_cookbooks/cookbook2/files/test.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/spec/data/cb_version_cookbooks/cookbook2/files/test.txt diff --git a/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb b/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb diff --git a/spec/data/cookbooks/ignorken/files/default/not_me.rb b/spec/data/cookbooks/ignorken/files/default/not_me.rb new file mode 100644 index 0000000000..8063e32a95 --- /dev/null +++ b/spec/data/cookbooks/ignorken/files/default/not_me.rb @@ -0,0 +1,2 @@ +a cat walked on the keyboard one day... +(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===)))))) diff --git a/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb b/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb new file mode 100644 index 0000000000..8063e32a95 --- /dev/null +++ b/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb @@ -0,0 +1,2 @@ +a cat walked on the keyboard one day... +(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===)))))) diff --git a/spec/data/cookbooks/openldap/libraries/openldap.rb b/spec/data/cookbooks/openldap/libraries/openldap.rb new file mode 100644 index 0000000000..6a3f058f95 --- /dev/null +++ b/spec/data/cookbooks/openldap/libraries/openldap.rb @@ -0,0 +1,4 @@ +require_relative './openldap/version.rb' + +class OpenLDAP +end diff --git a/spec/data/cookbooks/openldap/libraries/openldap/version.rb b/spec/data/cookbooks/openldap/libraries/openldap/version.rb new file mode 100644 index 0000000000..4bff12b01c --- /dev/null +++ b/spec/data/cookbooks/openldap/libraries/openldap/version.rb @@ -0,0 +1,3 @@ +class OpenLDAP + VERSION = '8.9.10' +end diff --git a/spec/data/lwrp/providers/buck_passer.rb b/spec/data/lwrp/providers/buck_passer.rb index 1f83e5098b..8d5156af81 100644 --- a/spec/data/lwrp/providers/buck_passer.rb +++ b/spec/data/lwrp/providers/buck_passer.rb @@ -1,3 +1,10 @@ -action :buck_stops_here do - log "This should be overwritten by ../lwrp_override/buck_passer.rb" +action :pass_buck do + lwrp_foo :prepared_thumbs do + action :prepare_thumbs + provider :lwrp_thumb_twiddler + end + lwrp_foo :twiddled_thumbs do + action :twiddle_thumbs + provider :lwrp_thumb_twiddler + end end diff --git a/spec/data/lwrp/resources/foo.rb b/spec/data/lwrp/resources/foo.rb index c881c80530..0ee83f0cd0 100644 --- a/spec/data/lwrp/resources/foo.rb +++ b/spec/data/lwrp/resources/foo.rb @@ -1,3 +1,4 @@ -actions :never_execute +actions :prepare_thumbs, :twiddle_thumbs +default_action :pass_buck -attribute :ever, :kind_of => String +attribute :monkey, :kind_of => String diff --git a/spec/data/lwrp_override/providers/buck_passer.rb b/spec/data/lwrp_override/providers/buck_passer.rb index 75917a58c9..2061b391dc 100644 --- a/spec/data/lwrp_override/providers/buck_passer.rb +++ b/spec/data/lwrp_override/providers/buck_passer.rb @@ -1,10 +1,5 @@ -action :pass_buck do - lwrp_foo :prepared_thumbs do - action :prepare_thumbs - provider :lwrp_thumb_twiddler - end - lwrp_foo :twiddled_thumbs do - action :twiddle_thumbs - provider :lwrp_thumb_twiddler - end -end
\ No newline at end of file +# Starting with Chef 12 reloading an LWRP shouldn't reload the file anymore + +action :buck_stops_here do + log "This should be overwritten by ../lwrp_override/buck_passer.rb" +end diff --git a/spec/data/lwrp_override/resources/foo.rb b/spec/data/lwrp_override/resources/foo.rb index 0ee83f0cd0..14decb9634 100644 --- a/spec/data/lwrp_override/resources/foo.rb +++ b/spec/data/lwrp_override/resources/foo.rb @@ -1,4 +1,5 @@ -actions :prepare_thumbs, :twiddle_thumbs -default_action :pass_buck +# Starting with Chef 12 reloading an LWRP shouldn't reload the file anymore -attribute :monkey, :kind_of => String +actions :never_execute + +attribute :ever, :kind_of => String diff --git a/spec/functional/assets/chefinittest b/spec/functional/assets/chefinittest new file mode 100755 index 0000000000..79e064cd5f --- /dev/null +++ b/spec/functional/assets/chefinittest @@ -0,0 +1,34 @@ +#!/bin/ksh + +function create_chef_txt { + touch /tmp/chefinittest.txt +} + +function delete_chef_txt { + rm /tmp/chefinittest.txt +} + +function rename_chef_txt { + mv /tmp/chefinittest.txt /tmp/$1 +} + +case "$1" in +start ) + create_chef_txt + ;; +stop ) + delete_chef_txt + ;; +status ) + [ -f /tmp/chefinittest.txt ] || [ -f /tmp/chefinittest_reload.txt ] || [ -f /tmp/chefinittest_restart.txt ] + ;; +reload ) + rename_chef_txt "chefinittest_reload.txt" + ;; +restart ) + rename_chef_txt "chefinittest_restart.txt" + ;; +* ) + echo "Usage: $0 (start | stop | restart | reload)" + exit 1 +esac diff --git a/spec/functional/assets/testchefsubsys b/spec/functional/assets/testchefsubsys new file mode 100755 index 0000000000..e9ff30d4aa --- /dev/null +++ b/spec/functional/assets/testchefsubsys @@ -0,0 +1,11 @@ +#!/bin/bash +# trapchild + +sleep 120 & + +pid="$!" + +trap 'echo I am going down, so killing off my processes..; kill $pid; exit' SIGHUP SIGINT + SIGQUIT SIGTERM + +wait
\ No newline at end of file diff --git a/spec/functional/event_loggers/windows_eventlog_spec.rb b/spec/functional/event_loggers/windows_eventlog_spec.rb new file mode 100644 index 0000000000..9da9f60fa9 --- /dev/null +++ b/spec/functional/event_loggers/windows_eventlog_spec.rb @@ -0,0 +1,82 @@ +# +# Author:: Jay Mundrawala (<jdm@getchef.com>) +# +# Copyright:: 2014, Chef Software, Inc. +# +# 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 'securerandom' +require 'chef/event_loggers/windows_eventlog' +if Chef::Platform.windows? + require 'win32/eventlog' + include Win32 +end + +describe Chef::EventLoggers::WindowsEventLogger, :windows_only do + let(:run_id) { SecureRandom.uuid } + let(:version) { SecureRandom.uuid } + let(:elapsed_time) { SecureRandom.random_number(100) } + let(:logger) { Chef::EventLoggers::WindowsEventLogger.new } + let(:flags) { nil } + let(:node) { nil } + let(:run_status) { double('Run Status', {run_id: run_id, elapsed_time: elapsed_time }) } + let(:event_log) { EventLog.new("Application") } + let!(:offset) { event_log.read_last_event.record_number } + let(:mock_exception) { double('Exception', {message: SecureRandom.uuid, backtrace:[SecureRandom.uuid, SecureRandom.uuid]})} + + it 'is available' do + Chef::EventLoggers::WindowsEventLogger.available?.should be_true + end + + it 'writes run_start event with event_id 10000 and contains version' do + logger.run_start(version) + + expect(event_log.read(flags, offset).any? { |e| e.source == 'Chef' && e.event_id == 10000 && + e.string_inserts[0].include?(version)}).to be_true + end + + it 'writes run_started event with event_id 10001 and contains the run_id' do + logger.run_started(run_status) + + expect(event_log.read(flags, offset).any? { |e| e.source == 'Chef' && e.event_id == 10001 && + e.string_inserts[0].include?(run_id)}).to be_true + end + + it 'writes run_completed event with event_id 10002 and contains the run_id and elapsed time' do + logger.run_started(run_status) + logger.run_completed(node) + + expect(event_log.read(flags, offset).any? { |e| e.source == 'Chef' && e.event_id == 10002 && + e.string_inserts[0].include?(run_id) && + e.string_inserts[1].include?(elapsed_time.to_s) + }).to be_true + end + + it 'writes run_failed event with event_id 10003 and contains the run_id, elapsed time, and exception info' do + logger.run_started(run_status) + logger.run_failed(mock_exception) + + expect(event_log.read(flags, offset).any? do |e| + e.source == 'Chef' && e.event_id == 10003 && + e.string_inserts[0].include?(run_id) && + e.string_inserts[1].include?(elapsed_time.to_s) && + e.string_inserts[2].include?(mock_exception.class.name) && + e.string_inserts[3].include?(mock_exception.message) && + e.string_inserts[4].include?(mock_exception.backtrace[0]) && + e.string_inserts[4].include?(mock_exception.backtrace[1]) + end).to be_true + end + +end diff --git a/spec/functional/resource/aix_service_spec.rb b/spec/functional/resource/aix_service_spec.rb new file mode 100755 index 0000000000..6008fdea8f --- /dev/null +++ b/spec/functional/resource/aix_service_spec.rb @@ -0,0 +1,136 @@ +# encoding: UTF-8 +# +# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.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' +require 'functional/resource/base' +require 'chef/mixin/shell_out' + +shared_examples "src service" do + + include Chef::Mixin::ShellOut + + def service_should_be_started + expect(shell_out!("lssrc -a | grep #{new_resource.service_name}").stdout.split(' ').last).to eq("active") + end + + def service_should_be_stopped + expect(shell_out!("lssrc -a | grep #{new_resource.service_name}").stdout.split(' ').last).to eq("inoperative") + end + + def get_service_pid + args = shell_out!("lssrc -a | grep #{new_resource.service_name}").stdout.split(' ') + if args.length == 3 + args[1] + else + args[2] + end + end + + describe "start service" do + it "should start the service" do + new_resource.run_action(:start) + service_should_be_started + end + end + + describe "stop service" do + before do + new_resource.run_action(:start) + end + + it "should stop the service" do + new_resource.run_action(:stop) + service_should_be_stopped + end + end + + describe "restart service" do + before do + new_resource.run_action(:start) + end + + it "should restart the service" do + new_resource.run_action(:restart) + service_should_be_started + end + end +end + +describe Chef::Resource::Service, :requires_root, :aix_only do + def get_user_id + shell_out("id -u #{ENV['USER']}").stdout.chomp + end + + describe "When service is a subsystem" do + before(:all) do + script_dir = File.join(File.dirname(__FILE__), "/../assets/") + shell_out!("mkssys -s ctestsys -p #{script_dir}/testchefsubsys -u #{get_user_id} -S -n 15 -f 9 -R -Q") + end + + after(:each) do + shell_out("stopsrc -s ctestsys") + end + + after(:all) do + shell_out!("rmssys -s ctestsys") + end + + + let(:new_resource) do + new_resource = Chef::Resource::Service.new("ctestsys", run_context) + new_resource + end + + let(:provider) do + provider = new_resource.provider_for_action(new_resource.action) + provider + end + + it_behaves_like "src service" + end + + + describe "When service is a group" do + before(:all) do + script_dir = File.join(File.dirname(__FILE__), "/../assets/") + shell_out!("mkssys -s ctestsys -p #{script_dir}/testchefsubsys -u #{get_user_id} -S -n 15 -f 9 -R -Q -G ctestgrp") + end + + after(:each) do + shell_out("stopsrc -g ctestgrp") + end + + after(:all) do + # rmssys supports only -s option. + shell_out!("rmssys -s ctestsys") + end + + let(:new_resource) do + new_resource = Chef::Resource::Service.new("ctestgrp", run_context) + new_resource + end + + let(:provider) do + provider = new_resource.provider_for_action(new_resource.action) + provider + end + + it_behaves_like "src service" + end +end diff --git a/spec/functional/resource/aixinit_service_spec.rb b/spec/functional/resource/aixinit_service_spec.rb new file mode 100755 index 0000000000..a99309187c --- /dev/null +++ b/spec/functional/resource/aixinit_service_spec.rb @@ -0,0 +1,211 @@ +# encoding: UTF-8 +# +# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.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' +require 'functional/resource/base' +require 'chef/mixin/shell_out' +require 'fileutils' + +describe Chef::Resource::Service, :requires_root, :aix_only do + + include Chef::Mixin::ShellOut + + # Platform specific validation routines. + def service_should_be_started(file_name) + # The existance of this file indicates that the service was started. + expect(File.exists?("/tmp/#{file_name}")).to be_true + end + + def service_should_be_stopped(file_name) + expect(File.exists?("/tmp/#{file_name}")).to be_false + end + + def valide_symlinks(expected_output, run_level = nil, status = nil, priority = nil) + directory = [] + if priority.is_a?Hash + priority.each do |level,o| + directory << "/etc/rc.d/rc#{level}.d/#{(o[0] == :start ? 'S' : 'K')}#{o[1]}#{new_resource.service_name}" + end + directory + else + directory << "/etc/rc.d/rc#{run_level}.d/#{status}#{priority}#{new_resource.service_name}" + end + expect(Dir.glob(directory)).to eq(expected_output) + File.delete(*directory) + end + + def delete_test_files + files = Dir.glob("/tmp/chefinit[a-z_]*.txt") + File.delete(*files) + end + + # Actual tests + let(:new_resource) do + new_resource = Chef::Resource::Service.new("chefinittest", run_context) + new_resource.provider Chef::Provider::Service::AixInit + new_resource.supports({:status => true, :restart => true, :reload => true}) + new_resource + end + + let(:provider) do + provider = new_resource.provider_for_action(new_resource.action) + provider + end + + before(:all) do + File.delete("/etc/rc.d/init.d/chefinittest") if File.exists?("/etc/rc.d/init.d/chefinittest") + FileUtils.cp("#{File.join(File.dirname(__FILE__), "/../assets/chefinittest")}", "/etc/rc.d/init.d/chefinittest") + end + + after(:all) do + File.delete("/etc/rc.d/init.d/chefinittest") if File.exists?("/etc/rc.d/init.d/chefinittest") + end + + before(:each) do + delete_test_files + end + + after(:each) do + delete_test_files + end + + describe "start service" do + it "should start the service" do + new_resource.run_action(:start) + service_should_be_started("chefinittest.txt") + end + end + + describe "stop service" do + before do + new_resource.run_action(:start) + end + + it "should stop the service" do + new_resource.run_action(:stop) + service_should_be_stopped("chefinittest.txt") + end + end + + describe "restart service" do + before do + new_resource.run_action(:start) + end + + it "should restart the service" do + new_resource.run_action(:restart) + service_should_be_started("chefinittest_restart.txt") + end + end + + describe "reload service" do + before do + new_resource.run_action(:start) + end + + it "should reload the service" do + new_resource.run_action(:reload) + service_should_be_started("chefinittest_reload.txt") + end + end + + describe "enable service" do + + context "when the service doesn't set a priority" do + it "creates symlink with status S" do + new_resource.run_action(:enable) + valide_symlinks(["/etc/rc.d/rc2.d/Schefinittest"],2,'S') + end + end + + context "when the service sets a simple priority (integer)" do + before do + new_resource.priority(75) + end + + it "creates a symlink with status S and a priority" do + new_resource.run_action(:enable) + valide_symlinks(["/etc/rc.d/rc2.d/S75chefinittest"], 2,'S',75) + end + end + + context "when the service sets complex priorities (hash)" do + before do + priority = {2 => [:start, 20], 3 => [:stop, 10]} + new_resource.priority(priority) + end + + it "create symlink with status start (S) or stop (K) and a priority " do + new_resource.run_action(:enable) + valide_symlinks(["/etc/rc.d/rc2.d/S20chefinittest", "/etc/rc.d/rc3.d/K10chefinittest"], 2,'S',new_resource.priority) + end + end + end + + describe "disable_service" do + + context "when the service doesn't set a priority" do + before do + File.symlink("/etc/rc.d/init.d/chefinittest", "/etc/rc.d/rc2.d/Schefinittest") + end + + after do + File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exists?("/etc/rc.d/rc2.d/chefinittest") + end + + it "creates symlink with status K" do + new_resource.run_action(:disable) + valide_symlinks(["/etc/rc.d/rc2.d/Kchefinittest"], 2,'K') + end + end + + context "when the service sets a simple priority (integer)" do + before do + new_resource.priority(75) + File.symlink("/etc/rc.d/init.d/chefinittest", "/etc/rc.d/rc2.d/Schefinittest") + end + + after do + File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exists?("/etc/rc.d/rc2.d/chefinittest") + end + + it "creates a symlink with status K and a priority" do + new_resource.run_action(:disable) + valide_symlinks(["/etc/rc.d/rc2.d/K25chefinittest"], 2,'K',25) + end + end + + context "when the service sets complex priorities (hash)" do + before do + @priority = {2 => [:stop, 20], 3 => [:start, 10]} + new_resource.priority(@priority) + File.symlink("/etc/rc.d/init.d/chefinittest", "/etc/rc.d/rc2.d/Schefinittest") + end + + after do + File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exists?("/etc/rc.d/rc2.d/chefinittest") + end + + it "create symlink with status stop (K) and a priority " do + new_resource.run_action(:disable) + valide_symlinks(["/etc/rc.d/rc2.d/K80chefinittest"], 2,'K',80) + end + end + end +end
\ No newline at end of file diff --git a/spec/functional/resource/batch_spec.rb b/spec/functional/resource/batch_spec.rb index baa01ee5d9..39133fd40b 100644 --- a/spec/functional/resource/batch_spec.rb +++ b/spec/functional/resource/batch_spec.rb @@ -21,17 +21,10 @@ require 'spec_helper' describe Chef::Resource::WindowsScript::Batch, :windows_only do include_context Chef::Resource::WindowsScript - let(:script_content) { "whoami" } + let(:output_command) { ' > ' } - let!(:resource) do - Chef::Resource::WindowsScript::Batch.new("Batch resource functional test", @run_context) - end + let (:architecture_command) { '@echo %PROCESSOR_ARCHITECTURE%' } + + it_behaves_like "a Windows script running on Windows" - describe "when the run action is invoked on Windows" do - it "executes the script code" do - resource.code(script_content + " > #{script_output_path}") - resource.returns(0) - resource.run_action(:run) - end - end end diff --git a/spec/functional/resource/cron_spec.rb b/spec/functional/resource/cron_spec.rb index 0a19fae0ed..287b5a1391 100644 --- a/spec/functional/resource/cron_spec.rb +++ b/spec/functional/resource/cron_spec.rb @@ -17,6 +17,7 @@ # limitations under the License. # +require 'spec_helper' require 'functional/resource/base' require 'chef/mixin/shell_out' @@ -55,7 +56,12 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do let(:new_resource) do new_resource = Chef::Resource::Cron.new("Chef functional test cron", run_context) new_resource.user 'root' - new_resource.minute '@hourly' + # @hourly is not supported on solaris + if ohai[:platform] == "solaris" || ohai[:platform] == "solaris2" + new_resource.minute "0 * * * *" + else + new_resource.minute '@hourly' + end new_resource.hour '' new_resource.day '' new_resource.month '' diff --git a/spec/functional/resource/dsc_script_spec.rb b/spec/functional/resource/dsc_script_spec.rb index fa13296c02..a736949c6b 100644 --- a/spec/functional/resource/dsc_script_spec.rb +++ b/spec/functional/resource/dsc_script_spec.rb @@ -81,17 +81,28 @@ describe Chef::Resource::DscScript, :windows_powershell_dsc_only do let(:test_registry_value) { 'Registration' } let(:test_registry_data1) { 'LL927' } let(:test_registry_data2) { 'LL928' } - let(:dsc_code) { <<-EOH + let(:reg_key_name_param_name) { 'testregkeyname' } + let(:reg_key_value_param_name) { 'testregvaluename' } + let(:registry_embedded_parameters) { "$#{reg_key_name_param_name} = '#{test_registry_key}';$#{reg_key_value_param_name} = '#{test_registry_value}'"} + let(:dsc_reg_code) { <<-EOH + #{registry_embedded_parameters} Registry "ChefRegKey" { - Key = '#{test_registry_key}' - ValueName = '#{test_registry_value}' + Key = $#{reg_key_name_param_name} + ValueName = $#{reg_key_value_param_name} ValueData = '#{test_registry_data}' Ensure = 'Present' } EOH } + let(:dsc_code) { dsc_reg_code } + let(:dsc_reg_script) { <<-EOH + param($testregkeyname, $testregvaluename) + #{dsc_reg_code} +EOH + } + let(:dsc_user_prefix) { 'dsc' } let(:dsc_user_suffix) { 'chefx' } let(:dsc_user) {"#{dsc_user_prefix}_usr_#{dsc_user_suffix}" } @@ -175,7 +186,7 @@ environment "whatsmydir" Ensure = 'Present' } EOH - } +} let(:dsc_config_name) { dsc_test_resource_base.name @@ -227,41 +238,79 @@ environment 'removethis' EOH removal_resource.run_action(:run) end - let(:dsc_code) { dsc_environment_config } - it 'should not raise an exception if the cwd is not etc' do - dsc_test_resource.cwd(dsc_environment_no_fail_not_etc_directory) - expect {dsc_test_resource.run_action(:run)}.not_to raise_error - end - it 'should raise an exception if the cwd is etc' do - dsc_test_resource.cwd(dsc_environment_fail_etc_directory) - expect {dsc_test_resource.run_action(:run)}.to raise_error(Chef::Exceptions::PowershellCmdletException) - begin - dsc_test_resource.run_action(:run) - rescue Chef::Exceptions::PowershellCmdletException => e - expect(e.message).to match(exception_message_signature) + describe 'when the DSC configuration contains code that raises an exception if cwd has a specific value' do + let(:dsc_code) { dsc_environment_config } + it 'should not raise an exception if the cwd is not etc' do + dsc_test_resource.cwd(dsc_environment_no_fail_not_etc_directory) + expect {dsc_test_resource.run_action(:run)}.not_to raise_error + end + + it 'should raise an exception if the cwd is etc' do + dsc_test_resource.cwd(dsc_environment_fail_etc_directory) + expect {dsc_test_resource.run_action(:run)}.to raise_error(Chef::Exceptions::PowershellCmdletException) + begin + dsc_test_resource.run_action(:run) + rescue Chef::Exceptions::PowershellCmdletException => e + expect(e.message).to match(exception_message_signature) + end end end end shared_examples_for 'a parameterized DSC configuration script' do - context 'when specifying environment variables in the environment attribute' do - let(:dsc_user_prefix_code) { dsc_user_prefix_env_code } - let(:dsc_user_suffix_code) { dsc_user_suffix_env_code } - it_behaves_like 'a dsc_script with configuration that uses environment variables' + let(:dsc_user_prefix_code) { dsc_user_prefix_env_code } + let(:dsc_user_suffix_code) { dsc_user_suffix_env_code } + it_behaves_like 'a dsc_script with configuration that uses environment variables' + end + + shared_examples_for 'a dsc_script without configuration data that takes parameters' do + context 'when configuration data is not specified' do + + before(:each) do + test_key_resource = Chef::Resource::RegistryKey.new(test_registry_key, dsc_test_run_context) + test_key_resource.recursive(true) + test_key_resource.run_action(:delete_key) + end + + after(:each) do + test_key_resource = Chef::Resource::RegistryKey.new(test_registry_key, dsc_test_run_context) + test_key_resource.recursive(true) + test_key_resource.run_action(:delete_key) + end + + let(:test_registry_data) { test_registry_data1 } + let(:dsc_parameterized_env_param_value) { "val" + Random::rand.to_s } + + it 'should have a default value of nil for the configuration_data attribute' do + expect(dsc_test_resource.configuration_data).to eql(nil) + end + + it 'should have a default value of nil for the configuration_data_path attribute' do + expect(dsc_test_resource.configuration_data_script).to eql(nil) + end + + let(:dsc_test_resource) { dsc_resource_from_path } + let(:registry_embedded_parameters) { '' } + let(:dsc_code) { dsc_reg_script } + + it 'should set a registry key according to parameters passed to the configuration' do + dsc_test_resource.configuration_name(config_name_value) + dsc_test_resource.flags({:"#{reg_key_name_param_name}" => test_registry_key, :"#{reg_key_value_param_name}" => test_registry_value}) + expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(false) + dsc_test_resource.run_action(:run) + expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(true) + expect(dsc_test_resource.registry_value_exists?(test_registry_key, {:name => test_registry_value, :type => :string, :data => test_registry_data})).to eq(true) + end end end shared_examples_for 'a dsc_script with configuration data' do - context 'when using the configuration_data attribute' do - let(:configuration_data_attribute) { 'configuration_data' } - it_behaves_like 'a dsc_script with configuration data set via an attribute' - end + let(:configuration_data_attribute) { 'configuration_data' } + it_behaves_like 'a dsc_script with configuration data set via an attribute' - context 'when using the configuration_data_script attribute' do - let(:configuration_data_attribute) { 'configuration_data_script' } - it_behaves_like 'a dsc_script with configuration data set via an attribute' - end + let(:configuration_data_attribute) { 'configuration_data_script' } + it_behaves_like 'a dsc_script with configuration data set via an attribute' end shared_examples_for 'a dsc_script with configuration data set via an attribute' do @@ -282,33 +331,28 @@ EOH end shared_examples_for 'a dsc_script with configuration data that takes parameters' do - context 'when script code takes parameters for configuration' do - let(:dsc_user_code) { dsc_user_param_code } - let(:config_param_section) { config_params } - let(:config_flags) {{:"#{dsc_user_prefix_param_name}" => "#{dsc_user_prefix}", :"#{dsc_user_suffix_param_name}" => "#{dsc_user_suffix}"}} - it 'does not directly contain the user name' do - configuration_script_content = ::File.open(dsc_test_resource.command) do | file | - file.read - end - expect(configuration_script_content.include?(dsc_user)).to be(false) + let(:dsc_user_code) { dsc_user_param_code } + let(:config_param_section) { config_params } + let(:config_flags) {{:"#{dsc_user_prefix_param_name}" => "#{dsc_user_prefix}", :"#{dsc_user_suffix_param_name}" => "#{dsc_user_suffix}"}} + it 'does not directly contain the user name' do + configuration_script_content = ::File.open(dsc_test_resource.command) do | file | + file.read end - it_behaves_like 'a dsc_script with configuration data' + expect(configuration_script_content.include?(dsc_user)).to be(false) end - + it_behaves_like 'a dsc_script with configuration data' end shared_examples_for 'a dsc_script with configuration data that uses environment variables' do - context 'when script code uses environment variables' do - let(:dsc_user_code) { dsc_user_env_code } + let(:dsc_user_code) { dsc_user_env_code } - it 'does not directly contain the user name' do - configuration_script_content = ::File.open(dsc_test_resource.command) do | file | - file.read - end - expect(configuration_script_content.include?(dsc_user)).to be(false) + it 'does not directly contain the user name' do + configuration_script_content = ::File.open(dsc_test_resource.command) do | file | + file.read end - it_behaves_like 'a dsc_script with configuration data' + expect(configuration_script_content.include?(dsc_user)).to be(false) end + it_behaves_like 'a dsc_script with configuration data' end context 'when supplying configuration through the configuration attribute' do @@ -333,5 +377,6 @@ EOH it_behaves_like 'a dsc_script with configuration data' it_behaves_like 'a dsc_script with configuration data that uses environment variables' it_behaves_like 'a dsc_script with configuration data that takes parameters' + it_behaves_like 'a dsc_script without configuration data that takes parameters' end end diff --git a/spec/functional/resource/env_spec.rb b/spec/functional/resource/env_spec.rb index 24fe5e1dff..8178eeba3d 100755 --- a/spec/functional/resource/env_spec.rb +++ b/spec/functional/resource/env_spec.rb @@ -22,6 +22,7 @@ describe Chef::Resource::Env, :windows_only do context 'when running on Windows' do let(:chef_env_test_lower_case) { 'chefenvtest' } let(:chef_env_test_mixed_case) { 'chefENVtest' } + let(:env_dne_key) { 'env_dne_key' } let(:env_value1) { 'value1' } let(:env_value2) { 'value2' } @@ -126,7 +127,8 @@ describe Chef::Resource::Env, :windows_only do context 'when using PATH' do let(:random_name) { Time.now.to_i } let(:env_val) { "#{env_value_expandable}_#{random_name}"} - let(:path_before) { test_resource.provider_for_action(test_resource.action).env_value('PATH') } + let!(:path_before) { test_resource.provider_for_action(test_resource.action).env_value('PATH') || '' } + let!(:env_path_before) { ENV['PATH'] } it 'should expand PATH' do path_before.should_not include(env_val) @@ -142,9 +144,7 @@ describe Chef::Resource::Env, :windows_only do test_resource.key_name('PATH') test_resource.value(path_before) test_resource.run_action(:create) - if test_resource.provider_for_action(test_resource.action).env_value('PATH') != path_before - raise 'Failed to cleanup after ourselves' - end + ENV['PATH'] = env_path_before end end @@ -178,6 +178,14 @@ describe Chef::Resource::Env, :windows_only do expect(ENV[chef_env_test_lower_case]).to eq(nil) expect(ENV[chef_env_test_mixed_case]).to eq(nil) end + + it "should delete a value from the current process even if it is not in the registry" do + expect(ENV[env_dne_key]).to eq(nil) + ENV[env_dne_key] = env_value1 + test_resource.key_name(env_dne_key) + test_resource.run_action(:delete) + expect(ENV[env_dne_key]).to eq(nil) + end end end end diff --git a/spec/functional/resource/execute_spec.rb b/spec/functional/resource/execute_spec.rb new file mode 100644 index 0000000000..ff358fe045 --- /dev/null +++ b/spec/functional/resource/execute_spec.rb @@ -0,0 +1,113 @@ +# +# Author:: Serdar Sutay (<serdar@opscode.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 'spec_helper' +require 'functional/resource/base' + +describe Chef::Resource::Execute do + let(:execute_resource) { + exec_resource = Chef::Resource::Execute.new("foo_resource", run_context) + + exec_resource.environment(resource_environment) if resource_environment + exec_resource.cwd(resource_cwd) if resource_cwd + exec_resource.command("echo hello") + if guard + if guard_options + exec_resource.only_if(guard, guard_options) + else + exec_resource.only_if(guard) + end + end + exec_resource + } + + let(:resource_environment) { nil } + let(:resource_cwd) { nil } + let(:guard) { nil } + let(:guard_options) { nil } + + describe "when guard is ruby block" do + it "guard can still run" do + execute_resource.only_if do + true + end + execute_resource.run_action(:run) + execute_resource.should be_updated_by_last_action + end + end + + describe "when parent resource sets :cwd" do + let(:resource_cwd) { CHEF_SPEC_DATA } + + let(:guard) { %{ruby -e 'exit 1 unless File.exists?("./big_json_plus_one.json")'} } + + it "guard inherits :cwd from resource" do + execute_resource.run_action(:run) + execute_resource.should be_updated_by_last_action + end + end + + describe "when parent resource sets :environment" do + let(:resource_environment) do + { + "SAWS_SECRET" => "supersecret", + "SAWS_KEY" => "qwerty" + } + end + + # We use ruby command so that we don't need to deal with platform specific + # commands while testing execute resource. We set it so that the resource + # will be updated if the ENV variable is set to what we are intending + let(:guard) { %{ruby -e 'exit 1 if ENV["SAWS_SECRET"] != "supersecret"'} } + + it "guard inherits :environment value from resource" do + execute_resource.run_action(:run) + execute_resource.should be_updated_by_last_action + end + + describe "when guard sets additional values in the :environment" do + let(:guard) { %{ruby -e 'exit 1 if ENV["SGCE_SECRET"] != "regularsecret"'} } + + let(:guard_options) do + { + :environment => { 'SGCE_SECRET' => "regularsecret" } + } + end + + it "guard sees merged value for in its ENV" do + execute_resource.run_action(:run) + execute_resource.should be_updated_by_last_action + end + end + + describe "when guard sets same value in the :environment" do + let(:guard) { %{ruby -e 'exit 1 if ENV["SAWS_SECRET"] != "regularsecret"'} } + + let(:guard_options) do + { + :environment => { 'SAWS_SECRET' => "regularsecret" } + } + end + + it "guard sees value from guard options in its ENV" do + execute_resource.run_action(:run) + execute_resource.should be_updated_by_last_action + end + end + end +end diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb index 99966f85c8..83f051ea06 100644 --- a/spec/functional/resource/file_spec.rb +++ b/spec/functional/resource/file_spec.rb @@ -17,6 +17,7 @@ # require 'spec_helper' +require 'tmpdir' describe Chef::Resource::File do include_context Chef::Resource::File @@ -30,6 +31,7 @@ describe Chef::Resource::File do run_context = Chef::RunContext.new(node, {}, events) use_path = if opts[:use_relative_path] + Dir.chdir(Dir.tmpdir) File.basename(path) else path diff --git a/spec/functional/resource/powershell_spec.rb b/spec/functional/resource/powershell_spec.rb index 96a356f441..e1e9f787a3 100644 --- a/spec/functional/resource/powershell_spec.rb +++ b/spec/functional/resource/powershell_spec.rb @@ -22,6 +22,12 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do include_context Chef::Resource::WindowsScript + let (:architecture_command) { 'echo $env:PROCESSOR_ARCHITECTURE' } + let (:output_command) { ' | out-file -encoding ASCII ' } + + it_behaves_like "a Windows script running on Windows" + + let(:successful_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe $env:systemroot" } let(:failed_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe /badargument" } let(:processor_architecture_script_content) { "echo $env:PROCESSOR_ARCHITECTURE" } @@ -36,6 +42,7 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do let(:arbitrary_nonzero_process_exit_code_content) { "exit #{arbitrary_nonzero_process_exit_code}" } let(:invalid_powershell_interpreter_flag) { "/thisflagisinvalid" } let(:valid_powershell_interpreter_flag) { "-Sta" } + let!(:resource) do r = Chef::Resource::WindowsScript::PowershellScript.new("Powershell resource functional test", @run_context) r.code(successful_executable_script_content) @@ -214,32 +221,36 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do before(:each) do resource.not_if.clear resource.only_if.clear - # resource.guard_interpreter should be :default by default end - it "evaluates a succeeding not_if block using cmd.exe as false by default" do - resource.not_if "exit /b 0" - resource.should_skip?(:run).should be_true - end + context "when the guard_interpreter's default value of :powershell_script is overridden to :default" do + before(:each) do + resource.guard_interpreter :default + end - it "evaluates a failing not_if block using cmd.exe as true by default" do - resource.not_if "exit /b 2" - resource.should_skip?(:run).should be_false - end + it "evaluates a succeeding not_if block using cmd.exe as false by default" do + resource.not_if "exit /b 0" + resource.should_skip?(:run).should be_true + end - it "evaluates an succeeding only_if block using cmd.exe as true by default" do - resource.only_if "exit /b 0" - resource.should_skip?(:run).should be_false - end + it "evaluates a failing not_if block using cmd.exe as true by default" do + 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.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.only_if "exit /b 2" - resource.should_skip?(:run).should be_true + it "evaluates a failing only_if block using cmd.exe as false by default" do + resource.only_if "exit /b 2" + resource.should_skip?(:run).should be_true + end end context "the only_if is specified before the guard" do before do - # force the guard_interpreter to :default in case the default changes later resource.guard_interpreter :default end @@ -251,8 +262,9 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end context "with powershell_script as the guard_interpreter" do - before(:each) do - resource.guard_interpreter :powershell_script + + it "has a guard_interpreter attribute set to :powershell_script" do + expect(resource.guard_interpreter).to eq(:powershell_script) end it "evaluates a powershell $false for a not_if block as true" do diff --git a/spec/functional/resource/rpm_spec.rb b/spec/functional/resource/rpm_spec.rb index 7825377c6b..b37ee781f1 100644 --- a/spec/functional/resource/rpm_spec.rb +++ b/spec/functional/resource/rpm_spec.rb @@ -16,6 +16,7 @@ # limitations under the License. # +require 'spec_helper' require 'functional/resource/base' require 'chef/mixin/shell_out' @@ -60,12 +61,12 @@ describe Chef::Resource::RpmPackage, :requires_root, :external => exclude_test d @pkg_name = "dummy" @pkg_version = "1-0" @pkg_path = "/tmp/dummy-1-0.aix6.1.noarch.rpm" - FileUtils.cp 'spec/functional/assets/dummy-1-0.aix6.1.noarch.rpm' , @pkg_path + FileUtils.cp(File.join(CHEF_SPEC_ASSETS, 'dummy-1-0.aix6.1.noarch.rpm') , @pkg_path) when "centos", "redhat", "suse" @pkg_name = "mytest" @pkg_version = "1.0-1" @pkg_path = "/tmp/mytest-1.0-1.noarch.rpm" - FileUtils.cp 'spec/functional/assets/mytest-1.0-1.noarch.rpm' , @pkg_path + FileUtils.cp(File.join(CHEF_SPEC_ASSETS, 'mytest-1.0-1.noarch.rpm') , @pkg_path) end end @@ -101,11 +102,11 @@ describe Chef::Resource::RpmPackage, :requires_root, :external => exclude_test d if ohai[:platform] == 'aix' @pkg_version = "2-0" @pkg_path = "/tmp/dummy-2-0.aix6.1.noarch.rpm" - FileUtils.cp 'spec/functional/assets/dummy-2-0.aix6.1.noarch.rpm' , @pkg_path + FileUtils.cp(File.join(CHEF_SPEC_ASSETS, 'dummy-2-0.aix6.1.noarch.rpm') , @pkg_path) else @pkg_version = "2.0-1" @pkg_path = "/tmp/mytest-2.0-1.noarch.rpm" - FileUtils.cp 'spec/functional/assets/mytest-2.0-1.noarch.rpm' , @pkg_path + FileUtils.cp(File.join(CHEF_SPEC_ASSETS, 'mytest-2.0-1.noarch.rpm') , @pkg_path) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1760aab871..b1d7cdbd64 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -119,6 +119,7 @@ RSpec.configure do |config| config.filter_run_excluding :solaris_only => true unless solaris? config.filter_run_excluding :system_windows_service_gem_only => true unless system_windows_service_gem? config.filter_run_excluding :unix_only => true unless unix? + config.filter_run_excluding :aix_only => true unless aix? config.filter_run_excluding :supports_cloexec => true unless supports_cloexec? config.filter_run_excluding :selinux_only => true unless selinux_enabled? config.filter_run_excluding :ruby_18_only => true unless ruby_18? @@ -133,6 +134,7 @@ RSpec.configure do |config| config.filter_run_excluding :ruby_gte_20_and_openssl_gte_101 => true unless (ruby_gte_20? && openssl_gte_101?) config.filter_run_excluding :openssl_lt_101 => true unless openssl_lt_101? config.filter_run_excluding :ruby_lt_20 => true unless ruby_lt_20? + config.filter_run_excluding :aes_256_gcm_only => true unless aes_256_gcm? running_platform_arch = `uname -m`.strip @@ -162,6 +164,10 @@ RSpec.configure do |config| config.before(:each) do Chef::Config.reset end + + config.before(:suite) do + ARGV.clear + end end require 'webrick/utils' diff --git a/spec/support/chef_helpers.rb b/spec/support/chef_helpers.rb index f31355f50a..237543748c 100644 --- a/spec/support/chef_helpers.rb +++ b/spec/support/chef_helpers.rb @@ -14,6 +14,7 @@ # limitations under the License. # CHEF_SPEC_DATA = File.expand_path(File.dirname(__FILE__) + "/../data/") +CHEF_SPEC_ASSETS = File.expand_path(File.dirname(__FILE__) + "/../functional/assets/") CHEF_SPEC_BACKUP_PATH = File.join(Dir.tmpdir, 'test-backup-path') Chef::Config[:log_level] = :fatal diff --git a/spec/support/lib/chef/provider/snakeoil.rb b/spec/support/lib/chef/provider/snakeoil.rb index e9d01f654f..485d37329f 100644 --- a/spec/support/lib/chef/provider/snakeoil.rb +++ b/spec/support/lib/chef/provider/snakeoil.rb @@ -19,6 +19,7 @@ class Chef class Provider class SnakeOil < Chef::Provider + def load_current_resource true end diff --git a/spec/support/lib/chef/resource/zen_follower.rb b/spec/support/lib/chef/resource/zen_follower.rb index 0fa0c4af5b..ddc289e48d 100644 --- a/spec/support/lib/chef/resource/zen_follower.rb +++ b/spec/support/lib/chef/resource/zen_follower.rb @@ -21,20 +21,14 @@ require 'chef/json_compat' class Chef class Resource class ZenFollower < Chef::Resource - attr_accessor :created_as_type - provides :follower, :on_platforms => ["zen"] + provides :follower, platform: "zen" def initialize(name, run_context=nil) @resource_name = :zen_follower - @created_as_type = "zen_follower" super end - def to_s - "#{created_as_type}[#{name}]" - end - def master(arg=nil) if !arg.nil? @master = arg diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb index f8cad6de7f..8d17af3993 100644 --- a/spec/support/platform_helpers.rb +++ b/spec/support/platform_helpers.rb @@ -165,3 +165,7 @@ end def openssl_lt_101? !openssl_gte_101? end + +def aes_256_gcm? + OpenSSL::Cipher.ciphers.include?("aes-256-gcm") +end diff --git a/spec/support/shared/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb index fc06fb55d0..f677828167 100644 --- a/spec/support/shared/functional/windows_script.rb +++ b/spec/support/shared/functional/windows_script.rb @@ -44,4 +44,122 @@ shared_context Chef::Resource::WindowsScript do after(:each) do File.delete(script_output_path) if File.exists?(script_output_path) end + + let!(:resource) do + Chef::Resource::WindowsScript::Batch.new("Batch resource functional test", @run_context) + end + + shared_examples_for "a script resource with architecture attribute" do + context "with the given architecture attribute value" do + let(:resource_architecture) { architecture } + let(:expected_architecture) do + if architecture + expected_architecture = architecture + else + expected_architecture = :i386 + end + end + let(:expected_architecture_output) do + expected_architecture == :i386 ? 'X86' : 'AMD64' + end + let(:guard_script_suffix) do + "guard" + end + let(:guard_script_output_path) do + "#{script_output_path}#{guard_script_suffix}" + end + let(:resource_command) do + "#{architecture_command} #{output_command} #{script_output_path}" + end + let(:resource_guard_command) do + "#{architecture_command} #{output_command} #{guard_script_output_path}" + end + + before(:each) do + resource.code resource_command + (resource.architecture architecture) if architecture + resource.returns(0) + end + + it "should create a process with the expected architecture" do + resource.run_action(:run) + get_process_architecture.should == expected_architecture_output.downcase + end + + it "should execute guards with the same architecture as the resource" do + resource.only_if resource_guard_command + resource.run_action(:run) + get_process_architecture.should == expected_architecture_output.downcase + get_guard_process_architecture.should == expected_architecture_output.downcase + get_guard_process_architecture.should == get_process_architecture + end + + let (:architecture) { :x86_64 } + it "should execute a 64-bit guard if the guard's architecture is specified as 64-bit" do + resource.only_if resource_guard_command, :architecture => :x86_64 + resource.run_action(:run) + get_guard_process_architecture.should == 'amd64' + end + + let (:architecture) { :i386 } + it "should execute a 32-bit guard if the guard's architecture is specified as 32-bit" do + resource.only_if resource_guard_command, :architecture => :i386 + resource.run_action(:run) + get_guard_process_architecture.should == 'x86' + end + end + end + + shared_examples_for "a Windows script running on Windows" do + + describe "when the run action is invoked on Windows" do + it "executes the script code" do + resource.code("@whoami > #{script_output_path}") + resource.returns(0) + resource.run_action(:run) + end + end + + context "when evaluating guards" do + it "has a guard_interpreter attribute set to the short name of the resource" do + resource.guard_interpreter.should == resource.resource_name + resource.not_if "findstr.exe /thiscommandhasnonzeroexitstatus" + expect(Chef::Resource).to receive(:resource_for_node).and_call_original + expect(resource.class).to receive(:new).and_call_original + resource.should_skip?(:run).should be_false + end + end + + context "when the architecture attribute is not set" do + let(:architecture) { nil } + it_behaves_like "a script resource with architecture attribute" + end + + context "when the architecture attribute is :i386" do + let(:architecture) { :i386 } + it_behaves_like "a script resource with architecture attribute" + end + + context "when the architecture attribute is :x86_64" do + let(:architecture) { :x86_64 } + it_behaves_like "a script resource with architecture attribute" + end + end + + def get_windows_script_output(suffix = '') + File.read("#{script_output_path}#{suffix}") + end + + def source_contains_case_insensitive_content?( source, content ) + source.downcase.include?(content.downcase) + end + + def get_guard_process_architecture + get_process_architecture(guard_script_suffix) + end + + def get_process_architecture(suffix = '') + get_windows_script_output(suffix).strip.downcase + end + end diff --git a/spec/support/shared/unit/execute_resource.rb b/spec/support/shared/unit/execute_resource.rb index 609e77ad63..298e0c5baf 100644 --- a/spec/support/shared/unit/execute_resource.rb +++ b/spec/support/shared/unit/execute_resource.rb @@ -76,11 +76,6 @@ shared_examples_for "an execute resource" do @resource.group.should eql(1) end - it "should accept an array for the execution path" do - @resource.path ["woot"] - @resource.path.should eql(["woot"]) - end - it "should accept an integer for the return code" do @resource.returns 1 @resource.returns.should eql(1) @@ -112,7 +107,6 @@ shared_examples_for "an execute resource" do @resource.cwd("/tmp/") @resource.environment({ :one => :two }) @resource.group("legos") - @resource.path(["/var/local/"]) @resource.returns(1) @resource.user("root") end @@ -122,4 +116,3 @@ shared_examples_for "an execute resource" do end end end - diff --git a/spec/support/shared/unit/resource/static_provider_resolution.rb b/spec/support/shared/unit/resource/static_provider_resolution.rb new file mode 100644 index 0000000000..147852598a --- /dev/null +++ b/spec/support/shared/unit/resource/static_provider_resolution.rb @@ -0,0 +1,71 @@ +# +# Author:: Lamont Granquist (<lamont@chef.io>) +# 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. +# + + + +# +# This is for typical "static" provider resolution which maps resources onto +# providers based only on the node data. Its not really 'static' because it +# all goes through the Chef::ProviderResolver, but the effective result is +# a static mapping for the node (unlike the service resource which is +# complicated). +# +def static_provider_resolution(opts={}) + action = opts[:action] + provider_class = opts[:provider] + resource_class = opts[:resource] + name = opts[:name] + os = opts[:os] + platform_family = opts[:platform_family] + platform_version = opts[:platform_version] + + describe resource_class, "static provider initialization" do + let(:node) { + node = Chef::Node.new + node.automatic_attrs[:os] = os + node.automatic_attrs[:platform_family] = platform_family + node.automatic_attrs[:platform_version] = platform_version + node + } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:provider_resolver) { Chef::ProviderResolver.new(node) } + let(:run_context) { + run_context = Chef::RunContext.new(node, {}, events) + run_context.provider_resolver = provider_resolver + run_context + } + let(:resource) { resource_class.new("foo", run_context) } + + it "should return a #{resource_class}" do + expect(resource).to be_a_kind_of(resource_class) + end + + it "should set the resource_name to #{name}" do + expect(resource.resource_name).to eql(name) + end + + it "should leave the provider nil" do + expect(resource.provider).to eql(nil) + end + + it "should resolve to a #{provider_class}" do + expect(resource.provider_for_action(action)).to be_a(provider_class) + end + end +end + diff --git a/spec/support/shared/unit/script_resource.rb b/spec/support/shared/unit/script_resource.rb index 1137958420..a34f930fc8 100644 --- a/spec/support/shared/unit/script_resource.rb +++ b/spec/support/shared/unit/script_resource.rb @@ -72,8 +72,8 @@ shared_examples_for "a script resource" do 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) + Chef::GuardInterpreter::DefaultGuardInterpreter.any_instance.should_receive(:evaluate).and_return(true) resource.only_if 'echo hi' resource.should_skip?(:run).should == nil end diff --git a/spec/support/shared/unit/windows_script_resource.rb b/spec/support/shared/unit/windows_script_resource.rb index 23dbfbe722..888ad600c5 100644 --- a/spec/support/shared/unit/windows_script_resource.rb +++ b/spec/support/shared/unit/windows_script_resource.rb @@ -39,8 +39,41 @@ shared_examples_for "a Windows script resource" do @resource.should be_a_kind_of(Chef::Resource::WindowsScript) end - context "script" do - let(:script_resource) { resource_instance } + context "when evaluating guards" do + it "should have a default_guard_interpreter attribute that is the same as the resource" do + @resource.default_guard_interpreter.should == @resource.resource_name + end + + it "should default to using guard_interpreter attribute that is the same as the resource" do + @resource.guard_interpreter.should == @resource.resource_name + end + + it "should use a resource to evaluate the guard when guard_interpreter is not specified" do + Chef::GuardInterpreter::ResourceGuardInterpreter.any_instance.should_receive(:evaluate_action).and_return(true) + Chef::GuardInterpreter::DefaultGuardInterpreter.any_instance.should_not_receive(:evaluate) + @resource.only_if 'echo hi' + @resource.should_skip?(:run).should == nil + end + + describe "when the guard is given a ruby block" do + it "should evaluate the guard if the guard_interpreter is set to its default value" do + @resource.only_if { true } + @resource.should_skip?(:run).should == nil + end + + it "should raise an exception if the guard_interpreter is overridden from its default value" do + @resource.guard_interpreter :bash + @resource.only_if { true } + expect { @resource.should_skip?(:run) }.to raise_error + end + end + end + + context "script with a default guard interpreter" do + let(:script_resource) do + resource_instance.guard_interpreter :default + resource_instance + end it_should_behave_like "a script resource" end diff --git a/spec/unit/application/apply.rb b/spec/unit/application/apply_spec.rb index 62a53c2a31..e29c038340 100644 --- a/spec/unit/application/apply.rb +++ b/spec/unit/application/apply_spec.rb @@ -35,24 +35,32 @@ describe Chef::Application::Apply do describe "read_recipe_file" do before do @recipe_file_name = "foo.rb" - @recipe_path = File.expand_path("foo.rb") + @recipe_path = File.expand_path(@recipe_file_name) @recipe_file = double("Tempfile (mock)", :read => @recipe_text) @app.stub(:open).with(@recipe_path).and_return(@recipe_file) - File.stub(:exist?).with("foo.rb").and_return(true) + File.stub(:exist?).with(@recipe_path).and_return(true) Chef::Application.stub(:fatal!).and_return(true) end + it "should read text properly" do @app.read_recipe_file(@recipe_file_name)[0].should == @recipe_text end it "should return a file_handle" do @app.read_recipe_file(@recipe_file_name)[1].should be_instance_of(RSpec::Mocks::Mock) end + + describe "when recipe is nil" do + it "should raise a fatal with the missing filename message" do + Chef::Application.should_receive(:fatal!).with("No recipe file was provided", 1) + @app.read_recipe_file(nil) + end + end describe "when recipe doesn't exist" do before do - File.stub(:exist?).with(@recipe_file_name).and_return(false) + File.stub(:exist?).with(@recipe_path).and_return(false) end - it "should raise a fatal" do - Chef::Application.should_receive(:fatal!) + it "should raise a fatal with the file doesn't exist message" do + Chef::Application.should_receive(:fatal!).with(/^No file exists at/, 1) @app.read_recipe_file(@recipe_file_name) end end diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index e05245c413..e03773ae03 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -61,6 +61,7 @@ describe Chef::Client do let(:client_opts) { {} } let(:client) do + Chef::Config[:event_loggers] = [] Chef::Client.new(json_attribs, client_opts).tap do |c| c.node = node end @@ -384,7 +385,6 @@ describe Chef::Client do @events = double("Chef::EventDispatch::Dispatcher").as_null_object Chef::EventDispatch::Dispatcher.stub(:new).and_return(@events) - # @events is created on Chef::Client.new, so we need to recreate it after mocking client = Chef::Client.new client.stub(:load_node).and_raise(Exception) diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb index 41411669e6..cc83ca3c45 100644 --- a/spec/unit/config_spec.rb +++ b/spec/unit/config_spec.rb @@ -417,6 +417,95 @@ describe Chef::Config do end end end + + describe "Chef::Config[:internal_locale]" do + let(:shell_out) do + double("Chef::Mixin::ShellOut double", :exitstatus => 0, :stdout => locales) + end + + let(:locales) { locale_array.join("\n") } + + before do + allow(Chef::Config).to receive(:shell_out_with_systems_locale).with("locale -a").and_return(shell_out) + end + + shared_examples_for "a suitable locale" do + it "returns an English UTF-8 locale" do + expect(Chef::Log).to_not receive(:warn).with(/Please install an English UTF-8 locale for Chef to use/) + expect(Chef::Log).to_not receive(:debug).with(/Defaulting to locale en_US.UTF-8 on Windows/) + expect(Chef::Log).to_not receive(:debug).with(/No usable locale -a command found/) + expect(Chef::Config[:internal_locale]).to eq expected_locale + end + end + + context "when the result includes 'C.UTF-8'" do + include_examples "a suitable locale" do + let(:locale_array) { [expected_locale, "en_US.UTF-8"] } + let(:expected_locale) { "C.UTF-8" } + end + end + + context "when the result includes 'en_US.UTF-8'" do + include_examples "a suitable locale" do + let(:locale_array) { ["en_CA.UTF-8", expected_locale, "en_NZ.UTF-8"] } + let(:expected_locale) { "en_US.UTF-8" } + end + end + + context "when the result includes 'en_US.utf8'" do + include_examples "a suitable locale" do + let(:locale_array) { ["en_CA.utf8", "en_US.utf8", "en_NZ.utf8"] } + let(:expected_locale) { "en_US.UTF-8" } + end + end + + context "when the result includes 'en.UTF-8'" do + include_examples "a suitable locale" do + let(:locale_array) { ["en.ISO8859-1", expected_locale] } + let(:expected_locale) { "en.UTF-8" } + end + end + + context "when the result includes 'en_*.UTF-8'" do + include_examples "a suitable locale" do + let(:locale_array) { [expected_locale, "en_CA.UTF-8", "en_GB.UTF-8"] } + let(:expected_locale) { "en_AU.UTF-8" } + end + end + + context "when the result includes 'en_*.utf8'" do + include_examples "a suitable locale" do + let(:locale_array) { ["en_AU.utf8", "en_CA.utf8", "en_GB.utf8"] } + let(:expected_locale) { "en_AU.UTF-8" } + end + end + + context "when the result does not include 'en_*.UTF-8'" do + let(:locale_array) { ["af_ZA", "af_ZA.ISO8859-1", "af_ZA.ISO8859-15", "af_ZA.UTF-8"] } + + it "should fall back to C locale" do + expect(Chef::Log).to receive(:warn).with("Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support.") + expect(Chef::Config[:internal_locale]).to eq 'C' + end + end + + context "on error" do + let(:locale_array) { [] } + + before do + allow(Chef::Config).to receive(:shell_out_with_systems_locale).and_raise("THIS IS AN ERROR") + end + + it "should default to 'en_US.UTF-8'" do + if is_windows + expect(Chef::Log).to receive(:debug).with("Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else.") + else + expect(Chef::Log).to receive(:debug).with("No usable locale -a command found, assuming you have en_US.UTF-8 installed.") + end + expect(Chef::Config[:internal_locale]).to eq "en_US.UTF-8" + end + end + end end end end diff --git a/spec/unit/cookbook/cookbook_version_loader_spec.rb b/spec/unit/cookbook/cookbook_version_loader_spec.rb index 5772c5352d..4ba4e1de57 100644 --- a/spec/unit/cookbook/cookbook_version_loader_spec.rb +++ b/spec/unit/cookbook/cookbook_version_loader_spec.rb @@ -57,6 +57,11 @@ describe Chef::Cookbook::CookbookVersionLoader do expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/return.rb")) end + it "loads libraries" do + expect(loaded_cookbook.library_filenames).to include(full_path('/libraries/openldap.rb')) + expect(loaded_cookbook.library_filenames).to include(full_path('/libraries/openldap/version.rb')) + end + it "loads static files in the files/ dir" do expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file1.txt")) expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file2.txt")) diff --git a/spec/unit/cookbook/metadata_spec.rb b/spec/unit/cookbook/metadata_spec.rb index 86be0d2390..51814320d4 100644 --- a/spec/unit/cookbook/metadata_spec.rb +++ b/spec/unit/cookbook/metadata_spec.rb @@ -29,7 +29,8 @@ describe Chef::Cookbook::Metadata do @fields = [ :name, :description, :long_description, :maintainer, :maintainer_email, :license, :platforms, :dependencies, :recommendations, :suggestions, :conflicting, :providing, - :replacing, :attributes, :groupings, :recipes, :version] + :replacing, :attributes, :groupings, :recipes, :version, + :source_url, :issues_url ] end it "does not depend on object identity for equality" do @@ -140,6 +141,13 @@ describe Chef::Cookbook::Metadata do metadata.recipes.should eq(Mash.new) end + it "has an empty source_url string" do + metadata.source_url.should eq('') + end + + it "has an empty issues_url string" do + metadata.issues_url.should eq('') + end end describe "validation" do @@ -188,7 +196,9 @@ describe Chef::Cookbook::Metadata do :license => "Apache v2.0", :description => "Foobar!", :long_description => "Much Longer\nSeriously", - :version => "0.6.0" + :version => "0.6.0", + :source_url => "http://example.com", + :issues_url => "http://example.com/issues" } params.sort { |a,b| a.to_s <=> b.to_s }.each do |field, field_value| describe field do @@ -333,7 +343,9 @@ describe Chef::Cookbook::Metadata do "type" => 'string', "required" => 'recommended', "recipes" => [ "mysql::server", "mysql::master" ], - "default" => [ ] + "default" => [ ], + "source_url" => "http://example.com", + "issues_url" => "http://example.com/issues" } metadata.attribute("/db/mysql/databases", attrs).should == attrs end @@ -356,6 +368,24 @@ describe Chef::Cookbook::Metadata do }.should raise_error(ArgumentError) end + it "should not accept anything but a string for the source_url" do + lambda { + metadata.attribute("db/mysql/databases", :source_url => "foo") + }.should_not raise_error + lambda { + metadata.attribute("db/mysql/databases", :source_url => Hash.new) + }.should raise_error(ArgumentError) + end + + it "should not accept anything but a string for the issues_url" do + lambda { + metadata.attribute("db/mysql/databases", :issues_url => "foo") + }.should_not raise_error + lambda { + metadata.attribute("db/mysql/databases", :issues_url => Hash.new) + }.should raise_error(ArgumentError) + end + it "should not accept anything but an array of strings for choice" do lambda { metadata.attribute("db/mysql/databases", :choice => ['dedicated', 'shared']) @@ -652,6 +682,8 @@ describe Chef::Cookbook::Metadata do attributes recipes version + source_url + issues_url }.each do |t| it "should include '#{t}'" do deserialized_metadata[t].should == metadata.send(t.to_sym) @@ -685,6 +717,8 @@ describe Chef::Cookbook::Metadata do attributes recipes version + source_url + issues_url }.each do |t| it "should match '#{t}'" do deserialized_metadata.send(t.to_sym).should == metadata.send(t.to_sym) @@ -735,5 +769,4 @@ describe Chef::Cookbook::Metadata do end end - end diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb index cd1ce96716..4d22e0e920 100644 --- a/spec/unit/cookbook/syntax_check_spec.rb +++ b/spec/unit/cookbook/syntax_check_spec.rb @@ -28,10 +28,12 @@ describe Chef::Cookbook::SyntaxCheck do let(:syntax_check) { Chef::Cookbook::SyntaxCheck.new(cookbook_path) } let(:open_ldap_cookbook_files) do - %w{ attributes/default.rb + %w{ attributes/default.rb attributes/smokey.rb definitions/client.rb definitions/server.rb + libraries/openldap.rb + libraries/openldap/version.rb metadata.rb recipes/default.rb recipes/gigantor.rb @@ -44,9 +46,10 @@ describe Chef::Cookbook::SyntaxCheck do Chef::Log.level = :warn # suppress "Syntax OK" messages @attr_files = %w{default.rb smokey.rb}.map { |f| File.join(cookbook_path, 'attributes', f) } + @libr_files = %w{openldap.rb openldap/version.rb}.map { |f| File.join(cookbook_path, 'libraries', f) } @defn_files = %w{client.rb server.rb}.map { |f| File.join(cookbook_path, 'definitions', f)} @recipes = %w{default.rb gigantor.rb one.rb return.rb}.map { |f| File.join(cookbook_path, 'recipes', f) } - @ruby_files = @attr_files + @defn_files + @recipes + [File.join(cookbook_path, "metadata.rb")] + @ruby_files = @attr_files + @libr_files + @defn_files + @recipes + [File.join(cookbook_path, "metadata.rb")] basenames = %w{ helpers_via_partial_test.erb helper_test.erb openldap_stuff.conf.erb diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb index 25bc936569..8436e5c480 100644 --- a/spec/unit/cookbook_version_spec.rb +++ b/spec/unit/cookbook_version_spec.rb @@ -115,14 +115,13 @@ describe Chef::CookbookVersion do @cookbook[:provider_filenames] = Dir[File.join(@cookbook_root, 'providers', '**', '*.rb')] @cookbook[:root_filenames] = Array(File.join(@cookbook_root, 'README.rdoc')) @cookbook[:metadata_filenames] = Array(File.join(@cookbook_root, 'metadata.json')) - end describe "and a cookbook with the same name" do before do # Currently the cookbook loader finds all the files then tells CookbookVersion # where they are. - @cookbook_version = Chef::CookbookVersion.new("tatft", @cookbook_root) + @cookbook_version = Chef::CookbookVersion.new('tatft', @cookbook_root) @cookbook_version.attribute_filenames = @cookbook[:attribute_filenames] @cookbook_version.definition_filenames = @cookbook[:definition_filenames] @@ -350,6 +349,84 @@ describe Chef::CookbookVersion do readme["specificity"].should == "default" end end + end + + describe 'with a cookbook directory named cookbook2 that has unscoped files' do + before do + @cookbook = Hash.new { |hash, key| hash[key] = [] } + + @cookbook_root = File.join(CHEF_SPEC_DATA, 'cb_version_cookbooks', 'cookbook2') + + # Dunno if the paths here are representitive of what is set by CookbookLoader... + @cookbook[:attribute_filenames] = Dir[File.join(@cookbook_root, 'attributes', '**', '*.rb')] + @cookbook[:definition_filenames] = Dir[File.join(@cookbook_root, 'definitions', '**', '*.rb')] + @cookbook[:file_filenames] = Dir[File.join(@cookbook_root, 'files', '**', '*.*')] + @cookbook[:recipe_filenames] = Dir[File.join(@cookbook_root, 'recipes', '**', '*.rb')] + @cookbook[:template_filenames] = Dir[File.join(@cookbook_root, 'templates', '**', '*.*')] + @cookbook[:library_filenames] = Dir[File.join(@cookbook_root, 'libraries', '**', '*.rb')] + @cookbook[:resource_filenames] = Dir[File.join(@cookbook_root, 'resources', '**', '*.rb')] + @cookbook[:provider_filenames] = Dir[File.join(@cookbook_root, 'providers', '**', '*.rb')] + @cookbook[:root_filenames] = Array(File.join(@cookbook_root, 'README.rdoc')) + @cookbook[:metadata_filenames] = Array(File.join(@cookbook_root, 'metadata.json')) + + @cookbook_version = Chef::CookbookVersion.new('cookbook2', @cookbook_root) + @cookbook_version.attribute_filenames = @cookbook[:attribute_filenames] + @cookbook_version.definition_filenames = @cookbook[:definition_filenames] + @cookbook_version.recipe_filenames = @cookbook[:recipe_filenames] + @cookbook_version.template_filenames = @cookbook[:template_filenames] + @cookbook_version.file_filenames = @cookbook[:file_filenames] + @cookbook_version.library_filenames = @cookbook[:library_filenames] + @cookbook_version.resource_filenames = @cookbook[:resource_filenames] + @cookbook_version.provider_filenames = @cookbook[:provider_filenames] + @cookbook_version.root_filenames = @cookbook[:root_filenames] + @cookbook_version.metadata_filenames = @cookbook[:metadata_filenames] + + # Used to test file-specificity related file lookups + @node = Chef::Node.new + @node.set[:platform] = "ubuntu" + @node.set[:platform_version] = "13.04" + @node.name("testing") + end + + it "should see a template" do + @cookbook_version.should have_template_for_node(@node, "test.erb") + end + + it "should see a template using an array lookup" do + @cookbook_version.should have_template_for_node(@node, ["test.erb"]) + end + + it "should see a template using an array lookup with non-existant elements" do + @cookbook_version.should have_template_for_node(@node, ["missing.txt", "test.erb"]) + end + + it "should see a file" do + @cookbook_version.should have_cookbook_file_for_node(@node, "test.txt") + end + + it "should see a file using an array lookup" do + @cookbook_version.should have_cookbook_file_for_node(@node, ["test.txt"]) + end + + it "should see a file using an array lookup with non-existant elements" do + @cookbook_version.should have_cookbook_file_for_node(@node, ["missing.txt", "test.txt"]) + end + + it "should not see a non-existant template" do + @cookbook_version.should_not have_template_for_node(@node, "missing.erb") + end + + it "should not see a non-existant template using an array lookup" do + @cookbook_version.should_not have_template_for_node(@node, ["missing.erb"]) + end + + it "should not see a non-existant file" do + @cookbook_version.should_not have_cookbook_file_for_node(@node, "missing.txt") + end + + it "should not see a non-existant file using an array lookup" do + @cookbook_version.should_not have_cookbook_file_for_node(@node, ["missing.txt"]) + end end diff --git a/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb b/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb index 1da5efb36e..1e2b2a85e8 100644 --- a/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb +++ b/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb @@ -83,7 +83,7 @@ describe Chef::EncryptedDataBagItem::CheckEncrypted do end end - context "when encryption version is 3", :ruby_20_only do + context "when encryption version is 3", :aes_256_gcm_only, :ruby_20_only do include_examples "encryption detected" do let(:version) { 3 } let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor } diff --git a/spec/unit/encrypted_data_bag_item_spec.rb b/spec/unit/encrypted_data_bag_item_spec.rb index 9335889ef3..499fabdcf9 100644 --- a/spec/unit/encrypted_data_bag_item_spec.rb +++ b/spec/unit/encrypted_data_bag_item_spec.rb @@ -97,7 +97,7 @@ describe Chef::EncryptedDataBagItem::Encryptor do Chef::Config[:data_bag_encrypt_version] = 3 end - context "on supported platforms", :ruby_gte_20_and_openssl_gte_101 do + context "on supported platforms", :aes_256_gcm_only, :ruby_20_only do it "creates a version 3 encryptor" do encryptor.should be_a_instance_of(Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor) @@ -182,7 +182,7 @@ describe Chef::EncryptedDataBagItem::Decryptor do context "when decrypting a version 3 (JSON+aes-256-gcm+random iv+auth tag) encrypted value" do - context "on supported platforms", :ruby_gte_20_and_openssl_gte_101 do + context "on supported platforms", :aes_256_gcm_only, :ruby_20_only do let(:encrypted_value) do Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.new(plaintext_data, encryption_key).for_encrypted_item diff --git a/spec/unit/knife/cookbook_site_share_spec.rb b/spec/unit/knife/cookbook_site_share_spec.rb index ad3f32fecc..b85db98d53 100644 --- a/spec/unit/knife/cookbook_site_share_spec.rb +++ b/spec/unit/knife/cookbook_site_share_spec.rb @@ -25,6 +25,8 @@ describe Chef::Knife::CookbookSiteShare do before(:each) do @knife = Chef::Knife::CookbookSiteShare.new + # Merge default settings in. + @knife.merge_configs @knife.name_args = ['cookbook_name', 'AwesomeSausage'] @cookbook = Chef::CookbookVersion.new('cookbook_name') @@ -34,6 +36,9 @@ describe Chef::Knife::CookbookSiteShare do @cookbook_loader.stub(:[]).and_return(@cookbook) Chef::CookbookLoader.stub(:new).and_return(@cookbook_loader) + @noauth_rest = double(Chef::REST) + @knife.stub(:noauth_rest).and_return(@noauth_rest) + @cookbook_uploader = Chef::CookbookUploader.new('herpderp', :rest => "norest") Chef::CookbookUploader.stub(:new).and_return(@cookbook_uploader) @cookbook_uploader.stub(:validate_cookbooks).and_return(true) @@ -48,6 +53,20 @@ describe Chef::Knife::CookbookSiteShare do before(:each) do @knife.stub(:do_upload).and_return(true) + @category_response = { + "name" => "cookbook_name", + "category" => "Testing Category" + } + @bad_category_response = { + "error_code" => "NOT_FOUND", + "error_messages" => [ + "Resource does not exist." + ] + } + end + + it 'should set true to config[:dry_run] as default' do + @knife.config[:dry_run].should be_false end it 'should should print usage and exit when given no arguments' do @@ -57,9 +76,23 @@ describe Chef::Knife::CookbookSiteShare do lambda { @knife.run }.should raise_error(SystemExit) end - it 'should print usage and exit when given only 1 argument' do + it 'should not fail when given only 1 argument and can determine category' do @knife.name_args = ['cookbook_name'] - @knife.should_receive(:show_usage) + @noauth_rest.should_receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name").and_return(@category_response) + @knife.should_receive(:do_upload) + @knife.run + end + + it 'should print error and exit when given only 1 argument and cannot determine category' do + @knife.name_args = ['cookbook_name'] + @noauth_rest.should_receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name").and_return(@bad_category_response) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + + it 'should print error and exit when given only 1 argument and Chef::REST throws an exception' do + @knife.name_args = ['cookbook_name'] + @noauth_rest.should_receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name") { raise Errno::ECONNREFUSED, "Connection refused" } @knife.ui.should_receive(:fatal) lambda { @knife.run }.should raise_error(SystemExit) end @@ -93,6 +126,26 @@ describe Chef::Knife::CookbookSiteShare do FileUtils.should_receive(:rm_rf) @knife.run end + + context "when the --dry-run flag is specified" do + before do + Chef::CookbookSiteStreamingUploader.stub(:create_build_dir).and_return("/var/tmp/dummy") + @knife.config = { :dry_run => true } + @knife.stub_chain(:shell_out!, :stdout).and_return('file') + end + + it "should list files in the tarball" do + expect(@knife).to receive(:shell_out!).with("tar -czf #{@cookbook.name}.tgz #{@cookbook.name}", {:cwd => "/var/tmp/dummy"}) + expect(@knife).to receive(:shell_out!).with("tar -tzf #{@cookbook.name}.tgz", {:cwd => "/var/tmp/dummy"}) + @knife.run + end + + it "does not upload the cookbook" do + allow(@knife).to receive(:shell_out!).and_return(true) + expect(@knife).not_to receive(:do_upload) + @knife.run + end + end end describe 'do_upload' do diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb index 9044bc2f2f..ed1037ebd5 100644 --- a/spec/unit/knife/core/ui_spec.rb +++ b/spec/unit/knife/core/ui_spec.rb @@ -403,6 +403,34 @@ EOM @ui.format_cookbook_list_for_display(@item).should == response end end + + context "when running on Windows" do + before(:each) do + stdout = double('StringIO', :tty? => true) + @ui.stub(:stdout).and_return(stdout) + Chef::Platform.stub(:windows?) { true } + Chef::Config.reset + end + + after(:each) do + Chef::Config.reset + end + + it "should have color set to true if knife config has color explicitly set to true" do + Chef::Config[:color] = true + @ui.config[:color] = true + expect(@ui.color?).to eql(true) + end + + it "should have color set to false if knife config has color explicitly set to false" do + Chef::Config[:color] = false + expect(@ui.color?).to eql(false) + end + + it "should not have color set to false by default" do + expect(@ui.color?).to eql(false) + end + end end describe "confirm" do diff --git a/spec/unit/knife/status_spec.rb b/spec/unit/knife/status_spec.rb index 6d8d9d5b25..bb43dd25e5 100644 --- a/spec/unit/knife/status_spec.rb +++ b/spec/unit/knife/status_spec.rb @@ -17,7 +17,6 @@ # require 'spec_helper' -require 'highline' describe Chef::Knife::Status do before(:each) do @@ -30,7 +29,7 @@ describe Chef::Knife::Status do Chef::Search::Query.stub(:new).and_return(query) @knife = Chef::Knife::Status.new @stdout = StringIO.new - @knife.stub(:highline).and_return(HighLine.new(StringIO.new, @stdout)) + @knife.ui.stub(:stdout).and_return(@stdout) end describe "run" do diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb index 2db6b40b28..6d4763e087 100644 --- a/spec/unit/knife_spec.rb +++ b/spec/unit/knife_spec.rb @@ -260,8 +260,25 @@ describe Chef::Knife do knife_command.configure_chef knife_command.config[:opt_with_default].should == "from-cli" end - end + context "verbosity is greater than zero" do + let(:fake_config) { "/does/not/exist/knife.rb" } + + before do + @knife.config[:verbosity] = 1 + @knife.config[:config_file] = fake_config + config_loader = double("Chef::WorkstationConfigLoader", :load => true, :no_config_found? => false, :chef_config_dir => "/etc/chef", :config_location => fake_config) + allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader) + end + + it "prints the path to the configuration file used" do + @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new + @knife.ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {}) + expect(Chef::Log).to receive(:info).with("Using configuration from #{fake_config}") + @knife.configure_chef + end + end + end end describe "when first created" do diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb index 960aff3c36..452e1da2a4 100644 --- a/spec/unit/lwrp_spec.rb +++ b/spec/unit/lwrp_spec.rb @@ -42,7 +42,8 @@ describe "LWRP" do end Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| - Chef::Log.should_receive(:info).with(/overriding/) + Chef::Log.should_receive(:info).with(/Skipping/) + Chef::Log.should_receive(:debug).with(/anymore/) Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end end @@ -53,16 +54,15 @@ describe "LWRP" do end Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file| - Chef::Log.should_receive(:info).with(/overriding/) + Chef::Log.should_receive(:info).with(/Skipping/) + Chef::Log.should_receive(:debug).with(/anymore/) Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) end end - it "removes the old LRWP resource class from the list of resource subclasses [CHEF-3432]" do - # CHEF-3432 regression test: - # Chef::Resource keeps a list of all subclasses to assist class inflation - # for json parsing (see Chef::JSONCompat). When replacing LWRP resources, - # we need to ensure the old resource class is remove from that list. + it "keeps the old LRWP resource class in the list of resource subclasses" do + # This was originally CHEF-3432 regression test. But with Chef 12 we are + # not replacing the original classes anymore. Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end @@ -71,7 +71,7 @@ describe "LWRP" do Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end - Chef::Resource.resource_classes.should_not include(first_lwr_foo_class) + Chef::Resource.resource_classes.should include(first_lwr_foo_class) end it "does not attempt to remove classes from higher up namespaces [CHEF-4117]" do @@ -231,6 +231,27 @@ describe "LWRP" do expect(child.default_action).to eq(:dont_eat) end end + + context "when actions are already defined" do + let(:child) do + Class.new(parent) do + actions :eat + actions :sleep + actions :drink + end + end + + def raise_if_deprecated! + if Chef::VERSION.split('.').first.to_i > 12 + raise "This test should be removed and the associated code should be removed!" + end + end + + it "ammends actions when they are already defined" do + raise_if_deprecated! + expect(child.actions).to eq([:eat, :sleep, :drink]) + end + end end end diff --git a/spec/unit/node_map_spec.rb b/spec/unit/node_map_spec.rb new file mode 100644 index 0000000000..fe7372961b --- /dev/null +++ b/spec/unit/node_map_spec.rb @@ -0,0 +1,155 @@ +# +# Author:: Lamont Granquist (<lamont@chef.io>) +# 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' +require 'chef/node_map' + +describe Chef::NodeMap do + + let(:node_map) { Chef::NodeMap.new } + + let(:node) { Chef::Node.new } + + describe "with a bad filter name" do + it "should raise an error" do + expect{ node_map.set(node, :thing, on_platform_family: 'rhel') }.to raise_error + end + end + + describe "when no matchers are set at all" do + before do + node_map.set(:thing, :foo) + end + + it "returns the value" do + expect(node_map.get(node, :thing)).to eql(:foo) + end + + it "returns nil for keys that do not exist" do + expect(node_map.get(node, :other_thing)).to eql(nil) + end + end + + describe "filtering by os" do + before do + node_map.set(:thing, :foo, os: ["windows"]) + node_map.set(:thing, :bar, os: "linux") + end + it "returns the correct value for windows" do + allow(node).to receive(:[]).with(:os).and_return("windows") + expect(node_map.get(node, :thing)).to eql(:foo) + end + it "returns the correct value for linux" do + allow(node).to receive(:[]).with(:os).and_return("linux") + expect(node_map.get(node, :thing)).to eql(:bar) + end + it "returns nil for a non-matching os" do + allow(node).to receive(:[]).with(:os).and_return("freebsd") + expect(node_map.get(node, :thing)).to eql(nil) + end + end + + describe "rejecting an os" do + before do + node_map.set(:thing, :foo, os: "!windows") + end + it "returns nil for windows" do + allow(node).to receive(:[]).with(:os).and_return("windows") + expect(node_map.get(node, :thing)).to eql(nil) + end + it "returns the correct value for linux" do + allow(node).to receive(:[]).with(:os).and_return("linux") + expect(node_map.get(node, :thing)).to eql(:foo) + end + end + + describe "filtering by os and platform_family" do + before do + node_map.set(:thing, :bar, os: "linux", platform_family: "rhel") + end + + it "returns the correct value when both match" do + allow(node).to receive(:[]).with(:os).and_return("linux") + allow(node).to receive(:[]).with(:platform_family).and_return("rhel") + expect(node_map.get(node, :thing)).to eql(:bar) + end + + it "returns nil for a non-matching os" do + allow(node).to receive(:[]).with(:os).and_return("freebsd") + expect(node_map.get(node, :thing)).to eql(nil) + end + + it "returns nil when the platform_family does not match" do + allow(node).to receive(:[]).with(:os).and_return("linux") + allow(node).to receive(:[]).with(:platform_family).and_return("debian") + expect(node_map.get(node, :thing)).to eql(nil) + end + end + + describe "with a block doing platform_version checks" do + before do + node_map.set(:thing, :foo, platform_family: "rhel") do |node| + node[:platform_version].to_i >= 7 + end + end + + it "returns the value when the node matches" do + allow(node).to receive(:[]).with(:platform_family).and_return("rhel") + allow(node).to receive(:[]).with(:platform_version).and_return("7.0") + expect(node_map.get(node, :thing)).to eql(:foo) + end + + it "returns nil when the block does not match" do + allow(node).to receive(:[]).with(:platform_family).and_return("rhel") + allow(node).to receive(:[]).with(:platform_version).and_return("6.4") + expect(node_map.get(node, :thing)).to eql(nil) + end + + it "returns nil when the platform_family filter does not match" do + allow(node).to receive(:[]).with(:platform_family).and_return("debian") + allow(node).to receive(:[]).with(:platform_version).and_return("7.0") + expect(node_map.get(node, :thing)).to eql(nil) + end + + it "returns nil when both do not match" do + allow(node).to receive(:[]).with(:platform_family).and_return("debian") + allow(node).to receive(:[]).with(:platform_version).and_return("6.0") + expect(node_map.get(node, :thing)).to eql(nil) + end + end + + describe "resource back-compat testing" do + it "should handle :on_platforms => :all" do + node_map.set(:chef_gem, :foo, :on_platforms => :all) + allow(node).to receive(:[]).with(:platform).and_return("windows") + expect(node_map.get(node, :chef_gem)).to eql(:foo) + end + it "should handle :on_platforms => [ 'windows' ]" do + node_map.set(:dsc_script, :foo, :on_platforms => [ 'windows' ]) + allow(node).to receive(:[]).with(:platform).and_return("windows") + expect(node_map.get(node, :dsc_script)).to eql(:foo) + end + it "should handle :on_platform => :all" do + node_map.set(:link, :foo, :on_platform => :all) + allow(node).to receive(:[]).with(:platform).and_return("windows") + expect(node_map.get(node, :link)).to eql(:foo) + end + end + +end + diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 00879dcb13..da7a67aec1 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -700,6 +700,10 @@ describe Chef::Node do h["run_list"].should be_include("recipe[stalinist]") h["chef_environment"].should == "dev" end + + it 'should return an empty array for empty run_list' do + node.to_hash["run_list"].should == [] + end end describe "converting to or from json" do diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb index 029fc29aae..9a65cbe878 100644 --- a/spec/unit/platform_spec.rb +++ b/spec/unit/platform_spec.rb @@ -278,6 +278,29 @@ describe Chef::Platform do pmap[:package].should eql(Chef::Provider::Package::Ips) end + it "should use the Redhat service provider on SLES11" do + 1.upto(3) do |sp| + pmap = Chef::Platform.find("SUSE", "11.#{sp}") + pmap[:service].should eql(Chef::Provider::Service::Redhat) + end + end + + it "should use the Systemd service provider on SLES12" do + pmap = Chef::Platform.find("SUSE", "12.0") + pmap[:service].should eql(Chef::Provider::Service::Systemd) + end + + it "should use the SUSE group provider on SLES11" do + 1.upto(3) do |sp| + pmap = Chef::Platform.find("SUSE", "11.#{sp}") + pmap[:group].should eql(Chef::Provider::Group::Suse) + end + end + + it "should use the Gpasswd group provider on SLES12" do + pmap = Chef::Platform.find("SUSE", "12.0") + pmap[:group].should eql(Chef::Provider::Group::Gpasswd) + end end end diff --git a/spec/unit/provider/cron/unix_spec.rb b/spec/unit/provider/cron/unix_spec.rb index 60e09baceb..3d7a5675fc 100644 --- a/spec/unit/provider/cron/unix_spec.rb +++ b/spec/unit/provider/cron/unix_spec.rb @@ -21,26 +21,34 @@ require 'spec_helper' describe Chef::Provider::Cron::Unix do - before do - @node = Chef::Node.new - @events = Chef::EventDispatch::Dispatcher.new - @run_context = Chef::RunContext.new(@node, {}, @events) - @new_resource = Chef::Resource::Cron.new("cronhole some stuff") - @new_resource.user "root" - @new_resource.minute "30" - @new_resource.command "/bin/true" - - @provider = Chef::Provider::Cron::Unix.new(@new_resource, @run_context) + + subject(:provider) { Chef::Provider::Cron::Unix.new(new_resource, run_context) } + + let(:username) { "root" } + + let(:node) { Chef::Node.new } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, {}, events) } + let(:new_resource) do + Chef::Resource::Cron.new("cronhole some stuff").tap do |resource| + resource.user username + resource.minute "30" + resource.command "/bin/true" + end end - it "should inherit from Chef::Provider:Cron" do - @provider.should be_a(Chef::Provider::Cron) + let(:status) { double('Process::Status', :exitstatus => exitstatus) } + let(:exitstatus) { 0 } + let(:shell_out) { double('Mixlib::ShellOut', :status => status, :stdout => stdout, :stderr => stderr) } + + it "is a Chef::Provider:Cron" do + expect(provider).to be_a(Chef::Provider::Cron) end describe "read_crontab" do - before :each do - @status = double("Status", :exitstatus => 0) - @stdout = StringIO.new(<<-CRONTAB) + let(:stderr) { "" } + let(:stdout) do + String.new(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: something else @@ -48,74 +56,84 @@ describe Chef::Provider::Cron::Unix do # Another comment CRONTAB - @provider.stub(:popen4).and_yield(1234, StringIO.new, @stdout, StringIO.new).and_return(@status) + end + + before do + allow(Chef::Log).to receive(:debug) + allow(shell_out).to receive(:format_for_exception).and_return("formatted command output") + allow(provider).to receive(:shell_out).with('/usr/bin/crontab -l', :user => username).and_return(shell_out) end it "should call crontab -l with the user" do - @provider.should_receive(:popen4).with("crontab -l #{@new_resource.user}").and_return(@status) - @provider.send(:read_crontab) + provider.send(:read_crontab) + expect(provider).to have_received(:shell_out).with('/usr/bin/crontab -l', :user => username) end it "should return the contents of the crontab" do - crontab = @provider.send(:read_crontab) - crontab.should == <<-CRONTAB -0 2 * * * /some/other/command + crontab = provider.send(:read_crontab) + expect(crontab).to eq(stdout) + end -# Chef Name: something else -* 5 * * * /bin/true + context "when the user has no crontab" do + let(:exitstatus) { 1 } -# Another comment -CRONTAB - end + it "should return nil if the user has no crontab" do + expect(provider.send(:read_crontab)).to be_nil + end - it "should return nil if the user has no crontab" do - status = double("Status", :exitstatus => 1) - @provider.stub(:popen4).and_return(status) - @provider.send(:read_crontab).should == nil + it "logs the crontab output to debug" do + provider.send(:read_crontab) + expect(Chef::Log).to have_received(:debug).with("formatted command output") + end end - it "should raise an exception if another error occurs" do - status = double("Status", :exitstatus => 2) - @provider.stub(:popen4).and_return(status) - lambda do - @provider.send(:read_crontab) - end.should raise_error(Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: 2") + context "when any other error occurs" do + let (:exitstatus) { 2 } + + it "should raise an exception if another error occurs" do + expect { + provider.send(:read_crontab) + }.to raise_error(Chef::Exceptions::Cron, "Error determining state of #{new_resource.name}, exit: 2") + end + + it "logs the crontab output to debug" do + provider.send(:read_crontab) rescue nil + expect(Chef::Log).to have_received(:debug).with("formatted command output") + end end end describe "write_crontab" do - before :each do - @status = double("Status", :exitstatus => 0) - @provider.stub(:run_command_and_return_stdout_stderr).and_return(@status, String.new, String.new) - @tempfile = double("foo", :path => "/tmp/foo", :close => true) - Tempfile.stub(:new).and_return(@tempfile) - @tempfile.should_receive(:flush) - @tempfile.should_receive(:chmod).with(420) - @tempfile.should_receive(:close!) + let(:stdout) { "" } + let(:stderr) { "" } + let(:tempfile) { double("foo", :path => "/tmp/foo", :close => true) } + + before do + expect(Tempfile).to receive(:new).and_return(tempfile) + expect(tempfile).to receive(:flush) + expect(tempfile).to receive(:chmod).with(420) + expect(tempfile).to receive(:close!) + allow(tempfile).to receive(:<<) + allow(provider).to receive(:shell_out).with("/usr/bin/crontab #{tempfile.path}", :user => username).and_return(shell_out) end it "should call crontab for the user" do - @provider.should_receive(:run_command_and_return_stdout_stderr).with(hash_including(:user => @new_resource.user)) - @tempfile.should_receive(:<<).with("Foo") - @provider.send(:write_crontab, "Foo") + provider.send(:write_crontab, "Foo") + expect(provider).to have_received(:shell_out).with("/usr/bin/crontab #{tempfile.path}", :user => username) end it "should call crontab with a file containing the crontab" do - @provider.should_receive(:run_command_and_return_stdout_stderr) do |args| - (args[:command] =~ %r{\A/usr/bin/crontab (/\S+)\z}).should be_true - $1.should == "/tmp/foo" - @status - end - @tempfile.should_receive(:<<).with("Foo\n# wibble\n wah!!") - @provider.send(:write_crontab, "Foo\n# wibble\n wah!!") + provider.send(:write_crontab, "Foo\n# wibble\n wah!!") + expect(tempfile).to have_received(:<<).with("Foo\n# wibble\n wah!!") end - it "should raise an exception if the command returns non-zero" do - @tempfile.should_receive(:<<).with("Foo") - @status.stub(:exitstatus).and_return(1) - lambda do - @provider.send(:write_crontab, "Foo") - end.should raise_error(Chef::Exceptions::Cron, /Error updating state of #{@new_resource.name}, exit: 1/) + context "when writing the crontab fails" do + let(:exitstatus) { 1 } + it "should raise an exception if the command returns non-zero" do + expect { + provider.send(:write_crontab, "Foo") + }.to raise_error(Chef::Exceptions::Cron, /Error updating state of #{new_resource.name}, exit: 1/) + end end end end diff --git a/spec/unit/provider/env/windows_spec.rb b/spec/unit/provider/env/windows_spec.rb index 2ea137c1d9..84582d8b4d 100644 --- a/spec/unit/provider/env/windows_spec.rb +++ b/spec/unit/provider/env/windows_spec.rb @@ -53,7 +53,7 @@ describe Chef::Provider::Env::Windows, :windows_only do end it "should update the ruby ENV object when it updates the value" do - provider.should_receive(:compare_value).and_return(true) + provider.should_receive(:requires_modify_or_create?).and_return(true) new_resource.value("foobar") provider.action_modify expect(ENV['CHEF_WINDOWS_ENV_TEST']).to eql('foobar') @@ -92,7 +92,7 @@ describe Chef::Provider::Env::Windows, :windows_only do end it "replaces Windows system variables" do - provider.should_receive(:compare_value).and_return(true) + provider.should_receive(:requires_modify_or_create?).and_return(true) provider.should_receive(:expand_path).with(system_root).and_return(system_root_value) provider.action_modify expect(ENV['PATH']).to eql(system_root_value) diff --git a/spec/unit/provider/env_spec.rb b/spec/unit/provider/env_spec.rb index dc6176d45c..f8803f9bb6 100644 --- a/spec/unit/provider/env_spec.rb +++ b/spec/unit/provider/env_spec.rb @@ -88,20 +88,20 @@ describe Chef::Provider::Env do it "should check to see if the values are the same if the key exists" do @provider.key_exists = true - @provider.should_receive(:compare_value).and_return(false) + @provider.should_receive(:requires_modify_or_create?).and_return(false) @provider.action_create end it "should call modify_env if the key exists and values are not equal" do @provider.key_exists = true - @provider.stub(:compare_value).and_return(true) + @provider.stub(:requires_modify_or_create?).and_return(true) @provider.should_receive(:modify_env).and_return(true) @provider.action_create end it "should set the new_resources updated flag when it updates an existing value" do @provider.key_exists = true - @provider.stub(:compare_value).and_return(true) + @provider.stub(:requires_modify_or_create?).and_return(true) @provider.stub(:modify_env).and_return(true) @provider.action_create @new_resource.should be_updated @@ -147,20 +147,20 @@ describe Chef::Provider::Env do end it "should call modify_group if the key exists and values are not equal" do - @provider.should_receive(:compare_value).and_return(true) + @provider.should_receive(:requires_modify_or_create?).and_return(true) @provider.should_receive(:modify_env).and_return(true) @provider.action_modify end it "should set the new resources updated flag to true if modify_env is called" do - @provider.stub(:compare_value).and_return(true) + @provider.stub(:requires_modify_or_create?).and_return(true) @provider.stub(:modify_env).and_return(true) @provider.action_modify @new_resource.should be_updated end it "should not call modify_env if the key exists but the values are equal" do - @provider.should_receive(:compare_value).and_return(false) + @provider.should_receive(:requires_modify_or_create?).and_return(false) @provider.should_not_receive(:modify_env) @provider.action_modify end @@ -198,9 +198,31 @@ describe Chef::Provider::Env do @provider.delete_element.should eql(true) @new_resource.should be_updated end + + context "when new_resource's value contains the delimiter" do + it "should return false if all the elements are deleted" do + # This indicates that the entire key needs to be deleted + @new_resource.value("C:/foo/bin;C:/bar/bin") + @provider.delete_element.should eql(false) + @new_resource.should_not be_updated # This will be updated in action_delete + end + + it "should return true if any, but not all, of the elements are deleted" do + @new_resource.value("C:/foo/bin;C:/notbaz/bin") + @provider.should_receive(:create_env) + @provider.delete_element.should eql(true) + @new_resource.should be_updated + end + + it "should return true if none of the elements are deleted" do + @new_resource.value("C:/notfoo/bin;C:/notbaz/bin") + @provider.delete_element.should eql(true) + @new_resource.should_not be_updated + end + end end - describe "compare_value" do + describe "requires_modify_or_create?" do before(:each) do @new_resource.value("C:/bar") @current_resource = @new_resource.clone @@ -208,25 +230,41 @@ describe Chef::Provider::Env do end it "should return false if the values are equal" do - @provider.compare_value.should be_false + @provider.requires_modify_or_create?.should be_false end it "should return true if the values not are equal" do @new_resource.value("C:/elsewhere") - @provider.compare_value.should be_true + @provider.requires_modify_or_create?.should be_true end it "should return false if the current value contains the element" do @new_resource.delim(";") @current_resource.value("C:/bar;C:/foo;C:/baz") - @provider.compare_value.should be_false + @provider.requires_modify_or_create?.should be_false end it "should return true if the current value does not contain the element" do @new_resource.delim(";") @current_resource.value("C:/biz;C:/foo/bin;C:/baz") - @provider.compare_value.should be_true + @provider.requires_modify_or_create?.should be_true + end + + context "when new_resource's value contains the delimiter" do + it "should return false if all the current values are contained" do + @new_resource.value("C:/biz;C:/baz") + @new_resource.delim(";") + @current_resource.value("C:/biz;C:/foo/bin;C:/baz") + @provider.requires_modify_or_create?.should be_false + end + + it "should return true if any of the new values are not contained" do + @new_resource.value("C:/biz;C:/baz;C:/bin") + @new_resource.delim(";") + @current_resource.value("C:/biz;C:/foo/bin;C:/baz") + @provider.requires_modify_or_create?.should be_true + end end end @@ -247,5 +285,13 @@ describe Chef::Provider::Env do @provider.modify_env passed_value.should == new_value end + + it "should only add values not already contained when a delimiter is provided" do + @new_resource.value("C:/foo;C:/bar;C:/baz") + @new_resource.delim(";") + @current_resource.value("C:/foo/bar;C:/bar;C:/baz") + @provider.modify_env + @new_resource.value.should eq("C:/foo;C:/foo/bar;C:/bar;C:/baz") + end end end diff --git a/spec/unit/provider/execute_spec.rb b/spec/unit/provider/execute_spec.rb index 78216a89fa..6aa48f1e2a 100644 --- a/spec/unit/provider/execute_spec.rb +++ b/spec/unit/provider/execute_spec.rb @@ -36,22 +36,38 @@ describe Chef::Provider::Execute do STDOUT.stub(:tty?).and_return(true) end + let(:opts) do + { + timeout: @new_resource.timeout, + returns: @new_resource.returns, + log_level: :info, + log_tag: @new_resource.to_s, + live_stream: STDOUT + } + end it "should execute foo_resource" do @provider.stub(:load_current_resource) - opts = {} - opts[:timeout] = @new_resource.timeout - opts[:returns] = @new_resource.returns - opts[:log_level] = :info - opts[:log_tag] = @new_resource.to_s - opts[:live_stream] = STDOUT @provider.should_receive(:shell_out!).with(@new_resource.command, opts) + @provider.should_receive(:converge_by).with("execute foo_resource").and_call_original Chef::Log.should_not_receive(:warn) @provider.run_action(:run) @new_resource.should be_updated end + it "should honor sensitive attribute" do + @new_resource.sensitive true + @provider = Chef::Provider::Execute.new(@new_resource, @run_context) + @provider.stub(:load_current_resource) + # Since the resource is sensitive, it should not have :live_stream set + @provider.should_receive(:shell_out!).with(@new_resource.command, opts.reject { |k| k == :live_stream }) + Chef::Log.should_not_receive(:warn) + @provider.should_receive(:converge_by).with("execute sensitive resource").and_call_original + @provider.run_action(:run) + @new_resource.should be_updated + end + it "should do nothing if the sentinel file exists" do @provider.stub(:load_current_resource) File.should_receive(:exists?).with(@new_resource.creates).and_return(true) diff --git a/spec/unit/provider/git_spec.rb b/spec/unit/provider/git_spec.rb index ff1c0b0398..02d155efbd 100644 --- a/spec/unit/provider/git_spec.rb +++ b/spec/unit/provider/git_spec.rb @@ -106,6 +106,52 @@ describe Chef::Provider::Git do @provider.target_revision.should eql("663c22a5e41f5ae3193460cca044ed1435029f53") end + it "converts resource.revision from a tag to a SHA using an exact match" do + @resource.revision "v1.0" + @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/releases/v1.0\n" + + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n") + @provider.should_receive(:shell_out!).with(@git_ls_remote + "\"v1.0*\"", {:log_tag=>"git[web2.0 app]"}).and_return(double("ShellOut result", :stdout => @stdout)) + @provider.target_revision.should eql("503c22a5e41f5ae3193460cca044ed1435029f53") + end + + it "converts resource.revision from a tag to a SHA, matching tags first, then heads" do + @resource.revision "v1.0" + @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n" + + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") + @provider.should_receive(:shell_out!).with(@git_ls_remote + "\"v1.0*\"", {:log_tag=>"git[web2.0 app]"}).and_return(double("ShellOut result", :stdout => @stdout)) + @provider.target_revision.should eql("663c22a5e41f5ae3193460cca044ed1435029f53") + end + + it "converts resource.revision from a tag to a SHA, matching heads if no tags match" do + @resource.revision "v1.0" + @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.1\n" + + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") + @provider.should_receive(:shell_out!).with(@git_ls_remote + "\"v1.0*\"", {:log_tag=>"git[web2.0 app]"}).and_return(double("ShellOut result", :stdout => @stdout)) + @provider.target_revision.should eql("503c22a5e41f5ae3193460cca044ed1435029f53") + end + + it "converts resource.revision from a tag to a SHA, matching tags first, then heads, then revision" do + @resource.revision "refs/pulls/v1.0" + @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n" + + "805c22a5e41f5ae3193460cca044ed1435029f53\trefs/pulls/v1.0\n" + + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") + @provider.should_receive(:shell_out!).with(@git_ls_remote + "\"refs/pulls/v1.0*\"", {:log_tag=>"git[web2.0 app]"}).and_return(double("ShellOut result", :stdout => @stdout)) + @provider.target_revision.should eql("805c22a5e41f5ae3193460cca044ed1435029f53") + end + + it "converts resource.revision from a tag to a SHA, using full path if provided" do + @resource.revision "refs/heads/v1.0" + @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n" + + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") + @provider.should_receive(:shell_out!).with(@git_ls_remote + "\"refs/heads/v1.0*\"", {:log_tag=>"git[web2.0 app]"}).and_return(double("ShellOut result", :stdout => @stdout)) + @provider.target_revision.should eql("503c22a5e41f5ae3193460cca044ed1435029f53") + end + it "raises an invalid remote reference error if you try to deploy from ``origin'' and assertions are run" do @resource.revision "origin/" @provider.action = :checkout diff --git a/spec/unit/provider/log_spec.rb b/spec/unit/provider/log_spec.rb index a270ee4822..1ecc633ce1 100644 --- a/spec/unit/provider/log_spec.rb +++ b/spec/unit/provider/log_spec.rb @@ -32,10 +32,6 @@ describe Chef::Provider::Log::ChefLog do let(:provider) { Chef::Provider::Log::ChefLog.new(new_resource, run_context) } - it "should be registered with the default platform hash" do - expect(Chef::Platform.platforms[:default][:log]).not_to be_nil - end - it "should write the string to the Chef::Log object at default level (info)" do expect(Chef::Log).to receive(:info).with(log_str).and_return(true) provider.run_action(:write) diff --git a/spec/unit/provider/mount/mount_spec.rb b/spec/unit/provider/mount/mount_spec.rb index d6f71bc613..abab7640c0 100644 --- a/spec/unit/provider/mount/mount_spec.rb +++ b/spec/unit/provider/mount/mount_spec.rb @@ -107,16 +107,12 @@ describe Chef::Provider::Mount::Mount do lambda { @provider.load_current_resource();@provider.mountable? }.should raise_error(Chef::Exceptions::Mount) end - it "does not expect the device to exist for tmpfs" do - @new_resource.fstype("tmpfs") - @new_resource.device("whatever") - lambda { @provider.load_current_resource();@provider.mountable? }.should_not raise_error - end - - it "does not expect the device to exist for Fuse filesystems" do - @new_resource.fstype("fuse") - @new_resource.device("nilfs#xxx") - lambda { @provider.load_current_resource();@provider.mountable? }.should_not raise_error + [ "tmpfs", "fuse", "cgroup" ].each do |fstype| + it "does not expect the device to exist for #{fstype}" do + @new_resource.fstype(fstype) + @new_resource.device("whatever") + lambda { @provider.load_current_resource();@provider.mountable? }.should_not raise_error + end end it "does not expect the device to exist if it's none" do diff --git a/spec/unit/provider/package/apt_spec.rb b/spec/unit/provider/package/apt_spec.rb index a94b248418..90e9dd6d3f 100644 --- a/spec/unit/provider/package/apt_spec.rb +++ b/spec/unit/provider/package/apt_spec.rb @@ -229,7 +229,7 @@ SHOWPKG_STDOUT @new_resource = nil @new_resource = Chef::Resource::AptPackage.new("irssi", @run_context) @new_resource.default_release("lenny-backports") - + @new_resource.provider = @provider @provider.new_resource = @new_resource @provider.should_receive(:shell_out!).with( diff --git a/spec/unit/provider/package/freebsd/port_spec.rb b/spec/unit/provider/package/freebsd/port_spec.rb index e946719451..8725e5440f 100644 --- a/spec/unit/provider/package/freebsd/port_spec.rb +++ b/spec/unit/provider/package/freebsd/port_spec.rb @@ -26,7 +26,7 @@ describe Chef::Provider::Package::Freebsd::Port do @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) - @new_resource = Chef::Resource::Package.new("zsh") + @new_resource = Chef::Resource::FreebsdPackage.new("zsh", @run_context) @provider = Chef::Provider::Package::Freebsd::Port.new(@new_resource, @run_context) end @@ -66,24 +66,34 @@ describe Chef::Provider::Package::Freebsd::Port do describe "determining current installed version" do before(:each) do - @provider.stub(:supports_pkgng?) @pkg_info = OpenStruct.new(:stdout => "zsh-3.1.7\n") end it "should check 'pkg_info' if system uses pkg_* tools" do - @provider.should_receive(:supports_pkgng?).and_return(false) + @new_resource.stub(:supports_pkgng?) + @new_resource.should_receive(:supports_pkgng?).and_return(false) @provider.should_receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(@pkg_info) @provider.current_installed_version.should == "3.1.7" end - it "should check 'pkg info' if system uses pkgng" do - @provider.should_receive(:supports_pkgng?).and_return(true) + it "should check 'pkg info' if make supports WITH_PKGNG if freebsd version is < 1000017" do + pkg_enabled = OpenStruct.new(:stdout => "yes\n") + [1000016, 1000000, 901503, 902506, 802511].each do |__freebsd_version| + @node.automatic_attrs[:os_version] = __freebsd_version + @new_resource.should_receive(:shell_out!).with('make -V WITH_PKGNG', :env => nil).and_return(pkg_enabled) + @provider.should_receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info) + @provider.current_installed_version.should == "3.1.7" + end + end + + it "should check 'pkg info' if the freebsd version is greater than or equal to 1000017" do + __freebsd_version = 1000017 + @node.automatic_attrs[:os_version] = __freebsd_version @provider.should_receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info) @provider.current_installed_version.should == "3.1.7" end end - describe "determining candidate version" do before(:each) do @port_version = OpenStruct.new(:stdout => "5.0.5\n", :exitstatus => 0) diff --git a/spec/unit/provider/package/homebrew_spec.rb b/spec/unit/provider/package/homebrew_spec.rb index d38458546d..dccd8edf11 100644 --- a/spec/unit/provider/package/homebrew_spec.rb +++ b/spec/unit/provider/package/homebrew_spec.rb @@ -158,6 +158,15 @@ describe Chef::Provider::Package::Homebrew do allow(provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => 'homestarrunner')) expect(provider.brew('info', 'opts', 'bananas')).to eql('homestarrunner') end + + context "when new_resource is Package" do + let(:new_resource) { Chef::Resource::Package.new('emacs') } + + it "does not try to read homebrew_user from Package, which does not have it" do + allow(provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => 'zombo')) + expect(provider.brew).to eql('zombo') + end + end end context 'when testing actions' do diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb index 0c1c487980..ed10513350 100644 --- a/spec/unit/provider/package/pacman_spec.rb +++ b/spec/unit/provider/package/pacman_spec.rb @@ -94,10 +94,7 @@ PACMAN end it "should set the candidate version if pacman has one" do - @stdout.stub(:each).and_yield("core/nano 2.2.3-1 (base)"). - and_yield(" Pico editor clone with enhancements"). - and_yield("community/nanoblogger 3.4.1-1"). - and_yield(" NanoBlogger is a small weblog engine written in Bash for the command line") + @stdout.stub(:each).and_yield("core nano 2.2.3-1") @provider.stub(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource @provider.candidate_version.should eql("2.2.3-1") @@ -124,8 +121,7 @@ PACMAN_CONF ::File.stub(:exists?).with("/etc/pacman.conf").and_return(true) ::File.stub(:read).with("/etc/pacman.conf").and_return(@pacman_conf) - @stdout.stub(:each).and_yield("customrepo/nano 1.2.3-4"). - and_yield(" My custom package") + @stdout.stub(:each).and_yield("customrepo nano 1.2.3-4") @provider.stub(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource diff --git a/spec/unit/provider/package/paludis_spec.rb b/spec/unit/provider/package/paludis_spec.rb index c99600e535..8387bb1252 100644 --- a/spec/unit/provider/package/paludis_spec.rb +++ b/spec/unit/provider/package/paludis_spec.rb @@ -59,7 +59,7 @@ PKG_STATUS end it "should run pkg info with the package name" do - @provider.should_receive(:shell_out!).with("cave -L warning print-ids -M none -m \"*/#{@new_resource.package_name.split('/').last}\" -f \"%c/%p %v %r\n\"").and_return(@shell_out) + @provider.should_receive(:shell_out!).with("cave -L warning print-ids -M none -m \"#{@new_resource.package_name}\" -f \"%c/%p %v %r\n\"").and_return(@shell_out) @provider.load_current_resource end diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb index d3cb9cf7fa..f4d0cebf62 100644 --- a/spec/unit/provider/package/rubygems_spec.rb +++ b/spec/unit/provider/package/rubygems_spec.rb @@ -359,10 +359,12 @@ RBX_GEM_ENV end describe Chef::Provider::Package::Rubygems do + let(:target_version) { nil } + before(:each) do @node = Chef::Node.new @new_resource = Chef::Resource::GemPackage.new("rspec-core") - @spec_version = @new_resource.version RSpec::Core::Version::STRING + @spec_version = @new_resource.version(target_version) @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @@ -371,269 +373,281 @@ describe Chef::Provider::Package::Rubygems do @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) end - it "triggers a gem configuration load so a later one will not stomp its config values" do - # ugly, is there a better way? - Gem.instance_variable_get(:@configuration).should_not be_nil - end + describe "when new_resource version is nil" do + let(:target_version) { nil } - it "uses the CurrentGemEnvironment implementation when no gem_binary_path is provided" do - @provider.gem_env.should be_a_kind_of(Chef::Provider::Package::Rubygems::CurrentGemEnvironment) + it "target_version_already_installed? should return false so that we can search for candidates" do + @provider.load_current_resource + @provider.target_version_already_installed?.should be_false + end end - it "uses the AlternateGemEnvironment implementation when a gem_binary_path is provided" do - @new_resource.gem_binary('/usr/weird/bin/gem') - provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) - provider.gem_env.gem_binary_location.should == '/usr/weird/bin/gem' - end + describe "when new_resource version is current rspec version" do + let(:target_version) { RSpec::Core::Version::STRING } - it "searches for a gem binary when running on Omnibus on Unix" do - platform_mock :unix do - RbConfig::CONFIG.stub(:[]).with('bindir').and_return("/opt/chef/embedded/bin") - ENV.stub(:[]).with('PATH').and_return("/usr/bin:/usr/sbin:/opt/chef/embedded/bin") - File.stub(:exists?).with('/usr/bin/gem').and_return(false) - File.stub(:exists?).with('/usr/sbin/gem').and_return(true) - File.stub(:exists?).with('/opt/chef/embedded/bin/gem').and_return(true) # should not get here - provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) - provider.gem_env.gem_binary_location.should == '/usr/sbin/gem' + it "triggers a gem configuration load so a later one will not stomp its config values" do + # ugly, is there a better way? + Gem.instance_variable_get(:@configuration).should_not be_nil end - end - it "searches for a gem binary when running on Omnibus on Windows" do - platform_mock :windows do - RbConfig::CONFIG.stub(:[]).with('bindir').and_return("d:/opscode/chef/embedded/bin") - ENV.stub(:[]).with('PATH').and_return('C:\windows\system32;C:\windows;C:\Ruby186\bin;d:\opscode\chef\embedded\bin') - File.stub(:exists?).with('C:\\windows\\system32\\gem').and_return(false) - File.stub(:exists?).with('C:\\windows\\gem').and_return(false) - File.stub(:exists?).with('C:\\Ruby186\\bin\\gem').and_return(true) - File.stub(:exists?).with('d:\\opscode\\chef\\bin\\gem').and_return(false) # should not get here - File.stub(:exists?).with('d:\\opscode\\chef\\embedded\\bin\\gem').and_return(false) # should not get here - provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) - provider.gem_env.gem_binary_location.should == 'C:\Ruby186\bin\gem' + it "uses the CurrentGemEnvironment implementation when no gem_binary_path is provided" do + @provider.gem_env.should be_a_kind_of(Chef::Provider::Package::Rubygems::CurrentGemEnvironment) end - end - - it "smites you when you try to use a hash of install options with an explicit gem binary" do - @new_resource.gem_binary('/foo/bar') - @new_resource.options(:fail => :burger) - lambda {Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)}.should raise_error(ArgumentError) - end - it "converts the new resource into a gem dependency" do - @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', @spec_version) - @new_resource.version('~> 1.2.0') - @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', '~> 1.2.0') - end + it "uses the AlternateGemEnvironment implementation when a gem_binary_path is provided" do + @new_resource.gem_binary('/usr/weird/bin/gem') + provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + provider.gem_env.gem_binary_location.should == '/usr/weird/bin/gem' + end - describe "when determining the currently installed version" do + it "searches for a gem binary when running on Omnibus on Unix" do + platform_mock :unix do + RbConfig::CONFIG.stub(:[]).with('bindir').and_return("/opt/chef/embedded/bin") + ENV.stub(:[]).with('PATH').and_return("/usr/bin:/usr/sbin:/opt/chef/embedded/bin") + File.stub(:exists?).with('/usr/bin/gem').and_return(false) + File.stub(:exists?).with('/usr/sbin/gem').and_return(true) + File.stub(:exists?).with('/opt/chef/embedded/bin/gem').and_return(true) # should not get here + provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + provider.gem_env.gem_binary_location.should == '/usr/sbin/gem' + end + end - it "sets the current version to the version specified by the new resource if that version is installed" do - @provider.load_current_resource - @provider.current_resource.version.should == @spec_version + it "searches for a gem binary when running on Omnibus on Windows" do + platform_mock :windows do + RbConfig::CONFIG.stub(:[]).with('bindir').and_return("d:/opscode/chef/embedded/bin") + ENV.stub(:[]).with('PATH').and_return('C:\windows\system32;C:\windows;C:\Ruby186\bin;d:\opscode\chef\embedded\bin') + File.stub(:exists?).with('C:\\windows\\system32\\gem').and_return(false) + File.stub(:exists?).with('C:\\windows\\gem').and_return(false) + File.stub(:exists?).with('C:\\Ruby186\\bin\\gem').and_return(true) + File.stub(:exists?).with('d:\\opscode\\chef\\bin\\gem').and_return(false) # should not get here + File.stub(:exists?).with('d:\\opscode\\chef\\embedded\\bin\\gem').and_return(false) # should not get here + provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + provider.gem_env.gem_binary_location.should == 'C:\Ruby186\bin\gem' + end end - it "sets the current version to the highest installed version if the requested version is not installed" do - @new_resource.version('9000.0.2') - @provider.load_current_resource - @provider.current_resource.version.should == @spec_version + it "smites you when you try to use a hash of install options with an explicit gem binary" do + @new_resource.gem_binary('/foo/bar') + @new_resource.options(:fail => :burger) + lambda {Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)}.should raise_error(ArgumentError) end - it "leaves the current version at nil if the package is not installed" do - @new_resource.package_name("no-such-gem-should-exist-with-this-name") - @provider.load_current_resource - @provider.current_resource.version.should be_nil + it "converts the new resource into a gem dependency" do + @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', @spec_version) + @new_resource.version('~> 1.2.0') + @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', '~> 1.2.0') end - end + describe "when determining the currently installed version" do - describe "when determining the candidate version to install" do + it "sets the current version to the version specified by the new resource if that version is installed" do + @provider.load_current_resource + @provider.current_resource.version.should == @spec_version + end - it "does not query for available versions when the current version is the target version" do - @provider.current_resource = @new_resource.dup - @provider.candidate_version.should be_nil - end + it "sets the current version to the highest installed version if the requested version is not installed" do + @new_resource.version('9000.0.2') + @provider.load_current_resource + @provider.current_resource.version.should == @spec_version + end - it "determines the candidate version by querying the remote gem servers" do - @new_resource.source('http://mygems.example.com') - version = Gem::Version.new(@spec_version) - @provider.gem_env.should_receive(:candidate_version_from_remote). - with(Gem::Dependency.new('rspec-core', @spec_version), "http://mygems.example.com"). - and_return(version) - @provider.candidate_version.should == @spec_version - end + it "leaves the current version at nil if the package is not installed" do + @new_resource.package_name("no-such-gem-should-exist-with-this-name") + @provider.load_current_resource + @provider.current_resource.version.should be_nil + end - it "parses the gem's specification if the requested source is a file" do - @new_resource.package_name('chef-integration-test') - @new_resource.version('>= 0') - @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider.candidate_version.should == '0.1.0' end - end - - describe "when installing a gem" do - before do - @current_resource = Chef::Resource::GemPackage.new('rspec-core') - @provider.current_resource = @current_resource - @gem_dep = Gem::Dependency.new('rspec-core', @spec_version) - @provider.stub(:load_current_resource) - end + describe "when determining the candidate version to install" do - describe "in the current gem environment" do - it "installs the gem via the gems api when no explicit options are used" do - @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil) - @provider.action_install.should be_true + it "does not query for available versions when the current version is the target version" do + @provider.current_resource = @new_resource.dup + @provider.candidate_version.should be_nil end - it "installs the gem via the gems api when a remote source is provided" do - @new_resource.source('http://gems.example.org') - sources = ['http://gems.example.org'] - @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => sources) - @provider.action_install.should be_true + it "determines the candidate version by querying the remote gem servers" do + @new_resource.source('http://mygems.example.com') + version = Gem::Version.new(@spec_version) + @provider.gem_env.should_receive(:candidate_version_from_remote). + with(Gem::Dependency.new('rspec-core', @spec_version), "http://mygems.example.com"). + and_return(version) + @provider.candidate_version.should == @spec_version end - it "installs the gem from file via the gems api when no explicit options are used" do + it "parses the gem's specification if the requested source is a file" do + @new_resource.package_name('chef-integration-test') + @new_resource.version('>= 0') @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider.action_install.should be_true + @provider.candidate_version.should == '0.1.0' end - it "installs the gem from file via the gems api when the package is a path and the source is nil" do - @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + end + + describe "when installing a gem" do + before do + @current_resource = Chef::Resource::GemPackage.new('rspec-core') @provider.current_resource = @current_resource - @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem' - @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider.action_install.should be_true + @gem_dep = Gem::Dependency.new('rspec-core', @spec_version) + @provider.stub(:load_current_resource) end - # this catches 'gem_package "foo"' when "./foo" is a file in the cwd, and instead of installing './foo' it fetches the remote gem - it "installs the gem via the gems api, when the package has no file separator characters in it, but a matching file exists in cwd" do - ::File.stub(:exists?).and_return(true) - @new_resource.package_name('rspec-core') - @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil) - @provider.action_install.should be_true - end + describe "in the current gem environment" do + it "installs the gem via the gems api when no explicit options are used" do + @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil) + @provider.action_install.should be_true + end - it "installs the gem by shelling out when options are provided as a String" do - @new_resource.options('-i /alt/install/location') - expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" -i /alt/install/location" - @provider.should_receive(:shell_out!).with(expected, :env => nil) - @provider.action_install.should be_true - end + it "installs the gem via the gems api when a remote source is provided" do + @new_resource.source('http://gems.example.org') + sources = ['http://gems.example.org'] + @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => sources) + @provider.action_install.should be_true + end - it "installs the gem via the gems api when options are given as a Hash" do - @new_resource.options(:install_dir => '/alt/install/location') - @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil, :install_dir => '/alt/install/location') - @provider.action_install.should be_true - end + it "installs the gem from file via the gems api when no explicit options are used" do + @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @provider.action_install.should be_true + end - describe "at a specific version" do - before do - @gem_dep = Gem::Dependency.new('rspec-core', @spec_version) + it "installs the gem from file via the gems api when the package is a path and the source is nil" do + @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + @provider.current_resource = @current_resource + @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem' + @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @provider.action_install.should be_true end - it "installs the gem via the gems api" do + # this catches 'gem_package "foo"' when "./foo" is a file in the cwd, and instead of installing './foo' it fetches the remote gem + it "installs the gem via the gems api, when the package has no file separator characters in it, but a matching file exists in cwd" do + ::File.stub(:exists?).and_return(true) + @new_resource.package_name('rspec-core') @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil) @provider.action_install.should be_true end - end - describe "at version specified with comparison operator" do - it "skips install if current version satisifies requested version" do - @current_resource.stub(:version).and_return("2.3.3") - @new_resource.stub(:version).and_return(">=2.3.0") - @provider.gem_env.should_not_receive(:install) - @provider.action_install + it "installs the gem by shelling out when options are provided as a String" do + @new_resource.options('-i /alt/install/location') + expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" -i /alt/install/location" + @provider.should_receive(:shell_out!).with(expected, :env => nil) + @provider.action_install.should be_true + end + + it "installs the gem via the gems api when options are given as a Hash" do + @new_resource.options(:install_dir => '/alt/install/location') + @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil, :install_dir => '/alt/install/location') + @provider.action_install.should be_true end - it "allows user to specify gem version with fuzzy operator" do - @current_resource.stub(:version).and_return("2.3.3") - @new_resource.stub(:version).and_return("~>2.3.0") + describe "at a specific version" do + before do + @gem_dep = Gem::Dependency.new('rspec-core', @spec_version) + end - @provider.gem_env.should_not_receive(:install) - @provider.action_install + it "installs the gem via the gems api" do + @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil) + @provider.action_install.should be_true + end + end + describe "at version specified with comparison operator" do + it "skips install if current version satisifies requested version" do + @current_resource.stub(:version).and_return("2.3.3") + @new_resource.stub(:version).and_return(">=2.3.0") + + @provider.gem_env.should_not_receive(:install) + @provider.action_install + end + + it "allows user to specify gem version with fuzzy operator" do + @current_resource.stub(:version).and_return("2.3.3") + @new_resource.stub(:version).and_return("~>2.3.0") + + @provider.gem_env.should_not_receive(:install) + @provider.action_install + end end end - end - describe "in an alternate gem environment" do - it "installs the gem by shelling out to gem install" do - @new_resource.gem_binary('/usr/weird/bin/gem') - @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", :env=>nil) - @provider.action_install.should be_true - end + describe "in an alternate gem environment" do + it "installs the gem by shelling out to gem install" do + @new_resource.gem_binary('/usr/weird/bin/gem') + @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", :env=>nil) + @provider.action_install.should be_true + end - it "installs the gem from file by shelling out to gem install" do - @new_resource.gem_binary('/usr/weird/bin/gem') - @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @new_resource.version('>= 0') - @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil) - @provider.action_install.should be_true - end + it "installs the gem from file by shelling out to gem install" do + @new_resource.gem_binary('/usr/weird/bin/gem') + @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @new_resource.version('>= 0') + @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil) + @provider.action_install.should be_true + end - it "installs the gem from file by shelling out to gem install when the package is a path and the source is nil" do - @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) - @provider.current_resource = @current_resource - @new_resource.gem_binary('/usr/weird/bin/gem') - @new_resource.version('>= 0') - @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem' - @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil) - @provider.action_install.should be_true + it "installs the gem from file by shelling out to gem install when the package is a path and the source is nil" do + @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') + @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context) + @provider.current_resource = @current_resource + @new_resource.gem_binary('/usr/weird/bin/gem') + @new_resource.version('>= 0') + @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem' + @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil) + @provider.action_install.should be_true + end end - end - end - - describe "when uninstalling a gem" do - before do - @new_resource = Chef::Resource::GemPackage.new("rspec") - @current_resource = @new_resource.dup - @current_resource.version('1.2.3') - @provider.new_resource = @new_resource - @provider.current_resource = @current_resource end - describe "in the current gem environment" do - it "uninstalls via the api when no explicit options are used" do - # pre-reqs for action_remove to actually remove the package: - @provider.new_resource.version.should be_nil - @provider.current_resource.version.should_not be_nil - # the behavior we're testing: - @provider.gem_env.should_receive(:uninstall).with('rspec', nil) - @provider.action_remove + describe "when uninstalling a gem" do + before do + @new_resource = Chef::Resource::GemPackage.new("rspec") + @current_resource = @new_resource.dup + @current_resource.version('1.2.3') + @provider.new_resource = @new_resource + @provider.current_resource = @current_resource end - it "uninstalls via the api when options are given as a Hash" do - # pre-reqs for action_remove to actually remove the package: - @provider.new_resource.version.should be_nil - @provider.current_resource.version.should_not be_nil - # the behavior we're testing: - @new_resource.options(:install_dir => '/alt/install/location') - @provider.gem_env.should_receive(:uninstall).with('rspec', nil, :install_dir => '/alt/install/location') - @provider.action_remove - end + describe "in the current gem environment" do + it "uninstalls via the api when no explicit options are used" do + # pre-reqs for action_remove to actually remove the package: + @provider.new_resource.version.should be_nil + @provider.current_resource.version.should_not be_nil + # the behavior we're testing: + @provider.gem_env.should_receive(:uninstall).with('rspec', nil) + @provider.action_remove + end - it "uninstalls via the gem command when options are given as a String" do - @new_resource.options('-i /alt/install/location') - @provider.should_receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", :env=>nil) - @provider.action_remove - end + it "uninstalls via the api when options are given as a Hash" do + # pre-reqs for action_remove to actually remove the package: + @provider.new_resource.version.should be_nil + @provider.current_resource.version.should_not be_nil + # the behavior we're testing: + @new_resource.options(:install_dir => '/alt/install/location') + @provider.gem_env.should_receive(:uninstall).with('rspec', nil, :install_dir => '/alt/install/location') + @provider.action_remove + end + + it "uninstalls via the gem command when options are given as a String" do + @new_resource.options('-i /alt/install/location') + @provider.should_receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", :env=>nil) + @provider.action_remove + end - it "uninstalls a specific version of a gem when a version is provided" do - @new_resource.version('1.2.3') - @provider.gem_env.should_receive(:uninstall).with('rspec', '1.2.3') - @provider.action_remove + it "uninstalls a specific version of a gem when a version is provided" do + @new_resource.version('1.2.3') + @provider.gem_env.should_receive(:uninstall).with('rspec', '1.2.3') + @provider.action_remove + end end - end - describe "in an alternate gem environment" do - it "uninstalls via the gem command" do - @new_resource.gem_binary('/usr/weird/bin/gem') - @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", :env=>nil) - @provider.action_remove + describe "in an alternate gem environment" do + it "uninstalls via the gem command" do + @new_resource.gem_binary('/usr/weird/bin/gem') + @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", :env=>nil) + @provider.action_remove + end end end end end - diff --git a/spec/unit/provider/service/aix_service_spec.rb b/spec/unit/provider/service/aix_service_spec.rb new file mode 100644 index 0000000000..070f618b99 --- /dev/null +++ b/spec/unit/provider/service/aix_service_spec.rb @@ -0,0 +1,181 @@ +# +# Author:: Kaustubh <kaustubh@clogeny.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::Provider::Service::Aix do + before(:each) do + @node = Chef::Node.new + @events = Chef::EventDispatch::Dispatcher.new + @run_context = Chef::RunContext.new(@node, {}, @events) + + @new_resource = Chef::Resource::Service.new("chef") + @current_resource = Chef::Resource::Service.new("chef") + + @provider = Chef::Provider::Service::Aix.new(@new_resource, @run_context) + Chef::Resource::Service.stub(:new).and_return(@current_resource) + end + + describe "load current resource" do + it "should create a current resource with the name of the new resource and determine the status" do + @status = double("Status", :exitstatus => 0, :stdout => @stdout) + @provider.stub(:shell_out!).and_return(@status) + + Chef::Resource::Service.should_receive(:new).and_return(@current_resource) + @current_resource.should_receive(:service_name).with("chef") + @provider.should_receive(:determine_current_status!) + + @provider.load_current_resource + end + end + + describe "determine current status" do + context "when the service is active" do + before do + @status = double("Status", :exitstatus => 0, :stdout => "chef chef 12345 active\n") + end + + it "current resource is running" do + @provider.should_receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status) + @provider.should_receive(:is_resource_group?).with(["chef chef 12345 active"]) + + @provider.load_current_resource + @current_resource.running.should be_true + end + end + + context "when the service is inoprative" do + before do + @status = double("Status", :exitstatus => 0, :stdout => "chef chef inoperative\n") + end + + it "current resource is not running" do + @provider.should_receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status) + @provider.should_receive(:is_resource_group?).with(["chef chef inoperative"]) + + @provider.load_current_resource + @current_resource.running.should be_false + end + end + end + + describe "is resource group" do + context "when there are mutiple subsystems associated with group" do + before do + @status = double("Status", :exitstatus => 0, :stdout => "chef1 chef 12345 active\nchef2 chef 12334 active\nchef3 chef inoperative") + end + + it "service is a group" do + @provider.should_receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status) + @provider.load_current_resource + @provider.instance_eval("@is_resource_group").should be_true + end + end + + context "when there is a single subsystem in the group" do + before do + @status = double("Status", :exitstatus => 0, :stdout => "chef1 chef inoperative\n") + end + + it "service is a group" do + @provider.should_receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status) + @provider.load_current_resource + @provider.instance_eval("@is_resource_group").should be_true + end + end + + context "when there service is a subsytem" do + before do + @status = double("Status", :exitstatus => 0, :stdout => "chef chef123 inoperative\n") + end + + it "service is a subsystem" do + @provider.should_receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status) + @provider.load_current_resource + @provider.instance_eval("@is_resource_group").should be_false + end + end + end + + describe "when starting the service" do + before do + @new_resource.service_name "apache" + end + + it "should call the start command for groups" do + @provider.instance_eval('@is_resource_group = true') + @provider.should_receive(:shell_out!).with("startsrc -g #{@new_resource.service_name}") + + @provider.start_service + end + + it "should call the start command for subsystem" do + @provider.should_receive(:shell_out!).with("startsrc -s #{@new_resource.service_name}") + + @provider.start_service + end + end + + describe "when stopping a service" do + before do + @new_resource.service_name "apache" + end + + it "should call the stop command for groups" do + @provider.instance_eval('@is_resource_group = true') + @provider.should_receive(:shell_out!).with("stopsrc -g #{@new_resource.service_name}") + + @provider.stop_service + end + + it "should call the stop command for subsystem" do + @provider.should_receive(:shell_out!).with("stopsrc -s #{@new_resource.service_name}") + + @provider.stop_service + end + end + + describe "when reloading a service" do + before do + @new_resource.service_name "apache" + end + + it "should call the reload command for groups" do + @provider.instance_eval('@is_resource_group = true') + @provider.should_receive(:shell_out!).with("refresh -g #{@new_resource.service_name}") + + @provider.reload_service + end + + it "should call the reload command for subsystem" do + @provider.should_receive(:shell_out!).with("refresh -s #{@new_resource.service_name}") + + @provider.reload_service + end + end + + describe "when restarting the service" do + it "should call stop service followed by start service" do + @provider.should_receive(:stop_service) + @provider.should_receive(:start_service) + + @provider.restart_service + end + end +end + diff --git a/spec/unit/provider/service/aixinit_service_spec.rb b/spec/unit/provider/service/aixinit_service_spec.rb new file mode 100644 index 0000000000..bb090b112c --- /dev/null +++ b/spec/unit/provider/service/aixinit_service_spec.rb @@ -0,0 +1,269 @@ +# +# Author:: kaustubh (<kaustubh@clogeny.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::Provider::Service::AixInit do + before(:each) do + @node = Chef::Node.new + @node.automatic_attrs[:command] = {:ps => 'fuuuu'} + @events = Chef::EventDispatch::Dispatcher.new + @run_context = Chef::RunContext.new(@node, {}, @events) + + @new_resource = Chef::Resource::Service.new("chef") + @provider = Chef::Provider::Service::AixInit.new(@new_resource, @run_context) + + @current_resource = Chef::Resource::Service.new("chef") + @provider.current_resource = @current_resource + + @pid, @stdin, @stdout, @stderr = nil, nil, nil, nil + end + + describe "load_current_resource" do + it "sets current resource attributes" do + @provider.should_receive(:set_current_resource_attributes) + + @provider.load_current_resource + end + end + + describe "action_enable" do + shared_examples_for "the service is up to date" do + it "does not enable the service" do + @provider.should_not_receive(:enable_service) + @provider.action_enable + @provider.set_updated_status + @provider.new_resource.should_not be_updated + end + end + + shared_examples_for "the service is not up to date" do + it "enables the service and sets the resource as updated" do + @provider.should_receive(:enable_service).and_return(true) + @provider.action_enable + @provider.set_updated_status + @provider.new_resource.should be_updated + end + end + + context "when the service is disabled" do + before do + @current_resource.enabled(false) + end + + it_behaves_like "the service is not up to date" + end + + context "when the service is enabled" do + before do + @current_resource.enabled(true) + @current_resource.priority(80) + end + + context "and the service sets no priority" do + it_behaves_like "the service is up to date" + end + + context "and the service requests the same priority as is set" do + before do + @new_resource.priority(80) + end + it_behaves_like "the service is up to date" + end + + context "and the service requests a different priority than is set" do + before do + @new_resource.priority(20) + end + it_behaves_like "the service is not up to date" + end + end + end + + describe "enable_service" do + before do + Dir.stub(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).and_return([]) + end + + context "when the service doesn't set a priority" do + it "creates symlink with status S" do + @provider.should_receive(:create_symlink).with(2,'S','') + + @provider.enable_service + end + end + + context "when the service sets a simple priority (integer)" do + before do + @new_resource.priority(75) + end + + it "creates a symlink with status S and a priority" do + @provider.should_receive(:create_symlink).with(2,'S',75) + + @provider.enable_service + end + end + + context "when the service sets complex priorities (hash)" do + before do + priority = {2 => [:start, 20], 3 => [:stop, 10]} + @new_resource.priority(priority) + end + + it "create symlink with status start (S) or stop (K) and a priority " do + @provider.should_receive(:create_symlink).with(2,'S',20) + @provider.should_receive(:create_symlink).with(3,'K',10) + + @provider.enable_service + end + end + end + + describe "disable_service" do + before do + Dir.stub(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).and_return([]) + end + + context "when the service doesn't set a priority" do + it "creates symlinks with status stop (K)" do + @provider.should_receive(:create_symlink).with(2,'K','') + + @provider.disable_service + end + end + + context "when the service sets a simple priority (integer)" do + before do + @new_resource.priority(75) + end + + it "create symlink with status stop (k) and a priority " do + @provider.should_receive(:create_symlink).with(2,'K',25) + + @provider.disable_service + end + end + + context "when the service sets complex priorities (hash)" do + before do + @priority = {2 => [:start, 20], 3 => [:stop, 10]} + @new_resource.priority(@priority) + end + + it "create symlink with status stop (k) and a priority " do + @provider.should_receive(:create_symlink).with(3,'K',90) + + @provider.disable_service + end + end + end + + describe "set_current_resource_attributes" do + context "when rc2.d contains only start script" do + before do + files = ["/etc/rc.d/rc2.d/S20apache"] + + Dir.stub(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]chef"]).and_return(files) + end + + it "the service is enabled" do + @provider.current_resource.should_receive(:enabled).with(true) + @provider.current_resource.should_receive(:priority).with(20) + + @provider.set_current_resource_attributes + end + end + + context "when rc2.d contains only stop script" do + before do + files = ["/etc/rc.d/rc2.d/K20apache"] + @priority = {2 => [:stop, 20]} + + Dir.stub(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]chef"]).and_return(files) + end + it "the service is not enabled" do + @provider.current_resource.should_receive(:enabled).with(false) + @provider.current_resource.should_receive(:priority).with(@priority) + + @provider.set_current_resource_attributes + end + end + + context "when rc2.d contains both start and stop scripts" do + before do + @files = ["/etc/rc.d/rc2.d/S20apache", "/etc/rc.d/rc2.d/K80apache"] + @priority = {2 => [:start, 20], 2 => [:stop, 80]} + + Dir.stub(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]chef"]).and_return(@files) + end + it "the service is enabled" do + @current_resource.should_receive(:enabled).with(true) + @current_resource.should_receive(:priority).with(@priority) + + @provider.set_current_resource_attributes + end + end + + context "when rc2.d contains only start script (without priority)" do + before do + files = ["/etc/rc.d/rc2.d/Sapache"] + + Dir.stub(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).and_return(files) + end + + it "the service is enabled" do + @provider.current_resource.should_receive(:enabled).with(true) + @provider.current_resource.should_receive(:priority).with('') + + @provider.set_current_resource_attributes + end + end + + context "when rc2.d contains only stop script (without priority)" do + before do + files = ["/etc/rc.d/rc2.d/Kapache"] + @priority = {2 => [:stop, '']} + + Dir.stub(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).and_return(files) + end + it "the service is not enabled" do + @provider.current_resource.should_receive(:enabled).with(false) + @provider.current_resource.should_receive(:priority).with(@priority) + + @provider.set_current_resource_attributes + end + end + + context "when rc2.d contains both start and stop scripts" do + before do + files = ["/etc/rc.d/rc2.d/Sapache", "/etc/rc.d/rc2.d/Kapache"] + @priority = {2 => [:start, ''], 2 => [:stop, '']} + + Dir.stub(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).and_return(files) + end + it "the service is enabled" do + @current_resource.should_receive(:enabled).with(true) + @current_resource.should_receive(:priority).with(@priority) + + @provider.set_current_resource_attributes + end + end + end +end + diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb new file mode 100644 index 0000000000..927cca4f58 --- /dev/null +++ b/spec/unit/provider_resolver_spec.rb @@ -0,0 +1,387 @@ +# +# Author:: Lamont Granquist (<lamont@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 'spec_helper' +require 'chef/mixin/convert_to_class_name' + +include Chef::Mixin::ConvertToClassName + +describe Chef::ProviderResolver do + + let(:node) do + node = Chef::Node.new + allow(node).to receive(:[]).with(:os).and_return(os) + allow(node).to receive(:[]).with(:platform_family).and_return(platform_family) + allow(node).to receive(:[]).with(:platform).and_return(platform) + allow(node).to receive(:[]).with(:platform_version).and_return(platform_version) + allow(node).to receive(:is_a?).and_return(Chef::Node) + node + end + + let(:provider_resolver) { Chef::ProviderResolver.new(node) } + + let(:action) { :start } + + let(:resolved_provider) { provider_resolver.resolve(resource, action) } + + let(:provider) { nil } + + let(:resource_name) { :service } + + let(:resource) { double(Chef::Resource, provider: provider, resource_name: resource_name) } + + describe "resolving service resource" do + def stub_service_providers(*services) + services ||= [] + allow(Chef::Platform::ServiceHelpers).to receive(:service_resource_providers) + .and_return(services) + end + + def stub_service_configs(*configs) + configs ||= [] + allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp") + .and_return(configs) + end + + before do + expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup) + allow(resource).to receive(:service_name).and_return("ntp") + end + + shared_examples_for "a debian platform with upstart and update-rc.d" do + before do + stub_service_providers(:debian, :invokercd, :upstart) + end + + it "when only the SysV init script exists, it returns a Service::Debian provider" do + allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp") + .and_return( [ :initd ] ) + expect(resolved_provider).to eql(Chef::Provider::Service::Debian) + end + + it "when both SysV and Upstart scripts exist, it returns a Service::Upstart provider" do + allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp") + .and_return( [ :initd, :upstart ] ) + expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) + end + + it "when only the Upstart script exists, it returns a Service::Upstart provider" do + allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp") + .and_return( [ :upstart ] ) + expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) + end + + it "when both do not exist, it calls the old style provider resolver and returns a Debian Provider" do + allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp") + .and_return( [ ] ) + expect(resolved_provider).to eql(Chef::Provider::Service::Debian) + end + end + + shared_examples_for "a debian platform using the insserv provider" do + context "with a default install" do + before do + stub_service_providers(:debian, :invokercd, :insserv) + end + + it "uses the Service::Insserv Provider to manage sysv init scripts" do + allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp") + .and_return( [ :initd ] ) + expect(resolved_provider).to eql(Chef::Provider::Service::Insserv) + end + end + + context "when the user has installed upstart" do + before do + stub_service_providers(:debian, :invokercd, :insserv, :upstart) + end + + it "when only the SysV init script exists, it returns a Service::Debian provider" do + allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp") + .and_return( [ :initd ] ) + expect(resolved_provider).to eql(Chef::Provider::Service::Insserv) + end + + it "when both SysV and Upstart scripts exist, it returns a Service::Upstart provider" do + allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp") + .and_return( [ :initd, :upstart ] ) + expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) + end + + it "when only the Upstart script exists, it returns a Service::Upstart provider" do + allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp") + .and_return( [ :upstart ] ) + expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) + end + + it "when both do not exist, it calls the old style provider resolver and returns a Debian Provider" do + allow(Chef::Platform::ServiceHelpers).to receive(:config_for_service).with("ntp") + .and_return( [ ] ) + expect(resolved_provider).to eql(Chef::Provider::Service::Insserv) + end + end + end + + describe "on Linux" do + end + + describe "on Ubuntu 14.04" do + let(:os) { "linux" } + let(:platform) { "ubuntu" } + let(:platform_family) { "debian" } + let(:platform_version) { "14.04" } + + it_behaves_like "a debian platform with upstart and update-rc.d" + end + + describe "on Ubuntu 10.04" do + let(:os) { "linux" } + let(:platform) { "ubuntu" } + let(:platform_family) { "debian" } + let(:platform_version) { "10.04" } + + it_behaves_like "a debian platform with upstart and update-rc.d" + end + + # old debian uses the Debian provider (does not have insserv or upstart, or update-rc.d???) + describe "on Debian 4.0" do + let(:os) { "linux" } + let(:platform) { "debian" } + let(:platform_family) { "debian" } + let(:platform_version) { "4.0" } + + #it_behaves_like "a debian platform using the debian provider" + end + + # Debian replaced the debian provider with insserv in the FIXME:VERSION distro + describe "on Debian 7.0" do + let(:os) { "linux" } + let(:platform) { "debian" } + let(:platform_family) { "debian" } + let(:platform_version) { "7.0" } + + it_behaves_like "a debian platform using the insserv provider" + end + + %w{solaris2 openindiana opensolaris nexentacore omnios smartos}.each do |platform| + describe "on #{platform}" do + let(:os) { "solaris2" } + let(:platform) { platform } + let(:platform_family) { platform } + let(:platform_version) { "5.11" } + + it "returns a Solaris provider" do + stub_service_providers + stub_service_configs + expect(resolved_provider).to eql(Chef::Provider::Service::Solaris) + end + + it "always returns a Solaris provider" do + # no matter what we stub on the next two lines we should get a Solaris provider + stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) + stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd) + expect(resolved_provider).to eql(Chef::Provider::Service::Solaris) + end + end + end + + %w{mswin mingw32 windows}.each do |platform| + describe "on #{platform}" do + let(:os) { "windows" } + let(:platform) { platform } + let(:platform_family) { "windows" } + let(:platform_version) { "5.11" } + + it "returns a Windows provider" do + stub_service_providers + stub_service_configs + expect(resolved_provider).to eql(Chef::Provider::Service::Windows) + end + + it "always returns a Windows provider" do + # no matter what we stub on the next two lines we should get a Windows provider + stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) + stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd) + expect(resolved_provider).to eql(Chef::Provider::Service::Windows) + end + end + end + + %w{mac_os_x mac_os_x_server}.each do |platform| + describe "on #{platform}" do + let(:os) { "darwin" } + let(:platform) { platform } + let(:platform_family) { "mac_os_x" } + let(:platform_version) { "10.9.2" } + + it "returns a Macosx provider" do + stub_service_providers + stub_service_configs + expect(resolved_provider).to eql(Chef::Provider::Service::Macosx) + end + + it "always returns a Macosx provider" do + # no matter what we stub on the next two lines we should get a Macosx provider + stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) + stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd) + expect(resolved_provider).to eql(Chef::Provider::Service::Macosx) + end + end + end + + %w{freebsd netbsd}.each do |platform| + describe "on #{platform}" do + let(:os) { platform } + let(:platform) { platform } + let(:platform_family) { platform } + let(:platform_version) { "10.0-RELEASE" } + + it "returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do + stub_service_providers + stub_service_configs(:usr_local_etc_rcd) + expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) + end + + it "returns a Freebsd provider if it finds the /etc/rc.d initscript" do + stub_service_providers + stub_service_configs(:etc_rcd) + expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) + end + + it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do + # should only care about :usr_local_etc_rcd stub in the service configs + stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) + stub_service_configs(:usr_local_etc_rcd, :initd, :upstart, :xinetd, :systemd) + expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) + end + + it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do + # should only care about :etc_rcd stub in the service configs + stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) + stub_service_configs(:etc_rcd, :initd, :upstart, :xinetd, :systemd) + expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) + end + + it "foo" do + stub_service_providers + stub_service_configs + expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) + end + end + end + + end + + describe "resolving static providers" do + def resource_class(resource) + Chef::Resource.const_get(convert_to_class_name(resource.to_s)) + end + static_mapping = { + apt_package: Chef::Provider::Package::Apt, + bash: Chef::Provider::Script, + bff_package: Chef::Provider::Package::Aix, + breakpoint: Chef::Provider::Breakpoint, + chef_gem: Chef::Provider::Package::Rubygems, + cookbook_file: Chef::Provider::CookbookFile, + csh: Chef::Provider::Script, + deploy: Chef::Provider::Deploy::Timestamped, + deploy_revision: Chef::Provider::Deploy::Revision, + directory: Chef::Provider::Directory, + dpkg_package: Chef::Provider::Package::Dpkg, + dsc_script: Chef::Provider::DscScript, + easy_install_package: Chef::Provider::Package::EasyInstall, + erl_call: Chef::Provider::ErlCall, + execute: Chef::Provider::Execute, + file: Chef::Provider::File, + gem_package: Chef::Provider::Package::Rubygems, + git: Chef::Provider::Git, + homebrew_package: Chef::Provider::Package::Homebrew, + http_request: Chef::Provider::HttpRequest, + ips_package: Chef::Provider::Package::Ips, + link: Chef::Provider::Link, + log: Chef::Provider::Log::ChefLog, + macports_package: Chef::Provider::Package::Macports, + pacman_package: Chef::Provider::Package::Pacman, + paludis_package: Chef::Provider::Package::Paludis, + perl: Chef::Provider::Script, + portage_package: Chef::Provider::Package::Portage, + 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, + smartos_package: Chef::Provider::Package::SmartOS, + solaris_package: Chef::Provider::Package::Solaris, + subversion: Chef::Provider::Subversion, + template: Chef::Provider::Template, + timestamped_deploy: Chef::Provider::Deploy::Timestamped, + whyrun_safe_ruby_block: Chef::Provider::WhyrunSafeRubyBlock, + windows_package: Chef::Provider::Package::Windows, + windows_service: Chef::Provider::Service::Windows, + yum_package: Chef::Provider::Package::Yum, + } + + describe "on Ubuntu 14.04" do + let(:os) { "linux" } + let(:platform) { "ubuntu" } + let(:platform_family) { "debian" } + let(:platform_version) { "14.04" } + + supported_providers = [ + :apt_package, :bash, :breakpoint, :chef_gem, :cookbook_file, :csh, :deploy, + :deploy_revision, :directory, :dpkg_package, :easy_install_package, + :erl_call, :execute, :file, :gem_package, :git, :http_request, :link, :log, :pacman_package, :paludis_package, + :perl, :python, :remote_directory, :route, :rpm_package, :ruby, :ruby_block, :script, + :subversion, :template, :timestamped_deploy, :whyrun_safe_ruby_block, :yum_package, + ] + + supported_providers.each do |static_resource| + static_provider = static_mapping[static_resource] + context "when the resource is a #{static_resource}" do + let(:resource) { double(Chef::Resource, provider: nil, resource_name: static_resource) } + let(:action) { :start } # in reality this doesn't matter much + it "should resolve to a #{static_provider} provider" do + expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup) + expect(resolved_provider).to eql(static_provider) + end + end + end + + unsupported_providers = [ + :bff_package, :dsc_script, :homebrew_package, :ips_package, :macports_package, + :smartos_package, :solaris_package, :windows_package, + :windows_service, + ] + + unsupported_providers.each do |static_resource| + static_provider = static_mapping[static_resource] + context "when the resource is a #{static_resource}" do + let(:resource) { double(Chef::Resource, provider: nil, resource_name: static_resource) } + let(:action) { :start } # in reality this doesn't matter much + it "should fall back into the old provider mapper code and hooks" do + retval = Object.new + expect(provider_resolver).to receive(:maybe_chef_platform_lookup).and_return(retval) + expect(resolved_provider).to equal(retval) + end + end + end + end + end +end diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb index c795ba3788..0e660dc0cc 100644 --- a/spec/unit/recipe_spec.rb +++ b/spec/unit/recipe_spec.rb @@ -119,7 +119,8 @@ describe Chef::Recipe do describe "should locate platform mapped resources" do it "locate resource for particular platform" do - Object.const_set('ShaunTheSheep', Class.new(Chef::Resource){ provides :laughter, :on_platforms => ["television"] }) + ShaunTheSheep = Class.new(Chef::Resource) + ShaunTheSheep.provides :laughter, :on_platforms => ["television"] node.automatic[:platform] = "television" node.automatic[:platform_version] = "123" res = recipe.laughter "timmy" @@ -128,7 +129,8 @@ describe Chef::Recipe do end it "locate a resource for all platforms" do - Object.const_set("YourMom", Class.new(Chef::Resource){ provides :love_and_caring }) + YourMom = Class.new(Chef::Resource) + YourMom.provides :love_and_caring res = recipe.love_and_caring "mommy" res.name.should eql("mommy") res.kind_of?(YourMom) @@ -188,16 +190,7 @@ describe Chef::Recipe do # zen_follower resource has this: # provides :follower, :on_platforms => ["zen"] before do - node.stub(:[]) do |key| - case key - when :platform - :zen - when :platform_version - "1.0.0" - else - nil - end - end + node.automatic_attrs[:platform] = "zen" end let(:resource_follower) do @@ -345,6 +338,17 @@ describe Chef::Recipe do end recipe.resources(:zen_master => "lao tzu").something.should eql(false) end + + it "should return the last statement in the definition as the retval" do + crow_define = Chef::ResourceDefinition.new + crow_define.define :crow, :peace => false, :something => true do + "the return val" + end + run_context.definitions[:crow] = crow_define + recipe.crow "mine" do + peace true + end.should eql("the return val") + end end end @@ -361,6 +365,15 @@ describe Chef::Recipe do end end + describe "handle exec calls" do + it "should raise ResourceNotFound error if exec is used" do + code = <<-CODE + exec 'do_not_try_to_exec' + CODE + lambda { recipe.instance_eval(code) }.should raise_error(Chef::Exceptions::ResourceNotFound) + end + end + describe "from_file" do it "should load a resource from a ruby file" do recipe.from_file(File.join(CHEF_SPEC_DATA, "recipes", "test.rb")) @@ -414,6 +427,14 @@ describe Chef::Recipe do end describe "tags" do + describe "with the default node object" do + let(:node) { Chef::Node.new } + + it "should return false for any tags" do + recipe.tagged?("foo").should be(false) + end + end + it "should set tags via tag" do recipe.tag "foo" node[:tags].should include("foo") diff --git a/spec/unit/resource/apt_package_spec.rb b/spec/unit/resource/apt_package_spec.rb index 58b007c327..9503e0cbe1 100644 --- a/spec/unit/resource/apt_package_spec.rb +++ b/spec/unit/resource/apt_package_spec.rb @@ -17,25 +17,22 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::AptPackage, "initialize" do + static_provider_resolution( + resource: Chef::Resource::AptPackage, + provider: Chef::Provider::Package::Apt, + name: :apt_package, + action: :install, + os: "linux", + ) + before(:each) do @resource = Chef::Resource::AptPackage.new("foo") end - it "should return a Chef::Resource::AptPackage" do - @resource.should be_a_kind_of(Chef::Resource::AptPackage) - end - - it "should set the resource_name to :apt_package" do - @resource.resource_name.should eql(:apt_package) - end - - it "should set the provider to Chef::Provider::Package::Apt" do - @resource.provider.should eql(Chef::Provider::Package::Apt) - end - it "should support default_release" do @resource.default_release("lenny-backports") @resource.default_release.should eql("lenny-backports") diff --git a/spec/unit/resource/breakpoint_spec.rb b/spec/unit/resource/breakpoint_spec.rb index 412211a038..ed655b84a6 100644 --- a/spec/unit/resource/breakpoint_spec.rb +++ b/spec/unit/resource/breakpoint_spec.rb @@ -17,9 +17,17 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::Breakpoint do + static_provider_resolution( + resource: Chef::Resource::Breakpoint, + provider: Chef::Provider::Breakpoint, + name: :breakpoint, + action: :break, + ) + before do @breakpoint = Chef::Resource::Breakpoint.new end @@ -36,8 +44,4 @@ describe Chef::Resource::Breakpoint do @breakpoint.name.should match(/breakpoint_spec\.rb\:[\d]{2}\:in \`new\'$/) end - it "uses the breakpoint provider" do - @breakpoint.provider.should == Chef::Provider::Breakpoint - end - end diff --git a/spec/unit/resource/chef_gem_spec.rb b/spec/unit/resource/chef_gem_spec.rb index dda65f8741..6a419b3f3b 100644 --- a/spec/unit/resource/chef_gem_spec.rb +++ b/spec/unit/resource/chef_gem_spec.rb @@ -18,24 +18,17 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::ChefGem, "initialize" do - before(:each) do - @resource = Chef::Resource::ChefGem.new("foo") - end - - it "should return a Chef::Resource::ChefGem" do - @resource.should be_a_kind_of(Chef::Resource::ChefGem) - end - - it "should set the resource_name to :chef_gem" do - @resource.resource_name.should eql(:chef_gem) - end + static_provider_resolution( + resource: Chef::Resource::ChefGem, + provider: Chef::Provider::Package::Rubygems, + name: :chef_gem, + action: :install, + ) - it "should set the provider to Chef::Provider::Package::Rubygems" do - @resource.provider.should eql(Chef::Provider::Package::Rubygems) - end end describe Chef::Resource::ChefGem, "gem_binary" do diff --git a/spec/unit/resource/deploy_revision_spec.rb b/spec/unit/resource/deploy_revision_spec.rb index 1f509992aa..d136aa251e 100644 --- a/spec/unit/resource/deploy_revision_spec.rb +++ b/spec/unit/resource/deploy_revision_spec.rb @@ -17,31 +17,26 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::DeployRevision do - it "defaults to the revision deploy provider" do - @resource = Chef::Resource::DeployRevision.new("deploy _this_!") - @resource.provider.should == Chef::Provider::Deploy::Revision - end - - it "has a name of deploy_revision" do - @resource = Chef::Resource::DeployRevision.new("deploy _this_!") - @resource.resource_name.should == :deploy_revision - end + static_provider_resolution( + resource: Chef::Resource::DeployRevision, + provider: Chef::Provider::Deploy::Revision, + name: :deploy_revision, + action: :deploy, + ) end describe Chef::Resource::DeployBranch do - it "defaults to the revision deploy provider" do - @resource = Chef::Resource::DeployBranch.new("deploy _this_!") - @resource.provider.should == Chef::Provider::Deploy::Revision - end - - it "has a name of deploy_branch" do - @resource = Chef::Resource::DeployBranch.new("deploy _this_!") - @resource.resource_name.should == :deploy_branch - end + static_provider_resolution( + resource: Chef::Resource::DeployBranch, + provider: Chef::Provider::Deploy::Revision, + name: :deploy_branch, + action: :deploy, + ) end diff --git a/spec/unit/resource/deploy_spec.rb b/spec/unit/resource/deploy_spec.rb index 7cc25ed41c..914ea19030 100644 --- a/spec/unit/resource/deploy_spec.rb +++ b/spec/unit/resource/deploy_spec.rb @@ -17,9 +17,18 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::Deploy do + static_provider_resolution( + resource: Chef::Resource::Deploy, + provider: Chef::Provider::Deploy::Timestamped, + name: :deploy, + action: :deploy, + ) + + class << self def resource_has_a_string_attribute(attr_name) it "has a String attribute for #{attr_name.to_s}" do @@ -196,10 +205,6 @@ describe Chef::Resource::Deploy do @resource.restart.should == restart_like_this end - it "defaults to using the Deploy::Timestamped provider" do - @resource.provider.should == Chef::Provider::Deploy::Timestamped - end - it "allows providers to be set with a full class name" do @resource.provider Chef::Provider::Deploy::Timestamped @resource.provider.should == Chef::Provider::Deploy::Timestamped diff --git a/spec/unit/resource/dpkg_package_spec.rb b/spec/unit/resource/dpkg_package_spec.rb index 9ef498a577..931e6763bd 100644 --- a/spec/unit/resource/dpkg_package_spec.rb +++ b/spec/unit/resource/dpkg_package_spec.rb @@ -17,22 +17,16 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::DpkgPackage, "initialize" do - before(:each) do - @resource = Chef::Resource::DpkgPackage.new("foo") - end + static_provider_resolution( + resource: Chef::Resource::DpkgPackage, + provider: Chef::Provider::Package::Dpkg, + name: :dpkg_package, + action: :install, + os: 'linux', + ) - it "should return a Chef::Resource::DpkgPackage" do - @resource.should be_a_kind_of(Chef::Resource::DpkgPackage) - end - - it "should set the resource_name to :dpkg_package" do - @resource.resource_name.should eql(:dpkg_package) - end - - it "should set the provider to Chef::Provider::Package::Dpkg" do - @resource.provider.should eql(Chef::Provider::Package::Dpkg) - end end diff --git a/spec/unit/resource/easy_install_package_spec.rb b/spec/unit/resource/easy_install_package_spec.rb index 9682c8177b..d3a5f4a0fe 100644 --- a/spec/unit/resource/easy_install_package_spec.rb +++ b/spec/unit/resource/easy_install_package_spec.rb @@ -17,30 +17,21 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::EasyInstallPackage, "initialize" do + static_provider_resolution( + resource: Chef::Resource::EasyInstallPackage, + provider: Chef::Provider::Package::EasyInstall, + name: :easy_install_package, + action: :install, + ) + before(:each) do @resource = Chef::Resource::EasyInstallPackage.new("foo") end - it "should create a new Chef::Resource::EasyInstallPackage" do - @resource.should be_a_kind_of(Chef::Resource) - @resource.should be_a_kind_of(Chef::Resource::EasyInstallPackage) - end - - it "should return a Chef::Resource::EasyInstallPackage" do - @resource.should be_a_kind_of(Chef::Resource::EasyInstallPackage) - end - - it "should set the resource_name to :easy_install_package" do - @resource.resource_name.should eql(:easy_install_package) - end - - it "should set the provider to Chef::Provider::Package::EasyInstall" do - @resource.provider.should eql(Chef::Provider::Package::EasyInstall) - end - it "should allow you to set the easy_install_binary attribute" do @resource.easy_install_binary "/opt/local/bin/easy_install" @resource.easy_install_binary.should eql("/opt/local/bin/easy_install") diff --git a/spec/unit/resource/execute_spec.rb b/spec/unit/resource/execute_spec.rb index 8c8dcfb6ca..e1728b7892 100644 --- a/spec/unit/resource/execute_spec.rb +++ b/spec/unit/resource/execute_spec.rb @@ -23,4 +23,9 @@ describe Chef::Resource::Execute do let(:resource_instance_name) { "some command" } let(:execute_resource) { Chef::Resource::Execute.new(resource_instance_name) } it_behaves_like "an execute resource" + + it "default guard interpreter should be :execute interpreter" do + execute_resource.guard_interpreter.should be(:execute) + end + end diff --git a/spec/unit/resource/freebsd_package_spec.rb b/spec/unit/resource/freebsd_package_spec.rb index ae12abac6e..04a6962270 100644 --- a/spec/unit/resource/freebsd_package_spec.rb +++ b/spec/unit/resource/freebsd_package_spec.rb @@ -57,7 +57,7 @@ describe Chef::Resource::FreebsdPackage do describe "if __Freebsd_version is greater than or equal to 1000017" do it "should be Freebsd::Pkgng" do [1000017, 1000018, 1000500, 1001001, 1100000].each do |__freebsd_version| - @node.normal[:os_version] = __freebsd_version + @node.automatic_attrs[:os_version] = __freebsd_version @resource.after_created @resource.provider.should == Chef::Provider::Package::Freebsd::Pkgng end @@ -79,8 +79,7 @@ describe Chef::Resource::FreebsdPackage do @resource.stub(:shell_out!).with("make -V WITH_PKGNG", :env => nil).and_return(pkg_enabled) [1000016, 1000000, 901503, 902506, 802511].each do |__freebsd_version| - @node[:os_version] == __freebsd_version - @node.normal[:os_version] = __freebsd_version + @node.automatic_attrs[:os_version] = __freebsd_version @resource.after_created @resource.provider.should == Chef::Provider::Package::Freebsd::Pkg end diff --git a/spec/unit/resource/gem_package_spec.rb b/spec/unit/resource/gem_package_spec.rb index 98703455de..01c4fb6106 100644 --- a/spec/unit/resource/gem_package_spec.rb +++ b/spec/unit/resource/gem_package_spec.rb @@ -17,24 +17,17 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::GemPackage, "initialize" do - before(:each) do - @resource = Chef::Resource::GemPackage.new("foo") - end - - it "should return a Chef::Resource::GemPackage" do - @resource.should be_a_kind_of(Chef::Resource::GemPackage) - end - - it "should set the resource_name to :gem_package" do - @resource.resource_name.should eql(:gem_package) - end + static_provider_resolution( + resource: Chef::Resource::GemPackage, + provider: Chef::Provider::Package::Rubygems, + name: :gem_package, + action: :install, + ) - it "should set the provider to Chef::Provider::Package::Rubygems" do - @resource.provider.should eql(Chef::Provider::Package::Rubygems) - end end describe Chef::Resource::GemPackage, "gem_binary" do diff --git a/spec/unit/resource/git_spec.rb b/spec/unit/resource/git_spec.rb index 95a30c28a4..da7aee1014 100644 --- a/spec/unit/resource/git_spec.rb +++ b/spec/unit/resource/git_spec.rb @@ -17,9 +17,17 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::Git do + static_provider_resolution( + resource: Chef::Resource::Git, + provider: Chef::Provider::Git, + name: :git, + action: :sync, + ) + before(:each) do @git = Chef::Resource::Git.new("my awesome webapp") end @@ -29,10 +37,6 @@ describe Chef::Resource::Git do @git.should be_an_instance_of(Chef::Resource::Git) end - it "uses the git provider" do - @git.provider.should eql(Chef::Provider::Git) - end - it "uses aliases revision as branch" do @git.branch "HEAD" @git.revision.should eql("HEAD") diff --git a/spec/unit/resource/homebrew_package_spec.rb b/spec/unit/resource/homebrew_package_spec.rb index bb657607b7..bb248d1189 100644 --- a/spec/unit/resource/homebrew_package_spec.rb +++ b/spec/unit/resource/homebrew_package_spec.rb @@ -16,26 +16,19 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::HomebrewPackage, 'initialize' do - let(:resource) { Chef::Resource::HomebrewPackage.new('emacs') } - - it 'returns a Chef::Resource::HomebrewPackage' do - expect(resource).to be_a_kind_of(Chef::Resource::HomebrewPackage) - end - - it 'sets the resource_name to :homebrew_package' do - expect(resource.resource_name).to eql(:homebrew_package) - end + static_provider_resolution( + resource: Chef::Resource::HomebrewPackage, + provider: Chef::Provider::Package::Homebrew, + name: :homebrew_package, + action: :install, + os: "mac_os_x", + ) - it 'sets the provider to Chef::Provider::Package::Homebrew' do - expect(resource.provider).to eql(Chef::Provider::Package::Homebrew) - end - - it 'sets the homebrew_user to nil' do - expect(resource.homebrew_user).to eql(nil) - end + let(:resource) { Chef::Resource::HomebrewPackage.new('emacs') } shared_examples 'home_brew user set and returned' do it 'returns the configured homebrew_user' do diff --git a/spec/unit/resource/ips_package_spec.rb b/spec/unit/resource/ips_package_spec.rb index 61661922fa..8718c5c093 100644 --- a/spec/unit/resource/ips_package_spec.rb +++ b/spec/unit/resource/ips_package_spec.rb @@ -17,25 +17,22 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::IpsPackage, "initialize" do + static_provider_resolution( + resource: Chef::Resource::IpsPackage, + provider: Chef::Provider::Package::Ips, + name: :ips_package, + action: :install, + os: "solaris2", + ) + before(:each) do @resource = Chef::Resource::IpsPackage.new("crypto/gnupg") end - it "should return a Chef::Resource::IpsPackage" do - @resource.should be_a_kind_of(Chef::Resource::IpsPackage) - end - - it "should set the resource_name to :ips_package" do - @resource.resource_name.should eql(:ips_package) - end - - it "should set the provider to Chef::Provider::Package::Ips" do - @resource.provider.should eql(Chef::Provider::Package::Ips) - end - it "should support accept_license" do @resource.accept_license(true) @resource.accept_license.should eql(true) diff --git a/spec/unit/resource/macports_package_spec.rb b/spec/unit/resource/macports_package_spec.rb index 7e2e200487..0a203b2e97 100644 --- a/spec/unit/resource/macports_package_spec.rb +++ b/spec/unit/resource/macports_package_spec.rb @@ -17,21 +17,16 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::MacportsPackage, "initialize" do - before(:each) do - @resource = Chef::Resource::MacportsPackage.new("foo") - end - it "should return a Chef::Resource::MacportsPackage" do - @resource.should be_a_kind_of(Chef::Resource::MacportsPackage) - end + static_provider_resolution( + resource: Chef::Resource::MacportsPackage, + provider: Chef::Provider::Package::Macports, + name: :macports_package, + action: :install, + os: "mac_os_x", + ) - it "should set the resource_name to :macports_package" do - @resource.resource_name.should eql(:macports_package) - end - - it "should set the provider to Chef::Provider::Package::Macports" do - @resource.provider.should eql(Chef::Provider::Package::Macports) - end end diff --git a/spec/unit/resource/pacman_package_spec.rb b/spec/unit/resource/pacman_package_spec.rb index ec5feeb82c..975863d04f 100644 --- a/spec/unit/resource/pacman_package_spec.rb +++ b/spec/unit/resource/pacman_package_spec.rb @@ -17,22 +17,16 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::PacmanPackage, "initialize" do - before(:each) do - @resource = Chef::Resource::PacmanPackage.new("foo") - end + static_provider_resolution( + resource: Chef::Resource::PacmanPackage, + provider: Chef::Provider::Package::Pacman, + name: :pacman_package, + action: :install, + os: "linux", + ) - it "should return a Chef::Resource::PacmanPackage" do - @resource.should be_a_kind_of(Chef::Resource::PacmanPackage) - end - - it "should set the resource_name to :pacman_package" do - @resource.resource_name.should eql(:pacman_package) - end - - it "should set the provider to Chef::Provider::Package::Pacman" do - @resource.provider.should eql(Chef::Provider::Package::Pacman) - end end diff --git a/spec/unit/resource/rpm_package_spec.rb b/spec/unit/resource/rpm_package_spec.rb index 25930a5484..d209c6a5a2 100644 --- a/spec/unit/resource/rpm_package_spec.rb +++ b/spec/unit/resource/rpm_package_spec.rb @@ -17,22 +17,18 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::RpmPackage, "initialize" do - before(:each) do - @resource = Chef::Resource::RpmPackage.new("foo") + %w{linux aix}.each do |os| + static_provider_resolution( + resource: Chef::Resource::RpmPackage, + provider: Chef::Provider::Package::Rpm, + name: :rpm_package, + action: :install, + os: os + ) end - it "should return a Chef::Resource::RpmPackage" do - @resource.should be_a_kind_of(Chef::Resource::RpmPackage) - end - - it "should set the resource_name to :rpm_package" do - @resource.resource_name.should eql(:rpm_package) - end - - it "should set the provider to Chef::Provider::Package::Rpm" do - @resource.provider.should eql(Chef::Provider::Package::Rpm) - end end diff --git a/spec/unit/resource/service_spec.rb b/spec/unit/resource/service_spec.rb index ec62d012bb..9b779e5830 100644 --- a/spec/unit/resource/service_spec.rb +++ b/spec/unit/resource/service_spec.rb @@ -29,20 +29,10 @@ describe Chef::Resource::Service do @resource.should be_a_kind_of(Chef::Resource) @resource.should be_a_kind_of(Chef::Resource::Service) end - + it "should not set a provider unless node[:init_package] is defined as systemd" do @resource.provider.should == nil end - - it "should set the provider to Chef::Provider::Service::Systemd if node[:init_package] is systemd" do - node = Chef::Node.new - node.set[:init_package] = "systemd" - cookbook_collection = Chef::CookbookCollection.new([]) - events = Chef::EventDispatch::Dispatcher.new - run_context = Chef::RunContext.new(node, cookbook_collection, events) - @resource = Chef::Resource::Service.new("chef", run_context) - @resource.provider.should == Chef::Provider::Service::Systemd - end it "should set the service_name to the first argument to new" do @resource.service_name.should eql("chef") diff --git a/spec/unit/resource/smartos_package_spec.rb b/spec/unit/resource/smartos_package_spec.rb index 391713c8ff..c2cf546dd5 100644 --- a/spec/unit/resource/smartos_package_spec.rb +++ b/spec/unit/resource/smartos_package_spec.rb @@ -16,23 +16,18 @@ # limitations under the License. # -require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) +require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::SmartosPackage, "initialize" do - before(:each) do - @resource = Chef::Resource::SmartosPackage.new("foo") - end + static_provider_resolution( + resource: Chef::Resource::SmartosPackage, + provider: Chef::Provider::Package::SmartOS, + name: :smartos_package, + action: :install, + os: "solaris2", + platform_family: "smartos", + ) - it "should return a Chef::Resource::SmartosPackage" do - @resource.should be_a_kind_of(Chef::Resource::SmartosPackage) - end - - it "should set the resource_name to :smartos_package" do - @resource.resource_name.should eql(:smartos_package) - end - - it "should set the provider to Chef::Provider::Package::SmartOS" do - @resource.provider.should eql(Chef::Provider::Package::SmartOS) - end end diff --git a/spec/unit/resource/solaris_package_spec.rb b/spec/unit/resource/solaris_package_spec.rb index 6d0260ab5a..ab4e03807d 100644 --- a/spec/unit/resource/solaris_package_spec.rb +++ b/spec/unit/resource/solaris_package_spec.rb @@ -17,41 +17,26 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::SolarisPackage, "initialize" do - before(:each) do - @resource = Chef::Resource::SolarisPackage.new("foo") - end - - it "should return a Chef::Resource::SolarisPackage object" do - @resource.should be_a_kind_of(Chef::Resource::SolarisPackage) + %w{solaris2 nexentacore}.each do |platform_family| + static_provider_resolution( + resource: Chef::Resource::SolarisPackage, + provider: Chef::Provider::Package::Solaris, + name: :solaris_package, + action: :install, + os: "solaris2", + platform_family: platform_family, + ) end - it "should not raise any Error when valid number of arguments are provided" do - expect { Chef::Resource::SolarisPackage.new("foo") }.to_not raise_error - end - - it "should raise ArgumentError when incorrect number of arguments are provided" do - expect { Chef::Resource::SolarisPackage.new }.to raise_error(ArgumentError) + before(:each) do + @resource = Chef::Resource::SolarisPackage.new("foo") end it "should set the package_name to the name provided" do @resource.package_name.should eql("foo") end - - it "should set the resource_name to :solaris_package" do - @resource.resource_name.should eql(:solaris_package) - end - - it "should set the run_context to the run_context provided" do - @run_context = double() - @run_context.stub(:node) - resource = Chef::Resource::SolarisPackage.new("foo", @run_context) - resource.run_context.should eql(@run_context) - end - - it "should set the provider to Chef::Provider::Package::Solaris" do - @resource.provider.should eql(Chef::Provider::Package::Solaris) - end end diff --git a/spec/unit/resource/subversion_spec.rb b/spec/unit/resource/subversion_spec.rb index ae06ce665a..a52d8421c6 100644 --- a/spec/unit/resource/subversion_spec.rb +++ b/spec/unit/resource/subversion_spec.rb @@ -17,9 +17,17 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::Subversion do + static_provider_resolution( + resource: Chef::Resource::Subversion, + provider: Chef::Provider::Subversion, + name: :subversion, + action: :install, + ) + before do @svn = Chef::Resource::Subversion.new("ohai, svn project!") end @@ -29,10 +37,6 @@ describe Chef::Resource::Subversion do @svn.should be_a_kind_of(Chef::Resource::Scm) end - it "uses the subversion provider" do - @svn.provider.should eql(Chef::Provider::Subversion) - end - it "allows the force_export action" do @svn.allowed_actions.should include(:force_export) end diff --git a/spec/unit/resource/timestamped_deploy_spec.rb b/spec/unit/resource/timestamped_deploy_spec.rb index f380ffca87..2af9d8bb0f 100644 --- a/spec/unit/resource/timestamped_deploy_spec.rb +++ b/spec/unit/resource/timestamped_deploy_spec.rb @@ -18,11 +18,37 @@ require 'spec_helper' -describe Chef::Resource::TimestampedDeploy do +describe Chef::Resource::TimestampedDeploy, "initialize" do - it "defaults to the TimestampedDeploy provider" do - @resource = Chef::Resource::TimestampedDeploy.new("stuff") - @resource.provider.should == Chef::Provider::Deploy::Timestamped + let(:node) { + node = Chef::Node.new + node.automatic_attrs[:os] = 'linux' + node.automatic_attrs[:platform_family] = 'rhel' + node + } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:provider_resolver) { Chef::ProviderResolver.new(node) } + let(:run_context) { + run_context = Chef::RunContext.new(node, {}, events) + run_context.provider_resolver = provider_resolver + run_context + } + let(:resource) { Chef::Resource::TimestampedDeploy.new("stuff", run_context) } + + it "should return a Chef::Resource::TimestampedDeploy" do + expect(resource).to be_a_kind_of(Chef::Resource::TimestampedDeploy) + end + + it "should set the resource_name to :timestamped_deploy" do + expect(resource.resource_name).to eql(:deploy) end + it "should leave the provider nil" do + expect(resource.provider).to eql(nil) + end + + it "should resolve to a Chef::Provider::Deploy::Timestamped" do + expect(resource.provider_for_action(:install)).to be_a(Chef::Provider::Deploy::Timestamped) + end end + diff --git a/spec/unit/resource/yum_package_spec.rb b/spec/unit/resource/yum_package_spec.rb index 57ab4dfcd9..7e1979fdfd 100644 --- a/spec/unit/resource/yum_package_spec.rb +++ b/spec/unit/resource/yum_package_spec.rb @@ -17,24 +17,19 @@ # require 'spec_helper' +require 'support/shared/unit/resource/static_provider_resolution' describe Chef::Resource::YumPackage, "initialize" do - before(:each) do - @resource = Chef::Resource::YumPackage.new("foo") - end - - it "should return a Chef::Resource::YumPackage" do - @resource.should be_a_kind_of(Chef::Resource::YumPackage) - end - - it "should set the resource_name to :yum_package" do - @resource.resource_name.should eql(:yum_package) - end + static_provider_resolution( + resource: Chef::Resource::YumPackage, + provider: Chef::Provider::Package::Yum, + name: :yum_package, + action: :install, + os: 'linux', + platform_family: 'rhel', + ) - it "should set the provider to Chef::Provider::Package::Yum" do - @resource.provider.should eql(Chef::Provider::Package::Yum) - end end describe Chef::Resource::YumPackage, "arch" do diff --git a/spec/unit/resource_collection/resource_list_spec.rb b/spec/unit/resource_collection/resource_list_spec.rb new file mode 100644 index 0000000000..641fe55c8d --- /dev/null +++ b/spec/unit/resource_collection/resource_list_spec.rb @@ -0,0 +1,137 @@ +# +# Author:: Serdar Sutay (<serdar@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::ResourceCollection::ResourceList do + let(:resource_list) { Chef::ResourceCollection::ResourceList.new() } + let(:resource) { Chef::Resource::ZenMaster.new("makoto") } + let(:second_resource) { Chef::Resource::ZenMaster.new("hattori") } + + def insert_resource(res) + expect{ resource_list.insert(res) }.not_to raise_error + end + + describe "initialize" do + it "should return a Chef::ResourceList" do + expect(resource_list).to be_instance_of(Chef::ResourceCollection::ResourceList) + end + end + + describe "insert" do + it "should be able to insert a Chef::Resource" do + insert_resource(resource) + expect(resource_list[0]).to be(resource) + end + + it "should insert things in order" do + insert_resource(resource) + insert_resource(second_resource) + expect(resource_list[0]).to be(resource) + expect(resource_list[1]).to be(second_resource) + end + + it "should raise error when trying to install something other than Chef::Resource" do + expect{ resource_list.insert("not a resource") }.to raise_error(ArgumentError) + end + end + + describe "accessors" do + it "should be able to insert with []=" do + expect{ resource_list[0] = resource }.not_to raise_error + expect{ resource_list[1] = second_resource }.not_to raise_error + expect(resource_list[0]).to be(resource) + expect(resource_list[1]).to be(second_resource) + end + + it "should be empty by default" do + expect(resource_list.empty?).to be_true + end + + describe "when resources are inserted" do + before do + insert_resource(resource) + insert_resource(second_resource) + end + + it "should get resources with all_resources method" do + resources = resource_list.all_resources + + expect(resources[0]).to be(resource) + expect(resources[1]).to be(second_resource) + end + + it "should be able to get resources with each" do + current = 0 + expected_resources = [resource, second_resource] + + resource_list.each do |r| + expect(r).to be(expected_resources[current]) + current += 1 + end + + expect(current).to eq(2) + end + + it "should be able to get resources with each_index" do + current = 0 + + resource_list.each_index do |i| + expect(i).to eq(current) + current += 1 + end + + expect(current).to eq(2) + end + + it "should be able to check if the list is empty" do + expect(resource_list.empty?).to be_false + end + end + end + + describe "during execute" do + before(:each) do + insert_resource(resource) + insert_resource(second_resource) + end + + it "should execute resources in order" do + current = 0 + expected_resources = [resource, second_resource] + + resource_list.execute_each_resource do |r| + expect(r).to be(expected_resources[current]) + current += 1 + end + + expect(current).to eq(2) + end + + it "should be able to insert resources on the fly" do + resource_to_inject = Chef::Resource::ZenMaster.new("there is no spoon") + expected_resources = [resource, resource_to_inject, second_resource] + + resource_list.execute_each_resource do |r| + resource_list.insert(resource_to_inject) if r == resource + end + + expect(resource_list.all_resources).to eq(expected_resources) + end + end +end diff --git a/spec/unit/resource_collection/resource_set_spec.rb b/spec/unit/resource_collection/resource_set_spec.rb new file mode 100644 index 0000000000..8be31cd9f6 --- /dev/null +++ b/spec/unit/resource_collection/resource_set_spec.rb @@ -0,0 +1,199 @@ +# +# Author:: Tyler Ball (<tball@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::ResourceCollection::ResourceSet do + let(:collection) { Chef::ResourceCollection::ResourceSet.new } + + let(:zen_master_name) { "Neo" } + let(:zen_master2_name) { "Morpheus" } + let(:zen_follower_name) { "Squid" } + let(:zen_master) { Chef::Resource::ZenMaster.new(zen_master_name) } + let(:zen_master2) { Chef::Resource::ZenMaster.new(zen_master2_name) } + let(:zen_follower) { Chef::Resource::ZenFollower.new(zen_follower_name) } + + describe "initialize" do + it "should return a Chef::ResourceSet" do + expect(collection).to be_instance_of(Chef::ResourceCollection::ResourceSet) + end + end + + describe "keys" do + it "should return an empty list for an empty ResourceSet" do + expect(collection.keys).to eq([]) + end + + it "should return the keys for a non-empty ResourceSet" do + collection.instance_variable_get(:@resources_by_key)["key"] = nil + expect(collection.keys).to eq(["key"]) + end + end + + describe "insert_as, lookup and find" do + # To validate insert_as you need lookup, and vice-versa - putting all tests in 1 context to avoid duplication + it "should accept only Chef::Resources" do + expect { collection.insert_as(zen_master) }.to_not raise_error + expect { collection.insert_as("string") }.to raise_error(ArgumentError) + end + + it "should allow you to lookup resources by a default .to_s" do + collection.insert_as(zen_master) + expect(collection.lookup(zen_master.to_s)).to equal(zen_master) + end + + it "should use a custom type and name to insert" do + collection.insert_as(zen_master, "OtherResource", "other_resource") + expect(collection.lookup("OtherResource[other_resource]")).to equal(zen_master) + end + + it "should raise an exception if you send something strange to lookup" do + expect { collection.lookup(:symbol) }.to raise_error(ArgumentError) + end + + it "should raise an exception if it cannot find a resource with lookup" do + expect { collection.lookup(zen_master.to_s) }.to raise_error(Chef::Exceptions::ResourceNotFound) + end + + it "should find a resource by type symbol and name" do + collection.insert_as(zen_master) + expect(collection.find(:zen_master => zen_master_name)).to equal(zen_master) + end + + it "should find a resource by type symbol and array of names" do + collection.insert_as(zen_master) + collection.insert_as(zen_master2) + check_by_names(collection.find(:zen_master => [zen_master_name,zen_master2_name]), zen_master_name, zen_master2_name) + end + + it "should find a resource by type symbol and array of names with custom names" do + collection.insert_as(zen_master, :zzz, "name1") + collection.insert_as(zen_master2, :zzz, "name2") + check_by_names(collection.find( :zzz => ["name1","name2"]), zen_master_name, zen_master2_name) + end + + it "should find resources of multiple kinds (:zen_master => a, :zen_follower => b)" do + collection.insert_as(zen_master) + collection.insert_as(zen_follower) + check_by_names(collection.find(:zen_master => [zen_master_name], :zen_follower => [zen_follower_name]), + zen_master_name, zen_follower_name) + end + + it "should find resources of multiple kinds (:zen_master => a, :zen_follower => b with custom names)" do + collection.insert_as(zen_master, :zzz, "name1") + collection.insert_as(zen_master2, :zzz, "name2") + collection.insert_as(zen_follower, :yyy, "name3") + check_by_names(collection.find(:zzz => ["name1","name2"], :yyy => ["name3"]), + zen_master_name, zen_follower_name, zen_master2_name) + end + + it "should find a resource by string zen_master[a]" do + collection.insert_as(zen_master) + expect(collection.find("zen_master[#{zen_master_name}]")).to eq(zen_master) + end + + it "should find a resource by string zen_master[a] with custom names" do + collection.insert_as(zen_master, :zzz, "name1") + expect(collection.find("zzz[name1]")).to eq(zen_master) + end + + it "should find resources by strings of zen_master[a,b]" do + collection.insert_as(zen_master) + collection.insert_as(zen_master2) + check_by_names(collection.find("zen_master[#{zen_master_name},#{zen_master2_name}]"), + zen_master_name, zen_master2_name) + end + + it "should find resources by strings of zen_master[a,b] with custom names" do + collection.insert_as(zen_master, :zzz, "name1") + collection.insert_as(zen_master2, :zzz, "name2") + check_by_names(collection.find("zzz[name1,name2]"), + zen_master_name, zen_master2_name) + end + + it "should find resources of multiple types by strings of zen_master[a]" do + collection.insert_as(zen_master) + collection.insert_as(zen_follower) + check_by_names(collection.find("zen_master[#{zen_master_name}]", "zen_follower[#{zen_follower_name}]"), + zen_master_name, zen_follower_name) + end + + it "should find resources of multiple types by strings of zen_master[a] with custom names" do + collection.insert_as(zen_master, :zzz, "name1") + collection.insert_as(zen_master2, :zzz, "name2") + collection.insert_as(zen_follower, :yyy, "name3") + check_by_names(collection.find("zzz[name1,name2]", "yyy[name3]"), + zen_master_name, zen_follower_name,zen_master2_name) + end + + it "should only keep the last copy when multiple instances of a Resource are inserted" do + collection.insert_as(zen_master) + expect(collection.find("zen_master[#{zen_master_name}]")).to eq(zen_master) + new_zm =zen_master.dup + new_zm.retries = 10 + expect(new_zm).to_not eq(zen_master) + collection.insert_as(new_zm) + expect(collection.find("zen_master[#{zen_master_name}]")).to eq(new_zm) + end + + it "should raise an exception if you pass a bad name to resources" do + expect { collection.find("michael jackson") }.to raise_error(ArgumentError) + end + + it "should raise an exception if you pass something other than a string or hash to resource" do + expect { collection.find([Array.new]) }.to raise_error(ArgumentError) + end + + it "raises an error when attempting to find a resource that does not exist" do + expect { collection.find("script[nonesuch]") }.to raise_error(Chef::Exceptions::ResourceNotFound) + end + end + + describe "validate_lookup_spec!" do + it "accepts a string of the form 'resource_type[resource_name]'" do + expect(collection.validate_lookup_spec!("resource_type[resource_name]")).to be_true + end + + it "accepts a single-element :resource_type => 'resource_name' Hash" do + expect(collection.validate_lookup_spec!(:service => "apache2")).to be_true + end + + it "accepts a chef resource object" do + expect(collection.validate_lookup_spec!(zen_master)).to be_true + end + + it "rejects a malformed query string" do + expect { collection.validate_lookup_spec!("resource_type[missing-end-bracket") }.to \ + raise_error(Chef::Exceptions::InvalidResourceSpecification) + end + + it "rejects an argument that is not a String, Hash, or Chef::Resource" do + expect { collection.validate_lookup_spec!(Object.new) }.to \ + raise_error(Chef::Exceptions::InvalidResourceSpecification) + end + + end + + def check_by_names(results, *names) + expect(results.size).to eq(names.size) + names.each do |name| + expect(results.detect{|r| r.name == name}).to_not eq(nil) + end + end + +end diff --git a/spec/unit/resource_collection_spec.rb b/spec/unit/resource_collection_spec.rb index cf119f1ab0..5966fdd1f2 100644 --- a/spec/unit/resource_collection_spec.rb +++ b/spec/unit/resource_collection_spec.rb @@ -20,104 +20,77 @@ require 'spec_helper' describe Chef::ResourceCollection do + let(:rc) { Chef::ResourceCollection.new() } + let(:resource) { Chef::Resource::ZenMaster.new("makoto") } - before(:each) do - @rc = Chef::ResourceCollection.new() - @resource = Chef::Resource::ZenMaster.new("makoto") + it "should throw an error when calling a non-delegated method" do + expect { rc.not_a_method }.to raise_error(NoMethodError) end describe "initialize" do it "should return a Chef::ResourceCollection" do - @rc.should be_kind_of(Chef::ResourceCollection) + rc.should be_kind_of(Chef::ResourceCollection) end end describe "[]" do it "should accept Chef::Resources through [index]" do - lambda { @rc[0] = @resource }.should_not raise_error - lambda { @rc[0] = "string" }.should raise_error + lambda { rc[0] = resource }.should_not raise_error + lambda { rc[0] = "string" }.should raise_error(ArgumentError) end it "should allow you to fetch Chef::Resources by position" do - @rc[0] = @resource - @rc[0].should eql(@resource) + rc[0] = resource + rc[0].should eql(resource) end end describe "push" do it "should accept Chef::Resources through pushing" do - lambda { @rc.push(@resource) }.should_not raise_error - lambda { @rc.push("string") }.should raise_error + lambda { rc.push(resource) }.should_not raise_error + lambda { rc.push("string") }.should raise_error(ArgumentError) end end describe "<<" do it "should accept the << operator" do - lambda { @rc << @resource }.should_not raise_error + lambda { rc << resource }.should_not raise_error end end describe "insert" do it "should accept only Chef::Resources" do - lambda { @rc.insert(@resource) }.should_not raise_error - lambda { @rc.insert("string") }.should raise_error + lambda { rc.insert(resource) }.should_not raise_error + lambda { rc.insert("string") }.should raise_error(ArgumentError) + end + + it "should accept named arguments in any order" do + rc.insert(resource, :instance_name => 'foo', :resource_type =>'bar') + expect(rc[0]).to eq(resource) end it "should append resources to the end of the collection when not executing a run" do zmr = Chef::Resource::ZenMaster.new("there is no spoon") - @rc.insert(@resource) - @rc.insert(zmr) - @rc[0].should eql(@resource) - @rc[1].should eql(zmr) + rc.insert(resource) + rc.insert(zmr) + rc[0].should eql(resource) + rc[1].should eql(zmr) end it "should insert resources to the middle of the collection if called while executing a run" do resource_to_inject = Chef::Resource::ZenMaster.new("there is no spoon") zmr = Chef::Resource::ZenMaster.new("morpheus") dummy = Chef::Resource::ZenMaster.new("keanu reeves") - @rc.insert(zmr) - @rc.insert(dummy) + rc.insert(zmr) + rc.insert(dummy) - @rc.execute_each_resource do |resource| - @rc.insert(resource_to_inject) if resource == zmr + rc.execute_each_resource do |resource| + rc.insert(resource_to_inject) if resource == zmr end - @rc[0].should eql(zmr) - @rc[1].should eql(resource_to_inject) - @rc[2].should eql(dummy) - end - end - - describe "insert_at" do - it "should accept only Chef::Resources" do - lambda { @rc.insert_at(0, @resource, @resource) }.should_not raise_error - lambda { @rc.insert_at(0, "string") }.should raise_error - lambda { @rc.insert_at(0, @resource, "string") }.should raise_error - end - - it "should toss an error if it receives a bad index" do - @rc.insert_at(10, @resource) - end - - it "should insert resources at the beginning when asked" do - @rc.insert(Chef::Resource::ZenMaster.new('1')) - @rc.insert(Chef::Resource::ZenMaster.new('2')) - @rc.insert_at(0, Chef::Resource::ZenMaster.new('X')) - @rc.all_resources.map { |r| r.name }.should == [ 'X', '1', '2' ] - end - - it "should insert resources in the middle when asked" do - @rc.insert(Chef::Resource::ZenMaster.new('1')) - @rc.insert(Chef::Resource::ZenMaster.new('2')) - @rc.insert_at(1, Chef::Resource::ZenMaster.new('X')) - @rc.all_resources.map { |r| r.name }.should == [ '1', 'X', '2' ] - end - - it "should insert resources at the end when asked" do - @rc.insert(Chef::Resource::ZenMaster.new('1')) - @rc.insert(Chef::Resource::ZenMaster.new('2')) - @rc.insert_at(2, Chef::Resource::ZenMaster.new('X')) - @rc.all_resources.map { |r| r.name }.should == [ '1', '2', 'X' ] + rc[0].should eql(zmr) + rc[1].should eql(resource_to_inject) + rc[2].should eql(dummy) end end @@ -126,7 +99,7 @@ describe Chef::ResourceCollection do load_up_resources results = Array.new lambda { - @rc.each do |r| + rc.each do |r| results << r.name end }.should_not raise_error @@ -148,8 +121,8 @@ describe Chef::ResourceCollection do load_up_resources results = Array.new lambda { - @rc.each_index do |i| - results << @rc[i].name + rc.each_index do |i| + results << rc[i].name end }.should_not raise_error results.each_index do |i| @@ -168,24 +141,24 @@ describe Chef::ResourceCollection do describe "lookup" do it "should allow you to find resources by name via lookup" do zmr = Chef::Resource::ZenMaster.new("dog") - @rc << zmr - @rc.lookup(zmr.to_s).should eql(zmr) + rc << zmr + rc.lookup(zmr.to_s).should eql(zmr) zmr = Chef::Resource::ZenMaster.new("cat") - @rc[0] = zmr - @rc.lookup(zmr).should eql(zmr) + rc[0] = zmr + rc.lookup(zmr).should eql(zmr) zmr = Chef::Resource::ZenMaster.new("monkey") - @rc.push(zmr) - @rc.lookup(zmr).should eql(zmr) + rc.push(zmr) + rc.lookup(zmr).should eql(zmr) end it "should raise an exception if you send something strange to lookup" do - lambda { @rc.lookup(:symbol) }.should raise_error(ArgumentError) + lambda { rc.lookup(:symbol) }.should raise_error(ArgumentError) end it "should raise an exception if it cannot find a resource with lookup" do - lambda { @rc.lookup("zen_master[dog]") }.should raise_error(Chef::Exceptions::ResourceNotFound) + lambda { rc.lookup("zen_master[dog]") }.should raise_error(Chef::Exceptions::ResourceNotFound) end end @@ -193,80 +166,80 @@ describe Chef::ResourceCollection do it "should find a resource by symbol and name (:zen_master => monkey)" do load_up_resources - @rc.resources(:zen_master => "monkey").name.should eql("monkey") + rc.resources(:zen_master => "monkey").name.should eql("monkey") end it "should find a resource by symbol and array of names (:zen_master => [a,b])" do load_up_resources - results = @rc.resources(:zen_master => [ "monkey", "dog" ]) + results = rc.resources(:zen_master => [ "monkey", "dog" ]) results.length.should eql(2) check_by_names(results, "monkey", "dog") end it "should find resources of multiple kinds (:zen_master => a, :file => b)" do load_up_resources - results = @rc.resources(:zen_master => "monkey", :file => "something") + results = rc.resources(:zen_master => "monkey", :file => "something") results.length.should eql(2) check_by_names(results, "monkey", "something") end it "should find a resource by string zen_master[a]" do load_up_resources - @rc.resources("zen_master[monkey]").name.should eql("monkey") + rc.resources("zen_master[monkey]").name.should eql("monkey") end it "should find resources by strings of zen_master[a,b]" do load_up_resources - results = @rc.resources("zen_master[monkey,dog]") + results = rc.resources("zen_master[monkey,dog]") results.length.should eql(2) check_by_names(results, "monkey", "dog") end it "should find resources of multiple types by strings of zen_master[a]" do load_up_resources - results = @rc.resources("zen_master[monkey]", "file[something]") + results = rc.resources("zen_master[monkey]", "file[something]") results.length.should eql(2) check_by_names(results, "monkey", "something") end it "should raise an exception if you pass a bad name to resources" do - lambda { @rc.resources("michael jackson") }.should raise_error(ArgumentError) + lambda { rc.resources("michael jackson") }.should raise_error(ArgumentError) end it "should raise an exception if you pass something other than a string or hash to resource" do - lambda { @rc.resources([Array.new]) }.should raise_error(ArgumentError) + lambda { rc.resources([Array.new]) }.should raise_error(ArgumentError) end it "raises an error when attempting to find a resource that does not exist" do - lambda {@rc.find("script[nonesuch]")}.should raise_error(Chef::Exceptions::ResourceNotFound) + lambda {rc.find("script[nonesuch]")}.should raise_error(Chef::Exceptions::ResourceNotFound) end end describe "when validating a resource query object" do it "accepts a string of the form 'resource_type[resource_name]'" do - @rc.validate_lookup_spec!("resource_type[resource_name]").should be_true + rc.validate_lookup_spec!("resource_type[resource_name]").should be_true end it "accepts a single-element :resource_type => 'resource_name' Hash" do - @rc.validate_lookup_spec!(:service => "apache2").should be_true + rc.validate_lookup_spec!(:service => "apache2").should be_true end it "accepts a chef resource object" do res = Chef::Resource.new("foo", nil) - @rc.validate_lookup_spec!(res).should be_true + rc.validate_lookup_spec!(res).should be_true end it "rejects a malformed query string" do lambda do - @rc.validate_lookup_spec!("resource_type[missing-end-bracket") + rc.validate_lookup_spec!("resource_type[missing-end-bracket") end.should raise_error(Chef::Exceptions::InvalidResourceSpecification) end it "rejects an argument that is not a String, Hash, or Chef::Resource" do lambda do - @rc.validate_lookup_spec!(Object.new) + rc.validate_lookup_spec!(Object.new) end.should raise_error(Chef::Exceptions::InvalidResourceSpecification) end @@ -274,40 +247,40 @@ describe Chef::ResourceCollection do describe "to_json" do it "should serialize to json" do - json = @rc.to_json + json = rc.to_json json.should =~ /json_class/ json.should =~ /instance_vars/ end include_examples "to_json equalivent to Chef::JSONCompat.to_json" do - let(:jsonable) { @rc } + let(:jsonable) { rc } end end describe "self.from_json" do it "should not respond to this method" do - expect(@rc.respond_to?(:from_json)).to eq(false) + expect(rc.respond_to?(:from_json)).to eq(false) end it "should convert from json using the CHEF::JSONCompat library" do - @rc << @resource - json = Chef::JSONCompat.to_json(@rc) + rc << resource + json = Chef::JSONCompat.to_json(rc) s_rc = Chef::JSONCompat.from_json(json) s_rc.should be_a_kind_of(Chef::ResourceCollection) - s_rc[0].name.should eql(@resource.name) + s_rc[0].name.should eql(resource.name) end end describe "provides access to the raw resources array" do it "returns the resources via the all_resources method" do - @rc.all_resources.should equal(@rc.instance_variable_get(:@resources)) + rc.all_resources.should equal(rc.instance_variable_get(:@resource_list).instance_variable_get(:@resources)) end end describe "provides access to stepable iterator" do it "returns the iterator object" do - @rc.instance_variable_set(:@iterator, :fooboar) - @rc.iterator.should == :fooboar + rc.instance_variable_get(:@resource_list).instance_variable_set(:@iterator, :fooboar) + rc.iterator.should == :fooboar end end @@ -319,9 +292,9 @@ describe Chef::ResourceCollection do def load_up_resources %w{dog cat monkey}.each do |n| - @rc << Chef::Resource::ZenMaster.new(n) + rc << Chef::Resource::ZenMaster.new(n) end - @rc << Chef::Resource::File.new("something") + rc << Chef::Resource::File.new("something") end end diff --git a/spec/unit/resource_definition_spec.rb b/spec/unit/resource_definition_spec.rb index f24254cfce..01e28bf091 100644 --- a/spec/unit/resource_definition_spec.rb +++ b/spec/unit/resource_definition_spec.rb @@ -19,24 +19,22 @@ require 'spec_helper' describe Chef::ResourceDefinition do - before(:each) do - @def = Chef::ResourceDefinition.new() - end + let(:defn) { Chef::ResourceDefinition.new() } describe "initialize" do it "should be a Chef::ResourceDefinition" do - @def.should be_a_kind_of(Chef::ResourceDefinition) + expect(defn).to be_a_kind_of(Chef::ResourceDefinition) end it "should not initialize a new node if one is not provided" do - @def.node.should eql(nil) + expect(defn.node).to eql(nil) end it "should accept a node as an argument" do node = Chef::Node.new node.name("bobo") - @def = Chef::ResourceDefinition.new(node) - @def.node.name.should == "bobo" + defn = Chef::ResourceDefinition.new(node) + expect(defn.node.name).to eq("bobo") end end @@ -44,76 +42,76 @@ describe Chef::ResourceDefinition do it "should set the node with node=" do node = Chef::Node.new node.name("bobo") - @def.node = node - @def.node.name.should == "bobo" + defn.node = node + expect(defn.node.name).to eq("bobo") end it "should return the node" do - @def.node = Chef::Node.new - @def.node.should be_a_kind_of(Chef::Node) + defn.node = Chef::Node.new + expect(defn.node).to be_a_kind_of(Chef::Node) end end it "should accept a new definition with a symbol for a name" do - lambda { - @def.define :smoke do + expect { + defn.define :smoke do end - }.should_not raise_error - lambda { - @def.define "george washington" do + }.not_to raise_error + expect { + defn.define "george washington" do end - }.should raise_error(ArgumentError) - @def.name.should eql(:smoke) + }.to raise_error(ArgumentError) + expect(defn.name).to eql(:smoke) end it "should accept a new definition with a hash" do - lambda { - @def.define :smoke, :cigar => "cuban", :cigarette => "marlboro" do + expect { + defn.define :smoke, :cigar => "cuban", :cigarette => "marlboro" do end - }.should_not raise_error + }.not_to raise_error end it "should expose the prototype hash params in the params hash" do - @def.define :smoke, :cigar => "cuban", :cigarette => "marlboro" do; end - @def.params[:cigar].should eql("cuban") - @def.params[:cigarette].should eql("marlboro") + defn.define :smoke, :cigar => "cuban", :cigarette => "marlboro" do; end + expect(defn.params[:cigar]).to eql("cuban") + expect(defn.params[:cigarette]).to eql("marlboro") end it "should store the block passed to define as a proc under recipe" do - @def.define :smoke do + defn.define :smoke do "I am what I am" end - @def.recipe.should be_a_kind_of(Proc) - @def.recipe.call.should eql("I am what I am") + expect(defn.recipe).to be_a_kind_of(Proc) + expect(defn.recipe.call).to eql("I am what I am") end it "should set paramaters based on method_missing" do - @def.mind "to fly" - @def.params[:mind].should eql("to fly") + defn.mind "to fly" + expect(defn.params[:mind]).to eql("to fly") end it "should raise an exception if prototype_params is not a hash" do - lambda { - @def.define :monkey, Array.new do + expect { + defn.define :monkey, Array.new do end - }.should raise_error(ArgumentError) + }.to raise_error(ArgumentError) end it "should raise an exception if define is called without a block" do - lambda { - @def.define :monkey - }.should raise_error(ArgumentError) + expect { + defn.define :monkey + }.to raise_error(ArgumentError) end it "should load a description from a file" do - @def.from_file(File.join(CHEF_SPEC_DATA, "definitions", "test.rb")) - @def.name.should eql(:rico_suave) - @def.params[:rich].should eql("smooth") + defn.from_file(File.join(CHEF_SPEC_DATA, "definitions", "test.rb")) + expect(defn.name).to eql(:rico_suave) + expect(defn.params[:rich]).to eql("smooth") end it "should turn itself into a string based on the name with to_s" do - @def.name = :woot - @def.to_s.should eql("woot") + defn.name = :woot + expect(defn.to_s).to eql("woot") end end diff --git a/spec/unit/resource_platform_map_spec.rb b/spec/unit/resource_platform_map_spec.rb deleted file mode 100644 index 99673d868f..0000000000 --- a/spec/unit/resource_platform_map_spec.rb +++ /dev/null @@ -1,164 +0,0 @@ -# -# Author:: Seth Chisamore (<schisamo@opscode.com>) -# Copyright:: Copyright (c) 2011 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' - -describe Chef::Resource::PlatformMap do - - before(:each) do - @platform_map = Chef::Resource::PlatformMap.new({ - :windows => { - "6.1" => { - :file => "softiefile", - :else => "thing" - }, - :default => { - :file => Chef::Resource::File, - :ping => "pong", - :cat => "nice" - } - }, - :pop_tron => { - }, - :default => { - :soundwave => "lazerbeak", - :directory => Chef::Resource::Directory, - } - }) - end - - describe 'filtering the map' do - it "returns resources for platform and version" do - pmap = @platform_map.filter("Windows", "6.1") - pmap.should be_a_kind_of(Hash) - pmap[:file].should eql("softiefile") - end - - it "returns platform default resources if version does not exist" do - pmap = @platform_map.filter("windows", "1") - pmap.should be_a_kind_of(Hash) - pmap[:file].should eql(Chef::Resource::File) - end - - it "returns global default resources if none exist for plaform" do - pmap = @platform_map.filter("pop_tron", "1") - pmap.should be_a_kind_of(Hash) - pmap[:directory].should eql(Chef::Resource::Directory) - end - - it "returns global default resources if platform does not exist" do - pmap = @platform_map.filter("BeOS", "1") - pmap.should be_a_kind_of(Hash) - pmap[:soundwave].should eql("lazerbeak") - end - - it "returns a merged map of platform version and plaform default resources" do - pmap = @platform_map.filter("Windows", "6.1") - pmap[:file].should eql("softiefile") - pmap[:ping].should eql("pong") - end - - it "returns a merged map of platform specific version and global defaults" do - pmap = @platform_map.filter("Windows", "6.1") - pmap[:file].should eql("softiefile") - pmap[:soundwave].should eql("lazerbeak") - end - end - - describe 'finding a resource' do - it "returns a resource for a platform directly by short name" do - @platform_map.get(:file, "windows", "6.1").should eql("softiefile") - end - - it "returns a default resource if platform and version don't exist" do - @platform_map.get(:remote_file).should eql(Chef::Resource::RemoteFile) - end - - it "raises an exception if a resource cannot be found" do - lambda { @platform_map.get(:coffee, "windows", "6.1")}.should raise_error(NameError) - end - - it "returns a resource with a Chef::Resource object" do - kitty = Chef::Resource::Cat.new("loulou") - @platform_map.get(kitty, "windows", "6.1").should eql("nice") - end - end - - describe 'building the map' do - it "allows passing of a resource map at creation time" do - @new_map = Chef::Resource::PlatformMap.new({:the_dude => {:default => 'abides'}}) - @new_map.map[:the_dude][:default].should eql("abides") - end - - it "defaults to a resource map with :default key" do - @new_map = Chef::Resource::PlatformMap.new - @new_map.map.has_key?(:default) - end - - it "updates the resource map with a map" do - @platform_map.set( - :platform => :darwin, - :version => "9.2.2", - :short_name => :file, - :resource => "masterful" - ) - @platform_map.map[:darwin]["9.2.2"][:file].should eql("masterful") - - @platform_map.set( - :platform => :darwin, - :short_name => :file, - :resource => "masterful" - ) - @platform_map.map[:darwin][:default][:file].should eql("masterful") - - @platform_map.set( - :short_name => :file, - :resource => "masterful" - ) - @platform_map.map[:default][:file].should eql("masterful") - - @platform_map.set( - :platform => :hero, - :version => "9.2.2", - :short_name => :file, - :resource => "masterful" - ) - @platform_map.map[:hero]["9.2.2"][:file].should eql("masterful") - - @platform_map.set( - :short_name => :file, - :resource => "masterful" - ) - @platform_map.map[:default][:file].should eql("masterful") - - @platform_map.set( - :short_name => :file, - :resource => "masterful" - ) - @platform_map.map[:default][:file].should eql("masterful") - - @platform_map.set( - :platform => :neurosis, - :short_name => :package, - :resource => "masterful" - ) - @platform_map.map[:neurosis][:default][:package].should eql("masterful") - end - end - -end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 692345c943..2163cf181e 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -174,12 +174,12 @@ describe Chef::Resource do end it "should load the attributes of a prior resource" do - @resource.load_prior_resource + @resource.load_prior_resource(@resource.resource_name, @resource.name) @resource.supports.should == { :funky => true } end it "should not inherit the action from the prior resource" do - @resource.load_prior_resource + @resource.load_prior_resource(@resource.resource_name, @resource.name) @resource.action.should_not == @prior_resource.action end end @@ -349,7 +349,7 @@ describe Chef::Resource do :updated_by_last_action, :before, :supports, :noop, :ignore_failure, :name, :source_line, :action, :retries, :retry_delay, :elapsed_time, - :guard_interpreter, :sensitive ] + :default_guard_interpreter, :guard_interpreter, :sensitive ] (hash.keys - expected_keys).should == [] (expected_keys - hash.keys).should == [] hash[:name].should eql("funk") @@ -394,22 +394,44 @@ describe Chef::Resource do end describe "retries" do + before do + @retriable_resource = Chef::Resource::Cat.new("precious", @run_context) + @retriable_resource.provider = Chef::Provider::SnakeOil + @retriable_resource.action = :purr + + @node.automatic_attrs[:platform] = "fubuntu" + @node.automatic_attrs[:platform_version] = '10.04' + end + it "should default to not retrying if a provider fails for a resource" do - @resource.retries.should == 0 + @retriable_resource.retries.should == 0 end it "should allow you to set how many retries a provider should attempt after a failure" do - @resource.retries(2) - @resource.retries.should == 2 + @retriable_resource.retries(2) + @retriable_resource.retries.should == 2 end it "should default to a retry delay of 2 seconds" do - @resource.retry_delay.should == 2 + @retriable_resource.retry_delay.should == 2 end it "should allow you to set the retry delay" do - @resource.retry_delay(10) - @resource.retry_delay.should == 10 + @retriable_resource.retry_delay(10) + @retriable_resource.retry_delay.should == 10 + end + + it "should keep given value of retries intact after the provider fails for a resource" do + @retriable_resource.retries(3) + @retriable_resource.retry_delay(0) # No need to wait. + + provider = Chef::Provider::SnakeOil.new(@retriable_resource, @run_context) + Chef::Provider::SnakeOil.stub(:new).and_return(provider) + provider.stub(:action_purr).and_raise + + @retriable_resource.should_receive(:sleep).exactly(3).times + expect { @retriable_resource.run_action(:purr) }.to raise_error + @retriable_resource.retries.should == 3 end end @@ -670,24 +692,33 @@ describe Chef::Resource do describe "building the platform map" do + let(:klz) { Class.new(Chef::Resource) } + + before do + Chef::Resource::Klz = klz + end + + after do + Chef::Resource.send(:remove_const, :Klz) + end + it 'adds mappings for a single platform' do - klz = Class.new(Chef::Resource) - Chef::Resource.platform_map.should_receive(:set).with( - :platform => :autobots, :short_name => :dinobot, :resource => klz + expect(Chef::Resource.node_map).to receive(:set).with( + :dinobot, Chef::Resource::Klz, { platform: ['autobots'] } ) - klz.provides :dinobot, :on_platforms => ['autobots'] + klz.provides :dinobot, platform: ['autobots'] end it 'adds mappings for multiple platforms' do - klz = Class.new(Chef::Resource) - Chef::Resource.platform_map.should_receive(:set).twice - klz.provides :energy, :on_platforms => ['autobots','decepticons'] + expect(Chef::Resource.node_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 - klz = Class.new(Chef::Resource) - Chef::Resource.platform_map.should_receive(:set).with( - :short_name => :tape_deck, :resource => klz + expect(Chef::Resource.node_map).to receive(:set).with( + :tape_deck, Chef::Resource::Klz, {} ) klz.provides :tape_deck end @@ -695,28 +726,26 @@ describe Chef::Resource do end describe "lookups from the platform map" do + let(:klz1) { Class.new(Chef::Resource) } + let(:klz2) { Class.new(Chef::Resource) } before(:each) do + Chef::Resource::Klz1 = klz1 + Chef::Resource::Klz2 = klz2 @node = Chef::Node.new @node.name("bumblebee") @node.automatic[:platform] = "autobots" @node.automatic[:platform_version] = "6.1" - Object.const_set('Soundwave', Class.new(Chef::Resource)) - Object.const_set('Grimlock', Class.new(Chef::Resource){ provides :dinobot, :on_platforms => ['autobots'] }) + Object.const_set('Soundwave', klz1) + klz2.provides :dinobot, :on_platforms => ['autobots'] + Object.const_set('Grimlock', klz2) end after(:each) do Object.send(:remove_const, :Soundwave) Object.send(:remove_const, :Grimlock) - end - - describe "resource_for_platform" do - it 'return a resource by short_name and platform' do - Chef::Resource.resource_for_platform(:dinobot,'autobots','6.1').should eql(Grimlock) - end - it "returns a resource by short_name if nothing else matches" do - Chef::Resource.resource_for_node(:soundwave, @node).should eql(Soundwave) - end + Chef::Resource.send(:remove_const, :Klz1) + Chef::Resource.send(:remove_const, :Klz2) end describe "resource_for_node" do diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb index 424fd12ee9..3047366c47 100644 --- a/spec/unit/rest_spec.rb +++ b/spec/unit/rest_spec.rb @@ -62,8 +62,8 @@ describe Chef::REST do let(:request_id) {"1234"} let(:rest) do - Chef::REST::CookieJar.stub(:instance).and_return({}) - Chef::RequestID.instance.stub(:request_id).and_return(request_id) + allow(Chef::REST::CookieJar).to receive(:instance).and_return({}) + allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id) rest = Chef::REST.new(base_url, nil, nil) Chef::REST::CookieJar.instance.clear rest @@ -81,9 +81,9 @@ describe Chef::REST do content_length = middlewares.find_index { |e| e.is_a? Chef::HTTP::ValidateContentLength } decompressor = middlewares.find_index { |e| e.is_a? Chef::HTTP::Decompressor } - content_length.should_not be_nil - decompressor.should_not be_nil - (decompressor < content_length).should be_true + expect(content_length).not_to be_nil + expect(decompressor).not_to be_nil + expect(decompressor < content_length).to be_true end it "should allow the options hash to be frozen" do @@ -102,43 +102,43 @@ describe Chef::REST do end it "makes a :GET request with the composed url object" do - rest.should_receive(:send_http_request). + expect(rest).to receive(:send_http_request). with(:GET, monkey_uri, standard_read_headers, false). and_return([1,2,3]) - rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3]) - rest.should_receive('success_response?'.to_sym).with(1).and_return(true) + expect(rest).to receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3]) + expect(rest).to receive('success_response?'.to_sym).with(1).and_return(true) rest.get_rest("monkey") end it "makes a :GET reqest for a streaming download with the composed url" do - rest.should_receive(:streaming_request).with('monkey', {}) + expect(rest).to receive(:streaming_request).with('monkey', {}) rest.get_rest("monkey", true) end it "makes a :DELETE request with the composed url object" do - rest.should_receive(:send_http_request). + expect(rest).to receive(:send_http_request). with(:DELETE, monkey_uri, standard_read_headers, false). and_return([1,2,3]) - rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3]) - rest.should_receive('success_response?'.to_sym).with(1).and_return(true) + expect(rest).to receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3]) + expect(rest).to receive('success_response?'.to_sym).with(1).and_return(true) rest.delete_rest("monkey") end it "makes a :POST request with the composed url object and data" do - rest.should_receive(:send_http_request). + expect(rest).to receive(:send_http_request). with(:POST, monkey_uri, standard_write_headers, "\"data\""). and_return([1,2,3]) - rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3]) - rest.should_receive('success_response?'.to_sym).with(1).and_return(true) + expect(rest).to receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3]) + expect(rest).to receive('success_response?'.to_sym).with(1).and_return(true) rest.post_rest("monkey", "data") end it "makes a :PUT request with the composed url object and data" do - rest.should_receive(:send_http_request). + expect(rest).to receive(:send_http_request). with(:PUT, monkey_uri, standard_write_headers, "\"data\""). and_return([1,2,3]) - rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3]) - rest.should_receive('success_response?'.to_sym).with(1).and_return(true) + expect(rest).to receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3]) + expect(rest).to receive('success_response?'.to_sym).with(1).and_return(true) rest.put_rest("monkey", "data") end end @@ -162,13 +162,13 @@ describe Chef::REST do auth_headers = standard_write_headers.merge({"auth_done"=>"yep"}) - rest.authenticator.should_receive(:handle_request). + expect(rest.authenticator).to receive(:handle_request). with(:POST, monkey_uri, standard_write_headers, data). and_return([:POST, monkey_uri, auth_headers, data]) - rest.should_receive(:send_http_request). + expect(rest).to receive(:send_http_request). with(:POST, monkey_uri, auth_headers, data). and_return([1,2,3]) - rest.should_receive('success_response?'.to_sym).with(1).and_return(true) + expect(rest).to receive('success_response?'.to_sym).with(1).and_return(true) rest.raw_http_request(:POST, monkey_uri, standard_write_headers, data) end @@ -176,10 +176,10 @@ describe Chef::REST do data = "\"secure data\"" method, uri, auth_headers, d = rest.authenticator.handle_request(:POST, monkey_uri, standard_write_headers, data) - rest.should_receive(:send_http_request). + expect(rest).to receive(:send_http_request). with(:POST, monkey_uri, auth_headers, data). and_return([1,2,3]) - rest.should_receive('success_response?'.to_sym).with(1).and_return(true) + expect(rest).to receive('success_response?'.to_sym).with(1).and_return(true) rest.raw_http_request(:POST, monkey_uri, standard_write_headers, data) end end @@ -241,8 +241,8 @@ describe Chef::REST do let(:http_response) do http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req") - http_response.stub(:read_body) - http_response.stub(:body).and_return(body) + allow(http_response).to receive(:read_body) + allow(http_response).to receive(:body).and_return(body) http_response["Content-Length"] = body.bytesize.to_s http_response end @@ -255,14 +255,14 @@ describe Chef::REST do let!(:http_client) do http_client = Net::HTTP.new(url.host, url.port) - http_client.stub(:request).and_yield(http_response).and_return(http_response) + allow(http_client).to receive(:request).and_yield(http_response).and_return(http_response) http_client end let(:rest) do - Net::HTTP.stub(:new).and_return(http_client) - Chef::REST::CookieJar.stub(:instance).and_return({}) - Chef::RequestID.instance.stub(:request_id).and_return(request_id) + allow(Net::HTTP).to receive(:new).and_return(http_client) + allow(Chef::REST::CookieJar).to receive(:instance).and_return({}) + allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id) rest = Chef::REST.new(base_url, nil, nil) Chef::REST::CookieJar.instance.clear rest @@ -300,16 +300,16 @@ describe Chef::REST do end before do - Net::HTTP::Get.stub(:new).and_return(request_mock) + allow(Net::HTTP::Get).to receive(:new).and_return(request_mock) end it "should always include the X-Chef-Version header" do - Net::HTTP::Get.should_receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) + expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) rest.request(:GET, url, {}) end it "should always include the X-Remote-Request-Id header" do - Net::HTTP::Get.should_receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) + expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) rest.request(:GET, url, {}) end @@ -323,7 +323,7 @@ describe Chef::REST do # CHEF-3140 context "when configured to disable compression" do let(:rest) do - Net::HTTP.stub(:new).and_return(http_client) + allow(Net::HTTP).to receive(:new).and_return(http_client) Chef::REST.new(base_url, nil, nil, :disable_gzip => true) end @@ -334,7 +334,7 @@ describe Chef::REST do it "does not decompress a response encoded as gzip" do http_response.add_field("content-encoding", "gzip") request = Net::HTTP::Get.new(url.path) - Net::HTTP::Get.should_receive(:new).and_return(request) + expect(Net::HTTP::Get).to receive(:new).and_return(request) # will raise a Zlib error if incorrect expect(rest.request(:GET, url, {})).to eq("ninja") end @@ -359,28 +359,28 @@ describe Chef::REST do it "should set them on the http request" do url_string = an_instance_of(String) header_hash = hash_including(custom_headers) - Net::HTTP::Get.should_receive(:new).with(url_string, header_hash) + expect(Net::HTTP::Get).to receive(:new).with(url_string, header_hash) rest.request(:GET, url, {}) end end context "when setting cookies" do let(:rest) do - Net::HTTP.stub(:new).and_return(http_client) + allow(Net::HTTP).to receive(:new).and_return(http_client) Chef::REST::CookieJar.instance["#{url.host}:#{url.port}"] = "cookie monster" - Chef::RequestID.instance.stub(:request_id).and_return(request_id) + allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id) rest = Chef::REST.new(base_url, nil, nil) rest end it "should set the cookie for this request if one exists for the given host:port" do - Net::HTTP::Get.should_receive(:new).with("/?foo=bar", base_headers.merge('Cookie' => "cookie monster")).and_return(request_mock) + expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers.merge('Cookie' => "cookie monster")).and_return(request_mock) rest.request(:GET, url, {}) end end it "should build a new HTTP GET request" do - Net::HTTP::Get.should_receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) + expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) rest.request(:GET, url, {}) end @@ -388,7 +388,7 @@ describe Chef::REST do request = Net::HTTP::Post.new(url.path) expected_headers = base_headers.merge("Content-Type" => 'application/json', 'Content-Length' => '13') - Net::HTTP::Post.should_receive(:new).with("/?foo=bar", expected_headers).and_return(request) + expect(Net::HTTP::Post).to receive(:new).with("/?foo=bar", expected_headers).and_return(request) rest.request(:POST, url, {}, {:one=>:two}) expect(request.body).to eq('{"one":"two"}') end @@ -396,13 +396,13 @@ describe Chef::REST do it "should build a new HTTP PUT request" do request = Net::HTTP::Put.new(url.path) expected_headers = base_headers.merge("Content-Type" => 'application/json', 'Content-Length' => '13') - Net::HTTP::Put.should_receive(:new).with("/?foo=bar",expected_headers).and_return(request) + expect(Net::HTTP::Put).to receive(:new).with("/?foo=bar",expected_headers).and_return(request) rest.request(:PUT, url, {}, {:one=>:two}) expect(request.body).to eq('{"one":"two"}') end it "should build a new HTTP DELETE request" do - Net::HTTP::Delete.should_receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) + expect(Net::HTTP::Delete).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) rest.request(:DELETE, url) end @@ -440,7 +440,7 @@ describe Chef::REST do resp_code = Net::HTTPResponse::CODE_TO_OBJ.keys.detect { |k| Net::HTTPResponse::CODE_TO_OBJ[k] == resp_cls } http_response = Net::HTTPFound.new("1.1", resp_code, "bob is somewhere else again") http_response.add_field("location", url.path) - http_response.stub(:read_body) + allow(http_response).to receive(:read_body) http_response end it "should call request again" do @@ -457,7 +457,7 @@ describe Chef::REST do context "when the response is 304 NotModified" do let (:http_response) do http_response = Net::HTTPNotModified.new("1.1", "304", "it's the same as when you asked 5 minutes ago") - http_response.stub(:read_body) + allow(http_response).to receive(:read_body) http_response end @@ -480,13 +480,13 @@ describe Chef::REST do let(:http_response) do http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth") http_response.add_field("content-type", "application/json") - http_response.stub(:body).and_return('{ "error":[ "Ears get sore!", "Not even four" ] }') - http_response.stub(:read_body) + allow(http_response).to receive(:body).and_return('{ "error":[ "Ears get sore!", "Not even four" ] }') + allow(http_response).to receive(:read_body) http_response end it "should show the JSON error message" do - rest.stub(:sleep) + allow(rest).to receive(:sleep) expect {rest.request(:GET, url)}.to raise_error(Net::HTTPFatalError) expect(log_stringio.string).to match(Regexp.escape('INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four')) @@ -502,17 +502,21 @@ describe Chef::REST do gzipped_body = Zlib::Deflate.deflate(unzipped_body) gzipped_body.force_encoding(Encoding::BINARY) if "strings".respond_to?(:force_encoding) - http_response.stub(:body).and_return gzipped_body - http_response.stub(:read_body) + allow(http_response).to receive(:body).and_return gzipped_body + allow(http_response).to receive(:read_body) http_response end - it "decompresses the JSON error message" do - rest.stub(:sleep) - rest.stub(:http_retry_count).and_return(0) + before do + allow(rest).to receive(:sleep) + allow(rest).to receive(:http_retry_count).and_return(0) + end + + it "decompresses the JSON error message" do expect {rest.request(:GET, url)}.to raise_error(Net::HTTPFatalError) expect(log_stringio.string).to match(Regexp.escape('INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four')) end + it "fails when the compressed body is truncated" do http_response["Content-Length"] = (body.bytesize + 99).to_s expect {rest.request(:GET, url)}.to raise_error(Chef::Exceptions::ContentLengthMismatch) @@ -522,13 +526,13 @@ describe Chef::REST do context "on a generic unsuccessful request" do let(:http_response) do http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth") - http_response.stub(:body) - http_response.stub(:read_body) + allow(http_response).to receive(:body) + allow(http_response).to receive(:read_body) http_response end it "retries then throws an exception" do - rest.stub(:sleep) + allow(rest).to receive(:sleep) expect {rest.request(:GET, url)}.to raise_error(Net::HTTPFatalError) count = Chef::Config[:http_retry_count] expect(log_stringio.string).to match(Regexp.escape("ERROR: Server returned error 500 for #{url}, retrying #{count}/#{count}")) @@ -545,8 +549,8 @@ describe Chef::REST do let(:http_response) do http_response = Net::HTTPSuccess.new("1.1",'200', "it-works") - http_response.stub(:read_body) - http_response.should_not_receive(:body) + allow(http_response).to receive(:read_body) + expect(http_response).not_to receive(:body) http_response["Content-Length"] = "0" # call set_content_length (in test), if otherwise http_response end @@ -560,8 +564,8 @@ describe Chef::REST do end before do - Tempfile.stub(:new).with("chef-rest").and_return(tempfile) - Net::HTTP::Get.stub(:new).and_return(request_mock) + allow(Tempfile).to receive(:new).with("chef-rest").and_return(tempfile) + allow(Net::HTTP::Get).to receive(:new).and_return(request_mock) end after do @@ -575,7 +579,7 @@ describe Chef::REST do 'Host' => host_header, 'X-REMOTE-REQUEST-ID'=> request_id } - Net::HTTP::Get.should_receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock) + expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock) rest.streaming_request(url, {}) end @@ -586,7 +590,7 @@ describe Chef::REST do 'Host' => host_header, 'X-REMOTE-REQUEST-ID'=> request_id } - Net::HTTP::Get.should_receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock) + expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock) rest.streaming_request(url, {}) end @@ -595,7 +599,7 @@ describe Chef::REST do end it "writes the response body to a tempfile" do - http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") + allow(http_response).to receive(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") set_content_length rest.streaming_request(url, {}) expect(IO.read(tempfile.path).chomp).to eq("realultimatepower") @@ -607,7 +611,7 @@ describe Chef::REST do end it "yields the tempfile containing the streamed response body and then unlinks it when given a block" do - http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") + allow(http_response).to receive(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") set_content_length tempfile_path = nil rest.streaming_request(url, {}) do |tempfile| @@ -620,24 +624,24 @@ describe Chef::REST do it "does not raise a divide by zero exception if the content's actual size is 0" do http_response['Content-Length'] = "5" - http_response.stub(:read_body).and_yield('') + allow(http_response).to receive(:read_body).and_yield('') expect { rest.streaming_request(url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end it "does not raise a divide by zero exception when the Content-Length is 0" do http_response['Content-Length'] = "0" - http_response.stub(:read_body).and_yield("ninja") + allow(http_response).to receive(:read_body).and_yield("ninja") expect { rest.streaming_request(url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end it "it raises an exception when the download is truncated" do http_response["Content-Length"] = (body.bytesize + 99).to_s - http_response.stub(:read_body).and_yield("ninja") + allow(http_response).to receive(:read_body).and_yield("ninja") expect { rest.streaming_request(url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end it "fetches a file and yields the tempfile it is streamed to" do - http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") + allow(http_response).to receive(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") set_content_length tempfile_path = nil rest.fetch("cookbooks/a_cookbook") do |tempfile| @@ -650,32 +654,32 @@ describe Chef::REST do it "closes and unlinks the tempfile if there is an error while streaming the content to the tempfile" do path = tempfile.path expect(path).not_to be_nil - tempfile.stub(:write).and_raise(IOError) + allow(tempfile).to receive(:write).and_raise(IOError) rest.fetch("cookbooks/a_cookbook") {|tmpfile| "shouldn't get here"} expect(File.exists?(path)).to be_false end it "closes and unlinks the tempfile when the response is a redirect" do tempfile = double("A tempfile", :path => "/tmp/ragefist", :close => true, :binmode => true) - tempfile.should_receive(:close!).at_least(1).times - Tempfile.stub(:new).with("chef-rest").and_return(tempfile) + expect(tempfile).to receive(:close!).at_least(1).times + allow(Tempfile).to receive(:new).with("chef-rest").and_return(tempfile) redirect = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today") redirect.add_field("location", url.path) - redirect.stub(:read_body) + allow(redirect).to receive(:read_body) - http_client.should_receive(:request).and_yield(redirect).and_return(redirect) - http_client.should_receive(:request).and_yield(http_response).and_return(http_response) + expect(http_client).to receive(:request).and_yield(redirect).and_return(redirect) + expect(http_client).to receive(:request).and_yield(http_response).and_return(http_response) rest.fetch("cookbooks/a_cookbook") {|tmpfile| "shouldn't get here"} end it "passes the original block to the redirected request" do http_redirect = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today") http_redirect.add_field("location","/that-thing-is-here-now") - http_redirect.stub(:read_body) + allow(http_redirect).to receive(:read_body) block_called = false - http_client.stub(:request).and_yield(http_response).and_return(http_redirect, http_response) + allow(http_client).to receive(:request).and_yield(http_response).and_return(http_redirect, http_response) rest.fetch("cookbooks/a_cookbook") do |tmpfile| block_called = true end diff --git a/spec/unit/runner_spec.rb b/spec/unit/runner_spec.rb index 68d8b0e011..9bd4199418 100644 --- a/spec/unit/runner_spec.rb +++ b/spec/unit/runner_spec.rb @@ -81,322 +81,329 @@ end describe Chef::Runner do - before(:each) do - @node = Chef::Node.new - @node.name "latte" - @node.automatic[:platform] = "mac_os_x" - @node.automatic[:platform_version] = "10.5.1" - @events = Chef::EventDispatch::Dispatcher.new - @run_context = Chef::RunContext.new(@node, Chef::CookbookCollection.new({}), @events) - @first_resource = Chef::Resource::Cat.new("loulou1", @run_context) - @run_context.resource_collection << @first_resource - Chef::Platform.set( - :resource => :cat, - :provider => Chef::Provider::SnakeOil - ) - @runner = Chef::Runner.new(@run_context) + let(:node) do + node = Chef::Node.new + node.name "latte" + node.automatic[:platform] = "mac_os_x" + node.automatic[:platform_version] = "10.5.1" + node end - it "should pass each resource in the collection to a provider" do - @run_context.resource_collection.should_receive(:execute_each_resource).once - @runner.converge - end + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, Chef::CookbookCollection.new({}), events) } + let(:first_resource) { Chef::Resource::Cat.new("loulou1", run_context) } + let(:runner) { Chef::Runner.new(run_context) } - it "should use the provider specified by the resource (if it has one)" do - provider = Chef::Provider::Easy.new(@run_context.resource_collection[0], @run_context) - # Expect provider to be called twice, because will fall back to old provider lookup - @run_context.resource_collection[0].should_receive(:provider).twice.and_return(Chef::Provider::Easy) - Chef::Provider::Easy.should_receive(:new).once.and_return(provider) - @runner.converge + before do + run_context.resource_collection << first_resource end - it "should use the platform provider if it has one" do - Chef::Platform.should_receive(:find_provider_for_node).once.and_return(Chef::Provider::SnakeOil) - @runner.converge - end + context "when we fall through to old Chef::Platform resolution" do + before do + # set up old Chef::Platform resolution instead of provider_resolver + Chef::Platform.set( + :resource => :cat, + :provider => Chef::Provider::SnakeOil + ) + allow(run_context.provider_resolver).to receive(:maybe_dynamic_provider_resolution).with(first_resource, anything()).and_return(nil) + end - it "should run the action for each resource" do - Chef::Platform.should_receive(:find_provider_for_node).once.and_return(Chef::Provider::SnakeOil) - provider = Chef::Provider::SnakeOil.new(@run_context.resource_collection[0], @run_context) - provider.should_receive(:action_sell).once.and_return(true) - Chef::Provider::SnakeOil.should_receive(:new).once.and_return(provider) - @runner.converge + it "should use the platform provider if it has one" do + expect(Chef::Platform).to receive(:find_provider_for_node).with(node, first_resource).and_call_original + runner.converge + end end - it "should raise exceptions as thrown by a provider" do - provider = Chef::Provider::SnakeOil.new(@run_context.resource_collection[0], @run_context) - Chef::Provider::SnakeOil.stub(:new).once.and_return(provider) - provider.stub(:action_sell).once.and_raise(ArgumentError) - lambda { @runner.converge }.should raise_error(ArgumentError) - end + context "when we are doing dynamic provider resolution" do - it "should not raise exceptions thrown by providers if the resource has ignore_failure set to true" do - @run_context.resource_collection[0].stub(:ignore_failure).and_return(true) - provider = Chef::Provider::SnakeOil.new(@run_context.resource_collection[0], @run_context) - Chef::Provider::SnakeOil.stub(:new).once.and_return(provider) - provider.stub(:action_sell).once.and_raise(ArgumentError) - lambda { @runner.converge }.should_not raise_error - end + it "should pass each resource in the collection to a provider" do + expect(run_context.resource_collection).to receive(:execute_each_resource).once + runner.converge + end - it "should retry with the specified delay if retries are specified" do - @first_resource.retries 3 - provider = Chef::Provider::SnakeOil.new(@run_context.resource_collection[0], @run_context) - Chef::Provider::SnakeOil.stub(:new).once.and_return(provider) - provider.stub(:action_sell).and_raise(ArgumentError) - @first_resource.should_receive(:sleep).with(2).exactly(3).times - lambda { @runner.converge }.should raise_error(ArgumentError) - end + it "should use the provider specified by the resource (if it has one)" do + provider = Chef::Provider::Easy.new(run_context.resource_collection[0], run_context) + # Expect provider to be called twice, because will fall back to old provider lookup + expect(run_context.resource_collection[0]).to receive(:provider).twice.and_return(Chef::Provider::Easy) + expect(Chef::Provider::Easy).to receive(:new).once.and_return(provider) + runner.converge + end - it "should execute immediate actions on changed resources" do - notifying_resource = Chef::Resource::Cat.new("peanut", @run_context) - notifying_resource.action = :purr # only action that will set updated on the resource + it "should run the action for each resource" do + provider = Chef::Provider::SnakeOil.new(run_context.resource_collection[0], run_context) + expect(provider).to receive(:action_sell).once.and_return(true) + expect(Chef::Provider::SnakeOil).to receive(:new).once.and_return(provider) + runner.converge + end - @run_context.resource_collection << notifying_resource - @first_resource.action = :nothing # won't be updated unless notified by other resource + it "should raise exceptions as thrown by a provider" do + provider = Chef::Provider::SnakeOil.new(run_context.resource_collection[0], run_context) + allow(Chef::Provider::SnakeOil).to receive(:new).once.and_return(provider) + allow(provider).to receive(:action_sell).once.and_raise(ArgumentError) + expect { runner.converge }.to raise_error(ArgumentError) + end - notifying_resource.notifies(:purr, @first_resource, :immediately) + it "should not raise exceptions thrown by providers if the resource has ignore_failure set to true" do + allow(run_context.resource_collection[0]).to receive(:ignore_failure).and_return(true) + provider = Chef::Provider::SnakeOil.new(run_context.resource_collection[0], run_context) + allow(Chef::Provider::SnakeOil).to receive(:new).once.and_return(provider) + allow(provider).to receive(:action_sell).once.and_raise(ArgumentError) + expect { runner.converge }.not_to raise_error + end - @runner.converge + it "should retry with the specified delay if retries are specified" do + first_resource.retries 3 + provider = Chef::Provider::SnakeOil.new(run_context.resource_collection[0], run_context) + allow(Chef::Provider::SnakeOil).to receive(:new).once.and_return(provider) + allow(provider).to receive(:action_sell).and_raise(ArgumentError) + expect(first_resource).to receive(:sleep).with(2).exactly(3).times + expect { runner.converge }.to raise_error(ArgumentError) + end - @first_resource.should be_updated - end + it "should execute immediate actions on changed resources" do + notifying_resource = Chef::Resource::Cat.new("peanut", run_context) + notifying_resource.action = :purr # only action that will set updated on the resource - it "should follow a chain of actions" do - @first_resource.action = :nothing + run_context.resource_collection << notifying_resource + first_resource.action = :nothing # won't be updated unless notified by other resource - middle_resource = Chef::Resource::Cat.new("peanut", @run_context) - middle_resource.action = :nothing - @run_context.resource_collection << middle_resource - middle_resource.notifies(:purr, @first_resource, :immediately) + notifying_resource.notifies(:purr, first_resource, :immediately) - last_resource = Chef::Resource::Cat.new("snuffles", @run_context) - last_resource.action = :purr - @run_context.resource_collection << last_resource - last_resource.notifies(:purr, middle_resource, :immediately) + runner.converge - @runner.converge + expect(first_resource).to be_updated + end - last_resource.should be_updated # by action(:purr) - middle_resource.should be_updated # by notification from last_resource - @first_resource.should be_updated # by notification from middle_resource - end + it "should follow a chain of actions" do + first_resource.action = :nothing - it "should execute delayed actions on changed resources" do - @first_resource.action = :nothing - second_resource = Chef::Resource::Cat.new("peanut", @run_context) - second_resource.action = :purr + middle_resource = Chef::Resource::Cat.new("peanut", run_context) + middle_resource.action = :nothing + run_context.resource_collection << middle_resource + middle_resource.notifies(:purr, first_resource, :immediately) - @run_context.resource_collection << second_resource - second_resource.notifies(:purr, @first_resource, :delayed) + last_resource = Chef::Resource::Cat.new("snuffles", run_context) + last_resource.action = :purr + run_context.resource_collection << last_resource + last_resource.notifies(:purr, middle_resource, :immediately) - @runner.converge + runner.converge - @first_resource.should be_updated - end + expect(last_resource).to be_updated # by action(:purr) + expect(middle_resource).to be_updated # by notification from last_resource + expect(first_resource).to be_updated # by notification from middle_resource + end - it "should execute delayed notifications when a failure occurs in the chef client run" do - @first_resource.action = :nothing - second_resource = Chef::Resource::Cat.new("peanut", @run_context) - second_resource.action = :purr + it "should execute delayed actions on changed resources" do + first_resource.action = :nothing + second_resource = Chef::Resource::Cat.new("peanut", run_context) + second_resource.action = :purr - @run_context.resource_collection << second_resource - second_resource.notifies(:purr, @first_resource, :delayed) + run_context.resource_collection << second_resource + second_resource.notifies(:purr, first_resource, :delayed) - third_resource = FailureResource.new("explode", @run_context) - @run_context.resource_collection << third_resource + runner.converge - lambda {@runner.converge}.should raise_error(FailureProvider::ChefClientFail) + expect(first_resource).to be_updated + end - @first_resource.should be_updated - end + it "should execute delayed notifications when a failure occurs in the chef client run" do + first_resource.action = :nothing + second_resource = Chef::Resource::Cat.new("peanut", run_context) + second_resource.action = :purr - it "should execute delayed notifications when a failure occurs in a notification" do - @first_resource.action = :nothing - second_resource = Chef::Resource::Cat.new("peanut", @run_context) - second_resource.action = :purr + run_context.resource_collection << second_resource + second_resource.notifies(:purr, first_resource, :delayed) - @run_context.resource_collection << second_resource + third_resource = FailureResource.new("explode", run_context) + run_context.resource_collection << third_resource - third_resource = FailureResource.new("explode", @run_context) - third_resource.action = :nothing - @run_context.resource_collection << third_resource + expect { runner.converge }.to raise_error(FailureProvider::ChefClientFail) - second_resource.notifies(:fail, third_resource, :delayed) - second_resource.notifies(:purr, @first_resource, :delayed) + expect(first_resource).to be_updated + end - lambda {@runner.converge}.should raise_error(FailureProvider::ChefClientFail) + it "should execute delayed notifications when a failure occurs in a notification" do + first_resource.action = :nothing + second_resource = Chef::Resource::Cat.new("peanut", run_context) + second_resource.action = :purr - @first_resource.should be_updated - end + run_context.resource_collection << second_resource - it "should execute delayed notifications when a failure occurs in multiple notifications" do - @first_resource.action = :nothing - second_resource = Chef::Resource::Cat.new("peanut", @run_context) - second_resource.action = :purr + third_resource = FailureResource.new("explode", run_context) + third_resource.action = :nothing + run_context.resource_collection << third_resource - @run_context.resource_collection << second_resource + second_resource.notifies(:fail, third_resource, :delayed) + second_resource.notifies(:purr, first_resource, :delayed) - third_resource = FailureResource.new("explode", @run_context) - third_resource.action = :nothing - @run_context.resource_collection << third_resource + expect {runner.converge}.to raise_error(FailureProvider::ChefClientFail) - fourth_resource = FailureResource.new("explode again", @run_context) - fourth_resource.action = :nothing - @run_context.resource_collection << fourth_resource + expect(first_resource).to be_updated + end - second_resource.notifies(:fail, third_resource, :delayed) - second_resource.notifies(:fail, fourth_resource, :delayed) - second_resource.notifies(:purr, @first_resource, :delayed) + it "should execute delayed notifications when a failure occurs in multiple notifications" do + first_resource.action = :nothing + second_resource = Chef::Resource::Cat.new("peanut", run_context) + second_resource.action = :purr - exception = nil - begin - @runner.converge - rescue => e - exception = e - end - exception.should be_a(Chef::Exceptions::MultipleFailures) + run_context.resource_collection << second_resource + + third_resource = FailureResource.new("explode", run_context) + third_resource.action = :nothing + run_context.resource_collection << third_resource + + fourth_resource = FailureResource.new("explode again", run_context) + fourth_resource.action = :nothing + run_context.resource_collection << fourth_resource + + second_resource.notifies(:fail, third_resource, :delayed) + second_resource.notifies(:fail, fourth_resource, :delayed) + second_resource.notifies(:purr, first_resource, :delayed) + + exception = nil + begin + runner.converge + rescue => e + exception = e + end + expect(exception).to be_a(Chef::Exceptions::MultipleFailures) - expected_message =<<-E + expected_message =<<-E Multiple failures occurred: * 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 - exception.message.should == expected_message + E + expect(exception.message).to eq(expected_message) - @first_resource.should be_updated - end - - it "does not duplicate delayed notifications" do - SnitchyProvider.clear_action_record - - Chef::Platform.set( - :resource => :cat, - :provider => SnitchyProvider - ) + expect(first_resource).to be_updated + end - @first_resource.action = :nothing + it "does not duplicate delayed notifications" do + SnitchyProvider.clear_action_record - second_resource = Chef::Resource::Cat.new("peanut", @run_context) - second_resource.action = :first_action - @run_context.resource_collection << second_resource + first_resource.action = :nothing + first_resource.provider = SnitchyProvider - third_resource = Chef::Resource::Cat.new("snickers", @run_context) - third_resource.action = :first_action - @run_context.resource_collection << third_resource + second_resource = Chef::Resource::Cat.new("peanut", run_context) + second_resource.action = :first_action + second_resource.provider = SnitchyProvider + run_context.resource_collection << second_resource - second_resource.notifies(:second_action, @first_resource, :delayed) - second_resource.notifies(:third_action, @first_resource, :delayed) + third_resource = Chef::Resource::Cat.new("snickers", run_context) + third_resource.action = :first_action + third_resource.provider = SnitchyProvider + run_context.resource_collection << third_resource - third_resource.notifies(:second_action, @first_resource, :delayed) - third_resource.notifies(:third_action, @first_resource, :delayed) + second_resource.notifies(:second_action, first_resource, :delayed) + second_resource.notifies(:third_action, first_resource, :delayed) - @runner.converge - # resources 2 and 3 call :first_action in the course of normal resource - # execution, and schedule delayed actions :second and :third on the first - # resource. The duplicate actions should "collapse" to a single notification - # and order should be preserved. - SnitchyProvider.all_actions_called.should == [:first, :first, :second, :third] - end + third_resource.notifies(:second_action, first_resource, :delayed) + third_resource.notifies(:third_action, first_resource, :delayed) - it "executes delayed notifications in the order they were declared" do - SnitchyProvider.clear_action_record + runner.converge + # resources 2 and 3 call :first_action in the course of normal resource + # execution, and schedule delayed actions :second and :third on the first + # resource. The duplicate actions should "collapse" to a single notification + # and order should be preserved. + expect(SnitchyProvider.all_actions_called).to eq([:first, :first, :second, :third]) + end - Chef::Platform.set( - :resource => :cat, - :provider => SnitchyProvider - ) + it "executes delayed notifications in the order they were declared" do + SnitchyProvider.clear_action_record - @first_resource.action = :nothing + first_resource.action = :nothing + first_resource.provider = SnitchyProvider - second_resource = Chef::Resource::Cat.new("peanut", @run_context) - second_resource.action = :first_action - @run_context.resource_collection << second_resource + second_resource = Chef::Resource::Cat.new("peanut", run_context) + second_resource.action = :first_action + second_resource.provider = SnitchyProvider + run_context.resource_collection << second_resource - third_resource = Chef::Resource::Cat.new("snickers", @run_context) - third_resource.action = :first_action - @run_context.resource_collection << third_resource + third_resource = Chef::Resource::Cat.new("snickers", run_context) + third_resource.action = :first_action + third_resource.provider = SnitchyProvider + run_context.resource_collection << third_resource - second_resource.notifies(:second_action, @first_resource, :delayed) - second_resource.notifies(:second_action, @first_resource, :delayed) + second_resource.notifies(:second_action, first_resource, :delayed) + second_resource.notifies(:second_action, first_resource, :delayed) - third_resource.notifies(:third_action, @first_resource, :delayed) - third_resource.notifies(:third_action, @first_resource, :delayed) + third_resource.notifies(:third_action, first_resource, :delayed) + third_resource.notifies(:third_action, first_resource, :delayed) - @runner.converge - SnitchyProvider.all_actions_called.should == [:first, :first, :second, :third] - end + runner.converge + expect(SnitchyProvider.all_actions_called).to eq([:first, :first, :second, :third]) + end - it "does not fire notifications if the resource was not updated by the last action executed" do - # REGRESSION TEST FOR CHEF-1452 - SnitchyProvider.clear_action_record + it "does not fire notifications if the resource was not updated by the last action executed" do + # REGRESSION TEST FOR CHEF-1452 + SnitchyProvider.clear_action_record - Chef::Platform.set( - :resource => :cat, - :provider => SnitchyProvider - ) + first_resource.action = :first_action + first_resource.provider = SnitchyProvider - @first_resource.action = :first_action + second_resource = Chef::Resource::Cat.new("peanut", run_context) + second_resource.action = :nothing + second_resource.provider = SnitchyProvider + run_context.resource_collection << second_resource - second_resource = Chef::Resource::Cat.new("peanut", @run_context) - second_resource.action = :nothing - @run_context.resource_collection << second_resource + third_resource = Chef::Resource::Cat.new("snickers", run_context) + third_resource.action = :nothing + third_resource.provider = SnitchyProvider + run_context.resource_collection << third_resource - third_resource = Chef::Resource::Cat.new("snickers", @run_context) - third_resource.action = :nothing - @run_context.resource_collection << third_resource + first_resource.notifies(:second_action, second_resource, :immediately) + second_resource.notifies(:third_action, third_resource, :immediately) - @first_resource.notifies(:second_action, second_resource, :immediately) - second_resource.notifies(:third_action, third_resource, :immediately) + runner.converge - @runner.converge + # All of the resources should only fire once: + expect(SnitchyProvider.all_actions_called).to eq([:first, :second, :third]) - # All of the resources should only fire once: - SnitchyProvider.all_actions_called.should == [:first, :second, :third] + # all of the resources should be marked as updated for reporting purposes + expect(first_resource).to be_updated + expect(second_resource).to be_updated + expect(third_resource).to be_updated + end - # all of the resources should be marked as updated for reporting purposes - @first_resource.should be_updated - second_resource.should be_updated - third_resource.should be_updated - end + it "should check a resource's only_if and not_if if notified by another resource" do + first_resource.action = :buy - it "should check a resource's only_if and not_if if notified by another resource" do - @first_resource.action = :buy + only_if_called_times = 0 + first_resource.only_if {only_if_called_times += 1; true} - only_if_called_times = 0 - @first_resource.only_if {only_if_called_times += 1; true} + not_if_called_times = 0 + first_resource.not_if {not_if_called_times += 1; false} - not_if_called_times = 0 - @first_resource.not_if {not_if_called_times += 1; false} + second_resource = Chef::Resource::Cat.new("carmel", run_context) + run_context.resource_collection << second_resource + second_resource.notifies(:purr, first_resource, :delayed) + second_resource.action = :purr - second_resource = Chef::Resource::Cat.new("carmel", @run_context) - @run_context.resource_collection << second_resource - second_resource.notifies(:purr, @first_resource, :delayed) - second_resource.action = :purr + # hits only_if first time when the resource is run in order, second on notify + runner.converge - # hits only_if first time when the resource is run in order, second on notify - @runner.converge + expect(only_if_called_times).to eq(2) + expect(not_if_called_times).to eq(2) + end - only_if_called_times.should == 2 - not_if_called_times.should == 2 - end + it "should resolve resource references in notifications when resources are defined lazily" do + first_resource.action = :nothing - it "should resolve resource references in notifications when resources are defined lazily" do - @first_resource.action = :nothing + lazy_resources = lambda { + last_resource = Chef::Resource::Cat.new("peanut", run_context) + run_context.resource_collection << last_resource + last_resource.notifies(:purr, first_resource.to_s, :delayed) + last_resource.action = :purr + } + second_resource = Chef::Resource::RubyBlock.new("myblock", run_context) + run_context.resource_collection << second_resource + second_resource.block { lazy_resources.call } - lazy_resources = lambda { - last_resource = Chef::Resource::Cat.new("peanut", @run_context) - @run_context.resource_collection << last_resource - last_resource.notifies(:purr, @first_resource.to_s, :delayed) - last_resource.action = :purr - } - second_resource = Chef::Resource::RubyBlock.new("myblock", @run_context) - @run_context.resource_collection << second_resource - second_resource.block { lazy_resources.call } + runner.converge - @runner.converge + expect(first_resource).to be_updated + end - @first_resource.should be_updated end - end - diff --git a/spec/unit/shell/shell_ext_spec.rb b/spec/unit/shell/shell_ext_spec.rb index c24acbca3e..8485b66d23 100644 --- a/spec/unit/shell/shell_ext_spec.rb +++ b/spec/unit/shell/shell_ext_spec.rb @@ -121,7 +121,7 @@ describe Shell::Extensions do Shell.session.stub(:rebuild_context) events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(Chef::Node.new, {}, events) - run_context.resource_collection.instance_variable_set(:@iterator, :the_iterator) + run_context.resource_collection.instance_variable_get(:@resource_list).instance_variable_set(:@iterator, :the_iterator) Shell.session.run_context = run_context @root_context.chef_run.should == :the_iterator end diff --git a/spec/unit/util/dsc/local_configuration_manager_spec.rb b/spec/unit/util/dsc/local_configuration_manager_spec.rb index fb6664bd40..eb27e9e94e 100644 --- a/spec/unit/util/dsc/local_configuration_manager_spec.rb +++ b/spec/unit/util/dsc/local_configuration_manager_spec.rb @@ -32,7 +32,7 @@ EOH } let(:no_whatif_lcm_output) { <<-EOH -Start-DscConfiguration : A parameter cannot be found that matches parameter name 'whatif'. +Start-DscConfiguration : A parameter cannot be found\r\n that matches parameter name 'whatif'. At line:1 char:123 + run-somecommand -whatif + ~~~~~~~~ @@ -77,8 +77,13 @@ EOH let(:lcm_standard_error) { no_whatif_lcm_output } let(:lcm_cmdlet_success) { false } + it 'returns true when passed to #whatif_not_supported?' do + expect(lcm.send(:whatif_not_supported?, no_whatif_lcm_output)).to be_true + end + it 'should should return a (possibly empty) array of ResourceInfo instances' do expect(Chef::Log).to receive(:warn) + expect(lcm).to receive(:whatif_not_supported?).and_call_original test_configuration_result = nil expect {test_configuration_result = lcm.test_configuration('config')}.not_to raise_error expect(test_configuration_result.class).to be(Array) @@ -92,7 +97,7 @@ EOH it 'should log a warning if the message is formatted as expected when a resource import failure occurs' do expect(Chef::Log).to receive(:warn) - expect(lcm).to receive(:output_has_dsc_module_failure?).and_call_original + expect(lcm).to receive(:dsc_module_import_failure?).and_call_original test_configuration_result = nil expect {test_configuration_result = lcm.test_configuration('config')}.not_to raise_error end @@ -105,29 +110,29 @@ EOH end end - context 'that fails due to an PowerShell cmdlet error that cannot be handled' do + context 'that fails due to an unknown PowerShell cmdlet error' do let(:lcm_standard_output) { 'some output' } let(:lcm_standard_error) { 'Abort, Retry, Fail?' } let(:lcm_cmdlet_success) { false } - it 'should raise a Chef::Exceptions::PowershellCmdletException' do - expect(Chef::Log).not_to receive(:warn) - expect(lcm).to receive(:output_has_dsc_module_failure?).and_call_original - expect {lcm.test_configuration('config')}.to raise_error(Chef::Exceptions::PowershellCmdletException) + it 'should log a warning' do + expect(Chef::Log).to receive(:warn) + expect(lcm).to receive(:dsc_module_import_failure?).and_call_original + expect {lcm.test_configuration('config')}.not_to raise_error end end end it 'should identify a correctly formatted error message as a resource import failure' do - expect(lcm.send(:output_has_dsc_module_failure?, dsc_resource_import_failure_output)).to be(true) + expect(lcm.send(:dsc_module_import_failure?, dsc_resource_import_failure_output)).to be(true) end it 'should not identify an incorrectly formatted error message as a resource import failure' do - expect(lcm.send(:output_has_dsc_module_failure?, dsc_resource_import_failure_output.gsub('module', 'gibberish'))).to be(false) + expect(lcm.send(:dsc_module_import_failure?, dsc_resource_import_failure_output.gsub('module', 'gibberish'))).to be(false) end it 'should not identify a message without a CimException reference as a resource import failure' do - expect(lcm.send(:output_has_dsc_module_failure?, dsc_resource_import_failure_output.gsub('CimException', 'ArgumentException'))).to be(false) + expect(lcm.send(:dsc_module_import_failure?, dsc_resource_import_failure_output.gsub('CimException', 'ArgumentException'))).to be(false) end end end |