diff options
author | Claire McQuin <claire@getchef.com> | 2014-09-23 13:28:54 -0700 |
---|---|---|
committer | Claire McQuin <claire@getchef.com> | 2014-09-23 13:28:54 -0700 |
commit | b3ef58b5569c289df86feb1007b27be88608400a (patch) | |
tree | 07e37ac08f3b73fc5ca8e92075934023101f145e | |
parent | 8fb2ef7c795ec3df5c9ab5c91413e32edf758dba (diff) | |
parent | ed907ef4d64027212ceb0c082895b303844ff43f (diff) | |
download | chef-b3ef58b5569c289df86feb1007b27be88608400a.tar.gz |
Merge branch 'master' into mcquin/Issue-1944
Conflicts:
DOC_CHANGES.md
124 files changed, 4224 insertions, 556 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 50be057943..73abae07d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## Unreleased: 12.0.0 +* [**Nolan Davidson**](https://github.com/nsdavidson): + The chef-apply command now prints usage information when called without arguments +* [**Kazuki Saito**](https://github.com/sakazuki): + CHEF-4933: idempotency fixes for ifconfig provider +* [**Kirill Shirinkin**](https://github.com/Fodoj): + The knife bootstrap command expands the path of the secret-file * [**Malte Swart**](https://github.com/mswart): [CHEF-4101] DeepMerge - support overwriting hash values with nil * [**James Belchamber**](https://github.com/JamesBelchamber): @@ -126,6 +132,9 @@ * Add `:node_ssl_verify_mode` & `:node_verify_api_cert` options to bootstrap to be able to configure these settings on the bootstrapped node. * Add partial_search dsl method to Chef::Search::Query, add result filtering to search. +* Transfer trusted certificates under :trusted_certs_dir during bootstrap. +* Set :ssl_verify_mode to :verify_peer by default. +* Add homebrew provider for package resource, use it by default on OS X (Issue #1709) ## Last Release: 11.14.2 diff --git a/CHEF_MVPS.md b/CHEF_MVPS.md index 620a516080..28dbe3c8e8 100644 --- a/CHEF_MVPS.md +++ b/CHEF_MVPS.md @@ -18,6 +18,7 @@ After receiving three MVP awards, we add someone to the hall of fame. We want to | Release | Date | MVP | |---------|------|-----| +| [Client 11.16.0](http://www.getchef.com/blog/2014/09/08/release-chef-client-11-16-0-ohai-7-4-0/) | 2014-09-08 | Jesse Hu | | [Client 11.14.2](http://www.getchef.com/blog/2014/08/01/release-chef-client-11-14-2/) | 2014-08-01 | Nikhil Benesch | | [Client 11.12.0](http://www.getchef.com/blog/2014/04/08/release-chef-client-11-12-0-10-32-2/) | 2014-04-08 | Chris Bandy | | [Client 11.10.4](http://www.getchef.com/blog/2014/02/20/chef-client-patch-release-11-10-4/) | 2014-02-20 | Jon Cowie | diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md index c6fe7087c3..736dc4f8a5 100644 --- a/DOC_CHANGES.md +++ b/DOC_CHANGES.md @@ -367,3 +367,30 @@ DNS.3 = systems.example.net IP.1 = 192.168.1.1 IP.2 = 192.168.69.14 ``` + +### Reboot resource in core +The `reboot` resource will reboot the server, a necessary step in some installations, especially on Windows. If this resource is used with notifications, it must receive explicit `:immediate` notifications only: results of delayed notifications are undefined. Currently supported on Windows, Linux, and OS X; will work incidentally on some other Unixes. + +There are three actions: + +```ruby +reboot "app_requires_reboot" do + action :request_reboot + reason "Need to reboot when the run completes successfully." + delay_mins 5 +end + +reboot "cancel_reboot_request" do + action :cancel + reason "Cancel a previous end-of-run reboot request." +end + +reboot "now" do + action :reboot_now + reason "Cannot continue Chef run without a reboot." + delay_mins 2 +end + +# the `:immediate` is required for results to be defined. +notifies :reboot_now, "reboot[now]", :immediate +``` diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f5a74d6a43..12e0351623 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -66,6 +66,34 @@ In order to support configuring passwords for the users using shadow hashes two User resource on Mac supports setting password both using plain-text password or using the shadow hash. You can simply set the `password` attribute to the plain text password to configure the password for the user. However this is not ideal since including plain text passwords in cookbooks (even if they are private) is not a good idea. In order to set passwords using shadow hash you can follow the instructions below based on your Mac OS X version. +## Mac OS X default package provider is now Homebrew + +Per [Chef RFC 016](https://github.com/opscode/chef-rfc/blob/master/rfc016-homebrew-osx-package-provider.md), the default provider for the `package` resource on Mac OS X is now [Homebrew](http://brew.sh). The [homebrew cookbook's](https://supermarket.getchef.com/cookbooks/homebrew) default recipe, or some other method is still required for getting homebrew installed on the system. The cookbook won't be strictly required just to install packages from homebrew on OS X, though. To use this, simply use the `package` resource, or the `homebrew_package` shortcut resource: + +```ruby +package 'emacs' +``` + +Or, + +```ruby +homebrew_package 'emacs' +``` + +The macports provider will still be available, and can be used with the shortcut resource, or by using the `provider` attribute: + +```ruby +macports_package 'emacs' +``` + +Or, + +```ruby +package 'emacs' do + provider Chef::Provider::Package::Macports +end +``` + ### Mac OS X 10.7 10.7 calculates the password hash using **SALTED-SHA512**. Stored shadow hash length is 68 bytes; first 4 bytes being salt and the next 64 bytes being the shadow hash itself. You can use below code in order to calculate password hashes to be used in `password` attribute on Mac OS X 10.7: diff --git a/chef.gemspec b/chef.gemspec index 89b4ac2665..481b120fe6 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -19,8 +19,8 @@ Gem::Specification.new do |s| s.add_dependency "mixlib-cli", "~> 1.4" s.add_dependency "mixlib-log", "~> 1.3" s.add_dependency "mixlib-authentication", "~> 1.3" - s.add_dependency "mixlib-shellout", "~> 1.4" - s.add_dependency "ohai", "~> 7.2" + 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.0", ">= 1.0.2" s.add_dependency "net-ssh", "~> 2.6" diff --git a/lib/chef/application.rb b/lib/chef/application.rb index 5b1d53d741..0430d4acfa 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -46,6 +46,7 @@ class Chef::Application configure_chef configure_logging configure_proxy_environment_variables + configure_encoding end # Get this party started @@ -175,6 +176,11 @@ class Chef::Application configure_no_proxy end + # Sets the default external encoding to UTF-8 (users can change this, but they shouldn't) + def configure_encoding + Encoding.default_external = Chef::Config[:ruby_encoding] + end + # Called prior to starting the application, by the run method def setup_application raise Chef::Exceptions::Application, "#{self.to_s}: you must override setup_application" diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb index ab35b35389..ea9154c6f2 100644 --- a/lib/chef/application/apply.rb +++ b/lib/chef/application/apply.rb @@ -134,6 +134,10 @@ class Chef::Application::Apply < Chef::Application @recipe_text = STDIN.read temp_recipe_file else + if !ARGV[0] + puts opt_parser + Chef::Application.exit! "No recipe file provided", 1 + end @recipe_filename = ARGV[0] @recipe_text,@recipe_fh = read_recipe_file @recipe_filename end diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb index 09a66a9ab2..484ab07390 100644 --- a/lib/chef/chef_fs/chef_fs_data_store.rb +++ b/lib/chef/chef_fs/chef_fs_data_store.rb @@ -27,7 +27,61 @@ require 'fileutils' class Chef module ChefFS + # + # Translation layer between chef-zero's DataStore (a place where it expects + # files to be stored) and ChefFS (the user's repository directory layout). + # + # chef-zero expects the data store to store files *its* way--for example, it + # expects get("nodes/blah") to return the JSON text for the blah node, and + # it expects get("cookbooks/blah/1.0.0") to return the JSON definition of + # the blah cookbook version 1.0.0. + # + # The repository is defined the way the *user* wants their layout. These + # two things are very similar in layout (for example, nodes are stored under + # the nodes/ directory and their filename is the name of the node). + # + # However, there are a few differences that make this more than just a raw + # file store: + # + # 1. Cookbooks are stored much differently. + # - chef-zero places JSON text with the checksums for the cookbook at + # /cookbooks/NAME/VERSION, and expects the JSON to contain URLs to the + # actual files, which are stored elsewhere. + # - The repository contains an actual directory with just the cookbook + # files and a metadata.rb containing a version #. There is no JSON to + # be found. + # - Further, if versioned_cookbooks is false, that directory is named + # /cookbooks/NAME and only one version exists. If versioned_cookbooks + # is true, the directory is named /cookbooks/NAME-VERSION. + # - Therefore, ChefFSDataStore calculates the cookbook JSON by looking at + # the files in the cookbook and checksumming them, and reading metadata.rb + # for the version and dependency information. + # - ChefFSDataStore also modifies the cookbook file URLs so that they point + # to /file_store/repo/<filename> (the path to the actual file under the + # repository root). For example, /file_store/repo/apache2/metadata.rb or + # /file_store/repo/cookbooks/apache2/recipes/default.rb). + # + # 2. Sandboxes don't exist in the repository. + # - ChefFSDataStore lets cookbooks be uploaded into a temporary memory + # storage, and when the cookbook is committed, copies the files onto the + # disk in the correct place (/cookbooks/apache2/recipes/default.rb). + # 3. Data bags: + # - The Chef server expects data bags in /data/BAG/ITEM + # - The repository stores data bags in /data_bags/BAG/ITEM + # + # 4. JSON filenames are generally NAME.json in the repository (e.g. /nodes/foo.json). + # class ChefFSDataStore + # + # Create a new ChefFSDataStore + # + # ==== Arguments + # + # [chef_fs] + # A +ChefFS::FileSystem+ object representing the repository root. + # Generally will be a +ChefFS::FileSystem::ChefRepositoryFileSystemRoot+ + # object, created from +ChefFS::Config.local_fs+. + # def initialize(chef_fs) @chef_fs = chef_fs @memory_store = ChefZero::DataStore::MemoryStore.new diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb index ca273b2cca..fcad6c919f 100644 --- a/lib/chef/chef_fs/config.rb +++ b/lib/chef/chef_fs/config.rb @@ -22,9 +22,74 @@ require 'chef/chef_fs/path_utils' class Chef module ChefFS # - # Helpers to take Chef::Config and create chef_fs and local_fs from it + # Helpers to take Chef::Config and create chef_fs and local_fs (ChefFS + # objects representing the server and local repository, respectively). # class Config + # + # Create a new Config object which can produce a chef_fs and local_fs. + # + # ==== Arguments + # + # [chef_config] + # A hash that looks suspiciously like +Chef::Config+. These hash keys + # include: + # + # :chef_repo_path:: + # The root where all local chef object data is stored. Mirrors + # +Chef::Config.chef_repo_path+ + # :cookbook_path, node_path, ...:: + # Paths to cookbooks/, nodes/, data_bags/, etc. Mirrors + # +Chef::Config.cookbook_path+, etc. Defaults to + # +<chef_repo_path>/cookbooks+, etc. + # :repo_mode:: + # The directory format on disk. 'everything', 'hosted_everything' and + # 'static'. Default: autodetected based on whether the URL has + # "/organizations/NAME." + # :versioned_cookbooks:: + # If true, the repository contains cookbooks with versions in their + # name (apache2-1.0.0). If false, the repository just has one version + # of each cookbook and the directory has the cookbook name (apache2). + # Default: +false+ + # :chef_server_url:: + # The URL to the Chef server, e.g. https://api.opscode.com/organizations/foo. + # Used as the server for the remote chef_fs, and to "guess" repo_mode + # if not specified. + # :node_name:: The username to authenticate to the Chef server with. + # :client_key:: The private key for the user for authentication + # :environment:: The environment in which you are presently working + # :repo_mode:: + # The repository mode, :hosted_everything, :everything or :static. + # This determines the set of subdirectories the Chef server will offer + # up. + # :versioned_cookbooks:: Whether or not to include versions in cookbook names + # + # [cwd] + # The current working directory to base relative Chef paths from. + # Defaults to +Dir.pwd+. + # + # [options] + # A hash of other, not-suspiciously-like-chef-config options: + # :cookbook_version:: + # When downloading cookbooks, download this cookbook version instead + # of the latest. + # + # [ui] + # The object to print output to, with "output", "warn" and "error" + # (looks a little like a Chef::Knife::UI object, obtainable from + # Chef::Knife.ui). + # + # ==== Example + # + # require 'chef/chef_fs/config' + # config = Chef::ChefFS::Config.new + # config.chef_fs.child('cookbooks').children.each do |cookbook| + # puts "Cookbook on server: #{cookbook.name}" + # end + # config.local_fs.child('cookbooks').children.each do |cookbook| + # puts "Local cookbook: #{cookbook.name}" + # end + # def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {}, ui = nil) @chef_config = chef_config @cwd = cwd diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb index 050da60389..ac272d4c1a 100644 --- a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb @@ -42,15 +42,17 @@ class Chef # Create a new Chef Repository File System root. # # == Parameters - # - child_paths - a hash of child paths, e.g.: - # "nodes" => [ '/var/nodes', '/home/jkeiser/nodes' ], - # "roles" => [ '/var/roles' ], - # ... - # - root_paths - an array of paths representing the top level, where - # org.json, members.json, and invites.json will be stored. - # - chef_config - a hash of options that looks suspiciously like the ones + # [child_paths] + # A hash of child paths, e.g.: + # "nodes" => [ '/var/nodes', '/home/jkeiser/nodes' ], + # "roles" => [ '/var/roles' ], + # ... + # [root_paths] + # An array of paths representing the top level, where + # +org.json+, +members.json+, and +invites.json+ will be stored. + # [chef_config] - a hash of options that looks suspiciously like the ones # stored in Chef::Config, containing at least these keys: - # - :versioned_cookbooks - whether to include versions in cookbook names + # :versioned_cookbooks:: whether to include versions in cookbook names def initialize(child_paths, root_paths=[], chef_config=Chef::Config) super("", nil) @child_paths = child_paths diff --git a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb index 61a224c307..370308ee0a 100644 --- a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb @@ -46,19 +46,24 @@ class Chef # # == Parameters # - # - root_name - a friendly name for the root, for printing--like "remote" or "chef_central". - # - chef_config - a hash with options that look suspiciously like Chef::Config, including the - # following keys: - # - :chef_server_url - the URL to the Chef server or top of the organization - # - :node_name - the username to authenticate to the Chef server with - # - :client_key - the private key for the user for authentication - # - :environment - the environment in which you are presently working - # - :repo_mode - the repository mode, :hosted_everything, :everything or :static. - # This determines the set of subdirectories the Chef server - # will offer up. - # - :versioned_cookbooks - whether or not to include versions in cookbook names - # - options - other options: - # - :cookbook_version - when cookbooks are retrieved, grab this version for them. + # [root_name] + # A friendly name for the root, for printing--like "remote" or "chef_central". + # [chef_config] + # A hash with options that look suspiciously like Chef::Config, including the + # following keys: + # :chef_server_url:: The URL to the Chef server or top of the organization + # :node_name:: The username to authenticate to the Chef server with + # :client_key:: The private key for the user for authentication + # :environment:: The environment in which you are presently working + # :repo_mode:: + # The repository mode, :hosted_everything, :everything or :static. + # This determines the set of subdirectories the Chef server will + # offer up. + # :versioned_cookbooks:: whether or not to include versions in cookbook names + # [options] + # Other options: + # :cookbook_version:: when cookbooks are retrieved, grab this version for them. + # :freeze:: freeze cookbooks on upload # def initialize(root_name, chef_config, options = {}) super("", nil) diff --git a/lib/chef/client.rb b/lib/chef/client.rb index 2de3ca3e64..161ecddb0f 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -44,6 +44,7 @@ require 'chef/resource_reporter' require 'chef/run_lock' require 'chef/policy_builder' require 'chef/request_id' +require 'chef/platform/rebooter' require 'ohai' require 'rbconfig' @@ -427,7 +428,9 @@ class Chef run_context = setup_run_context - converge(run_context) + catch (:end_client_run_early) do + converge(run_context) + end save_updated_node @@ -435,6 +438,10 @@ class Chef Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds") run_completed_successfully @events.run_completed(node) + + # rebooting has to be the last thing we do, no exceptions. + Chef::Platform::Rebooter.reboot_if_needed!(node) + true rescue Exception => e # CHEF-3336: Send the error first in case something goes wrong below and we don't know why diff --git a/lib/chef/config.rb b/lib/chef/config.rb index 4e71645dcc..74607e8368 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -331,10 +331,11 @@ class Chef default :ssl_client_cert, nil default :ssl_client_key, nil - # Whether or not to verify the SSL cert for all HTTPS requests. If set to - # :verify_peer, all HTTPS requests will be validated regardless of other - # SSL verification settings. - default :ssl_verify_mode, :verify_none + # Whether or not to verify the SSL cert for all HTTPS requests. When set to + # :verify_peer (default), all HTTPS requests will be validated regardless of other + # SSL verification settings. When set to :verify_none no HTTPS requests will + # be validated. + default :ssl_verify_mode, :verify_peer # Whether or not to verify the SSL cert for HTTPS requests to the Chef # server API. If set to `true`, the server's cert will be validated @@ -587,6 +588,51 @@ class Chef default :normal_attribute_whitelist, nil default :override_attribute_whitelist, nil + # Chef requires an English-language UTF-8 locale to function properly. We attempt + # to use the 'locale -a' command and search through a list of preferences until we + # find one that we can use. On Ubuntu systems we should find 'C.UTF-8' and be + # able to use that even if there is no English locale on the server, but Mac, Solaris, + # AIX, etc do not have that locale. We then try to find an English locale and fall + # back to 'C' if we do not. The choice of fallback is pick-your-poison. If we try + # to do the work to return a non-US UTF-8 locale then we fail inside of providers when + # things like 'svn info' return Japanese and we can't parse them. OTOH, if we pick 'C' then + # we will blow up on UTF-8 characters. Between the warn we throw and the Encoding + # exception that ruby will throw it is more obvious what is broken if we drop UTF-8 by + # default rather than drop English. + # + # If there is no 'locale -a' then we return 'en_US.UTF-8' since that is the most commonly + # available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'. + default :internal_locale do + begin + locales = `locale -a`.split + case + when locales.include?('C.UTF-8') + 'C.UTF-8' + when locales.include?('en_US.UTF-8') + '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' + end + rescue + Chef::Log.warn "No usable locale -a command found, assuming you have en_US.UTF-8 installed." + 'en_US.UTF-8' + end + end + + # Force UTF-8 Encoding, for when we fire up in the 'C' locale or other strange locales (e.g. + # japanese windows encodings). If we do not do this, then knife upload will fail when a cookbook's + # README.md has UTF-8 characters that do not encode in whatever surrounding encoding we have been + # passed. Effectively, the Chef Ecosystem is globally UTF-8 by default. Anyone who wants to be + # able to upload Shift_JIS or ISO-8859-1 files needs to mark *those* files explicitly with + # magic tags to make ruby correctly identify the encoding being used. Changing this default will + # break Chef community cookbooks and is very highly discouraged. + default :ruby_encoding, Encoding::UTF_8 + # If installed via an omnibus installer, this gives the path to the # "embedded" directory which contains all of the software packaged with # omnibus. This is used to locate the cacert.pem file on windows. diff --git a/lib/chef/dsl/reboot_pending.rb b/lib/chef/dsl/reboot_pending.rb index 9f80d38c61..a81debce99 100644 --- a/lib/chef/dsl/reboot_pending.rb +++ b/lib/chef/dsl/reboot_pending.rb @@ -27,10 +27,13 @@ class Chef include Chef::DSL::PlatformIntrospection # Returns true if the system needs a reboot or is expected to reboot - # Raises UnsupportedPlatform if this functionality isn't provided yet + # Note that we will silently miss any other platform-specific reboot notices besides Windows+Ubuntu. def reboot_pending? - if platform?("windows") + # don't break when used as a mixin in contexts without #node (e.g. specs). + if self.respond_to?(:node, true) && node.run_context.reboot_requested? + true + elsif platform?("windows") # PendingFileRenameOperations contains pairs (REG_MULTI_SZ) of filenames that cannot be updated # due to a file being in use (usually a temporary file and a system file) # \??\c:\temp\test.sys!\??\c:\winnt\system32\test.sys @@ -53,7 +56,7 @@ class Chef # This should work for Debian as well if update-notifier-common happens to be installed. We need an API for that. File.exists?('/var/run/reboot-required') else - raise Chef::Exceptions::UnsupportedPlatform.new(node[:platform]) + false end end end diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 23e223f204..67429ac5a2 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -118,6 +118,10 @@ class Chef class InvalidDataBagPath < ArgumentError; end class DuplicateDataBagItem < RuntimeError; end + class PowershellCmdletException < RuntimeError; end + + class CannotDetermineHomebrewOwner < Package; end + # A different version of a cookbook was added to a # VersionedRecipeList than the one already there. class CookbookVersionConflict < ArgumentError ; end @@ -179,6 +183,8 @@ class Chef class ChildConvergeError < RuntimeError; end + class NoProviderAvailable < RuntimeError; end + class MissingRole < RuntimeError NULL = Object.new diff --git a/lib/chef/knife/bootstrap/archlinux-gems.erb b/lib/chef/knife/bootstrap/archlinux-gems.erb index bb84340c05..eb134b90d5 100644 --- a/lib/chef/knife/bootstrap/archlinux-gems.erb +++ b/lib/chef/knife/bootstrap/archlinux-gems.erb @@ -23,6 +23,11 @@ EOP chmod 0600 /etc/chef/encrypted_data_bag_secret <% end -%> +<% unless trusted_certs.empty? -%> +mkdir -p /etc/chef/trusted_certs +<%= trusted_certs %> +<% end -%> + <%# Generate Ohai Hints -%> <% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> mkdir -p /etc/chef/ohai/hints diff --git a/lib/chef/knife/bootstrap/chef-aix.erb b/lib/chef/knife/bootstrap/chef-aix.erb index 59993b478a..3a031ee738 100644 --- a/lib/chef/knife/bootstrap/chef-aix.erb +++ b/lib/chef/knife/bootstrap/chef-aix.erb @@ -36,6 +36,11 @@ EOP chmod 0600 /etc/chef/encrypted_data_bag_secret <% end -%> +<% unless trusted_certs.empty? -%> +mkdir -p /etc/chef/trusted_certs +<%= trusted_certs %> +<% end -%> + <%# Generate Ohai Hints -%> <% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> mkdir -p /etc/chef/ohai/hints diff --git a/lib/chef/knife/bootstrap/chef-full.erb b/lib/chef/knife/bootstrap/chef-full.erb index a4e85b9d67..6edb485f44 100644 --- a/lib/chef/knife/bootstrap/chef-full.erb +++ b/lib/chef/knife/bootstrap/chef-full.erb @@ -50,6 +50,11 @@ EOP chmod 0600 /etc/chef/encrypted_data_bag_secret <% end -%> +<% unless trusted_certs.empty? -%> +mkdir -p /etc/chef/trusted_certs +<%= trusted_certs %> +<% end -%> + <%# Generate Ohai Hints -%> <% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> mkdir -p /etc/chef/ohai/hints diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index 12d422a162..87c25ca160 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -44,14 +44,20 @@ class Chef def encrypted_data_bag_secret knife_config[:secret] || begin - if knife_config[:secret_file] && File.exist?(knife_config[:secret_file]) - IO.read(File.expand_path(knife_config[:secret_file])) + secret_file_path = knife_config[:secret_file] + expanded_secret_file_path = File.expand_path(secret_file_path.to_s) + if secret_file_path && File.exist?(expanded_secret_file_path) + IO.read(expanded_secret_file_path) else nil end end end + def trusted_certs + @trusted_certs ||= trusted_certs_content + end + def config_content client_rb = <<-CONFIG log_location STDOUT @@ -107,6 +113,10 @@ CONFIG client_rb << %Q{encrypted_data_bag_secret "/etc/chef/encrypted_data_bag_secret"\n} end + unless trusted_certs.empty? + client_rb << %Q{trusted_certs_dir "/etc/chef/trusted_certs"\n} + end + client_rb end @@ -153,6 +163,18 @@ CONFIG (@config[:first_boot_attributes] || {}).merge(:run_list => @run_list) end + private + def trusted_certs_content + content = "" + if @chef_config[:trusted_certs_dir] + Dir.glob(File.join(@chef_config[:trusted_certs_dir], "*.{crt,pem}")).each do |cert| + content << "cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'\n" + + IO.read(File.expand_path(cert)) + "\nEOP\n" + end + end + content + end + end end end diff --git a/lib/chef/mixin/homebrew_owner.rb b/lib/chef/mixin/homebrew_owner.rb new file mode 100644 index 0000000000..73bb22ddf5 --- /dev/null +++ b/lib/chef/mixin/homebrew_owner.rb @@ -0,0 +1,58 @@ +# +# Author:: Joshua Timberman (<joshua@getchef.com>) +# Author:: Graeme Mathieson (<mathie@woss.name>) +# +# Copyright 2011-2013, Opscode, Inc. +# Copyright 2014, Chef Software, Inc <legal@getchef.com> +# +# 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. +# +# Ported from the homebrew cookbook's Homebrew::Mixin owner helpers +# +# This lives here in Chef::Mixin because Chef's namespacing makes it +# awkward to use modules elsewhere (e.g., chef/provider/package/homebrew/owner) + +class Chef + module Mixin + module HomebrewOwner + def homebrew_owner(node) + @homebrew_owner ||= calculate_owner(node) + end + + private + + def calculate_owner(node) + owner = homebrew_owner_attr(node) || sudo_user || current_user + if owner == 'root' + raise Chef::Exceptions::CannotDetermineHomebrewOwner, + 'The homebrew owner is not specified and the current user is \"root\"' + + 'Homebrew does not support root installs, please specify the homebrew' + + 'owner by setting the attribute `node[\'homebrew\'][\'owner\']`.' + end + owner + end + + def homebrew_owner_attr(node) + node['homebrew']['owner'] if node.attribute?('homebrew') && node['homebrew'].attribute?('owner') + end + + def sudo_user + ENV['SUDO_USER'] + end + + def current_user + ENV['USER'] + end + end + end +end diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb index 881c94b862..82772b584a 100644 --- a/lib/chef/mixin/shell_out.rb +++ b/lib/chef/mixin/shell_out.rb @@ -30,32 +30,39 @@ class Chef # Generally speaking, 'extend Chef::Mixin::ShellOut' in your recipes and include 'Chef::Mixin::ShellOut' in your LWRPs # You can also call Mixlib::Shellout.new directly, but you lose all of the above functionality + # we use 'en_US.UTF-8' by default because we parse localized strings in English as an API and + # generally must support UTF-8 unicode. def shell_out(*command_args) - cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args)) - cmd.live_stream ||= io_for_live_stream - cmd.run_command - cmd + args = command_args.dup + if args.last.is_a?(Hash) + options = args.pop.dup + env_key = options.has_key?(:env) ? :env : :environment + options[env_key] ||= {} + options[env_key] = options[env_key].dup + options[env_key]['LC_ALL'] ||= Chef::Config[:internal_locale] unless options[env_key].has_key?('LC_ALL') + args << options + else + args << { :environment => { 'LC_ALL' => Chef::Config[:internal_locale] } } + end + + shell_out_command(*args) end + # call shell_out (using en_US.UTF-8) and raise errors def shell_out!(*command_args) - cmd= shell_out(*command_args) + cmd = shell_out(*command_args) cmd.error! cmd end - # environment['LC_ALL'] should be nil or what the user specified def shell_out_with_systems_locale(*command_args) - args = command_args.dup - if args.last.is_a?(Hash) - options = args.last - env_key = options.has_key?(:env) ? :env : :environment - options[env_key] ||= {} - options[env_key]['LC_ALL'] ||= nil - else - args << { :environment => { 'LC_ALL' => nil } } - end + shell_out_command(*command_args) + end - shell_out(*args) + def shell_out_with_systems_locale!(*command_args) + cmd = shell_out_with_systems_locale(*command_args) + cmd.error! + cmd end DEPRECATED_OPTIONS = @@ -82,6 +89,13 @@ class Chef private + def shell_out_command(*command_args) + cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args)) + cmd.live_stream ||= io_for_live_stream + cmd.run_command + cmd + end + def deprecate_option(old_option, new_option) Chef::Log.logger.warn "DEPRECATION: Chef::Mixin::ShellOut option :#{old_option} is deprecated. Use :#{new_option}" end diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb index ff118c1d3d..65ad042910 100644 --- a/lib/chef/mixin/windows_architecture_helper.rb +++ b/lib/chef/mixin/windows_architecture_helper.rb @@ -42,6 +42,22 @@ class Chef is_i386_process_on_x86_64_windows? end + def with_os_architecture(node) + wow64_redirection_state = nil + + if wow64_architecture_override_required?(node, node_windows_architecture(node)) + wow64_redirection_state = disable_wow64_file_redirection(node) + end + + begin + yield + ensure + if wow64_redirection_state + restore_wow64_file_redirection(node, wow64_redirection_state) + end + end + end + def node_supports_windows_architecture?(node, desired_architecture) assert_valid_windows_architecture!(desired_architecture) return (node_windows_architecture(node) == :x86_64 || diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index 7f79c38a6a..b10fecc53e 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -40,7 +40,7 @@ class Chef { :mac_os_x => { :default => { - :package => Chef::Provider::Package::Macports, + :package => Chef::Provider::Package::Homebrew, :service => Chef::Provider::Service::Macosx, :user => Chef::Provider::User::Dscl, :group => Chef::Provider::Group::Dscl @@ -48,7 +48,7 @@ class Chef }, :mac_os_x_server => { :default => { - :package => Chef::Provider::Package::Macports, + :package => Chef::Provider::Package::Homebrew, :service => Chef::Provider::Service::Macosx, :user => Chef::Provider::User::Dscl, :group => Chef::Provider::Group::Dscl diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb index f6f5309de5..334ab278d1 100644 --- a/lib/chef/platform/query_helpers.rb +++ b/lib/chef/platform/query_helpers.rb @@ -45,7 +45,11 @@ class Chef is_server_2003 end - end + def supports_dsc?(node) + node[:languages] && node[:languages][:powershell] && + node[:languages][:powershell][:version].to_i >= 4 + end + end end end diff --git a/lib/chef/platform/rebooter.rb b/lib/chef/platform/rebooter.rb new file mode 100644 index 0000000000..b46f0e394c --- /dev/null +++ b/lib/chef/platform/rebooter.rb @@ -0,0 +1,54 @@ +# +# Author:: Chris Doherty <cdoherty@getchef.com>) +# Copyright:: Copyright (c) 2014 Chef, 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/dsl/reboot_pending' +require 'chef/log' +require 'chef/platform' + +class Chef + class Platform + module Rebooter + extend Chef::Mixin::ShellOut + + class << self + + def reboot!(node) + reboot_info = node.run_context.reboot_info + + cmd = if Chef::Platform.windows? + # should this do /f as well? do we then need a minimum delay to let apps quit? + "shutdown /r /t #{reboot_info[:delay_mins]} /c \"#{reboot_info[:reason]}\"" + else + # probably Linux-only. + "shutdown -r +#{reboot_info[:delay_mins]} \"#{reboot_info[:reason]}\"" + end + + Chef::Log.warn "Rebooting server at a recipe's request. Details: #{reboot_info.inspect}" + shell_out!(cmd) + end + + # this is a wrapper function so Chef::Client only needs a single line of code. + def reboot_if_needed!(node) + if node.run_context.reboot_requested? + reboot!(node) + end + end + end + end + end +end diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb new file mode 100644 index 0000000000..5d7322842c --- /dev/null +++ b/lib/chef/provider/dsc_script.rb @@ -0,0 +1,148 @@ +# +# Author:: Adam Edwards (<adamed@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/util/powershell/cmdlet' +require 'chef/util/dsc/configuration_generator' +require 'chef/util/dsc/local_configuration_manager' + +class Chef + class Provider + class DscScript < Chef::Provider + def initialize(dsc_resource, run_context) + super(dsc_resource, run_context) + @dsc_resource = dsc_resource + @resource_converged = false + @operations = { + :set => Proc.new { |config_manager, document| + config_manager.set_configuration(document) + }, + :test => Proc.new { |config_manager, document| + config_manager.test_configuration(document) + }} + end + + def action_run + if ! @resource_converged + converge_by(generate_description) do + run_configuration(:set) + Chef::Log.info("DSC resource configuration completed successfully") + end + end + end + + def load_current_resource + @dsc_resources_info = run_configuration(:test) + @resource_converged = @dsc_resources_info.all? do |resource| + !resource.changes_state? + end + end + + def whyrun_supported? + true + end + + protected + + def run_configuration(operation) + config_directory = ::Dir.mktmpdir("chef-dsc-script") + configuration_data_path = get_configuration_data_path(config_directory) + configuration_flags = get_augmented_configuration_flags(configuration_data_path) + + config_manager = Chef::Util::DSC::LocalConfigurationManager.new(@run_context.node, config_directory) + + begin + configuration_document = generate_configuration_document(config_directory, configuration_flags) + @operations[operation].call(config_manager, configuration_document) + rescue Exception => e + Chef::Log.error("DSC operation failed: #{e.message.to_s}") + raise e + ensure + ::FileUtils.rm_rf(config_directory) + end + end + + def get_augmented_configuration_flags(configuration_data_path) + updated_flags = nil + 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 + updated_flags + end + + def generate_configuration_document(config_directory, configuration_flags) + shellout_flags = { + :cwd => @dsc_resource.cwd, + :environment => @dsc_resource.environment, + :timeout => @dsc_resource.timeout + } + + generator = Chef::Util::DSC::ConfigurationGenerator.new(@run_context.node, config_directory) + + if @dsc_resource.command + generator.configuration_document_from_script_path(@dsc_resource.command, configuration_name, configuration_flags, shellout_flags) + else + # If code is also not provided, we mimic what the other script resources do (execute nothing) + Chef::Log.warn("Neither code or command were provided for dsc_resource[#{@dsc_resource.name}].") unless @dsc_resource.code + generator.configuration_document_from_script_code(@dsc_resource.code || '', configuration_flags, shellout_flags) + end + end + + def get_configuration_data_path(config_directory) + if @dsc_resource.configuration_data_script + @dsc_resource.configuration_data_script + elsif @dsc_resource.configuration_data + configuration_data_path = "#{config_directory}/chef_dsc_config_data.psd1" + ::File.open(configuration_data_path, 'wt') do | script | + script.write(@dsc_resource.configuration_data) + end + configuration_data_path + end + end + + def configuration_name + @dsc_resource.configuration_name || @dsc_resource.name + end + + def configuration_friendly_name + if @dsc_resource.code + @dsc_resource.name + else + configuration_name + end + end + + private + + def generate_description + ["converge DSC configuration '#{configuration_friendly_name}'"] + + @dsc_resources_info.map do |resource| + if resource.changes_state? + # We ignore the last log message because it only contains the time it took, which looks weird + cleaned_messages = resource.change_log[0..-2].map { |c| c.sub(/^#{Regexp.escape(resource.name)}/, '').strip } + "converge DSC resource #{resource.name} by #{cleaned_messages.find_all{ |c| c != ''}.join("\n")}" + else + # This is needed because a dsc script can have resouces that are both converged and not + "converge DSC resource #{resource.name} by doing nothing because it is already converged" + end + end + end + end + end +end diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb index 31f88e5406..ac52100b56 100644 --- a/lib/chef/provider/ifconfig.rb +++ b/lib/chef/provider/ifconfig.rb @@ -19,6 +19,7 @@ require 'chef/log' require 'chef/mixin/command' require 'chef/provider' +require 'chef/resource/file' require 'chef/exceptions' require 'erb' @@ -109,11 +110,11 @@ class Chef :command => command ) Chef::Log.info("#{@new_resource} added") - # Write out the config files - generate_config end end end + # Write out the config files + generate_config end def action_enable @@ -140,12 +141,12 @@ class Chef run_command( :command => command ) - delete_config Chef::Log.info("#{@new_resource} deleted") end else Chef::Log.debug("#{@new_resource} does not exist - nothing to do") end + delete_config end def action_disable @@ -168,27 +169,25 @@ class Chef ! @config_template.nil? and ! @config_path.nil? end + def resource_for_config(path) + Chef::Resource::File.new(path, run_context) + end + def generate_config return unless can_generate_config? b = binding template = ::ERB.new(@config_template) - converge_by ("generate configuration file : #{@config_path}") do - network_file = ::File.new(@config_path, "w") - network_file.puts(template.result(b)) - network_file.close - end - Chef::Log.info("#{@new_resource} created configuration file") + config = resource_for_config(@config_path) + config.content(template.result(b)) + config.run_action(:create) + @new_resource.updated_by_last_action(true) if config.updated? end def delete_config return unless can_generate_config? - require 'fileutils' - if ::File.exist?(@config_path) - converge_by ("delete the #{@config_path}") do - FileUtils.rm_f(@config_path, :verbose => false) - end - end - Chef::Log.info("#{@new_resource} deleted configuration file") + config = resource_for_config(@config_path) + config.run_action(:delete) + @new_resource.updated_by_last_action(true) if config.updated? end private diff --git a/lib/chef/provider/link.rb b/lib/chef/provider/link.rb index d6602c2e03..af2fe4a84f 100644 --- a/lib/chef/provider/link.rb +++ b/lib/chef/provider/link.rb @@ -83,7 +83,7 @@ class Chef end def canonicalize(path) - Chef::Platform.windows? ? path.gsub('/', '\\') : path + Chef::Util::PathHelper.canonical_path(path) end def action_create diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb index 9fb87d6ea0..da3e6d1684 100644 --- a/lib/chef/provider/package/aix.rb +++ b/lib/chef/provider/package/aix.rb @@ -112,14 +112,10 @@ class Chef def install_package(name, version) Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}") if @new_resource.options.nil? - run_command_with_systems_locale( - :command => "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" - ) + shell_out!( "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" ) Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") else - run_command_with_systems_locale( - :command => "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" - ) + shell_out!( "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" ) Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") end end @@ -128,14 +124,10 @@ class Chef def remove_package(name, version) if @new_resource.options.nil? - run_command_with_systems_locale( - :command => "installp -u #{name}" - ) + shell_out!( "installp -u #{name}" ) Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}") else - run_command_with_systems_locale( - :command => "installp -u #{expand_options(@new_resource.options)} #{name}" - ) + shell_out!( "installp -u #{expand_options(@new_resource.options)} #{name}" ) Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}") end end diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb new file mode 100644 index 0000000000..d964703c87 --- /dev/null +++ b/lib/chef/provider/package/homebrew.rb @@ -0,0 +1,121 @@ +# +# Author:: Joshua Timberman (<joshua@getchef.com>) +# Author:: Graeme Mathieson (<mathie@woss.name>) +# +# Copyright 2011-2013, Opscode, Inc. +# Copyright 2014, Chef Software, Inc <legal@getchef.com> +# +# 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 'etc' +require 'chef/mixin/homebrew_owner' + +class Chef + class Provider + class Package + class Homebrew < Chef::Provider::Package + include Chef::Mixin::HomebrewOwner + def load_current_resource + self.current_resource = Chef::Resource::Package.new(new_resource.name) + current_resource.package_name(new_resource.package_name) + current_resource.version(current_installed_version) + Chef::Log.debug("#{new_resource} current version is #{current_resource.version}") if current_resource.version + + @candidate_version = candidate_version + + Chef::Log.debug("#{new_resource} candidate version is #{@candidate_version}") if @candidate_version + + current_resource + end + + def install_package(name, version) + unless current_resource.version == version + brew('install', new_resource.options, name) + end + end + + def upgrade_package(name, version) + current_version = current_resource.version + + if current_version.nil? or current_version.empty? + install_package(name, version) + elsif current_version != version + brew('upgrade', new_resource.options, name) + end + end + + def remove_package(name, version) + if current_resource.version + brew('uninstall', new_resource.options, name) + end + end + + # Homebrew doesn't really have a notion of purging, do a "force remove" + def purge_package(name, version) + new_resource.options((new_resource.options || '') << ' --force').strip + remove_package(name, version) + end + + def brew(*args) + get_response_from_command("brew #{args.join(' ')}") + end + + # We implement a querying method that returns the JSON-as-Hash + # data for a formula per the Homebrew documentation. Previous + # implementations of this provider in the homebrew cookbook + # performed a bit of magic with the load path to get this + # information, but that is not any more robust than using the + # command-line interface that returns the same thing. + # + # https://github.com/Homebrew/homebrew/wiki/Querying-Brew + def brew_info + @brew_info ||= Chef::JSONCompat.from_json(brew('info', '--json=v1', new_resource.package_name)).first + end + + # Some packages (formula) are "keg only" and aren't linked, + # because multiple versions installed can cause conflicts. We + # handle this by using the last installed version as the + # "current" (as in latest). Otherwise, we will use the version + # that brew thinks is linked as the current version. + # + def current_installed_version + brew_info['keg_only'] ? brew_info['installed'].last['version'] : brew_info['linked_keg'] + end + + # Packages (formula) available to install should have a + # "stable" version, per the Homebrew project's acceptable + # formula documentation, so we will rely on that being the + # case. Older implementations of this provider in the homebrew + # cookbook would fall back to +brew_info['version']+, but the + # schema has changed, and homebrew is a constantly rolling + # forward project. + # + # https://github.com/Homebrew/homebrew/wiki/Acceptable-Formulae#stable-versions + def candidate_version + brew_info['versions']['stable'] + end + + private + + def get_response_from_command(command) + home_dir = Etc.getpwnam(homebrew_owner(node)).dir + + Chef::Log.debug "Executing '#{command}' as user '#{homebrew_owner(node)}'" + output = shell_out!(command, :timeout => 1800, :user => homebrew_owner(node), :environment => { 'HOME' => home_dir, 'RUBYOPT' => nil }) + output.stdout.chomp + end + end + end + end +end diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb index 92b41b3627..4090507303 100644 --- a/lib/chef/provider/package/ips.rb +++ b/lib/chef/provider/package/ips.rb @@ -65,15 +65,13 @@ class Chef def install_package(name, version) package_name = "#{name}@#{version}" normal_command = "pkg#{expand_options(@new_resource.options)} install -q #{package_name}" - if @new_resource.respond_to?(:accept_license) and @new_resource.accept_license - command = normal_command.gsub('-q', '-q --accept') - else - command = normal_command - end - begin - run_command_with_systems_locale(:command => command) - rescue - end + command = + if @new_resource.respond_to?(:accept_license) and @new_resource.accept_license + normal_command.gsub('-q', '-q --accept') + else + normal_command + end + shell_out(command) end def upgrade_package(name, version) @@ -82,9 +80,7 @@ class Chef def remove_package(name, version) package_name = "#{name}@#{version}" - run_command_with_systems_locale( - :command => "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" - ) + shell_out!( "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" ) end end end diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb index 6ef303ee4f..05247e6d31 100644 --- a/lib/chef/provider/package/macports.rb +++ b/lib/chef/provider/package/macports.rb @@ -45,27 +45,21 @@ class Chef unless @current_resource.version == version command = "port#{expand_options(@new_resource.options)} install #{name}" command << " @#{version}" if version and !version.empty? - run_command_with_systems_locale( - :command => command - ) + shell_out!(command) end end def purge_package(name, version) command = "port#{expand_options(@new_resource.options)} uninstall #{name}" command << " @#{version}" if version and !version.empty? - run_command_with_systems_locale( - :command => command - ) + shell_out!(command) end def remove_package(name, version) command = "port#{expand_options(@new_resource.options)} deactivate #{name}" command << " @#{version}" if version and !version.empty? - run_command_with_systems_locale( - :command => command - ) + shell_out!(command) end def upgrade_package(name, version) @@ -78,9 +72,7 @@ class Chef # that hasn't been installed. install_package(name, version) elsif current_version != version - run_command_with_systems_locale( - :command => "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" - ) + shell_out!( "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" ) end end diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb index 2e8bb7850b..1014ebcaa5 100644 --- a/lib/chef/provider/package/pacman.rb +++ b/lib/chef/provider/package/pacman.rb @@ -86,9 +86,7 @@ class Chef end def install_package(name, version) - run_command_with_systems_locale( - :command => "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" - ) + shell_out!( "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" ) end def upgrade_package(name, version) @@ -96,9 +94,7 @@ class Chef end def remove_package(name, version) - run_command_with_systems_locale( - :command => "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" - ) + shell_out!( "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" ) end def purge_package(name, version) diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb index 6a3587558a..7e0eebd0d9 100644 --- a/lib/chef/provider/package/portage.rb +++ b/lib/chef/provider/package/portage.rb @@ -110,9 +110,7 @@ class Chef pkg = "~#{name}-#{$1}" end - run_command_with_systems_locale( - :command => "emerge -g --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" - ) + shell_out!( "emerge -g --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" ) end def upgrade_package(name, version) @@ -126,9 +124,7 @@ class Chef pkg = "#{@new_resource.package_name}" end - run_command_with_systems_locale( - :command => "emerge --unmerge --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" - ) + shell_out!( "emerge --unmerge --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" ) end def purge_package(name, version) diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb index bbb561bd15..c0a6444252 100644 --- a/lib/chef/provider/package/rpm.rb +++ b/lib/chef/provider/package/rpm.rb @@ -90,13 +90,9 @@ class Chef def install_package(name, version) unless @current_resource.version - run_command_with_systems_locale( - :command => "rpm #{@new_resource.options} -i #{@new_resource.source}" - ) + shell_out!( "rpm #{@new_resource.options} -i #{@new_resource.source}" ) else - run_command_with_systems_locale( - :command => "rpm #{@new_resource.options} -U #{@new_resource.source}" - ) + shell_out!( "rpm #{@new_resource.options} -U #{@new_resource.source}" ) end end @@ -104,13 +100,9 @@ class Chef def remove_package(name, version) if version - run_command_with_systems_locale( - :command => "rpm #{@new_resource.options} -e #{name}-#{version}" - ) + shell_out!( "rpm #{@new_resource.options} -e #{name}-#{version}" ) else - run_command_with_systems_locale( - :command => "rpm #{@new_resource.options} -e #{name}" - ) + shell_out!( "rpm #{@new_resource.options} -e #{name}" ) end end diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb index 0f45b61e18..19f844b66a 100644 --- a/lib/chef/provider/package/solaris.rb +++ b/lib/chef/provider/package/solaris.rb @@ -112,9 +112,7 @@ class Chef else command = "pkgadd -n -d #{@new_resource.source} all" end - run_command_with_systems_locale( - :command => command - ) + shell_out!(command) Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") else if ::File.directory?(@new_resource.source) # CHEF-4469 @@ -122,23 +120,17 @@ class Chef else command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all" end - run_command_with_systems_locale( - :command => command - ) + shell_out!(command) Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") end end def remove_package(name, version) if @new_resource.options.nil? - run_command_with_systems_locale( - :command => "pkgrm -n #{name}" - ) + shell_out!( "pkgrm -n #{name}" ) Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}") else - run_command_with_systems_locale( - :command => "pkgrm -n#{expand_options(@new_resource.options)} #{name}" - ) + shell_out!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" ) Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}") end end diff --git a/lib/chef/provider/reboot.rb b/lib/chef/provider/reboot.rb new file mode 100644 index 0000000000..8dde4653ec --- /dev/null +++ b/lib/chef/provider/reboot.rb @@ -0,0 +1,69 @@ +# +# Author:: Chris Doherty <cdoherty@getchef.com>) +# Copyright:: Copyright (c) 2014 Chef, 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/log' +require 'chef/provider' + +class Chef + class Provider + class Reboot < Chef::Provider + + def whyrun_supported? + true + end + + def load_current_resource + @current_resource ||= Chef::Resource::Reboot.new(@new_resource.name) + @current_resource.reason(@new_resource.reason) + @current_resource.delay_mins(@new_resource.delay_mins) + @current_resource + end + + def request_reboot + node.run_context.request_reboot( + :delay_mins => @new_resource.delay_mins, + :reason => @new_resource.reason, + :timestamp => Time.now, + :requested_by => @new_resource.name + ) + end + + def action_request_reboot + converge_by("request a system reboot to occur if the run succeeds") do + Chef::Log.warn "Reboot requested:'#{@new_resource.name}'" + request_reboot + end + end + + def action_reboot_now + converge_by("rebooting the system immediately") do + Chef::Log.warn "Rebooting system immediately, requested by '#{@new_resource.name}'" + request_reboot + throw :end_client_run_early + end + end + + def action_cancel + converge_by("cancel any existing end-of-run reboot request") do + Chef::Log.warn "Reboot canceled: '#{@new_resource.name}'" + node.run_context.cancel_reboot + end + end + end + end +end diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb index 06fe7fc480..1ebef90349 100644 --- a/lib/chef/provider/service/debian.rb +++ b/lib/chef/provider/service/debian.rb @@ -130,15 +130,15 @@ class Chef def enable_service if @new_resource.priority.is_a? Integer - run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") - run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults #{@new_resource.priority} #{100 - @new_resource.priority}") + 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 - run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") - run_command(:command => "/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 @@ -146,16 +146,16 @@ class Chef def disable_service if @new_resource.priority.is_a? Integer # Stop processes in reverse order of start using '100 - start_priority' - run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") - run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop #{100 - @new_resource.priority} 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 #{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 - run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") - run_command(:command => "/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 @@ -166,8 +166,8 @@ class Chef priority = o[1] args += "#{action} #{priority} #{level} . " end - run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") - run_command(:command => "/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 7b69957cc6..08d58232e1 100644 --- a/lib/chef/provider/service/freebsd.rb +++ b/lib/chef/provider/service/freebsd.rb @@ -78,7 +78,7 @@ class Chef if new_resource.start_command super else - shell_out!("#{init_command} faststart") + shell_out_with_systems_locale!("#{init_command} faststart") end end @@ -86,7 +86,7 @@ class Chef if new_resource.stop_command super else - shell_out!("#{init_command} faststop") + shell_out_with_systems_locale!("#{init_command} faststop") end end @@ -94,7 +94,7 @@ class Chef if new_resource.restart_command super elsif new_resource.supports[:restart] - shell_out!("#{init_command} fastrestart") + shell_out_with_systems_locale!("#{init_command} fastrestart") else stop_service sleep 1 diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb index 1559c7893f..a68abfebc9 100644 --- a/lib/chef/provider/service/gentoo.rb +++ b/lib/chef/provider/service/gentoo.rb @@ -58,10 +58,10 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init end def enable_service() - run_command(:command => "/sbin/rc-update add #{@new_resource.service_name} default") + shell_out!("/sbin/rc-update add #{@new_resource.service_name} default") end def disable_service() - run_command(:command => "/sbin/rc-update del #{@new_resource.service_name} default") + shell_out!("/sbin/rc-update del #{@new_resource.service_name} default") end end diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb index 23d9dde80a..5d8bb5bb38 100644 --- a/lib/chef/provider/service/init.rb +++ b/lib/chef/provider/service/init.rb @@ -50,7 +50,7 @@ class Chef if @new_resource.start_command super else - shell_out!("#{default_init_command} start") + shell_out_with_systems_locale!("#{default_init_command} start") end end @@ -58,7 +58,7 @@ class Chef if @new_resource.stop_command super else - shell_out!("#{default_init_command} stop") + shell_out_with_systems_locale!("#{default_init_command} stop") end end @@ -66,7 +66,7 @@ class Chef if @new_resource.restart_command super elsif @new_resource.supports[:restart] - shell_out!("#{default_init_command} restart") + shell_out_with_systems_locale!("#{default_init_command} restart") else stop_service sleep 1 @@ -78,7 +78,7 @@ class Chef if @new_resource.reload_command super elsif @new_resource.supports[:reload] - shell_out!("#{default_init_command} reload") + shell_out_with_systems_locale!("#{default_init_command} reload") end end end diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb index 35767ee7b9..f4c85dd9d3 100644 --- a/lib/chef/provider/service/insserv.rb +++ b/lib/chef/provider/service/insserv.rb @@ -37,12 +37,12 @@ class Chef end def enable_service() - run_command(:command => "/sbin/insserv -r -f #{@new_resource.service_name}") - run_command(:command => "/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() - run_command(:command => "/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/macosx.rb b/lib/chef/provider/service/macosx.rb index 36930ee4ac..cf5e554559 100644 --- a/lib/chef/provider/service/macosx.rb +++ b/lib/chef/provider/service/macosx.rb @@ -83,7 +83,7 @@ class Chef if @new_resource.start_command super else - shell_out!("launchctl load -w '#{@plist}'", :user => @owner_uid, :group => @owner_gid) + shell_out_with_systems_locale!("launchctl load -w '#{@plist}'", :user => @owner_uid, :group => @owner_gid) end end end @@ -95,7 +95,7 @@ class Chef if @new_resource.stop_command super else - shell_out!("launchctl unload '#{@plist}'", :user => @owner_uid, :group => @owner_gid) + shell_out_with_systems_locale!("launchctl unload '#{@plist}'", :user => @owner_uid, :group => @owner_gid) end end end diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb index 0eb983a0bf..bd51d15f84 100644 --- a/lib/chef/provider/service/simple.rb +++ b/lib/chef/provider/service/simple.rb @@ -85,16 +85,16 @@ class Chef end def start_service - shell_out!(@new_resource.start_command) + shell_out_with_systems_locale!(@new_resource.start_command) end def stop_service - shell_out!(@new_resource.stop_command) + shell_out_with_systems_locale!(@new_resource.stop_command) end def restart_service if @new_resource.restart_command - shell_out!(@new_resource.restart_command) + shell_out_with_systems_locale!(@new_resource.restart_command) else stop_service sleep 1 @@ -103,7 +103,7 @@ class Chef end def reload_service - shell_out!(@new_resource.reload_command) + shell_out_with_systems_locale!(@new_resource.reload_command) end protected diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb index 0c47a3462b..f0584dcf6d 100644 --- a/lib/chef/provider/service/solaris.rb +++ b/lib/chef/provider/service/solaris.rb @@ -56,7 +56,7 @@ class Chef alias_method :start_service, :enable_service def reload_service - shell_out!("#{default_init_command} refresh #{@new_resource.service_name}") + shell_out_with_systems_locale!("#{default_init_command} refresh #{@new_resource.service_name}") end def restart_service diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb index 6231603d03..31feee65d4 100644 --- a/lib/chef/provider/service/systemd.rb +++ b/lib/chef/provider/service/systemd.rb @@ -28,7 +28,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple if @new_resource.status_command Chef::Log.debug("#{@new_resource} you have specified a status command, running..") - unless shell_out_with_systems_locale(@new_resource.status_command).error? + unless shell_out(@new_resource.status_command).error? @current_resource.running(true) else @status_check_success = false @@ -61,7 +61,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple if @new_resource.start_command super else - shell_out_with_systems_locale("/bin/systemctl start #{@new_resource.service_name}") + shell_out_with_systems_locale!("/bin/systemctl start #{@new_resource.service_name}") end end end @@ -73,7 +73,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple if @new_resource.stop_command super else - shell_out_with_systems_locale("/bin/systemctl stop #{@new_resource.service_name}") + shell_out_with_systems_locale!("/bin/systemctl stop #{@new_resource.service_name}") end end end @@ -82,7 +82,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple if @new_resource.restart_command super else - shell_out_with_systems_locale("/bin/systemctl restart #{@new_resource.service_name}") + shell_out_with_systems_locale!("/bin/systemctl restart #{@new_resource.service_name}") end end @@ -91,7 +91,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple super else if @current_resource.running - shell_out_with_systems_locale("/bin/systemctl reload #{@new_resource.service_name}") + shell_out_with_systems_locale!("/bin/systemctl reload #{@new_resource.service_name}") else start_service end @@ -99,18 +99,18 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple end def enable_service - shell_out_with_systems_locale("/bin/systemctl enable #{@new_resource.service_name}") + shell_out!("/bin/systemctl enable #{@new_resource.service_name}") end def disable_service - shell_out_with_systems_locale("/bin/systemctl disable #{@new_resource.service_name}") + shell_out!("/bin/systemctl disable #{@new_resource.service_name}") end def is_active? - shell_out_with_systems_locale("/bin/systemctl is-active #{@new_resource.service_name} --quiet").exitstatus == 0 + shell_out("/bin/systemctl is-active #{@new_resource.service_name} --quiet").exitstatus == 0 end def is_enabled? - shell_out_with_systems_locale("/bin/systemctl is-enabled #{@new_resource.service_name} --quiet").exitstatus == 0 + shell_out("/bin/systemctl is-enabled #{@new_resource.service_name} --quiet").exitstatus == 0 end end diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb index c81a8a50dc..670bf9e5f8 100644 --- a/lib/chef/provider/service/upstart.rb +++ b/lib/chef/provider/service/upstart.rb @@ -97,10 +97,10 @@ class Chef Chef::Log.debug("#{@new_resource} you have specified a status command, running..") begin - if run_command_with_systems_locale(:command => @new_resource.status_command) == 0 + if shell_out!(@new_resource.status_command) == 0 @current_resource.running true end - rescue Chef::Exceptions::Exec + rescue @command_success = false @current_resource.running false nil @@ -153,7 +153,7 @@ class Chef if @new_resource.start_command super else - run_command_with_systems_locale(:command => "/sbin/start #{@job}") + shell_out_with_systems_locale!("/sbin/start #{@job}") end end end @@ -167,7 +167,7 @@ class Chef if @new_resource.stop_command super else - run_command_with_systems_locale(:command => "/sbin/stop #{@job}") + shell_out_with_systems_locale!("/sbin/stop #{@job}") end end end @@ -179,7 +179,7 @@ class Chef # Older versions of upstart would fail on restart if the service was currently stopped, check for that. LP:430883 else if @current_resource.running - run_command_with_systems_locale(:command => "/sbin/restart #{@job}") + shell_out_with_systems_locale!("/sbin/restart #{@job}") else start_service end @@ -191,7 +191,7 @@ class Chef super else # upstart >= 0.6.3-4 supports reload (HUP) - run_command_with_systems_locale(:command => "/sbin/reload #{@job}") + shell_out_with_systems_locale!("/sbin/reload #{@job}") end end diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb index 81ed639c53..6cf31c8ec8 100644 --- a/lib/chef/provider/subversion.rb +++ b/lib/chef/provider/subversion.rb @@ -60,7 +60,7 @@ class Chef def action_checkout if target_dir_non_existent_or_empty? converge_by("perform checkout of #{@new_resource.repository} into #{@new_resource.destination}") do - run_command(run_options(:command => checkout_command)) + shell_out!(run_options(command: checkout_command)) end else Chef::Log.debug "#{@new_resource} checkout destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do" @@ -77,7 +77,7 @@ class Chef def action_force_export converge_by("export #{@new_resource.repository} into #{@new_resource.destination}") do - run_command(run_options(:command => export_command)) + shell_out!(run_options(command: export_command)) end end @@ -88,7 +88,7 @@ class Chef Chef::Log.debug "#{@new_resource} current revision: #{current_rev} target revision: #{revision_int}" unless current_revision_matches_target_revision? converge_by("sync #{@new_resource.destination} from #{@new_resource.repository}") do - run_command(run_options(:command => sync_command)) + shell_out!(run_options(command: sync_command)) Chef::Log.info "#{@new_resource} updated to revision: #{revision_int}" end end @@ -100,14 +100,14 @@ class Chef def sync_command c = scm :update, @new_resource.svn_arguments, verbose, authentication, "-r#{revision_int}", @new_resource.destination Chef::Log.debug "#{@new_resource} updated working copy #{@new_resource.destination} to revision #{@new_resource.revision}" - c + c end def checkout_command c = scm :checkout, @new_resource.svn_arguments, verbose, authentication, "-r#{revision_int}", @new_resource.repository, @new_resource.destination Chef::Log.info "#{@new_resource} checked out #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}" - c + c end def export_command @@ -116,7 +116,7 @@ class Chef "-r#{revision_int}" << @new_resource.repository << @new_resource.destination c = scm :export, *args Chef::Log.info "#{@new_resource} exported #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}" - c + c end # If the specified revision isn't an integer ("HEAD" for example), look diff --git a/lib/chef/provider/whyrun_safe_ruby_block.rb b/lib/chef/provider/whyrun_safe_ruby_block.rb index 4b491a4f60..e5f35debd7 100644 --- a/lib/chef/provider/whyrun_safe_ruby_block.rb +++ b/lib/chef/provider/whyrun_safe_ruby_block.rb @@ -19,7 +19,7 @@ class Chef class Provider class WhyrunSafeRubyBlock < Chef::Provider::RubyBlock - def action_create + def action_run @new_resource.block.call @new_resource.updated_by_last_action(true) @run_context.events.resource_update_applied(@new_resource, :create, "execute the whyrun_safe_ruby_block #{@new_resource.name}") diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index 3c9e94e6f7..fec00d0e63 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -24,6 +24,7 @@ require 'chef/provider/cron/solaris' require 'chef/provider/cron/aix' require 'chef/provider/deploy' require 'chef/provider/directory' +require 'chef/provider/dsc_script' require 'chef/provider/env' require 'chef/provider/erl_call' require 'chef/provider/execute' @@ -39,6 +40,7 @@ require 'chef/provider/mdadm' require 'chef/provider/mount' require 'chef/provider/package' require 'chef/provider/powershell_script' +require 'chef/provider/reboot' require 'chef/provider/remote_directory' require 'chef/provider/remote_file' require 'chef/provider/route' @@ -58,6 +60,7 @@ require 'chef/provider/package/easy_install' require 'chef/provider/package/freebsd/port' require 'chef/provider/package/freebsd/pkg' require 'chef/provider/package/freebsd/pkgng' +require 'chef/provider/package/homebrew' require 'chef/provider/package/ips' require 'chef/provider/package/macports' require 'chef/provider/package/pacman' diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb new file mode 100644 index 0000000000..2972ace1aa --- /dev/null +++ b/lib/chef/resource/dsc_script.rb @@ -0,0 +1,140 @@ +# +# Author:: Adam Edwards (<adamed@getchef.com>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/exceptions' + +class Chef + class Resource + class DscScript < Chef::Resource + + provides :dsc_script, :on_platforms => ["windows"] + + def initialize(name, run_context=nil) + super + @allowed_actions.push(:run) + @action = :run + if(run_context && Chef::Platform.supports_dsc?(run_context.node)) + @provider = Chef::Provider::DscScript + else + raise Chef::Exceptions::NoProviderAvailable, + "#{powershell_info_str(run_context)}\nPowershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource." + end + end + + def code(arg=nil) + if arg && command + raise ArgumentError, "Only one of 'code' and 'command' attributes may be specified" + end + if arg && configuration_name + raise ArgumentError, "The 'code' and 'command' attributes may not be used together" + end + set_or_return( + :code, + arg, + :kind_of => [ String ] + ) + end + + def configuration_name(arg=nil) + if arg && code + raise ArgumentError, "Attribute `configuration_name` may not be set if `code` is set" + end + set_or_return( + :configuration_name, + arg, + :kind_of => [ String ] + ) + end + + def command(arg=nil) + if arg && code + raise ArgumentError, "The 'code' and 'command' attributes may not be used together" + end + set_or_return( + :command, + arg, + :kind_of => [ String ] + ) + end + + def configuration_data(arg=nil) + if arg && configuration_data_script + raise ArgumentError, "The 'configuration_data' and 'configuration_data_script' attributes may not be used together" + end + set_or_return( + :configuration_data, + arg, + :kind_of => [ String ] + ) + end + + def configuration_data_script(arg=nil) + if arg && configuration_data + raise ArgumentError, "The 'configuration_data' and 'configuration_data_script' attributes may not be used together" + end + set_or_return( + :configuration_data_script, + arg, + :kind_of => [ String ] + ) + end + + def flags(arg=nil) + set_or_return( + :flags, + arg, + :kind_of => [ Hash ] + ) + end + + def cwd(arg=nil) + set_or_return( + :cwd, + arg, + :kind_of => [ String ] + ) + end + + def environment(arg=nil) + set_or_return( + :environment, + arg, + :kind_of => [ Hash ] + ) + end + + def timeout(arg=nil) + set_or_return( + :timeout, + arg, + :kind_of => [ Integer ] + ) + end + + private + + def powershell_info_str(run_context) + if run_context && run_context.node[:languages] && run_context.node[:languages][:powershell] + install_info = "Powershell #{run_context.node[:languages][:powershell][:version]} was found on the system." + else + install_info = 'Powershell was not found.' + end + end + end + end +end diff --git a/lib/chef/resource/homebrew_package.rb b/lib/chef/resource/homebrew_package.rb new file mode 100644 index 0000000000..c3fa9ddffc --- /dev/null +++ b/lib/chef/resource/homebrew_package.rb @@ -0,0 +1,34 @@ +# +# Author:: Joshua Timberman (<joshua@getchef.com>) +# Author:: Graeme Mathieson (<mathie@woss.name>) +# +# Copyright 2011-2013, Opscode, Inc. +# Copyright 2014, Chef Software, Inc <legal@getchef.com> +# +# 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/package' +require 'chef/resource/package' + +class Chef + class Resource + class HomebrewPackage < Chef::Resource::Package + def initialize(name, run_context=nil) + super + @resource_name = :homebrew_package + @provider = Chef::Provider::Package::Homebrew + end + end + end +end diff --git a/lib/chef/resource/reboot.rb b/lib/chef/resource/reboot.rb new file mode 100644 index 0000000000..d6caafdea8 --- /dev/null +++ b/lib/chef/resource/reboot.rb @@ -0,0 +1,48 @@ +# +# Author:: Chris Doherty <cdoherty@getchef.com>) +# Copyright:: Copyright (c) 2014 Chef, 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' + +# In using this resource via notifications, it's important to *only* use +# immediate notifications. Delayed notifications produce unintuitive and +# probably undesired results. +class Chef + class Resource + class Reboot < Chef::Resource + def initialize(name, run_context=nil) + super + @resource_name = :reboot + @provider = Chef::Provider::Reboot + @allowed_actions = [:request_reboot, :reboot_now, :cancel] + + @reason = "Reboot by Chef" + @delay_mins = 0 + + # no default action. + end + + def reason(arg=nil) + set_or_return(:reason, arg, :kind_of => String) + end + + def delay_mins(arg=nil) + set_or_return(:delay_mins, arg, :kind_of => Fixnum) + end + end + end +end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 93ff682288..5b938095c6 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -28,6 +28,7 @@ require 'chef/resource/deploy' require 'chef/resource/deploy_revision' require 'chef/resource/directory' require 'chef/resource/dpkg_package' +require 'chef/resource/dsc_script' require 'chef/resource/easy_install_package' require 'chef/resource/env' require 'chef/resource/erl_call' @@ -39,6 +40,7 @@ require 'chef/resource/gem_package' require 'chef/resource/git' require 'chef/resource/group' require 'chef/resource/http_request' +require 'chef/resource/homebrew_package' require 'chef/resource/ifconfig' require 'chef/resource/link' require 'chef/resource/log' @@ -53,6 +55,7 @@ require 'chef/resource/perl' require 'chef/resource/portage_package' require 'chef/resource/powershell_script' require 'chef/resource/python' +require 'chef/resource/reboot' require 'chef/resource/registry_key' require 'chef/resource/remote_directory' require 'chef/resource/remote_file' diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index 3dd53f0f8f..bbe2f9eba0 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -61,6 +61,9 @@ class Chef # Event dispatcher for this run. attr_reader :events + # Hash of factoids for a reboot request. + attr_reader :reboot_info + # Creates a new Chef::RunContext object and populates its fields. This object gets # used by the Chef Server to generate a fully compiled recipe list for a node. # @@ -76,6 +79,7 @@ class Chef @loaded_recipes = {} @loaded_attributes = {} @events = events + @reboot_info = {} @node.run_context = self @@ -271,6 +275,27 @@ ERROR_MESSAGE end end + # there are options for how to handle multiple calls to these functions: + # 1. first call always wins (never change @reboot_info once set). + # 2. last call always wins (happily change @reboot_info whenever). + # 3. raise an exception on the first conflict. + # 4. disable reboot after this run if anyone ever calls :cancel. + # 5. raise an exception on any second call. + # 6. ? + def request_reboot(reboot_info) + Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to #{reboot_info.inspect}" + @reboot_info = reboot_info + end + + def cancel_reboot + Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to {}" + @reboot_info = {} + end + + def reboot_requested? + @reboot_info.size > 0 + end + private def loaded_recipe(cookbook, recipe) diff --git a/lib/chef/util/dsc/configuration_generator.rb b/lib/chef/util/dsc/configuration_generator.rb new file mode 100644 index 0000000000..12cd5dc3a2 --- /dev/null +++ b/lib/chef/util/dsc/configuration_generator.rb @@ -0,0 +1,115 @@ +# +# Author:: Adam Edwards (<adamed@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/util/powershell/cmdlet' + +class Chef::Util::DSC + class ConfigurationGenerator + def initialize(node, config_directory) + @node = node + @config_directory = config_directory + end + + def configuration_document_from_script_code(code, configuration_flags, shellout_flags) + Chef::Log.debug("DSC: DSC code:\n '#{code}'") + generated_script_path = write_document_generation_script(code, 'chef_dsc') + begin + configuration_document_from_script_path(generated_script_path, 'chef_dsc', configuration_flags, shellout_flags) + ensure + ::FileUtils.rm(generated_script_path) + end + end + + def configuration_document_from_script_path(script_path, configuration_name, configuration_flags, shellout_flags) + validate_configuration_name!(configuration_name) + + document_generation_cmdlet = Chef::Util::Powershell::Cmdlet.new( + @node, + configuration_document_generation_code(script_path, configuration_name)) + + merged_configuration_flags = get_merged_configuration_flags!(configuration_flags, configuration_name) + + document_generation_cmdlet.run!(merged_configuration_flags, shellout_flags) + configuration_document_location = find_configuration_document(configuration_name) + + if ! configuration_document_location + raise RuntimeError, "No DSC configuration for '#{configuration_name}' was generated from supplied DSC script" + end + + configuration_document = get_configuration_document(configuration_document_location) + ::FileUtils.rm_rf(configuration_document_location) + configuration_document + end + + protected + + # From PowerShell error help for the Configuration language element: + # Standard names may only contain letters (a-z, A-Z), numbers (0-9), and underscore (_). + # The name may not be null or empty, and should start with a letter. + def validate_configuration_name!(configuration_name) + if !!(configuration_name =~ /\A[A-Za-z]+[_a-zA-Z0-9]*\Z/) == false + raise ArgumentError, 'Configuration `#{configuration_name}` is not a valid PowerShell cmdlet name' + end + end + + def get_merged_configuration_flags!(configuration_flags, configuration_name) + merged_configuration_flags = { :outputpath => configuration_document_directory(configuration_name) } + if configuration_flags + configuration_flags.map do | switch, value | + if merged_configuration_flags.key?(switch.to_s.downcase.to_sym) + raise ArgumentError, "The `flags` attribute for the dsc_script resource contained a command line switch :#{switch.to_s} that is disallowed." + end + merged_configuration_flags[switch.to_s.downcase.to_sym] = value + end + end + merged_configuration_flags + end + + def configuration_code(code, configuration_name) + "$ProgressPreference = 'SilentlyContinue';Configuration '#{configuration_name}'\n{\n\tnode 'localhost'\n{\n\t#{code}\n}}\n" + end + + def configuration_document_generation_code(configuration_script, configuration_name) + ". '#{configuration_script}';#{configuration_name}" + end + + def write_document_generation_script(code, configuration_name) + script_path = "#{@config_directory}/chef_dsc_config.ps1" + ::File.open(script_path, 'wt') do | script | + script.write(configuration_code(code, configuration_name)) + end + script_path + end + + def find_configuration_document(configuration_name) + document_directory = configuration_document_directory(configuration_name) + document_file_name = ::Dir.entries(document_directory).find { | path | path =~ /.*.mof/ } + ::File.join(document_directory, document_file_name) if document_file_name + end + + def configuration_document_directory(configuration_name) + ::File.join(@config_directory, configuration_name) + end + + def get_configuration_document(document_path) + ::File.open(document_path, 'rb') do | file | + file.read + end + end + end +end diff --git a/lib/chef/util/dsc/lcm_output_parser.rb b/lib/chef/util/dsc/lcm_output_parser.rb new file mode 100644 index 0000000000..f8f853a33a --- /dev/null +++ b/lib/chef/util/dsc/lcm_output_parser.rb @@ -0,0 +1,133 @@ +# +# 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/log' +require 'chef/util/dsc/resource_info' + +class Chef + class Util + class DSC + class LocalConfigurationManager + module Parser + # Parses the output from LCM and returns a list of Chef::Util::DSC::ResourceInfo objects + # that describe how the resources affected the system + # + # Example: + # parse <<-EOF + # What if: [Machine]: LCM: [Start Set ] + # What if: [Machine]: LCM: [Start Resource ] [[File]FileToNotBeThere] + # What if: [Machine]: LCM: [Start Set ] [[File]FileToNotBeThere] + # What if: [C:\ShouldNotExist.txt] removed + # What if: [Machine]: LCM: [End Set ] [[File]FileToNotBeThere] in 0.1 seconds + # What if: [Machine]: LCM: [End Resource ] [[File]FileToNotBeThere] + # What if: [Machine]: LCM: [End Set ] + # EOF + # + # would return + # + # [ + # Chef::Util::DSC::ResourceInfo.new( + # '[[File]FileToNotBeThere]', + # true, + # [ + # '[[File]FileToNotBeThere]', + # '[C:\Shouldnotexist.txt]', + # '[[File]FileToNotBeThere] in 0.1 seconds' + # ] + # ) + # ] + # + def self.parse(lcm_output) + return [] unless lcm_output + + current_resource = Hash.new + + resources = [] + lcm_output.lines.each do |line| + op_action, op_type, info = parse_line(line) + + case op_action + when :start + case op_type + when :set + if current_resource[:name] + current_resource[:context] = :logging + current_resource[:logs] = [info] + end + when :resource + if current_resource[:name] + resources.push(current_resource) + end + current_resource = {:name => info} + else + Chef::Log.debug("Ignoring op_action #{op_action}: Read line #{line}") + end + when :end + # Make sure we log the last line + if current_resource[:context] == :logging and info.include? current_resource[:name] + current_resource[:logs].push(info) + end + current_resource[:context] = nil + when :skip + current_resource[:skipped] = true + when :info + if current_resource[:context] == :logging + current_resource[:logs].push(info) + end + end + end + + if current_resource[:name] + resources.push(current_resource) + end + + build_resource_info(resources) + end + + def self.parse_line(line) + if match = line.match(/^.*?:.*?:\s*LCM:\s*\[(.*?)\](.*)/) + # If the line looks like + # What If: [machinename]: LCM: [op_action op_type] message + # extract op_action, op_type, and message + operation, info = match.captures + op_action, op_type = operation.strip.split(' ').map {|m| m.downcase.to_sym} + else + op_action = op_type = :info + if match = line.match(/^.*?:.*?: \s+(.*)/) + info = match.captures[0] + else + info = line + end + end + info.strip! # Because this was formatted for humans + return [op_action, op_type, info] + end + private_class_method :parse_line + + def self.build_resource_info(resources) + resources.map do |r| + Chef::Util::DSC::ResourceInfo.new(r[:name], !r[:skipped], r[:logs]) + end + end + private_class_method :build_resource_info + + end + end + end + end +end diff --git a/lib/chef/util/dsc/local_configuration_manager.rb b/lib/chef/util/dsc/local_configuration_manager.rb new file mode 100644 index 0000000000..4a56b6a397 --- /dev/null +++ b/lib/chef/util/dsc/local_configuration_manager.rb @@ -0,0 +1,137 @@ +# +# Author:: Adam Edwards (<adamed@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/util/powershell/cmdlet' +require 'chef/util/dsc/lcm_output_parser' + +class Chef::Util::DSC + class LocalConfigurationManager + def initialize(node, configuration_path) + @node = node + @configuration_path = configuration_path + clear_execution_time + end + + def test_configuration(configuration_document) + status = run_configuration_cmdlet(configuration_document) + handle_what_if_exception!(status.stderr) unless status.succeeded? + configuration_update_required?(status.return_value) + end + + def set_configuration(configuration_document) + run_configuration_cmdlet(configuration_document, true) + end + + def last_operation_execution_time_seconds + if @operation_start_time && @operation_end_time + @operation_end_time - @operation_start_time + end + end + + private + + def run_configuration_cmdlet(configuration_document, apply_configuration = false) + Chef::Log.debug("DSC: Calling DSC Local Config Manager to #{apply_configuration ? "set" : "test"} configuration document.") + test_only_parameters = ! apply_configuration ? '-whatif; if (! $?) { exit 1 }' : '' + + start_operation_timing + command_code = lcm_command_code(@configuration_path, test_only_parameters) + status = nil + + begin + save_configuration_document(configuration_document) + cmdlet = ::Chef::Util::Powershell::Cmdlet.new(@node, "#{command_code}") + if apply_configuration + status = cmdlet.run! + else + status = cmdlet.run + end + ensure + end_operation_timing + remove_configuration_document + if last_operation_execution_time_seconds + Chef::Log.debug("DSC: DSC operation completed in #{last_operation_execution_time_seconds} seconds.") + end + end + Chef::Log.debug("DSC: Completed call to DSC Local Config Manager") + status + end + + def lcm_command_code(configuration_path, test_only_parameters) + <<-EOH +$ProgressPreference = 'SilentlyContinue';start-dscconfiguration -path #{@configuration_path} -wait -erroraction 'continue' -force #{test_only_parameters} +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 + end + + def output_has_dsc_module_failure?(what_if_output) + !! (what_if_output =~ /\sCimException/ && + what_if_output =~ /ProviderOperationExecutionFailure/ && + what_if_output =~ /\smodule\s+is\s+installed/) + end + + def configuration_update_required?(what_if_output) + Chef::Log.debug("DSC: DSC returned the following '-whatif' output from test operation:\n#{what_if_output}") + begin + Parser::parse(what_if_output) + rescue Chef::Util::DSC::LocalConfigurationManager::Parser => e + Chef::Log::warn("Could not parse LCM output: #{e}") + [Chef::Util::DSC::ResourceInfo.new('Unknown DSC Resources', true, ['Unknown changes because LCM output was not parsable.'])] + end + end + + def save_configuration_document(configuration_document) + ::FileUtils.mkdir_p(@configuration_path) + ::File.open(configuration_document_path, 'wb') do | file | + file.write(configuration_document) + end + end + + def remove_configuration_document + ::FileUtils.rm(configuration_document_path) + end + + def configuration_document_path + File.join(@configuration_path,'..mof') + end + + def clear_execution_time + @operation_start_time = nil + @operation_end_time = nil + end + + def start_operation_timing + clear_execution_time + @operation_start_time = Time.now + end + + def end_operation_timing + @operation_end_time = Time.now + end + end +end diff --git a/lib/chef/util/dsc/resource_info.rb b/lib/chef/util/dsc/resource_info.rb new file mode 100644 index 0000000000..4a32451721 --- /dev/null +++ b/lib/chef/util/dsc/resource_info.rb @@ -0,0 +1,26 @@ + +class Chef + class Util + class DSC + class ResourceInfo + # The name is the text following [Start Set] + attr_reader :name + + # A list of all log messages between [Start Set] and [End Set]. + # Each line is an element in the list. + attr_reader :change_log + + def initialize(name, sets, change_log) + @name = name + @sets = sets + @change_log = change_log || [] + end + + # Does this resource change the state of the system? + def changes_state? + @sets + end + end + end + end +end diff --git a/lib/chef/util/powershell/cmdlet.rb b/lib/chef/util/powershell/cmdlet.rb new file mode 100644 index 0000000000..40edbb13c6 --- /dev/null +++ b/lib/chef/util/powershell/cmdlet.rb @@ -0,0 +1,136 @@ +# +# Author:: Adam Edwards (<adamed@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 'mixlib/shellout' +require 'chef/mixin/windows_architecture_helper' +require 'chef/util/powershell/cmdlet_result' + +class Chef::Util::Powershell + class Cmdlet + def initialize(node, cmdlet, output_format=nil, output_format_options={}) + @output_format = output_format + @node = node + + case output_format + when nil + @json_format = false + when :json + @json_format = true + when :text + @json_format = false + when :object + @json_format = true + else + raise ArgumentError, "Invalid output format #{output_format.to_s} specified" + end + + @cmdlet = cmdlet + @output_format_options = output_format_options + end + + attr_reader :output_format + + def run(switches={}, execution_options={}, *arguments) + arguments_string = arguments.join(' ') + + switches_string = command_switches_string(switches) + + json_depth = 5 + + if @json_format && @output_format_options.has_key?(:depth) + json_depth = @output_format_options[:depth] + end + + json_command = @json_format ? " | convertto-json -compress -depth #{json_depth}" : "" + command_string = "powershell.exe -executionpolicy bypass -noprofile -noninteractive -command \"trap [Exception] {write-error -exception ($_.Exception.Message);exit 1};#{@cmdlet} #{switches_string} #{arguments_string}#{json_command}\";if ( ! $? ) { exit 1 }" + + augmented_options = {:returns => [0], :live_stream => false}.merge(execution_options) + command = Mixlib::ShellOut.new(command_string, augmented_options) + + os_architecture = "#{ENV['PROCESSOR_ARCHITEW6432']}" == 'AMD64' ? :x86_64 : :i386 + + status = nil + + with_os_architecture(@node) do + status = command.run_command + end + + CmdletResult.new(status, @output_format) + end + + def run!(switches={}, execution_options={}, *arguments) + result = run(switches, execution_options, arguments) + + if ! result.succeeded? + raise Chef::Exceptions::PowershellCmdletException, "Powershell Cmdlet failed: #{result.stderr}" + end + + result + end + + protected + + include Chef::Mixin::WindowsArchitectureHelper + + def validate_switch_name!(switch_parameter_name) + if !!(switch_parameter_name =~ /\A[A-Za-z]+[_a-zA-Z0-9]*\Z/) == false + raise ArgumentError, "`#{switch_parameter_name}` is not a valid PowerShell cmdlet switch parameter name" + end + end + + def escape_parameter_value(parameter_value) + parameter_value.gsub(/(`|'|"|#)/,'`\1') + end + + def escape_string_parameter_value(parameter_value) + "'#{escape_parameter_value(parameter_value)}'" + end + + def command_switches_string(switches) + command_switches = switches.map do | switch_name, switch_value | + if switch_name.class != Symbol + raise ArgumentError, "Invalid type `#{switch_name} `for PowerShell switch '#{switch_name.to_s}'. The switch must be specified as a Symbol'" + end + + validate_switch_name!(switch_name) + + switch_argument = '' + switch_present = true + + case switch_value + when Numeric + switch_argument = switch_value.to_s + when Float + switch_argument = switch_value.to_s + when FalseClass + switch_present = false + when TrueClass + when String + switch_argument = escape_string_parameter_value(switch_value) + else + raise ArgumentError, "Invalid argument type `#{switch_value.class}` specified for PowerShell switch `:#{switch_name.to_s}`. Arguments to PowerShell must be of type `String`, `Numeric`, `Float`, `FalseClass`, or `TrueClass`" + end + + switch_present ? ["-#{switch_name.to_s.downcase}", switch_argument].join(' ').strip : '' + end + + command_switches.join(' ') + end + end +end + diff --git a/lib/chef/util/powershell/cmdlet_result.rb b/lib/chef/util/powershell/cmdlet_result.rb new file mode 100644 index 0000000000..af7b3607cd --- /dev/null +++ b/lib/chef/util/powershell/cmdlet_result.rb @@ -0,0 +1,46 @@ +# +# Author:: Adam Edwards (<adamed@getchef.com>) +# +# Copyright:: Copyright (c) 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 'json' + +class Chef::Util::Powershell + class CmdletResult + attr_reader :output_format + + def initialize(status, output_format) + @status = status + @output_format = output_format + end + + def stderr + @status.stderr + end + + def return_value + if output_format == :object + JSON.parse(@status.stdout) + else + @status.stdout + end + end + + def succeeded? + @succeeded = @status.status.exitstatus == 0 + end + end +end diff --git a/lib/chef/version.rb b/lib/chef/version.rb index be449d4fa0..b2d3fe57ee 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -17,7 +17,7 @@ class Chef CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__))) - VERSION = '12.0.0.alpha.1' + VERSION = '12.0.0.alpha.2' end # diff --git a/lib/chef/win32/api/file.rb b/lib/chef/win32/api/file.rb index 7a8dafd8b5..da9713e119 100644 --- a/lib/chef/win32/api/file.rb +++ b/lib/chef/win32/api/file.rb @@ -459,11 +459,7 @@ BOOL WINAPI DeviceIoControl( # to be passed to the *W vesion of WinAPI File # functions def encode_path(path) - (path_prepender << path.gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR)).to_wstring - end - - def path_prepender - "\\\\?\\" + Chef::Util::PathHelper.canonical_path(path).to_wstring end # retrieves a file search handle and passes it diff --git a/pedant.gemfile b/pedant.gemfile index 7b135bdef4..d4ac849707 100644 --- a/pedant.gemfile +++ b/pedant.gemfile @@ -7,6 +7,9 @@ gem 'chef-pedant', :github => 'opscode/chef-pedant', :branch => "metadata-name-f # TODO figure out how to grab this stuff from the main Gemfile gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby" +gem "mixlib-shellout", github: "opscode/mixlib-shellout", branch: "master" +gem "ohai", github: "opscode/ohai", branch: "master" + group(:docgen) do gem "yard" end diff --git a/spec/functional/mixin/shell_out_spec.rb b/spec/functional/mixin/shell_out_spec.rb index 92492fcf6f..35e5b30eae 100644 --- a/spec/functional/mixin/shell_out_spec.rb +++ b/spec/functional/mixin/shell_out_spec.rb @@ -29,7 +29,7 @@ describe Chef::Mixin::ShellOut do shell_out_with_systems_locale('echo $LC_ALL') end - cmd.stdout.chomp.should match_environment_variable('LC_ALL') + expect(cmd.stdout.chomp).to match_environment_variable('LC_ALL') end end @@ -41,7 +41,7 @@ describe Chef::Mixin::ShellOut do shell_out_with_systems_locale('echo $LC_ALL', :environment => {'LC_ALL' => 'POSIX'}) end - cmd.stdout.chomp.should eq 'POSIX' + expect(cmd.stdout.chomp).to eq 'POSIX' end end end diff --git a/spec/functional/provider/whyrun_safe_ruby_block_spec.rb b/spec/functional/provider/whyrun_safe_ruby_block_spec.rb new file mode 100644 index 0000000000..150d46d384 --- /dev/null +++ b/spec/functional/provider/whyrun_safe_ruby_block_spec.rb @@ -0,0 +1,51 @@ +# +# 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' + +describe Chef::Resource::WhyrunSafeRubyBlock do + let(:node) { Chef::Node.new } + + let(:run_context) { + events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, events) + } + + before do + $evil_global_evil_laugh = :wahwah + Chef::Config[:why_run] = true + end + + after do + Chef::Config[:why_run] = false + end + + describe "when testing the resource" do + let(:new_resource) do + r = Chef::Resource::WhyrunSafeRubyBlock.new("reload all", run_context) + r.block { $evil_global_evil_laugh = :mwahahaha } + r + end + + it "updates the evil laugh, even in why-run mode" do + new_resource.run_action(new_resource.action) + $evil_global_evil_laugh.should == :mwahahaha + new_resource.should be_updated + end + end +end diff --git a/spec/functional/rebooter_spec.rb b/spec/functional/rebooter_spec.rb new file mode 100644 index 0000000000..8006580d5c --- /dev/null +++ b/spec/functional/rebooter_spec.rb @@ -0,0 +1,105 @@ +# +# Author:: Chris Doherty <cdoherty@getchef.com>) +# Copyright:: Copyright (c) 2014 Chef, 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::Platform::Rebooter do + + let(:reboot_info) do + { + :delay_mins => 5, + :requested_by => "reboot resource functional test", + :reason => "rebooter spec test" + } + end + + def create_resource + resource = Chef::Resource::Reboot.new(expected[:requested_by], run_context) + resource.delay_mins(expected[:delay_mins]) + resource.reason(expected[:reason]) + resource + end + + let(:run_context) do + node = Chef::Node.new + events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, events) + end + + let(:expected) do + { + :windows => 'shutdown /r /t 5 /c "rebooter spec test"', + :linux => 'shutdown -r +5 "rebooter spec test"' + } + end + + let(:rebooter) { Chef::Platform::Rebooter } + + describe '#reboot_if_needed!' do + + it 'should not call #shell_out! when reboot has not been requested' do + expect(rebooter).to receive(:shell_out!).exactly(0).times + expect(rebooter).to receive(:reboot_if_needed!).once.and_call_original + rebooter.reboot_if_needed!(run_context.node) + end + + describe 'calling #shell_out! to reboot' do + + before(:each) do + run_context.request_reboot(reboot_info) + end + + after(:each) do + run_context.cancel_reboot + end + + shared_context 'test a reboot method' do + def test_rebooter_method(method_sym, is_windows, expected_reboot_str) + Chef::Platform.stub(:windows?).and_return(is_windows) + expect(rebooter).to receive(:shell_out!).once.with(expected_reboot_str) + expect(rebooter).to receive(method_sym).once.and_call_original + rebooter.send(method_sym, run_context.node) + end + end + + describe 'when using #reboot_if_needed!' do + include_context 'test a reboot method' + + it 'should produce the correct string on Windows' do + test_rebooter_method(:reboot_if_needed!, true, expected[:windows]) + end + + it 'should produce the correct (Linux-specific) string on non-Windows' do + test_rebooter_method(:reboot_if_needed!, false, expected[:linux]) + end + end + + describe 'when using #reboot!' do + include_context 'test a reboot method' + + it 'should produce the correct string on Windows' do + test_rebooter_method(:reboot!, true, expected[:windows]) + end + + it 'should produce the correct (Linux-specific) string on non-Windows' do + test_rebooter_method(:reboot!, false, expected[:linux]) + end + end + end + end +end diff --git a/spec/functional/resource/deploy_revision_spec.rb b/spec/functional/resource/deploy_revision_spec.rb index 9ff1391e35..eae422ac1d 100644 --- a/spec/functional/resource/deploy_revision_spec.rb +++ b/spec/functional/resource/deploy_revision_spec.rb @@ -59,7 +59,6 @@ describe Chef::Resource::DeployRevision, :unix_only => true do let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) } - # These tests use git's bundle feature, which is a way to export an entire # git repo (or subset of commits) as a single file. # @@ -799,7 +798,6 @@ describe Chef::Resource::DeployRevision, :unix_only => true do deploy_to_latest_with_callback_tracking.run_action(:deploy) end - let(:deploy_that_fails) do resource = deploy_to_latest_rev.dup errant_callback = lambda {|x| raise Exception, "I am a failed deploy" } @@ -882,5 +880,3 @@ describe Chef::Resource::DeployRevision, :unix_only => true do end end - - diff --git a/spec/functional/resource/dsc_script_spec.rb b/spec/functional/resource/dsc_script_spec.rb new file mode 100644 index 0000000000..fa13296c02 --- /dev/null +++ b/spec/functional/resource/dsc_script_spec.rb @@ -0,0 +1,337 @@ +# +# Author:: Adam Edwards (<adamed@getchef.com>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' +require 'chef/mixin/shell_out' +require 'chef/mixin/windows_architecture_helper' + +describe Chef::Resource::DscScript, :windows_powershell_dsc_only do + include Chef::Mixin::WindowsArchitectureHelper + before(:all) do + @temp_dir = ::Dir.mktmpdir("dsc-functional-test") + end + + after(:all) do + ::FileUtils.rm_rf(@temp_dir) if ::Dir.exist?(@temp_dir) + end + + include Chef::Mixin::ShellOut + + def create_config_script_from_code(code, configuration_name, data = false) + script_code = data ? code : "Configuration '#{configuration_name}'\n{\n\t#{code}\n}\n" + data_suffix = data ? '_config_data' : '' + extension = data ? 'psd1' : 'ps1' + script_path = "#{@temp_dir}/dsc_functional_test#{data_suffix}.#{extension}" + ::File.open(script_path, 'wt') do | script | + script.write(script_code) + end + script_path + end + + def user_exists?(target_user) + result = false + begin + shell_out!("net user #{target_user}") + result = true + rescue Mixlib::ShellOut::ShellCommandFailed + end + result + end + + def delete_user(target_user) + begin + shell_out!("net user #{target_user} /delete") + rescue Mixlib::ShellOut::ShellCommandFailed + end + end + + let(:dsc_env_variable) { 'chefenvtest' } + let(:dsc_env_value1) { 'value1' } + let(:env_value2) { 'value2' } + let(:dsc_test_run_context) { + node = Chef::Node.new + node.automatic['platform'] = 'windows' + node.automatic['platform_version'] = '6.1' + node.automatic['kernel'][:machine] = + is_i386_process_on_x86_64_windows? ? :x86_64 : :i386 + node.automatic[:languages][:powershell][:version] = '4.0' + empty_events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, empty_events) + } + let(:dsc_test_resource_name) { 'DSCTest' } + let(:dsc_test_resource_base) { + Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) + } + let(:test_registry_key) { 'HKEY_LOCAL_MACHINE\Software\Chef\Spec\Functional\Resource\dsc_script_spec' } + let(:test_registry_value) { 'Registration' } + let(:test_registry_data1) { 'LL927' } + let(:test_registry_data2) { 'LL928' } + let(:dsc_code) { <<-EOH + Registry "ChefRegKey" + { + Key = '#{test_registry_key}' + ValueName = '#{test_registry_value}' + ValueData = '#{test_registry_data}' + Ensure = 'Present' + } +EOH + } + + let(:dsc_user_prefix) { 'dsc' } + let(:dsc_user_suffix) { 'chefx' } + let(:dsc_user) {"#{dsc_user_prefix}_usr_#{dsc_user_suffix}" } + let(:dsc_user_prefix_env_var_name) { 'dsc_user_env_prefix' } + let(:dsc_user_suffix_env_var_name) { 'dsc_user_env_suffix' } + let(:dsc_user_prefix_env_code) { "$env:#{dsc_user_prefix_env_var_name}"} + let(:dsc_user_suffix_env_code) { "$env:#{dsc_user_suffix_env_var_name}"} + let(:dsc_user_prefix_param_name) { 'dsc_user_prefix_param' } + let(:dsc_user_suffix_param_name) { 'dsc_user_suffix_param' } + let(:dsc_user_prefix_param_code) { "$#{dsc_user_prefix_param_name}"} + let(:dsc_user_suffix_param_code) { "$#{dsc_user_suffix_param_name}"} + let(:dsc_user_env_code) { "\"$(#{dsc_user_prefix_env_code})_usr_$(#{dsc_user_suffix_env_code})\""} + let(:dsc_user_param_code) { "\"$(#{dsc_user_prefix_param_code})_usr_$(#{dsc_user_suffix_param_code})\""} + + let(:config_flags) { nil } + let(:config_params) { <<-EOH + + [CmdletBinding()] + param + ( + $#{dsc_user_prefix_param_name}, + $#{dsc_user_suffix_param_name} + ) +EOH + } + + let(:config_param_section) { '' } + let(:dsc_user_code) { "'#{dsc_user}'" } + let(:dsc_user_prefix_code) { dsc_user_prefix } + let(:dsc_user_suffix_code) { dsc_user_suffix } + let(:dsc_script_environment_attribute) { nil } + let(:dsc_user_resources_code) { <<-EOH + #{config_param_section} +node localhost +{ +$testuser = #{dsc_user_code} +$testpassword = ConvertTo-SecureString -String "jf9a8m49jrajf4#" -AsPlainText -Force +$testcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $testuser, $testpassword + +User dsctestusercreate +{ + UserName = $testuser + Password = $testcred + Description = "DSC test user" + Ensure = "Present" + Disabled = $false + PasswordNeverExpires = $true + PasswordChangeRequired = $false +} +} +EOH + } + + let(:dsc_user_config_data) { +<<-EOH +@{ + AllNodes = @( + @{ + NodeName = "localhost"; + PSDscAllowPlainTextPassword = $true + } + ) +} + +EOH + } + + let(:dsc_environment_env_var_name) { 'dsc_test_cwd' } + let(:dsc_environment_no_fail_not_etc_directory) { "#{ENV['systemroot']}\\system32" } + let(:dsc_environment_fail_etc_directory) { "#{ENV['systemroot']}\\system32\\drivers\\etc" } + let(:exception_message_signature) { 'LL927-LL928' } + let(:dsc_environment_config) {<<-EOH +if (($pwd.path -eq '#{dsc_environment_fail_etc_directory}') -and (test-path('#{dsc_environment_fail_etc_directory}'))) +{ + throw 'Signature #{exception_message_signature}: Purposefully failing because cwd == #{dsc_environment_fail_etc_directory}' +} +environment "whatsmydir" +{ + Name = '#{dsc_environment_env_var_name}' + Value = $pwd.path + Ensure = 'Present' +} +EOH + } + + let(:dsc_config_name) { + dsc_test_resource_base.name + } + let(:dsc_resource_from_code) { + dsc_test_resource_base.code(dsc_code) + dsc_test_resource_base + } + let(:config_name_value) { dsc_test_resource_base.name } + + let(:dsc_resource_from_path) { + dsc_test_resource_base.command(create_config_script_from_code(dsc_code, config_name_value)) + dsc_test_resource_base + } + + 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 + + shared_examples_for 'a dsc_script resource with specified PowerShell configuration code' do + let(:test_registry_data) { test_registry_data1 } + it 'should create a registry key with a specific registry value and data' do + 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 + + it_should_behave_like 'a dsc_script resource with configuration affected by cwd' + end + + shared_examples_for 'a dsc_script resource with configuration affected by cwd' do + after(:each) do + removal_resource = Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) + removal_resource.code <<-EOH +environment 'removethis' +{ + Name = '#{dsc_environment_env_var_name}' + Ensure = 'Absent' +} +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) + 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' + 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 + + 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 + end + + shared_examples_for 'a dsc_script with configuration data set via an attribute' do + it 'should run a configuration script that creates a user' do + config_data_value = dsc_user_config_data + dsc_test_resource.configuration_name(config_name_value) + if configuration_data_attribute == 'configuration_data_script' + config_data_value = create_config_script_from_code(dsc_user_config_data, '', true) + end + dsc_test_resource.environment({dsc_user_prefix_env_var_name => dsc_user_prefix, + dsc_user_suffix_env_var_name => dsc_user_suffix}) + dsc_test_resource.send(configuration_data_attribute, config_data_value) + dsc_test_resource.flags(config_flags) + expect(user_exists?(dsc_user)).to eq(false) + expect {dsc_test_resource.run_action(:run)}.not_to raise_error + expect(user_exists?(dsc_user)).to eq(true) + end + 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) + end + it_behaves_like 'a dsc_script with configuration data' + end + + 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 } + + 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) + end + it_behaves_like 'a dsc_script with configuration data' + end + end + + context 'when supplying configuration through the configuration attribute' do + let(:dsc_test_resource) { dsc_resource_from_code } + it_behaves_like 'a dsc_script resource with specified PowerShell configuration code' + end + + context 'when supplying configuration using the path attribute' do + let(:dsc_test_resource) { dsc_resource_from_path } + it_behaves_like 'a dsc_script resource with specified PowerShell configuration code' + end + + context 'when running a configuration that manages users' do + before(:each) do + delete_user(dsc_user) + end + + let(:dsc_code) { dsc_user_resources_code } + let(:config_name_value) { 'DSCTestConfig' } + let(:dsc_test_resource) { dsc_resource_from_path } + + 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' + end +end diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb index d6f56db3e9..99966f85c8 100644 --- a/spec/functional/resource/file_spec.rb +++ b/spec/functional/resource/file_spec.rb @@ -24,12 +24,18 @@ describe Chef::Resource::File do let(:file_base) { "file_spec" } let(:expected_content) { "Don't fear the ruby." } - def create_resource + def create_resource(opts={}) events = Chef::EventDispatch::Dispatcher.new node = Chef::Node.new run_context = Chef::RunContext.new(node, {}, events) - resource = Chef::Resource::File.new(path, run_context) - resource + + use_path = if opts[:use_relative_path] + File.basename(path) + else + path + end + + Chef::Resource::File.new(use_path, run_context) end let(:resource) do @@ -42,6 +48,10 @@ describe Chef::Resource::File do create_resource end + let(:resource_with_relative_path) do + create_resource(:use_relative_path => true) + end + let(:unmanaged_content) do "This is file content that is not managed by chef" end @@ -74,6 +84,19 @@ describe Chef::Resource::File do end end + # github issue 1842. + describe "when running action :create on a relative path" do + before do + resource_with_relative_path.run_action(:create) + end + + context "and the file exists" do + it "should run without an exception" do + resource_with_relative_path.run_action(:create) + end + end + end + describe "when running action :touch" do context "and the target file does not exist" do before do diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb index 8e630d84f2..2220e973cf 100644 --- a/spec/functional/resource/link_spec.rb +++ b/spec/functional/resource/link_spec.rb @@ -72,8 +72,8 @@ describe Chef::Resource::Link do end end - def canonicalize(path) - windows? ? path.gsub('/', '\\') : path + def paths_eql?(path1, path2) + Chef::Util::PathHelper.paths_eql?(path1, path2) end def symlink(a, b) @@ -180,7 +180,7 @@ describe Chef::Resource::Link do it 'links to the target file' do symlink?(target_file).should be_true - readlink(target_file).should == canonicalize(to) + paths_eql?(readlink(target_file), to).should be_true end it 'marks the resource updated' do resource.should be_updated @@ -201,7 +201,7 @@ describe Chef::Resource::Link do it 'leaves the file linked' do symlink?(target_file).should be_true - readlink(target_file).should == canonicalize(to) + paths_eql?(readlink(target_file), to).should be_true end it 'does not mark the resource updated' do resource.should_not be_updated @@ -279,7 +279,7 @@ describe Chef::Resource::Link do before(:each) do symlink(to, target_file) symlink?(target_file).should be_true - readlink(target_file).should == canonicalize(to) + paths_eql?(readlink(target_file), to).should be_true end include_context 'create symbolic link is noop' include_context 'delete succeeds' @@ -294,7 +294,7 @@ describe Chef::Resource::Link do File.open(@other_target, 'w') { |file| file.write('eek') } symlink(@other_target, target_file) symlink?(target_file).should be_true - readlink(target_file).should == canonicalize(@other_target) + paths_eql?(readlink(target_file), @other_target).should be_true end after(:each) do File.delete(@other_target) @@ -311,7 +311,7 @@ describe Chef::Resource::Link do nonexistent = File.join(test_file_dir, make_tmpname('nonexistent_spec')) symlink(nonexistent, target_file) symlink?(target_file).should be_true - readlink(target_file).should == canonicalize(nonexistent) + paths_eql?(readlink(target_file), nonexistent).should be_true end include_context 'create symbolic link succeeds' include_context 'delete succeeds' @@ -393,7 +393,7 @@ describe Chef::Resource::Link do File.open(@other_target, "w") { |file| file.write("eek") } symlink(@other_target, to) symlink?(to).should be_true - readlink(to).should == canonicalize(@other_target) + paths_eql?(readlink(to), @other_target).should be_true end after(:each) do File.delete(@other_target) @@ -408,7 +408,7 @@ describe Chef::Resource::Link do @other_target = File.join(test_file_dir, make_tmpname("other_spec")) symlink(@other_target, to) symlink?(to).should be_true - readlink(to).should == canonicalize(@other_target) + paths_eql?(readlink(to), @other_target).should be_true end context 'and the link does not yet exist' do include_context 'create symbolic link succeeds' @@ -441,7 +441,7 @@ describe Chef::Resource::Link do before(:each) do symlink(to, target_file) symlink?(target_file).should be_true - readlink(target_file).should == canonicalize(to) + paths_eql?(readlink(target_file), to).should be_true end include_context 'create symbolic link is noop' include_context 'delete succeeds' @@ -450,7 +450,7 @@ describe Chef::Resource::Link do before(:each) do symlink(absolute_to, target_file) symlink?(target_file).should be_true - readlink(target_file).should == canonicalize(absolute_to) + paths_eql?(readlink(target_file), absolute_to).should be_true end include_context 'create symbolic link succeeds' include_context 'delete succeeds' @@ -478,7 +478,7 @@ describe Chef::Resource::Link do before(:each) do symlink(to, target_file) symlink?(target_file).should be_true - readlink(target_file).should == canonicalize(to) + paths_eql?(readlink(target_file), to).should be_true end include_context 'create hard link succeeds' it_behaves_like 'delete errors out' @@ -552,7 +552,7 @@ describe Chef::Resource::Link do File.open(@other_target, "w") { |file| file.write("eek") } symlink(@other_target, to) symlink?(to).should be_true - readlink(to).should == canonicalize(@other_target) + paths_eql?(readlink(to), @other_target).should be_true end after(:each) do File.delete(@other_target) @@ -564,7 +564,7 @@ describe Chef::Resource::Link do # OS X gets angry about this sort of link. Bug in OS X, IMO. pending('OS X/FreeBSD/AIX symlink? and readlink working on hard links to symlinks', :if => (os_x? or freebsd? or aix?)) do symlink?(target_file).should be_true - readlink(target_file).should == canonicalize(@other_target) + paths_eql?(readlink(target_file), @other_target).should be_true end end include_context 'delete is noop' @@ -575,7 +575,7 @@ describe Chef::Resource::Link do @other_target = File.join(test_file_dir, make_tmpname("other_spec")) symlink(@other_target, to) symlink?(to).should be_true - readlink(to).should == canonicalize(@other_target) + paths_eql?(readlink(to), @other_target).should be_true end context 'and the link does not yet exist' do it 'links to the target file' do @@ -588,7 +588,7 @@ describe Chef::Resource::Link do File.exists?(target_file).should be_false end symlink?(target_file).should be_true - readlink(target_file).should == canonicalize(@other_target) + paths_eql?(readlink(target_file), @other_target).should be_true end end include_context 'delete is noop' diff --git a/spec/functional/resource/reboot_spec.rb b/spec/functional/resource/reboot_spec.rb new file mode 100644 index 0000000000..735ca994c8 --- /dev/null +++ b/spec/functional/resource/reboot_spec.rb @@ -0,0 +1,103 @@ +# +# Author:: Chris Doherty <cdoherty@getchef.com>) +# Copyright:: Copyright (c) 2014 Chef, 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::Reboot do + + let(:expected) do + { + :delay_mins => 5, + :requested_by => "reboot resource functional test", + :reason => "reboot resource spec test" + } + end + + def create_resource + node = Chef::Node.new + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, {}, events) + resource = Chef::Resource::Reboot.new(expected[:requested_by], run_context) + resource.delay_mins(expected[:delay_mins]) + resource.reason(expected[:reason]) + resource + end + + let(:resource) do + create_resource + end + + shared_context 'testing run context modification' do + def test_reboot_action(resource) + reboot_info = resource.run_context.reboot_info + expect(reboot_info.keys.sort).to eq([:delay_mins, :reason, :requested_by, :timestamp]) + expect(reboot_info[:delay_mins]).to eq(expected[:delay_mins]) + expect(reboot_info[:reason]).to eq(expected[:reason]) + expect(reboot_info[:requested_by]).to eq(expected[:requested_by]) + + expect(resource.run_context.reboot_requested?).to be_true + end + end + + # the currently defined behavior for multiple calls to this resource is "last one wins." + describe 'the request_reboot_on_successful_run action' do + include_context 'testing run context modification' + + before do + resource.run_action(:request_reboot) + end + + after do + resource.run_context.cancel_reboot + end + + it 'should have modified the run context correctly' do + test_reboot_action(resource) + end + end + + describe 'the reboot_interrupt_run action' do + include_context 'testing run context modification' + + after do + resource.run_context.cancel_reboot + end + + it 'should have modified the run context correctly' do + # this doesn't actually test the flow of Chef::Client#do_run, unfortunately. + expect { + resource.run_action(:reboot_now) + }.to throw_symbol(:end_client_run_early) + + test_reboot_action(resource) + end + end + + describe "the cancel action" do + before do + resource.run_context.request_reboot(expected) + resource.run_action(:cancel) + end + + it 'should have cleared the reboot request' do + # arguably we shouldn't be querying RunContext's internal data directly. + expect(resource.run_context.reboot_info).to eq({}) + expect(resource.run_context.reboot_requested?).to be_false + end + end +end diff --git a/spec/functional/util/powershell/cmdlet_spec.rb b/spec/functional/util/powershell/cmdlet_spec.rb new file mode 100644 index 0000000000..63d1ac09b5 --- /dev/null +++ b/spec/functional/util/powershell/cmdlet_spec.rb @@ -0,0 +1,114 @@ +# +# Author:: Adam Edwards (<adamed@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 'json' +require File.expand_path('../../../../spec_helper', __FILE__) + +describe Chef::Util::Powershell::Cmdlet, :windows_only do + before(:all) do + ohai = Ohai::System.new + ohai.load_plugins + ohai.run_plugins(true, ['platform', 'kernel']) + @node = Chef::Node.new + @node.consume_external_attrs(ohai.data, {}) + end + let(:cmd_output_format) { :text } + let(:simple_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, 'get-childitem', cmd_output_format, {:depth => 2}) } + let(:invalid_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, 'get-idontexist', cmd_output_format) } + let(:cmdlet_get_item_requires_switch_or_argument) { Chef::Util::Powershell::Cmdlet.new(@node, 'get-item', cmd_output_format, {:depth => 2}) } + let(:cmdlet_alias_requires_switch_or_argument) { Chef::Util::Powershell::Cmdlet.new(@node, 'alias', cmd_output_format, {:depth => 2}) } + let(:etc_directory) { "#{ENV['systemroot']}\\system32\\drivers\\etc" } + let(:architecture_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, "$env:PROCESSOR_ARCHITECTURE")} + + it "executes a simple process" do + result = simple_cmdlet.run + expect(result.succeeded?).to eq(true) + end + + it "#run does not raise a PowershellCmdletException exception if the command cannot be executed" do + expect {invalid_cmdlet.run}.not_to raise_error + end + + it "#run! raises a PowershellCmdletException exception if the command cannot be executed" do + expect {invalid_cmdlet.run!}.to raise_error(Chef::Exceptions::PowershellCmdletException) + end + + it "executes a 64-bit command on a 64-bit OS, 32-bit otherwise" do + os_arch = ENV['PROCESSOR_ARCHITEW6432'] + if os_arch.nil? + os_arch = ENV['PROCESSOR_ARCHITECTURE'] + end + + result = architecture_cmdlet.run + execution_arch = result.return_value + execution_arch.strip! + expect(execution_arch).to eq(os_arch) + end + + it "passes command line switches to the command" do + result = cmdlet_alias_requires_switch_or_argument.run({:name => 'ls'}) + expect(result.succeeded?).to eq(true) + end + + it "passes command line arguments to the command" do + result = cmdlet_alias_requires_switch_or_argument.run({},{},'ls') + expect(result.succeeded?).to eq(true) + end + + it "passes command line arguments and switches to the command" do + result = cmdlet_get_item_requires_switch_or_argument.run({:path => etc_directory},{},' | select-object -property fullname | format-table -hidetableheaders') + expect(result.succeeded?).to eq(true) + returned_directory = result.return_value + returned_directory.strip! + expect(returned_directory).to eq(etc_directory) + end + + it "passes execution options to the command" do + result = cmdlet_get_item_requires_switch_or_argument.run({},{:cwd => etc_directory},'. | select-object -property fullname | format-table -hidetableheaders') + expect(result.succeeded?).to eq(true) + returned_directory = result.return_value + returned_directory.strip! + expect(returned_directory).to eq(etc_directory) + end + + context "when returning json" do + let(:cmd_output_format) { :json } + it "returns json format data", :windows_powershell_dsc_only do + result = cmdlet_alias_requires_switch_or_argument.run({},{},'ls') + expect(result.succeeded?).to eq(true) + expect(lambda{JSON.parse(result.return_value)}).not_to raise_error + end + end + + context "when returning Ruby objects" do + let(:cmd_output_format) { :object } + it "returns object format data", :windows_powershell_dsc_only do + result = simple_cmdlet.run({},{:cwd => etc_directory}, 'hosts') + expect(result.succeeded?).to eq(true) + data = result.return_value + expect(data['Name']).to eq('hosts') + end + end + + context "when constructor is given invalid arguments" do + let(:cmd_output_format) { :invalid } + it "throws an exception if an invalid format is passed to the constructor" do + expect(lambda{simple_cmdlet}).to raise_error + end + end +end diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb index 8a1a65249b..0144ae0ce3 100644 --- a/spec/integration/client/client_spec.rb +++ b/spec/integration/client/client_spec.rb @@ -16,7 +16,7 @@ describe "chef-client" do # machine that has omnibus chef installed. In that case we need to ensure # we're running `chef-client` from the source tree and not the external one. # cf. CHEF-4914 - let(:chef_client) { "ruby #{chef_dir}/chef-client" } + let(:chef_client) { "ruby '#{chef_dir}/chef-client'" } when_the_repository "has a cookbook with a no-op recipe" do before { file 'cookbooks/x/recipes/default.rb', '' } diff --git a/spec/integration/client/ipv6_spec.rb b/spec/integration/client/ipv6_spec.rb index f49b7b7711..76dd1938f7 100644 --- a/spec/integration/client/ipv6_spec.rb +++ b/spec/integration/client/ipv6_spec.rb @@ -76,7 +76,7 @@ END_CLIENT_RB let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") } - let(:chef_client_cmd) { %Q[ruby #{chef_dir}/chef-client -c "#{path_to('config/client.rb')}" -lwarn] } + let(:chef_client_cmd) { %Q[ruby '#{chef_dir}/chef-client' -c "#{path_to('config/client.rb')}" -lwarn] } after do FileUtils.rm_rf(cache_path) diff --git a/spec/integration/knife/cookbook_api_ipv6_spec.rb b/spec/integration/knife/cookbook_api_ipv6_spec.rb index 4191bb1731..c5b5b81abe 100644 --- a/spec/integration/knife/cookbook_api_ipv6_spec.rb +++ b/spec/integration/knife/cookbook_api_ipv6_spec.rb @@ -62,7 +62,7 @@ END_VALIDATION_PEM end let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") } - let(:knife) { "ruby #{chef_dir}/knife" } + let(:knife) { "ruby '#{chef_dir}/knife'" } let(:knife_config_flag) { "-c '#{path_to("config/knife.rb")}'" } diff --git a/spec/integration/recipes/lwrp_inline_resources_spec.rb b/spec/integration/recipes/lwrp_inline_resources_spec.rb index 9e2cf3fc8d..a0c13da6f7 100644 --- a/spec/integration/recipes/lwrp_inline_resources_spec.rb +++ b/spec/integration/recipes/lwrp_inline_resources_spec.rb @@ -16,7 +16,7 @@ describe "LWRPs with inline resources" do # machine that has omnibus chef installed. In that case we need to ensure # we're running `chef-client` from the source tree and not the external one. # cf. CHEF-4914 - let(:chef_client) { "ruby #{chef_dir}/chef-client" } + let(:chef_client) { "ruby '#{chef_dir}/chef-client'" } when_the_repository "has a cookbook with a nested LWRP" do before do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7c11957997..ed0a8f89f6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -107,8 +107,11 @@ RSpec.configure do |config| config.filter_run_excluding :not_supported_on_win2k3 => true if windows_win2k3? config.filter_run_excluding :not_supported_on_solaris => true if solaris? config.filter_run_excluding :win2k3_only => true unless windows_win2k3? + config.filter_run_excluding :windows_2008r2_or_later => true unless windows_2008r2_or_later? config.filter_run_excluding :windows64_only => true unless windows64? config.filter_run_excluding :windows32_only => true unless windows32? + config.filter_run_excluding :windows_powershell_dsc_only => true unless windows_powershell_dsc? + config.filter_run_excluding :windows_powershell_no_dsc_only => true unless ! windows_powershell_dsc? config.filter_run_excluding :windows_domain_joined_only => true unless windows_domain_joined? config.filter_run_excluding :solaris_only => true unless solaris? config.filter_run_excluding :system_windows_service_gem_only => true unless system_windows_service_gem? diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb index a7c616d7a7..f8cad6de7f 100644 --- a/spec/support/platform_helpers.rb +++ b/spec/support/platform_helpers.rb @@ -52,6 +52,30 @@ def windows_win2k3? (host['version'] && host['version'].start_with?("5.2")) end +def windows_2008r2_or_later? + return false unless windows? + wmi = WmiLite::Wmi.new + host = wmi.first_of('Win32_OperatingSystem') + version = host['version'] + return false unless version + components = version.split('.').map do | component | + component.to_i + end + components.length >=2 && components[0] >= 6 && components[1] >= 1 +end + +def windows_powershell_dsc? + return false unless windows? + supports_dsc = false + begin + wmi = WmiLite::Wmi.new('root/microsoft/windows/desiredstateconfiguration') + lcm = wmi.query("SELECT * FROM meta_class WHERE __this ISA 'MSFT_DSCLocalConfigurationManager'") + supports_dsc = !! lcm + rescue WmiLite::WmiException + end + supports_dsc +end + def mac_osx_106? if File.exists? "/usr/bin/sw_vers" result = shell_out("/usr/bin/sw_vers") diff --git a/spec/support/shared/functional/file_resource.rb b/spec/support/shared/functional/file_resource.rb index 804830fcdc..72b72912bd 100644 --- a/spec/support/shared/functional/file_resource.rb +++ b/spec/support/shared/functional/file_resource.rb @@ -284,6 +284,7 @@ shared_examples_for "a file resource" do before do Chef::Config[:why_run] = true + Chef::Config[:ssl_verify_mode] = :verify_none end after do @@ -333,6 +334,10 @@ shared_examples_for "file resource not pointing to a real file" do !symlink?(file_path) && File.file?(file_path) end + before do + Chef::Config[:ssl_verify_mode] = :verify_none + end + describe "when force_unlink is set to true" do it ":create unlinks the target" do real_file?(path).should be_false @@ -363,6 +368,7 @@ shared_examples_for "a configured file resource" do before do Chef::Log.level = :info + Chef::Config[:ssl_verify_mode] = :verify_none end # note the stripping of the drive letter from the tmpdir on windows diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb index 465633b9e0..b42f7f69d9 100644 --- a/spec/support/shared/integration/integration_helper.rb +++ b/spec/support/shared/integration/integration_helper.rb @@ -118,7 +118,10 @@ module IntegrationSupport Chef::Config.delete("#{object_name}_path".to_sym) end Chef::Config.delete(:chef_repo_path) - FileUtils.remove_entry_secure(@repository_dir) + # TODO: "force" actually means "silence all exceptions". this + # silences a weird permissions error on Windows that we should track + # down, but for now there's no reason for it to blow up our CI. + FileUtils.remove_entry_secure(@repository_dir, force=Chef::Platform.windows?) ensure @repository_dir = nil end diff --git a/spec/unit/application/apply.rb b/spec/unit/application/apply.rb index 32c98c6ed6..62a53c2a31 100644 --- a/spec/unit/application/apply.rb +++ b/spec/unit/application/apply.rb @@ -20,7 +20,7 @@ require 'spec_helper' describe Chef::Application::Apply do before do - @app = Chef::Application::Recipe.new + @app = Chef::Application::Apply.new @app.stub(:configure_logging).and_return(true) @recipe_text = "package 'nyancat'" Chef::Config[:solo] = true @@ -73,4 +73,14 @@ describe Chef::Application::Apply do @recipe_fh.path.should == @app.instance_variable_get(:@recipe_filename) end end + describe "recipe_file_arg" do + before do + ARGV.clear + end + it "should exit and log message" do + Chef::Log.should_receive(:debug).with(/^No recipe file provided/) + lambda { @app.run }.should raise_error(SystemExit) { |e| e.status.should == 1 } + end + + end end diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb index af71c43b77..41411669e6 100644 --- a/spec/unit/config_spec.rb +++ b/spec/unit/config_spec.rb @@ -242,8 +242,8 @@ describe Chef::Config do Chef::Config[:file_backup_path].should == backup_path end - it "Chef::Config[:ssl_verify_mode] defaults to :verify_none" do - Chef::Config[:ssl_verify_mode].should == :verify_none + it "Chef::Config[:ssl_verify_mode] defaults to :verify_peer" do + Chef::Config[:ssl_verify_mode].should == :verify_peer end it "Chef::Config[:ssl_ca_path] defaults to nil" do diff --git a/spec/unit/dsl/reboot_pending_spec.rb b/spec/unit/dsl/reboot_pending_spec.rb index 8576ae168a..0d643514e0 100644 --- a/spec/unit/dsl/reboot_pending_spec.rb +++ b/spec/unit/dsl/reboot_pending_spec.rb @@ -21,7 +21,7 @@ require "spec_helper" describe Chef::DSL::RebootPending do describe "reboot_pending?" do - describe "in isoloation" do + describe "in isolation" do let(:recipe) { Object.new.extend(Chef::DSL::RebootPending) } before do @@ -74,12 +74,6 @@ describe Chef::DSL::RebootPending do end end - context "platform is not supported" do - it 'should raise an exception' do - recipe.stub_chain(:node, :[]).with(:platform).and_return('msdos') - expect { recipe.reboot_pending? }.to raise_error(Chef::Exceptions::UnsupportedPlatform) - end - end end # describe in isolation describe "in a recipe" do diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb index 78be9632f6..a1c5d168dd 100644 --- a/spec/unit/knife/bootstrap_spec.rb +++ b/spec/unit/knife/bootstrap_spec.rb @@ -355,6 +355,44 @@ describe Chef::Knife::Bootstrap do end end + describe "when transferring trusted certificates" do + let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA, 'trusted_certs') } + + let(:rendered_template) do + knife.merge_configs + knife.render_template + end + + before do + Chef::Config[:trusted_certs_dir] = trusted_certs_dir + IO.stub(:read).and_call_original + IO.stub(:read).with(File.expand_path(Chef::Config[:validation_key])).and_return("") + end + + def certificates + Dir[File.join(trusted_certs_dir, "*.{crt,pem}")] + end + + it "creates /etc/chef/trusted_certs" do + rendered_template.should match(%r{mkdir -p /etc/chef/trusted_certs}) + end + + it "copies the certificates in the directory" do + certificates.each do |cert| + IO.should_receive(:read).with(File.expand_path(cert)) + end + + certificates.each do |cert| + rendered_template.should match(%r{cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'}) + end + end + + it "doesn't create /etc/chef/trusted_certs if :trusted_certs_dir is empty" do + Dir.should_receive(:glob).with(File.join(trusted_certs_dir, "*.{crt,pem}")).and_return([]) + rendered_template.should_not match(%r{mkdir -p /etc/chef/trusted_certs}) + end + end + describe "when configuring the underlying knife ssh command" do context "from the command line" do let(:knife_ssh) do diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb index 064f8c5621..6427071a6b 100644 --- a/spec/unit/knife/core/bootstrap_context_spec.rb +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -119,7 +119,20 @@ EXPECTED context "via config[:secret_file]" do let(:chef_config) do { - :knife => {:secret_file => secret_file} + :knife => {:secret_file => secret_file} + } + end + it "reads the encrypted_data_bag_secret" do + bootstrap_context.encrypted_data_bag_secret.should eq IO.read(secret_file) + end + end + + context "via config[:secret_file] with short home path" do + let(:chef_config) do + home_path = File.expand_path("~") + shorted_secret_file_path = secret_file.gsub(home_path, "~") + { + :knife => {:secret_file => shorted_secret_file_path} } end it "reads the encrypted_data_bag_secret" do diff --git a/spec/unit/mixin/homebrew_owner_spec.rb b/spec/unit/mixin/homebrew_owner_spec.rb new file mode 100644 index 0000000000..428cd827d9 --- /dev/null +++ b/spec/unit/mixin/homebrew_owner_spec.rb @@ -0,0 +1,65 @@ +# +# Author:: Joshua Timberman (<joshua@getchef.com>) +# +# Copyright 2014, Chef Software, Inc <legal@getchef.com> +# +# 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/homebrew_owner' + +class ExampleHomebrewOwner + include Chef::Mixin::HomebrewOwner +end + +describe Chef::Mixin::HomebrewOwner do + before(:each) do + node.default['homebrew']['owner'] = nil + end + + let(:homebrew_owner) { ExampleHomebrewOwner.new } + let(:node) { Chef::Node.new } + + describe 'when the homebrew owner node attribute is set' do + it 'raises an exception if the owner is root' do + node.default['homebrew']['owner'] = 'root' + expect { homebrew_owner.homebrew_owner(node) }.to raise_exception(Chef::Exceptions::CannotDetermineHomebrewOwner) + end + + it 'returns the owner set by attribute' do + node.default['homebrew']['owner'] = 'siouxsie' + expect(homebrew_owner.homebrew_owner(node)).to eql('siouxsie') + end + end + + describe 'when the owner attribute is not set and we use sudo' do + before(:each) do + ENV.stub(:[]).with('SUDO_USER').and_return('john_lydon') + end + + it 'uses the SUDO_USER environment variable' do + expect(homebrew_owner.homebrew_owner(node)).to eql('john_lydon') + end + end + + describe 'when the owner attribute is not set and we arent using sudo' do + before(:each) do + ENV.stub(:[]).with('USER').and_return('sid_vicious') + ENV.stub(:[]).with('SUDO_USER').and_return(nil) + end + + it 'uses the current user' do + expect(homebrew_owner.homebrew_owner(node)).to eql('sid_vicious') + end + end +end diff --git a/spec/unit/mixin/shell_out_spec.rb b/spec/unit/mixin/shell_out_spec.rb index 1f85ec6bf1..38a63a32ee 100644 --- a/spec/unit/mixin/shell_out_spec.rb +++ b/spec/unit/mixin/shell_out_spec.rb @@ -32,7 +32,7 @@ describe Chef::Mixin::ShellOut do let(:output) { StringIO.new } let!(:capture_log_output) { Chef::Log.logger = Logger.new(output) } - let(:assume_deprecation_log_level) { Chef::Log.stub(:level).and_return(:warn) } + let(:assume_deprecation_log_level) { allow(Chef::Log).to receive(:level).and_return(:warn) } context 'without options' do let(:command_args) { [ cmd ] } @@ -55,9 +55,9 @@ describe Chef::Mixin::ShellOut do it 'should emit a deprecation warning' do assume_deprecation_log_level and capture_log_output subject - output.string.should match /DEPRECATION:/ - output.string.should match Regexp.escape(old_option.to_s) - output.string.should match Regexp.escape(new_option.to_s) + expect(output.string).to match /DEPRECATION:/ + expect(output.string).to match Regexp.escape(old_option.to_s) + expect(output.string).to match Regexp.escape(new_option.to_s) end end @@ -106,7 +106,7 @@ describe Chef::Mixin::ShellOut do end end - describe "#shell_out_with_systems_locale" do + context "when testing individual methods" do before(:each) do @original_env = ENV.to_hash ENV.clear @@ -120,82 +120,152 @@ describe Chef::Mixin::ShellOut do let(:shell_out) { Chef::Mixin::ShellOut } let(:cmd) { "echo '#{rand(1000)}'" } - describe "when the last argument is a Hash" do - describe "and environment is an option" do - it "should not change environment['LC_ALL'] when set to nil" do - options = { :environment => { 'LC_ALL' => nil } } - shell_out.should_receive(:shell_out).with(cmd, options).and_return(true) - shell_out.shell_out_with_systems_locale(cmd, options) + describe "#shell_out" do + + describe "when the last argument is a Hash" do + describe "and environment is an option" do + it "should not change environment['LC_ALL'] when set to nil" do + options = { :environment => { 'LC_ALL' => nil } } + expect(shell_out).to receive(:shell_out_command).with(cmd, options).and_return(true) + shell_out.shell_out(cmd, options) + end + + it "should not change environment['LC_ALL'] when set to non-nil" do + options = { :environment => { 'LC_ALL' => 'en_US.UTF-8' } } + expect(shell_out).to receive(:shell_out_command).with(cmd, options).and_return(true) + shell_out.shell_out(cmd, options) + end + + it "should set environment['LC_ALL'] to 'en_US.UTF-8' when 'LC_ALL' not present" do + options = { :environment => { 'HOME' => '/Users/morty' } } + expect(shell_out).to receive(:shell_out_command).with(cmd, { + :environment => { 'HOME' => '/Users/morty', 'LC_ALL' => Chef::Config[:internal_locale] }, + }).and_return(true) + shell_out.shell_out(cmd, options) + end + + it "should not mutate the options hash when it adds LC_ALL" do + options = { :environment => { 'HOME' => '/Users/morty' } } + expect(shell_out).to receive(:shell_out_command).with(cmd, { + :environment => { 'HOME' => '/Users/morty', 'LC_ALL' => Chef::Config[:internal_locale] }, + }).and_return(true) + shell_out.shell_out(cmd, options) + expect(options[:environment].has_key?('LC_ALL')).to be false + end end - it "should not change environment['LC_ALL'] when set to non-nil" do - options = { :environment => { 'LC_ALL' => 'en_US.UTF-8' } } - shell_out.should_receive(:shell_out).with(cmd, options).and_return(true) - shell_out.shell_out_with_systems_locale(cmd, options) + describe "and env is an option" do + it "should not change env when set to nil" do + options = { :env => { 'LC_ALL' => nil } } + expect(shell_out).to receive(:shell_out_command).with(cmd, options).and_return(true) + shell_out.shell_out(cmd, options) + end + + it "should not change env when set to non-nil" do + options = { :env => { 'LC_ALL' => 'de_DE.UTF-8'}} + expect(shell_out).to receive(:shell_out_command).with(cmd, options).and_return(true) + shell_out.shell_out(cmd, options) + end + + it "should set env['LC_ALL'] to 'en_US.UTF-8' when 'LC_ALL' not present" do + options = { :env => { 'HOME' => '/Users/morty' } } + expect(shell_out).to receive(:shell_out_command).with(cmd, { + :env => { 'HOME' => '/Users/morty', 'LC_ALL' => Chef::Config[:internal_locale] }, + }).and_return(true) + shell_out.shell_out(cmd, options) + end + + it "should not mutate the options hash when it adds LC_ALL" do + options = { :env => { 'HOME' => '/Users/morty' } } + expect(shell_out).to receive(:shell_out_command).with(cmd, { + :env => { 'HOME' => '/Users/morty', 'LC_ALL' => Chef::Config[:internal_locale] }, + }).and_return(true) + shell_out.shell_out(cmd, options) + expect(options[:env].has_key?('LC_ALL')).to be false + end end - it "should set environment['LC_ALL'] to nil when 'LC_ALL' not present" do - options = { :environment => { 'HOME' => '/Users/morty' } } - shell_out.should_receive(:shell_out).with( - cmd, - { :environment => { - 'HOME' => '/Users/morty', - 'LC_ALL' => nil } - } - ).and_return(true) - shell_out.shell_out_with_systems_locale(cmd, options) + describe "and no env/environment option is present" do + it "should add environment option and set environment['LC_ALL'] to 'en_US.UTF_8'" do + options = { :user => 'morty' } + expect(shell_out).to receive(:shell_out_command).with(cmd, { + :user => 'morty', :environment => { 'LC_ALL' => Chef::Config[:internal_locale] }, + }).and_return(true) + shell_out.shell_out(cmd, options) + end end end - describe "and env is an option" do - it "should not change env when set to nil" do - options = { :env => { 'LC_ALL' => nil } } - shell_out.should_receive(:shell_out).with(cmd, options).and_return(true) - shell_out.shell_out_with_systems_locale(cmd, options) + describe "when the last argument is not a Hash" do + it "should add environment options and set environment['LC_ALL'] to 'en_US.UTF-8'" do + expect(shell_out).to receive(:shell_out_command).with(cmd, { + :environment => { 'LC_ALL' => Chef::Config[:internal_locale] }, + }).and_return(true) + shell_out.shell_out(cmd) end + end + + end - it "should not change env when set to non-nil" do - options = { :env => { 'LC_ALL' => 'en_US.UTF-8'}} - shell_out.should_receive(:shell_out).with(cmd, options).and_return(true) - shell_out.shell_out_with_systems_locale(cmd, options) + describe "#shell_out_with_systems_locale" do + + describe "when the last argument is a Hash" do + describe "and environment is an option" do + it "should not change environment['LC_ALL'] when set to nil" do + options = { :environment => { 'LC_ALL' => nil } } + expect(shell_out).to receive(:shell_out_command).with(cmd, options).and_return(true) + shell_out.shell_out_with_systems_locale(cmd, options) + end + + it "should not change environment['LC_ALL'] when set to non-nil" do + options = { :environment => { 'LC_ALL' => 'en_US.UTF-8' } } + expect(shell_out).to receive(:shell_out_command).with(cmd, options).and_return(true) + shell_out.shell_out_with_systems_locale(cmd, options) + end + + it "should no longer set environment['LC_ALL'] to nil when 'LC_ALL' not present" do + options = { :environment => { 'HOME' => '/Users/morty' } } + expect(shell_out).to receive(:shell_out_command).with(cmd, options).and_return(true) + shell_out.shell_out_with_systems_locale(cmd, options) + end end - it "should set env['LC_ALL'] to nil when 'LC_ALL' not present" do - options = { :env => { 'HOME' => '/Users/morty' } } - shell_out.should_receive(:shell_out).with( - cmd, - { :env => { - 'HOME' => '/Users/morty', - 'LC_ALL' => nil } - } - ).and_return(true) - shell_out.shell_out_with_systems_locale(cmd, options) + describe "and env is an option" do + it "should not change env when set to nil" do + options = { :env => { 'LC_ALL' => nil } } + expect(shell_out).to receive(:shell_out_command).with(cmd, options).and_return(true) + shell_out.shell_out_with_systems_locale(cmd, options) + end + + it "should not change env when set to non-nil" do + options = { :env => { 'LC_ALL' => 'en_US.UTF-8'}} + expect(shell_out).to receive(:shell_out_command).with(cmd, options).and_return(true) + shell_out.shell_out_with_systems_locale(cmd, options) + end + + it "should no longer set env['LC_ALL'] to nil when 'LC_ALL' not present" do + options = { :env => { 'HOME' => '/Users/morty' } } + expect(shell_out).to receive(:shell_out_command).with(cmd, options).and_return(true) + shell_out.shell_out_with_systems_locale(cmd, options) + end end - end - describe "and no env/environment option is present" do - it "should add environment option and set environment['LC_ALL'] to nil" do - options = { :user => 'morty' } - shell_out.should_receive(:shell_out).with( - cmd, - { :environment => { 'LC_ALL' => nil }, - :user => 'morty' - } - ).and_return(true) - shell_out.shell_out_with_systems_locale(cmd, options) + describe "and no env/environment option is present" do + it "should no longer add environment option and set environment['LC_ALL'] to nil" do + options = { :user => 'morty' } + expect(shell_out).to receive(:shell_out_command).with(cmd, options).and_return(true) + shell_out.shell_out_with_systems_locale(cmd, options) + end end end - end - describe "when the last argument is not a Hash" do - it "should add environment options and set environment['LC_ALL'] to nil" do - shell_out.should_receive(:shell_out).with( - cmd, - { :environment => { 'LC_ALL' => nil } } - ).and_return(true) - shell_out.shell_out_with_systems_locale(cmd) + describe "when the last argument is not a Hash" do + it "should no longer add environment options and set environment['LC_ALL'] to nil" do + expect(shell_out).to receive(:shell_out_command).with(cmd).and_return(true) + shell_out.shell_out_with_systems_locale(cmd) + end end end - end + end end diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index 2414bdf552..6adea5eecf 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -30,3 +30,26 @@ describe "Chef::Platform#windows_server_2003?" do expect { Thread.fork { Chef::Platform.windows_server_2003? }.join }.not_to raise_error end end + +describe 'Chef::Platform#supports_dsc?' do + it 'returns false if powershell is not present' do + node = Chef::Node.new + Chef::Platform.supports_dsc?(node).should be_false + end + + ['1.0', '2.0', '3.0'].each do |version| + it "returns false for Powershell #{version}" do + node = Chef::Node.new + node.automatic[:languages][:powershell][:version] = version + Chef::Platform.supports_dsc?(node).should be_false + end + end + + ['4.0', '5.0'].each do |version| + it "returns true for Powershell #{version}" do + node = Chef::Node.new + node.automatic[:languages][:powershell][:version] = version + Chef::Platform.supports_dsc?(node).should be_true + end + end +end diff --git a/spec/unit/provider/dsc_script_spec.rb b/spec/unit/provider/dsc_script_spec.rb new file mode 100644 index 0000000000..8a7a7b5c6a --- /dev/null +++ b/spec/unit/provider/dsc_script_spec.rb @@ -0,0 +1,145 @@ +# +# Author:: Jay Mundrawala (<jdm@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' +require 'chef/util/dsc/resource_info' +require 'spec_helper' + +describe Chef::Provider::DscScript do + let (:node) { + node = Chef::Node.new + node.automatic[:languages][:powershell][:version] = '4.0' + node + } + let (:events) { Chef::EventDispatch::Dispatcher.new } + let (:run_context) { Chef::RunContext.new(node, {}, events) } + let (:resource) { Chef::Resource::DscScript.new("script", run_context) } + let (:provider) do + Chef::Provider::DscScript.new(resource, run_context) + end + + describe '#load_current_resource' do + it "describes the resource as converged if there were 0 DSC resources" do + allow(provider).to receive(:run_configuration).with(:test).and_return([]) + provider.load_current_resource + provider.instance_variable_get('@resource_converged').should be_true + end + + it "describes the resource as not converged if there is 1 DSC resources that is converged" do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) + allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) + provider.load_current_resource + provider.instance_variable_get('@resource_converged').should be_true + end + + it "describes the resource as not converged if there is 1 DSC resources that is not converged" do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something']) + allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) + provider.load_current_resource + provider.instance_variable_get('@resource_converged').should be_false + end + + it "describes the resource as not converged if there are any DSC resources that are not converged" do + dsc_resource_info1 = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something']) + dsc_resource_info2 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) + + allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info1, dsc_resource_info2]) + provider.load_current_resource + provider.instance_variable_get('@resource_converged').should be_false + end + + it "describes the resource as converged if all DSC resources that are converged" do + dsc_resource_info1 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) + dsc_resource_info2 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something']) + + allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info1, dsc_resource_info2]) + provider.load_current_resource + provider.instance_variable_get('@resource_converged').should be_true + end + end + + describe '#generate_configuration_document' do + # I think integration tests should cover these cases + + it 'uses configuration_document_from_script_path when a dsc script file is given' do + allow(provider).to receive(:load_current_resource) + resource.command("path_to_script") + generator = double('Chef::Util::DSC::ConfigurationGenerator') + generator.should_receive(:configuration_document_from_script_path) + allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) + provider.send(:generate_configuration_document, 'tmp', nil) + end + + it 'uses configuration_document_from_script_code when a the dsc resource is given' do + allow(provider).to receive(:load_current_resource) + resource.code("ImADSCResource{}") + generator = double('Chef::Util::DSC::ConfigurationGenerator') + generator.should_receive(:configuration_document_from_script_code) + allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) + provider.send(:generate_configuration_document, 'tmp', nil) + end + + it 'should noop if neither code or command are provided' do + allow(provider).to receive(:load_current_resource) + generator = double('Chef::Util::DSC::ConfigurationGenerator') + generator.should_receive(:configuration_document_from_script_code).with('', anything(), anything()) + allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) + provider.send(:generate_configuration_document, 'tmp', nil) + end + end + + describe 'action_run' do + it 'should converge the script if it is not converged' do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something']) + allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) + allow(provider).to receive(:run_configuration).with(:set) + + provider.run_action(:run) + resource.should be_updated + end + + it 'should not converge if the script is already converged' do + allow(provider).to receive(:run_configuration).with(:test).and_return([]) + + provider.run_action(:run) + resource.should_not be_updated + end + end + + describe '#generate_description' do + it 'removes the resource name from the beginning of any log line from the LCM' do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', true, ['resourcename doing something', 'lastline']) + provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info]) + provider.send(:generate_description)[1].should match(/converge DSC resource resourcename by doing something/) + end + + it 'ignores the last line' do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', true, ['resourcename doing something', 'lastline']) + provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info]) + provider.send(:generate_description)[1].should_not match(/lastline/) + end + + it 'reports a dsc resource has not been changed if the LCM reported no change was required' do + dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', false, ['resourcename does nothing', 'lastline']) + provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info]) + provider.send(:generate_description)[1].should match(/converge DSC resource resourcename by doing nothing/) + end + end +end + diff --git a/spec/unit/provider/ifconfig/debian_spec.rb b/spec/unit/provider/ifconfig/debian_spec.rb index c6a37fdd5b..ebb16e22af 100644 --- a/spec/unit/provider/ifconfig/debian_spec.rb +++ b/spec/unit/provider/ifconfig/debian_spec.rb @@ -56,8 +56,6 @@ describe Chef::Provider::Ifconfig::Debian do describe "generate_config" do context "when writing a file" do - let(:config_file_ifcfg) { StringIO.new } - let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") } let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") } @@ -67,14 +65,13 @@ describe Chef::Provider::Ifconfig::Debian do before do stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path) stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path) - expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg) end it "should write a network-script" do provider.run_action(:add) - expect(config_file_ifcfg.string).to match(/^iface eth0 inet static\s*$/) - expect(config_file_ifcfg.string).to match(/^\s+address 10\.0\.0\.1\s*$/) - expect(config_file_ifcfg.string).to match(/^\s+netmask 255\.255\.254\.0\s*$/) + expect(File.read(config_filename_ifcfg)).to match(/^iface eth0 inet static\s*$/) + expect(File.read(config_filename_ifcfg)).to match(/^\s+address 10\.0\.0\.1\s*$/) + expect(File.read(config_filename_ifcfg)).to match(/^\s+netmask 255\.255\.254\.0\s*$/) end context "when the interface_dot_d directory does not exist" do @@ -123,7 +120,6 @@ iface eth0 inet static netmask 255.255.254.0 EOF ) - expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg) expect(File.exists?(tempdir_path)).to be_true # since the file exists, the enclosing dir must also exist end @@ -139,6 +135,8 @@ EOF before do tempfile.write(expected_string) tempfile.close + + expect(provider).not_to receive(:converge_by).with(/modifying #{tempfile.path} to source #{tempdir_path}/) end it "should preserve all the contents" do @@ -165,6 +163,9 @@ EOF before do tempfile.write("a line\nanother line\n") tempfile.close + + allow(provider).to receive(:converge_by).and_call_original + expect(provider).to receive(:converge_by).with(/modifying #{tempfile.path} to source #{tempdir_path}/).and_call_original end it "should preserve the original contents and add the source line" do @@ -316,12 +317,37 @@ source #{tempdir_path}/* describe "delete_config for action_delete" do + let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") } + + let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") } + + let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" } + + before do + stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path) + stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path) + File.open(config_filename_ifcfg, "w") do |fh| + fh.write "arbitrary text\n" + fh.close + end + end + + after do + Dir.rmdir(tempdir_path) + end + it "should delete network-script if it exists" do current_resource.device new_resource.device - expect(File).to receive(:exist?).with(config_filename_ifcfg).and_return(true) - expect(FileUtils).to receive(:rm_f).with(config_filename_ifcfg, :verbose => false) + # belt and suspenders testing? + Chef::Util::Backup.any_instance.should_receive(:do_backup).and_call_original + + # internal implementation detail of Ifconfig. + Chef::Provider::File.any_instance.should_receive(:action_delete).and_call_original + + expect(File.exist?(config_filename_ifcfg)).to be_true provider.run_action(:delete) + expect(File.exist?(config_filename_ifcfg)).to be_false end end diff --git a/spec/unit/provider/ifconfig/redhat_spec.rb b/spec/unit/provider/ifconfig/redhat_spec.rb index f4b98dfc32..138c2a389d 100644 --- a/spec/unit/provider/ifconfig/redhat_spec.rb +++ b/spec/unit/provider/ifconfig/redhat_spec.rb @@ -37,22 +37,26 @@ describe Chef::Provider::Ifconfig::Redhat do status = double("Status", :exitstatus => 0) @provider.instance_variable_set("@status", status) @provider.current_resource = @current_resource - end + + config_filename = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}" + @config = double("chef-resource-file") + @provider.should_receive(:resource_for_config).with(config_filename).and_return(@config) + end describe "generate_config for action_add" do - it "should write network-script for centos" do + it "should write network-script for centos" do @provider.stub(:load_current_resource) @provider.stub(:run_command) - config_filename = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}" - config_file = StringIO.new - File.should_receive(:new).with(config_filename, "w").and_return(config_file) - + @config.should_receive(:content) do |arg| + arg.should match(/^\s*DEVICE=eth0\s*$/) + arg.should match(/^\s*IPADDR=10\.0\.0\.1\s*$/) + arg.should match(/^\s*NETMASK=255\.255\.254\.0\s*$/) + end + @config.should_receive(:run_action).with(:create) + @config.should_receive(:updated?).and_return(true) @provider.run_action(:add) - config_file.string.should match(/^\s*DEVICE=eth0\s*$/) - config_file.string.should match(/^\s*IPADDR=10\.0\.0\.1\s*$/) - config_file.string.should match(/^\s*NETMASK=255\.255\.254\.0\s*$/) - end + end end describe "delete_config for action_delete" do @@ -61,10 +65,8 @@ describe Chef::Provider::Ifconfig::Redhat do @current_resource.device @new_resource.device @provider.stub(:load_current_resource) @provider.stub(:run_command) - config_filename = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}" - File.should_receive(:exist?).with(config_filename).and_return(true) - FileUtils.should_receive(:rm_f).with(config_filename, :verbose => false) - + @config.should_receive(:run_action).with(:delete) + @config.should_receive(:updated?).and_return(true) @provider.run_action(:delete) end end diff --git a/spec/unit/provider/ifconfig_spec.rb b/spec/unit/provider/ifconfig_spec.rb index fb8d0956d7..b09e365c65 100644 --- a/spec/unit/provider/ifconfig_spec.rb +++ b/spec/unit/provider/ifconfig_spec.rb @@ -72,7 +72,7 @@ describe Chef::Provider::Ifconfig do @provider.stub(:load_current_resource) @provider.should_not_receive(:run_command) @current_resource.inet_addr "10.0.0.1" - @provider.should_not_receive(:generate_config) + @provider.should_receive(:generate_config) @provider.run_action(:add) @new_resource.should_not be_updated @@ -123,7 +123,7 @@ describe Chef::Provider::Ifconfig do it "should not delete interface if it does not exist" do @provider.stub(:load_current_resource) @provider.should_not_receive(:run_command) - @provider.should_not_receive(:delete_config) + @provider.should_receive(:delete_config) @provider.run_action(:delete) @new_resource.should_not be_updated @@ -171,7 +171,7 @@ describe Chef::Provider::Ifconfig do @provider.stub(:load_current_resource) # This is so that nothing actually runs @provider.should_not_receive(:run_command) - @provider.should_not_receive(:delete_config) + @provider.should_receive(:delete_config) @provider.run_action(:delete) @new_resource.should_not be_updated diff --git a/spec/unit/provider/link_spec.rb b/spec/unit/provider/link_spec.rb index 6052f5dd3b..2f0a5f2020 100644 --- a/spec/unit/provider/link_spec.rb +++ b/spec/unit/provider/link_spec.rb @@ -38,8 +38,8 @@ describe Chef::Resource::Link, :not_supported_on_win2k3 do result end - def canonicalize(path) - Chef::Platform.windows? ? path.gsub('/', '\\') : path + def paths_eql?(path1, path2) + Chef::Util::PathHelper.paths_eql?(path1, path2) end describe "when the target is a symlink" do @@ -68,7 +68,7 @@ describe Chef::Resource::Link, :not_supported_on_win2k3 do provider.current_resource.link_type.should == :symbolic end it "should update the source of the existing link with the links target" do - provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile") + paths_eql?(provider.current_resource.to, "#{CHEF_SPEC_DATA}/fofile").should be_true end it "should set the owner" do provider.current_resource.owner.should == 501 @@ -110,7 +110,7 @@ describe Chef::Resource::Link, :not_supported_on_win2k3 do provider.current_resource.link_type.should == :symbolic end it "should update the source of the existing link to the link's target" do - provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile") + paths_eql?(provider.current_resource.to, "#{CHEF_SPEC_DATA}/fofile").should be_true end it "should not set the owner" do provider.current_resource.owner.should be_nil @@ -221,7 +221,7 @@ describe Chef::Resource::Link, :not_supported_on_win2k3 do provider.current_resource.link_type.should == :hard end it "should update the source of the existing link to the link's target" do - provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile") + paths_eql?(provider.current_resource.to, "#{CHEF_SPEC_DATA}/fofile").should be_true end it "should not set the owner" do provider.current_resource.owner.should == nil diff --git a/spec/unit/provider/package/aix_spec.rb b/spec/unit/provider/package/aix_spec.rb index 35f85b628f..5d6e23302f 100644 --- a/spec/unit/provider/package/aix_spec.rb +++ b/spec/unit/provider/package/aix_spec.rb @@ -59,7 +59,6 @@ describe Chef::Provider::Package::Aix do lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Package) end - it "should get the source package version from lslpp if provided" do @stdout = StringIO.new(@bffinfo) @stdin, @stderr = StringIO.new, StringIO.new @@ -125,9 +124,7 @@ describe Chef::Provider::Package::Aix do describe "install and upgrade" do it "should run installp -aYF -d with the package source to install" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "installp -aYF -d /tmp/samba.base samba.base" - }) + @provider.should_receive(:shell_out!).with("installp -aYF -d /tmp/samba.base samba.base") @provider.install_package("samba.base", "3.3.12.0") end @@ -135,37 +132,28 @@ describe Chef::Provider::Package::Aix do @new_resource = Chef::Resource::Package.new("/tmp/samba.base") @provider = Chef::Provider::Package::Aix.new(@new_resource, @run_context) @new_resource.source.should == "/tmp/samba.base" - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "installp -aYF -d /tmp/samba.base /tmp/samba.base" - }) + @provider.should_receive(:shell_out!).with("installp -aYF -d /tmp/samba.base /tmp/samba.base") @provider.install_package("/tmp/samba.base", "3.3.12.0") end it "should run installp with -eLogfile option." do @new_resource.stub(:options).and_return("-e/tmp/installp.log") - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "installp -aYF -e/tmp/installp.log -d /tmp/samba.base samba.base" - }) + @provider.should_receive(:shell_out!).with("installp -aYF -e/tmp/installp.log -d /tmp/samba.base samba.base") @provider.install_package("samba.base", "3.3.12.0") end end describe "remove" do it "should run installp -u samba.base to remove the package" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "installp -u samba.base" - }) + @provider.should_receive(:shell_out!).with("installp -u samba.base") @provider.remove_package("samba.base", "3.3.12.0") end it "should run installp -u -e/tmp/installp.log with options -e/tmp/installp.log" do @new_resource.stub(:options).and_return("-e/tmp/installp.log") - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "installp -u -e/tmp/installp.log samba.base" - }) + @provider.should_receive(:shell_out!).with("installp -u -e/tmp/installp.log samba.base") @provider.remove_package("samba.base", "3.3.12.0") end end end - diff --git a/spec/unit/provider/package/homebrew_spec.rb b/spec/unit/provider/package/homebrew_spec.rb new file mode 100644 index 0000000000..9f105c13b8 --- /dev/null +++ b/spec/unit/provider/package/homebrew_spec.rb @@ -0,0 +1,251 @@ +# +# Author:: Joshua Timberman (<joshua@getchef.com>) +# Copyright (c) 2014, Chef Software, Inc. <legal@getchef.com> +# +# 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::Package::Homebrew do + let(:node) { Chef::Node.new } + let(:events) { double('Chef::Events').as_null_object } + let(:run_context) { double('Chef::RunContext', node: node, events: events) } + let(:new_resource) { Chef::Resource::HomebrewPackage.new('emacs') } + let(:current_resource) { Chef::Resource::HomebrewPackage.new('emacs')} + + let(:provider) do + Chef::Provider::Package::Homebrew.new(new_resource, run_context) + end + + let(:uninstalled_brew_info) do + { + 'name' => 'emacs', + 'homepage' => 'http://www.gnu.org/software/emacs', + 'versions' => { + 'stable' => '24.3', + 'bottle' => false, + 'devel' => nil, + 'head' => nil + }, + 'revision' => 0, + 'installed' => [], + 'linked_keg' => nil, + 'keg_only' => nil, + 'dependencies' => [], + 'conflicts_with' => [], + 'caveats' => nil, + 'options' => [] + } + end + + let(:installed_brew_info) do + { + 'name' => 'emacs', + 'homepage' => 'http://www.gnu.org/software/emacs/', + 'versions' => { + 'stable' => '24.3', + 'bottle' => false, + 'devel' => nil, + 'head' => 'HEAD' + }, + 'revision' => 0, + 'installed' => [{ 'version' => '24.3' }], + 'linked_keg' => '24.3', + 'keg_only' => nil, + 'dependencies' => [], + 'conflicts_with' => [], + 'caveats' => '', + 'options' => [] + } + end + + let(:keg_only_brew_info) do + { + 'name' => 'emacs-kegger', + 'homepage' => 'http://www.gnu.org/software/emacs/', + 'versions' => { + 'stable' => '24.3-keggy', + 'bottle' => false, + 'devel' => nil, + 'head' => 'HEAD' + }, + 'revision' => 0, + 'installed' => [{ 'version' => '24.3-keggy' }], + 'linked_keg' => nil, + 'keg_only' => true, + 'dependencies' => [], + 'conflicts_with' => [], + 'caveats' => '', + 'options' => [] + } + end + + before(:each) do + node.default['homebrew']['owner'] = 'sid_vicious' + allow(Etc).to receive(:getpwnam).with('sid_vicious').and_return('/Users/sid_vicious') + end + + describe 'load_current_resource' do + before(:each) do + allow(provider).to receive(:current_installed_version).and_return(nil) + allow(provider).to receive(:candidate_version).and_return('24.3') + end + + it 'creates a current resource with the name of the new resource' do + provider.load_current_resource + expect(provider.current_resource).to be_a(Chef::Resource::Package) + expect(provider.current_resource.name).to eql('emacs') + end + + it 'creates a current resource with the version if the package is installed' do + expect(provider).to receive(:current_installed_version).and_return('24.3') + provider.load_current_resource + expect(provider.current_resource.version).to eql('24.3') + end + + it 'creates a current resource with a nil version if the package is not installed' do + provider.load_current_resource + expect(provider.current_resource.version).to be_nil + end + + it 'sets a candidate version if one exists' do + provider.load_current_resource + expect(provider.candidate_version).to eql('24.3') + end + end + + describe 'current_installed_version' do + it 'returns the latest version from brew info if the package is keg only' do + allow(provider).to receive(:brew_info).and_return(keg_only_brew_info) + expect(provider.current_installed_version).to eql('24.3-keggy') + end + + it 'returns the linked keg version if the package is not keg only' do + allow(provider).to receive(:brew_info).and_return(installed_brew_info) + expect(provider.current_installed_version).to eql('24.3') + end + + it 'returns nil if the package is not installed' do + allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info) + expect(provider.current_installed_version).to be_nil + end + end + + describe 'brew' do + it 'passes a single to the brew command and return stdout' do + allow(provider).to receive(:get_response_from_command).and_return('zombo') + expect(provider.brew).to eql('zombo') + end + + it 'takes multiple arguments as an array' do + allow(provider).to receive(:get_response_from_command).and_return('homestarrunner') + expect(provider.brew('info', 'opts', 'bananas')).to eql('homestarrunner') + end + end + + context 'when testing actions' do + before(:each) do + provider.current_resource = current_resource + end + + describe 'install_package' do + before(:each) do + allow(provider).to receive(:candidate_version).and_return('24.3') + end + + it 'installs the named package with brew install' do + allow(provider.new_resource).to receive(:version).and_return('24.3') + allow(provider.current_resource).to receive(:version).and_return(nil) + allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info) + expect(provider).to receive(:get_response_from_command).with('brew install emacs') + provider.install_package('emacs', '24.3') + end + + it 'does not do anything if the package is installed' do + allow(provider.current_resource).to receive(:version).and_return('24.3') + allow(provider).to receive(:brew_info).and_return(installed_brew_info) + expect(provider).not_to receive(:get_response_from_command) + provider.install_package('emacs', '24.3') + end + + it 'uses options to the brew command if specified' do + allow(provider.new_resource).to receive(:options).and_return('--cocoa') + allow(provider.current_resource).to receive(:version).and_return('24.3') + allow(provider).to receive(:get_response_from_command).with('brew install --cocoa emacs') + provider.install_package('emacs', '24.3') + end + end + + describe 'upgrade_package' do + it 'uses brew upgrade to upgrade the package if it is installed' do + allow(provider.current_resource).to receive(:version).and_return('24') + allow(provider).to receive(:brew_info).and_return(installed_brew_info) + expect(provider).to receive(:get_response_from_command).with('brew upgrade emacs') + provider.upgrade_package('emacs', '24.3') + end + + it 'does not do anything if the package version is already installed' do + allow(provider.current_resource).to receive(:version).and_return('24.3') + allow(provider).to receive(:brew_info).and_return(installed_brew_info) + expect(provider).not_to receive(:get_response_from_command) + provider.install_package('emacs', '24.3') + end + + it 'uses brew install to install the package if it is not installed' do + allow(provider.current_resource).to receive(:version).and_return(nil) + allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info) + expect(provider).to receive(:get_response_from_command).with('brew install emacs') + provider.upgrade_package('emacs', '24.3') + end + + it 'uses options to the brew command if specified' do + allow(provider.current_resource).to receive(:version).and_return('24') + allow(provider).to receive(:brew_info).and_return(installed_brew_info) + allow(provider.new_resource).to receive(:options).and_return('--cocoa') + expect(provider).to receive(:get_response_from_command).with('brew upgrade --cocoa emacs') + provider.upgrade_package('emacs', '24.3') + end + end + + describe 'remove_package' do + it 'uninstalls the package with brew uninstall' do + allow(provider.current_resource).to receive(:version).and_return('24.3') + allow(provider).to receive(:brew_info).and_return(installed_brew_info) + expect(provider).to receive(:get_response_from_command).with('brew uninstall emacs') + provider.remove_package('emacs', '24.3') + end + + it 'does not do anything if the package is not installed' do + allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info) + expect(provider).not_to receive(:get_response_from_command) + provider.remove_package('emacs', '24.3') + end + end + + describe 'purge_package' do + it 'uninstalls the package with brew uninstall --force' do + allow(provider.current_resource).to receive(:version).and_return('24.3') + allow(provider).to receive(:brew_info).and_return(installed_brew_info) + expect(provider).to receive(:get_response_from_command).with('brew uninstall --force emacs') + provider.purge_package('emacs', '24.3') + end + + it 'does not do anything if the package is not installed' do + allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info) + expect(provider).not_to receive(:get_response_from_command) + provider.purge_package('emacs', '24.3') + end + end + end +end diff --git a/spec/unit/provider/package/ips_spec.rb b/spec/unit/provider/package/ips_spec.rb index c5941c2a91..07bca7f6d5 100644 --- a/spec/unit/provider/package/ips_spec.rb +++ b/spec/unit/provider/package/ips_spec.rb @@ -39,7 +39,7 @@ describe Chef::Provider::Package::Ips do pkg: info: no packages matching the following patterns you specified are installed on the system. Try specifying -r to query remotely: - crypto/gnupg + crypto/gnupg PKG_STATUS return OpenStruct.new(:stdout => stdout,:stdin => stdin,:stderr => stderr,:status => @status,:exitstatus => 1) end @@ -59,7 +59,7 @@ Packaging Date: April 1, 2012 05:55:52 PM FMRI: pkg://omnios/security/sudo@1.8.4.1,5.11-0.151002:20120401T175552Z PKG_STATUS stdin = StringIO.new - stderr = '' + stderr = '' return OpenStruct.new(:stdout => stdout,:stdin => stdin,:stderr => stderr,:status => @status,:exitstatus => 0) end @@ -123,17 +123,12 @@ INSTALLED context "when installing a package" do it "should run pkg install with the package name and version" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pkg install -q crypto/gnupg@2.0.17" - }) + @provider.should_receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17") @provider.install_package("crypto/gnupg", "2.0.17") end - it "should run pkg install with the package name and version and options if specified" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pkg --no-refresh install -q crypto/gnupg@2.0.17" - }) + @provider.should_receive(:shell_out).with("pkg --no-refresh install -q crypto/gnupg@2.0.17") @new_resource.stub(:options).and_return("--no-refresh") @provider.install_package("crypto/gnupg", "2.0.17") end @@ -206,9 +201,7 @@ REMOTE end it "should run pkg install with the --accept flag" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pkg install -q --accept crypto/gnupg@2.0.17" - }) + @provider.should_receive(:shell_out).with("pkg install -q --accept crypto/gnupg@2.0.17") @provider.install_package("crypto/gnupg", "2.0.17") end end @@ -216,25 +209,19 @@ REMOTE context "when upgrading a package" do it "should run pkg install with the package name and version" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pkg install -q crypto/gnupg@2.0.17" - }) + @provider.should_receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17") @provider.upgrade_package("crypto/gnupg", "2.0.17") end end context "when uninstalling a package" do it "should run pkg uninstall with the package name and version" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pkg uninstall -q crypto/gnupg@2.0.17" - }) + @provider.should_receive(:shell_out!).with("pkg uninstall -q crypto/gnupg@2.0.17") @provider.remove_package("crypto/gnupg", "2.0.17") end it "should run pkg uninstall with the package name and version and options if specified" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pkg --no-refresh uninstall -q crypto/gnupg@2.0.17" - }) + @provider.should_receive(:shell_out!).with("pkg --no-refresh uninstall -q crypto/gnupg@2.0.17") @new_resource.stub(:options).and_return("--no-refresh") @provider.remove_package("crypto/gnupg", "2.0.17") end diff --git a/spec/unit/provider/package/macports_spec.rb b/spec/unit/provider/package/macports_spec.rb index 9ebf23860d..535a5d2459 100644 --- a/spec/unit/provider/package/macports_spec.rb +++ b/spec/unit/provider/package/macports_spec.rb @@ -105,7 +105,7 @@ EOF it "should run the port install command with the correct version" do @current_resource.should_receive(:version).and_return("4.1.6") @provider.current_resource = @current_resource - @provider.should_receive(:run_command_with_systems_locale).with(:command => "port install zsh @4.2.7") + @provider.should_receive(:shell_out!).with("port install zsh @4.2.7") @provider.install_package("zsh", "4.2.7") end @@ -113,7 +113,7 @@ EOF it "should not do anything if a package already exists with the same version" do @current_resource.should_receive(:version).and_return("4.2.7") @provider.current_resource = @current_resource - @provider.should_not_receive(:run_command_with_systems_locale) + @provider.should_not_receive(:shell_out!) @provider.install_package("zsh", "4.2.7") end @@ -122,7 +122,7 @@ EOF @current_resource.should_receive(:version).and_return("4.1.6") @provider.current_resource = @current_resource @new_resource.stub(:options).and_return("-f") - @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f install zsh @4.2.7") + @provider.should_receive(:shell_out!).with("port -f install zsh @4.2.7") @provider.install_package("zsh", "4.2.7") end @@ -130,36 +130,36 @@ EOF describe "purge_package" do it "should run the port uninstall command with the correct version" do - @provider.should_receive(:run_command_with_systems_locale).with(:command => "port uninstall zsh @4.2.7") + @provider.should_receive(:shell_out!).with("port uninstall zsh @4.2.7") @provider.purge_package("zsh", "4.2.7") end it "should purge the currently active version if no explicit version is passed in" do - @provider.should_receive(:run_command_with_systems_locale).with(:command => "port uninstall zsh") + @provider.should_receive(:shell_out!).with("port uninstall zsh") @provider.purge_package("zsh", nil) end it "should add options to the port command when specified" do @new_resource.stub(:options).and_return("-f") - @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f uninstall zsh @4.2.7") + @provider.should_receive(:shell_out!).with("port -f uninstall zsh @4.2.7") @provider.purge_package("zsh", "4.2.7") end end describe "remove_package" do it "should run the port deactivate command with the correct version" do - @provider.should_receive(:run_command_with_systems_locale).with(:command => "port deactivate zsh @4.2.7") + @provider.should_receive(:shell_out!).with("port deactivate zsh @4.2.7") @provider.remove_package("zsh", "4.2.7") end it "should remove the currently active version if no explicit version is passed in" do - @provider.should_receive(:run_command_with_systems_locale).with(:command => "port deactivate zsh") + @provider.should_receive(:shell_out!).with("port deactivate zsh") @provider.remove_package("zsh", nil) end it "should add options to the port command when specified" do @new_resource.stub(:options).and_return("-f") - @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f deactivate zsh @4.2.7") + @provider.should_receive(:shell_out!).with("port -f deactivate zsh @4.2.7") @provider.remove_package("zsh", "4.2.7") end end @@ -169,7 +169,7 @@ EOF @current_resource.should_receive(:version).at_least(:once).and_return("4.1.6") @provider.current_resource = @current_resource - @provider.should_receive(:run_command_with_systems_locale).with(:command => "port upgrade zsh @4.2.7") + @provider.should_receive(:shell_out!).with("port upgrade zsh @4.2.7") @provider.upgrade_package("zsh", "4.2.7") end @@ -177,7 +177,7 @@ EOF it "should not run the port upgrade command if the version is already installed" do @current_resource.should_receive(:version).at_least(:once).and_return("4.2.7") @provider.current_resource = @current_resource - @provider.should_not_receive(:run_command_with_systems_locale) + @provider.should_not_receive(:shell_out!) @provider.upgrade_package("zsh", "4.2.7") end @@ -195,7 +195,7 @@ EOF @current_resource.should_receive(:version).at_least(:once).and_return("4.1.6") @provider.current_resource = @current_resource - @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f upgrade zsh @4.2.7") + @provider.should_receive(:shell_out!).with("port -f upgrade zsh @4.2.7") @provider.upgrade_package("zsh", "4.2.7") end diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb index 528f7097e8..0c1c487980 100644 --- a/spec/unit/provider/package/pacman_spec.rb +++ b/spec/unit/provider/package/pacman_spec.rb @@ -155,16 +155,12 @@ PACMAN_CONF describe Chef::Provider::Package::Pacman, "install_package" do it "should run pacman install with the package name and version" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pacman --sync --noconfirm --noprogressbar nano" - }) + @provider.should_receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar nano") @provider.install_package("nano", "1.0") end it "should run pacman install with the package name and version and options if specified" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pacman --sync --noconfirm --noprogressbar --debug nano" - }) + @provider.should_receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar --debug nano") @new_resource.stub(:options).and_return("--debug") @provider.install_package("nano", "1.0") @@ -180,16 +176,12 @@ PACMAN_CONF describe Chef::Provider::Package::Pacman, "remove_package" do it "should run pacman remove with the package name" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pacman --remove --noconfirm --noprogressbar nano" - }) + @provider.should_receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar nano") @provider.remove_package("nano", "1.0") end it "should run pacman remove with the package name and options if specified" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pacman --remove --noconfirm --noprogressbar --debug nano" - }) + @provider.should_receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar --debug nano") @new_resource.stub(:options).and_return("--debug") @provider.remove_package("nano", "1.0") diff --git a/spec/unit/provider/package/portage_spec.rb b/spec/unit/provider/package/portage_spec.rb index 6f22952da2..570f123168 100644 --- a/spec/unit/provider/package/portage_spec.rb +++ b/spec/unit/provider/package/portage_spec.rb @@ -278,23 +278,17 @@ EOF describe Chef::Provider::Package::Portage, "install_package" do it "should install a normally versioned package using portage" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "emerge -g --color n --nospinner --quiet =dev-util/git-1.0.0" - }) + @provider.should_receive(:shell_out!).with("emerge -g --color n --nospinner --quiet =dev-util/git-1.0.0") @provider.install_package("dev-util/git", "1.0.0") end it "should install a tilde versioned package using portage" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "emerge -g --color n --nospinner --quiet ~dev-util/git-1.0.0" - }) + @provider.should_receive(:shell_out!).with("emerge -g --color n --nospinner --quiet ~dev-util/git-1.0.0") @provider.install_package("dev-util/git", "~1.0.0") end it "should add options to the emerge command when specified" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "emerge -g --color n --nospinner --quiet --oneshot =dev-util/git-1.0.0" - }) + @provider.should_receive(:shell_out!).with("emerge -g --color n --nospinner --quiet --oneshot =dev-util/git-1.0.0") @new_resource.stub(:options).and_return("--oneshot") @provider.install_package("dev-util/git", "1.0.0") @@ -303,16 +297,12 @@ EOF describe Chef::Provider::Package::Portage, "remove_package" do it "should un-emerge the package with no version specified" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "emerge --unmerge --color n --nospinner --quiet dev-util/git" - }) + @provider.should_receive(:shell_out!).with("emerge --unmerge --color n --nospinner --quiet dev-util/git") @provider.remove_package("dev-util/git", nil) end it "should un-emerge the package with a version specified" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "emerge --unmerge --color n --nospinner --quiet =dev-util/git-1.0.0" - }) + @provider.should_receive(:shell_out!).with("emerge --unmerge --color n --nospinner --quiet =dev-util/git-1.0.0") @provider.remove_package("dev-util/git", "1.0.0") end end diff --git a/spec/unit/provider/package/rpm_spec.rb b/spec/unit/provider/package/rpm_spec.rb index 6e46cf5e98..9a96d829b8 100644 --- a/spec/unit/provider/package/rpm_spec.rb +++ b/spec/unit/provider/package/rpm_spec.rb @@ -102,25 +102,19 @@ describe Chef::Provider::Package::Rpm do describe "when installing or upgrading" do it "should run rpm -i with the package source to install" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" - }) + @provider.should_receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") @provider.install_package("ImageMagick-c++", "6.5.4.7-7.el6_5") end it "should run rpm -U with the package source to upgrade" do @current_resource.version("21.4-19.el5") - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" - }) + @provider.should_receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") @provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5") end it "should install package if missing and set to upgrade" do @current_resource.version("ImageMagick-c++") - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" - }) + @provider.should_receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") @provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5") end @@ -130,9 +124,7 @@ describe Chef::Provider::Package::Rpm do @new_resource.source.should == "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" @current_resource = Chef::Resource::Package.new("ImageMagick-c++") @provider.current_resource = @current_resource - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" - }) + @provider.should_receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") @provider.install_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5") end @@ -143,30 +135,23 @@ describe Chef::Provider::Package::Rpm do @current_resource = Chef::Resource::Package.new("ImageMagick-c++") @current_resource.version("21.4-19.el5") @provider.current_resource = @current_resource - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" - }) + @provider.should_receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") @provider.upgrade_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5") end it "installs with custom options specified in the resource" do @provider.candidate_version = '11' @new_resource.options("--dbpath /var/lib/rpm") - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "rpm --dbpath /var/lib/rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" - }) + @provider.should_receive(:shell_out!).with("rpm --dbpath /var/lib/rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") @provider.install_package(@new_resource.name, @provider.candidate_version) end end describe "when removing the package" do it "should run rpm -e to remove the package" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "rpm -e ImageMagick-c++-6.5.4.7-7.el6_5" - }) + @provider.should_receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5") @provider.remove_package("ImageMagick-c++", "6.5.4.7-7.el6_5") end end end end - diff --git a/spec/unit/provider/package/solaris_spec.rb b/spec/unit/provider/package/solaris_spec.rb index 086e327cce..d83ccbdf06 100644 --- a/spec/unit/provider/package/solaris_spec.rb +++ b/spec/unit/provider/package/solaris_spec.rb @@ -69,7 +69,6 @@ PKGINFO lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Package) end - it "should get the source package version from pkginfo if provided" do @stdout = StringIO.new(@pkginfo) @stdin, @stderr = StringIO.new, StringIO.new @@ -136,9 +135,7 @@ PKGINFO describe "install and upgrade" do it "should run pkgadd -n -d with the package source to install" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pkgadd -n -d /tmp/bash.pkg all" - }) + @provider.should_receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all") @provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end @@ -146,34 +143,26 @@ PKGINFO @new_resource = Chef::Resource::Package.new("/tmp/bash.pkg") @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context) @new_resource.source.should == "/tmp/bash.pkg" - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pkgadd -n -d /tmp/bash.pkg all" - }) + @provider.should_receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all") @provider.install_package("/tmp/bash.pkg", "11.10.0,REV=2005.01.08.05.16") end it "should run pkgadd -n -a /tmp/myadmin -d with the package options -a /tmp/myadmin" do @new_resource.stub(:options).and_return("-a /tmp/myadmin") - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pkgadd -n -a /tmp/myadmin -d /tmp/bash.pkg all" - }) + @provider.should_receive(:shell_out!).with("pkgadd -n -a /tmp/myadmin -d /tmp/bash.pkg all") @provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end end describe "remove" do it "should run pkgrm -n to remove the package" do - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pkgrm -n SUNWbash" - }) + @provider.should_receive(:shell_out!).with("pkgrm -n SUNWbash") @provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end it "should run pkgrm -n -a /tmp/myadmin with options -a /tmp/myadmin" do @new_resource.stub(:options).and_return("-a /tmp/myadmin") - @provider.should_receive(:run_command_with_systems_locale).with({ - :command => "pkgrm -n -a /tmp/myadmin SUNWbash" - }) + @provider.should_receive(:shell_out!).with("pkgrm -n -a /tmp/myadmin SUNWbash") @provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end diff --git a/spec/unit/provider/service/arch_service_spec.rb b/spec/unit/provider/service/arch_service_spec.rb index b267915e44..38ed74cdee 100644 --- a/spec/unit/provider/service/arch_service_spec.rb +++ b/spec/unit/provider/service/arch_service_spec.rb @@ -20,7 +20,6 @@ require 'spec_helper' require 'ostruct' - # most of this code has been ripped from init_service_spec.rb # and is only slightly modified to match "arch" needs. @@ -36,7 +35,6 @@ describe Chef::Provider::Service::Arch, "load_current_resource" do @new_resource.pattern("chef") @new_resource.supports({:status => false}) - @provider = Chef::Provider::Service::Arch.new(@new_resource, @run_context) ::File.stub(:exists?).with("/etc/rc.conf").and_return(true) @@ -51,7 +49,6 @@ describe Chef::Provider::Service::Arch, "load_current_resource" do end end - describe "when the service supports status" do before do @new_resource.supports({:status => true}) @@ -82,7 +79,6 @@ describe Chef::Provider::Service::Arch, "load_current_resource" do end - describe "when a status command has been specified" do before do @new_resource.status_command("/etc/rc.d/chefhasmonkeypants status") @@ -109,7 +105,6 @@ describe Chef::Provider::Service::Arch, "load_current_resource" do lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service) end - it "should fail if file /etc/rc.conf does not exist" do ::File.stub(:exists?).with("/etc/rc.conf").and_return(false) lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Service) @@ -211,7 +206,6 @@ RUNNING_PS end end - describe Chef::Provider::Service::Arch, "start_service" do # before(:each) do # @new_resource = double("Chef::Resource::Service", @@ -228,12 +222,12 @@ RUNNING_PS it "should call the start command if one is specified" do @new_resource.stub(:start_command).and_return("/etc/rc.d/chef startyousillysally") - @provider.should_receive(:shell_out!).with("/etc/rc.d/chef startyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/rc.d/chef startyousillysally") @provider.start_service() end it "should call '/etc/rc.d/service_name start' if no start command is specified" do - @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} start") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} start") @provider.start_service() end end @@ -254,12 +248,12 @@ RUNNING_PS it "should call the stop command if one is specified" do @new_resource.stub(:stop_command).and_return("/etc/rc.d/chef itoldyoutostop") - @provider.should_receive(:shell_out!).with("/etc/rc.d/chef itoldyoutostop") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/rc.d/chef itoldyoutostop") @provider.stop_service() end it "should call '/etc/rc.d/service_name stop' if no stop command is specified" do - @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} stop") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} stop") @provider.stop_service() end end @@ -281,13 +275,13 @@ RUNNING_PS it "should call 'restart' on the service_name if the resource supports it" do @new_resource.stub(:supports).and_return({:restart => true}) - @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} restart") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} restart") @provider.restart_service() end it "should call the restart_command if one has been specified" do @new_resource.stub(:restart_command).and_return("/etc/rc.d/chef restartinafire") - @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} restartinafire") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} restartinafire") @provider.restart_service() end @@ -316,13 +310,13 @@ RUNNING_PS it "should call 'reload' on the service if it supports it" do @new_resource.stub(:supports).and_return({:reload => true}) - @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} reload") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} reload") @provider.reload_service() end it "should should run the user specified reload command if one is specified and the service doesn't support reload" do @new_resource.stub(:reload_command).and_return("/etc/rc.d/chef lollerpants") - @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} lollerpants") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} lollerpants") @provider.reload_service() end end diff --git a/spec/unit/provider/service/debian_service_spec.rb b/spec/unit/provider/service/debian_service_spec.rb index 567eb6744a..3e60857cbf 100644 --- a/spec/unit/provider/service/debian_service_spec.rb +++ b/spec/unit/provider/service/debian_service_spec.rb @@ -301,7 +301,7 @@ insserv: dryrun, not creating .depend.boot, .depend.start, and .depend.stop def expect_commands(provider, commands) commands.each do |command| - provider.should_receive(:run_command).with({:command => command}) + provider.should_receive(:shell_out!).with(command) end end diff --git a/spec/unit/provider/service/freebsd_service_spec.rb b/spec/unit/provider/service/freebsd_service_spec.rb index 6e58e82b97..eb55fac820 100644 --- a/spec/unit/provider/service/freebsd_service_spec.rb +++ b/spec/unit/provider/service/freebsd_service_spec.rb @@ -452,12 +452,12 @@ EOF describe Chef::Provider::Service::Freebsd, "start_service" do it "should call the start command if one is specified" do new_resource.start_command("/etc/rc.d/chef startyousillysally") - expect(provider).to receive(:shell_out!).with("/etc/rc.d/chef startyousillysally") + expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/chef startyousillysally") provider.start_service() end it "should call '/usr/local/etc/rc.d/service_name faststart' if no start command is specified" do - expect(provider).to receive(:shell_out!).with("/usr/local/etc/rc.d/#{new_resource.service_name} faststart") + expect(provider).to receive(:shell_out_with_systems_locale!).with("/usr/local/etc/rc.d/#{new_resource.service_name} faststart") provider.start_service() end end @@ -465,12 +465,12 @@ EOF describe Chef::Provider::Service::Freebsd, "stop_service" do it "should call the stop command if one is specified" do new_resource.stop_command("/etc/init.d/chef itoldyoutostop") - expect(provider).to receive(:shell_out!).with("/etc/init.d/chef itoldyoutostop") + expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef itoldyoutostop") provider.stop_service() end it "should call '/usr/local/etc/rc.d/service_name faststop' if no stop command is specified" do - expect(provider).to receive(:shell_out!).with("/usr/local/etc/rc.d/#{new_resource.service_name} faststop") + expect(provider).to receive(:shell_out_with_systems_locale!).with("/usr/local/etc/rc.d/#{new_resource.service_name} faststop") provider.stop_service() end end @@ -478,13 +478,13 @@ EOF describe Chef::Provider::Service::Freebsd, "restart_service" do it "should call 'restart' on the service_name if the resource supports it" do new_resource.supports({:restart => true}) - expect(provider).to receive(:shell_out!).with("/usr/local/etc/rc.d/#{new_resource.service_name} fastrestart") + expect(provider).to receive(:shell_out_with_systems_locale!).with("/usr/local/etc/rc.d/#{new_resource.service_name} fastrestart") provider.restart_service() end it "should call the restart_command if one has been specified" do new_resource.restart_command("/etc/init.d/chef restartinafire") - expect(provider).to receive(:shell_out!).with("/etc/init.d/chef restartinafire") + expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef restartinafire") provider.restart_service() end diff --git a/spec/unit/provider/service/gentoo_service_spec.rb b/spec/unit/provider/service/gentoo_service_spec.rb index 95dc04108d..022a73cc9a 100644 --- a/spec/unit/provider/service/gentoo_service_spec.rb +++ b/spec/unit/provider/service/gentoo_service_spec.rb @@ -128,14 +128,14 @@ describe Chef::Provider::Service::Gentoo do describe Chef::Provider::Service::Gentoo, "enable_service" do it "should call rc-update add *service* default" do - @provider.should_receive(:run_command).with({:command => "/sbin/rc-update add chef default"}) + @provider.should_receive(:shell_out!).with("/sbin/rc-update add chef default") @provider.enable_service() end end describe Chef::Provider::Service::Gentoo, "disable_service" do it "should call rc-update del *service* default" do - @provider.should_receive(:run_command).with({:command => "/sbin/rc-update del chef default"}) + @provider.should_receive(:shell_out!).with("/sbin/rc-update del chef default") @provider.disable_service() end end diff --git a/spec/unit/provider/service/init_service_spec.rb b/spec/unit/provider/service/init_service_spec.rb index ad887c84a5..b523f6d3a9 100644 --- a/spec/unit/provider/service/init_service_spec.rb +++ b/spec/unit/provider/service/init_service_spec.rb @@ -100,7 +100,7 @@ PS end it "should use the init_command if one has been specified" do - @provider.should_receive(:shell_out!).with("/opt/chef-server/service/erchef start") + @provider.should_receive(:shell_out_with_systems_locale!).with("/opt/chef-server/service/erchef start") @provider.start_service end @@ -126,7 +126,6 @@ PS end - describe "when we have a 'ps' attribute" do it "should shell_out! the node's ps command" do @provider.should_receive(:shell_out!).and_return(@status) @@ -165,12 +164,12 @@ RUNNING_PS describe "when starting the service" do it "should call the start command if one is specified" do @new_resource.start_command("/etc/init.d/chef startyousillysally") - @provider.should_receive(:shell_out!).with("/etc/init.d/chef startyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef startyousillysally") @provider.start_service() end it "should call '/etc/init.d/service_name start' if no start command is specified" do - @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} start") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/init.d/#{@new_resource.service_name} start") @provider.start_service() end end @@ -178,12 +177,12 @@ RUNNING_PS describe Chef::Provider::Service::Init, "stop_service" do it "should call the stop command if one is specified" do @new_resource.stop_command("/etc/init.d/chef itoldyoutostop") - @provider.should_receive(:shell_out!).with("/etc/init.d/chef itoldyoutostop") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef itoldyoutostop") @provider.stop_service() end it "should call '/etc/init.d/service_name stop' if no stop command is specified" do - @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} stop") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/init.d/#{@new_resource.service_name} stop") @provider.stop_service() end end @@ -191,13 +190,13 @@ RUNNING_PS describe "when restarting a service" do it "should call 'restart' on the service_name if the resource supports it" do @new_resource.supports({:restart => true}) - @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} restart") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/init.d/#{@new_resource.service_name} restart") @provider.restart_service() end it "should call the restart_command if one has been specified" do @new_resource.restart_command("/etc/init.d/chef restartinafire") - @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} restartinafire") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/init.d/#{@new_resource.service_name} restartinafire") @provider.restart_service() end @@ -212,13 +211,13 @@ RUNNING_PS describe "when reloading a service" do it "should call 'reload' on the service if it supports it" do @new_resource.supports({:reload => true}) - @provider.should_receive(:shell_out!).with("/etc/init.d/chef reload") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef reload") @provider.reload_service() end it "should should run the user specified reload command if one is specified and the service doesn't support reload" do @new_resource.reload_command("/etc/init.d/chef lollerpants") - @provider.should_receive(:shell_out!).with("/etc/init.d/chef lollerpants") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef lollerpants") @provider.reload_service() end end @@ -226,6 +225,7 @@ RUNNING_PS describe "when a custom command has been specified" do before do @new_resource.start_command("/etc/init.d/chef startyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef startyousillysally") end it "should still pass all why run assertions" do diff --git a/spec/unit/provider/service/insserv_service_spec.rb b/spec/unit/provider/service/insserv_service_spec.rb index 04e63458a0..9ed03b519f 100644 --- a/spec/unit/provider/service/insserv_service_spec.rb +++ b/spec/unit/provider/service/insserv_service_spec.rb @@ -60,17 +60,16 @@ describe Chef::Provider::Service::Insserv do describe "enable_service" do it "should call insserv and create the default links" do - @provider.should_receive(:run_command).with({:command=>"/sbin/insserv -r -f #{@new_resource.service_name}"}) - @provider.should_receive(:run_command).with({:command=>"/sbin/insserv -d -f #{@new_resource.service_name}"}) + @provider.should_receive(:shell_out!).with("/sbin/insserv -r -f #{@new_resource.service_name}") + @provider.should_receive(:shell_out!).with("/sbin/insserv -d -f #{@new_resource.service_name}") @provider.enable_service end end describe "disable_service" do it "should call insserv and remove the links" do - @provider.should_receive(:run_command).with({:command=>"/sbin/insserv -r -f #{@new_resource.service_name}"}) + @provider.should_receive(:shell_out!).with("/sbin/insserv -r -f #{@new_resource.service_name}") @provider.disable_service end end end - diff --git a/spec/unit/provider/service/invokercd_service_spec.rb b/spec/unit/provider/service/invokercd_service_spec.rb index b638b08b72..d8a9851837 100644 --- a/spec/unit/provider/service/invokercd_service_spec.rb +++ b/spec/unit/provider/service/invokercd_service_spec.rb @@ -110,7 +110,6 @@ PS end - describe "when we have a 'ps' attribute" do it "should shell_out! the node's ps command" do @status = double("Status", :exitstatus => 0, :stdout => @stdout) @@ -152,12 +151,12 @@ RUNNING_PS describe "when starting the service" do it "should call the start command if one is specified" do @new_resource.start_command("/usr/sbin/invoke-rc.d chef startyousillysally") - @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef startyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d chef startyousillysally") @provider.start_service() end it "should call '/usr/sbin/invoke-rc.d service_name start' if no start command is specified" do - @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} start") + @provider.should_receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} start") @provider.start_service() end end @@ -165,12 +164,12 @@ RUNNING_PS describe Chef::Provider::Service::Invokercd, "stop_service" do it "should call the stop command if one is specified" do @new_resource.stop_command("/usr/sbin/invoke-rc.d chef itoldyoutostop") - @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef itoldyoutostop") + @provider.should_receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d chef itoldyoutostop") @provider.stop_service() end it "should call '/usr/sbin/invoke-rc.d service_name stop' if no stop command is specified" do - @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} stop") + @provider.should_receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} stop") @provider.stop_service() end end @@ -178,13 +177,13 @@ RUNNING_PS describe "when restarting a service" do it "should call 'restart' on the service_name if the resource supports it" do @new_resource.supports({:restart => true}) - @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} restart") + @provider.should_receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} restart") @provider.restart_service() end it "should call the restart_command if one has been specified" do @new_resource.restart_command("/usr/sbin/invoke-rc.d chef restartinafire") - @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} restartinafire") + @provider.should_receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} restartinafire") @provider.restart_service() end @@ -199,13 +198,13 @@ RUNNING_PS describe "when reloading a service" do it "should call 'reload' on the service if it supports it" do @new_resource.supports({:reload => true}) - @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef reload") + @provider.should_receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d chef reload") @provider.reload_service() end it "should should run the user specified reload command if one is specified and the service doesn't support reload" do @new_resource.reload_command("/usr/sbin/invoke-rc.d chef lollerpants") - @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef lollerpants") + @provider.should_receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d chef lollerpants") @provider.reload_service() end end diff --git a/spec/unit/provider/service/macosx_spec.rb b/spec/unit/provider/service/macosx_spec.rb index 1202d80873..c5df1e0637 100644 --- a/spec/unit/provider/service/macosx_spec.rb +++ b/spec/unit/provider/service/macosx_spec.rb @@ -226,7 +226,7 @@ SVC_LIST it "calls the start command if one is specified and service is not running" do new_resource.stub(:start_command).and_return("cowsay dirty") - provider.should_receive(:shell_out!).with("cowsay dirty") + provider.should_receive(:shell_out_with_systems_locale!).with("cowsay dirty") provider.start_service end @@ -238,7 +238,7 @@ SVC_LIST end it "starts service via launchctl if service found" do - provider.should_receive(:shell_out!). + provider.should_receive(:shell_out_with_systems_locale!). with("launchctl load -w '/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist'", :group => 1001, :user => 101). and_return(0) @@ -258,7 +258,7 @@ SVC_LIST it "calls the stop command if one is specified and service is running" do new_resource.stub(:stop_command).and_return("kill -9 123") - provider.should_receive(:shell_out!).with("kill -9 123") + provider.should_receive(:shell_out_with_systems_locale!).with("kill -9 123") provider.stop_service end @@ -270,7 +270,7 @@ SVC_LIST end it "stops the service via launchctl if service found" do - provider.should_receive(:shell_out!). + provider.should_receive(:shell_out_with_systems_locale!). with("launchctl unload '/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist'", :group => 1001, :user => 101). and_return(0) @@ -291,7 +291,7 @@ SVC_LIST it "issues a command if given" do new_resource.stub(:restart_command).and_return("reload that thing") - provider.should_receive(:shell_out!).with("reload that thing") + provider.should_receive(:shell_out_with_systems_locale!).with("reload that thing") provider.restart_service end diff --git a/spec/unit/provider/service/simple_service_spec.rb b/spec/unit/provider/service/simple_service_spec.rb index 61fb30fe13..11ebf74725 100644 --- a/spec/unit/provider/service/simple_service_spec.rb +++ b/spec/unit/provider/service/simple_service_spec.rb @@ -107,7 +107,7 @@ NOMOCKINGSTRINGSPLZ describe "when starting the service" do it "should call the start command if one is specified" do @new_resource.stub(:start_command).and_return("#{@new_resource.start_command}") - @provider.should_receive(:shell_out!).with("#{@new_resource.start_command}") + @provider.should_receive(:shell_out_with_systems_locale!).with("#{@new_resource.start_command}") @provider.start_service() end @@ -121,7 +121,7 @@ NOMOCKINGSTRINGSPLZ describe "when stopping a service" do it "should call the stop command if one is specified" do @new_resource.stop_command("/etc/init.d/themadness stop") - @provider.should_receive(:shell_out!).with("/etc/init.d/themadness stop") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/init.d/themadness stop") @provider.stop_service() end @@ -135,7 +135,7 @@ NOMOCKINGSTRINGSPLZ describe Chef::Provider::Service::Simple, "restart_service" do it "should call the restart command if one has been specified" do @new_resource.restart_command("/etc/init.d/foo restart") - @provider.should_receive(:shell_out!).with("/etc/init.d/foo restart") + @provider.should_receive(:shell_out_with_systems_locale!).with("/etc/init.d/foo restart") @provider.restart_service() end @@ -162,7 +162,7 @@ NOMOCKINGSTRINGSPLZ it "should should run the user specified reload command if one is specified" do @new_resource.reload_command("kill -9 1") - @provider.should_receive(:shell_out!).with("kill -9 1") + @provider.should_receive(:shell_out_with_systems_locale!).with("kill -9 1") @provider.reload_service() end end diff --git a/spec/unit/provider/service/solaris_smf_service_spec.rb b/spec/unit/provider/service/solaris_smf_service_spec.rb index 978f149258..8df22efa7e 100644 --- a/spec/unit/provider/service/solaris_smf_service_spec.rb +++ b/spec/unit/provider/service/solaris_smf_service_spec.rb @@ -59,7 +59,6 @@ describe Chef::Provider::Service::Solaris do @provider.load_current_resource end - it "should return the current resource" do @provider.stub(:shell_out!).with("/bin/svcs -l chef").and_return(@status) @provider.load_current_resource.should eql(@current_resource) @@ -131,7 +130,6 @@ describe Chef::Provider::Service::Solaris do end end - describe "when disabling the service" do before(:each) do @provider.current_resource = @current_resource @@ -159,7 +157,7 @@ describe Chef::Provider::Service::Solaris do end it "should call svcadm refresh chef" do - @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm refresh chef").and_return(@status) + @provider.should_receive(:shell_out_with_systems_locale!).with("/usr/sbin/svcadm refresh chef").and_return(@status) @provider.reload_service end diff --git a/spec/unit/provider/service/systemd_service_spec.rb b/spec/unit/provider/service/systemd_service_spec.rb index 2aa7b539f2..7358f63b5e 100644 --- a/spec/unit/provider/service/systemd_service_spec.rb +++ b/spec/unit/provider/service/systemd_service_spec.rb @@ -74,25 +74,25 @@ describe Chef::Provider::Service::Systemd do end it "should run the services status command if one has been specified" do - @provider.stub(:shell_out_with_systems_locale).and_return(@shell_out_success) + @provider.stub(:shell_out).and_return(@shell_out_success) @current_resource.should_receive(:running).with(true) @provider.load_current_resource end it "should run the services status command if one has been specified and properly set status check state" do - @provider.stub(:shell_out_with_systems_locale).with("/bin/chefhasmonkeypants status").and_return(@shell_out_success) + @provider.stub(:shell_out).with("/bin/chefhasmonkeypants status").and_return(@shell_out_success) @provider.load_current_resource @provider.instance_variable_get("@status_check_success").should be_true end it "should set running to false if a status command fails" do - @provider.stub(:shell_out_with_systems_locale).and_return(@shell_out_failure) + @provider.stub(:shell_out).and_return(@shell_out_failure) @current_resource.should_receive(:running).with(false) @provider.load_current_resource end it "should update state to indicate status check failed when a status command fails" do - @provider.stub(:shell_out_with_systems_locale).and_return(@shell_out_failure) + @provider.stub(:shell_out).and_return(@shell_out_failure) @provider.load_current_resource @provider.instance_variable_get("@status_check_success").should be_false end @@ -129,31 +129,31 @@ describe Chef::Provider::Service::Systemd do it "should call the start command if one is specified" do @new_resource.stub(:start_command).and_return("/sbin/rsyslog startyousillysally") - @provider.should_receive(:shell_out!).with("/sbin/rsyslog startyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog startyousillysally") @provider.start_service end it "should call '/bin/systemctl start service_name' if no start command is specified" do - @provider.should_receive(:shell_out_with_systems_locale).with("/bin/systemctl start #{@new_resource.service_name}").and_return(@shell_out_success) + @provider.should_receive(:shell_out_with_systems_locale!).with("/bin/systemctl start #{@new_resource.service_name}").and_return(@shell_out_success) @provider.start_service end it "should not call '/bin/systemctl start service_name' if it is already running" do @current_resource.stub(:running).and_return(true) - @provider.should_not_receive(:shell_out_with_systems_locale).with("/bin/systemctl start #{@new_resource.service_name}") + @provider.should_not_receive(:shell_out_with_systems_locale!).with("/bin/systemctl start #{@new_resource.service_name}") @provider.start_service end it "should call the restart command if one is specified" do @current_resource.stub(:running).and_return(true) @new_resource.stub(:restart_command).and_return("/sbin/rsyslog restartyousillysally") - @provider.should_receive(:shell_out!).with("/sbin/rsyslog restartyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog restartyousillysally") @provider.restart_service end it "should call '/bin/systemctl restart service_name' if no restart command is specified" do @current_resource.stub(:running).and_return(true) - @provider.should_receive(:shell_out_with_systems_locale).with("/bin/systemctl restart #{@new_resource.service_name}").and_return(@shell_out_success) + @provider.should_receive(:shell_out_with_systems_locale!).with("/bin/systemctl restart #{@new_resource.service_name}").and_return(@shell_out_success) @provider.restart_service end @@ -162,7 +162,7 @@ describe Chef::Provider::Service::Systemd do it "should call the reload command" do @current_resource.stub(:running).and_return(true) @new_resource.stub(:reload_command).and_return("/sbin/rsyslog reloadyousillysally") - @provider.should_receive(:shell_out!).with("/sbin/rsyslog reloadyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog reloadyousillysally") @provider.reload_service end end @@ -170,7 +170,7 @@ describe Chef::Provider::Service::Systemd do context "when a reload command is not specified" do it "should call '/bin/systemctl reload service_name' if the service is running" do @current_resource.stub(:running).and_return(true) - @provider.should_receive(:shell_out_with_systems_locale).with("/bin/systemctl reload #{@new_resource.service_name}").and_return(@shell_out_success) + @provider.should_receive(:shell_out_with_systems_locale!).with("/bin/systemctl reload #{@new_resource.service_name}").and_return(@shell_out_success) @provider.reload_service end @@ -185,19 +185,19 @@ describe Chef::Provider::Service::Systemd do it "should call the stop command if one is specified" do @current_resource.stub(:running).and_return(true) @new_resource.stub(:stop_command).and_return("/sbin/rsyslog stopyousillysally") - @provider.should_receive(:shell_out!).with("/sbin/rsyslog stopyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog stopyousillysally") @provider.stop_service end it "should call '/bin/systemctl stop service_name' if no stop command is specified" do @current_resource.stub(:running).and_return(true) - @provider.should_receive(:shell_out_with_systems_locale).with("/bin/systemctl stop #{@new_resource.service_name}").and_return(@shell_out_success) + @provider.should_receive(:shell_out_with_systems_locale!).with("/bin/systemctl stop #{@new_resource.service_name}").and_return(@shell_out_success) @provider.stop_service end it "should not call '/bin/systemctl stop service_name' if it is already stopped" do @current_resource.stub(:running).and_return(false) - @provider.should_not_receive(:shell_out_with_systems_locale).with("/bin/systemctl stop #{@new_resource.service_name}") + @provider.should_not_receive(:shell_out_with_systems_locale!).with("/bin/systemctl stop #{@new_resource.service_name}") @provider.stop_service end end @@ -210,12 +210,12 @@ describe Chef::Provider::Service::Systemd do end it "should call '/bin/systemctl enable service_name' to enable the service" do - @provider.should_receive(:shell_out_with_systems_locale).with("/bin/systemctl enable #{@new_resource.service_name}").and_return(@shell_out_success) + @provider.should_receive(:shell_out!).with("/bin/systemctl enable #{@new_resource.service_name}").and_return(@shell_out_success) @provider.enable_service end it "should call '/bin/systemctl disable service_name' to disable the service" do - @provider.should_receive(:shell_out_with_systems_locale).with("/bin/systemctl disable #{@new_resource.service_name}").and_return(@shell_out_success) + @provider.should_receive(:shell_out!).with("/bin/systemctl disable #{@new_resource.service_name}").and_return(@shell_out_success) @provider.disable_service end end @@ -227,12 +227,12 @@ describe Chef::Provider::Service::Systemd do end it "should return true if '/bin/systemctl is-active service_name' returns 0" do - @provider.should_receive(:shell_out_with_systems_locale).with('/bin/systemctl is-active rsyslog.service --quiet').and_return(@shell_out_success) + @provider.should_receive(:shell_out).with('/bin/systemctl is-active rsyslog.service --quiet').and_return(@shell_out_success) @provider.is_active?.should be_true end it "should return false if '/bin/systemctl is-active service_name' returns anything except 0" do - @provider.should_receive(:shell_out_with_systems_locale).with('/bin/systemctl is-active rsyslog.service --quiet').and_return(@shell_out_failure) + @provider.should_receive(:shell_out).with('/bin/systemctl is-active rsyslog.service --quiet').and_return(@shell_out_failure) @provider.is_active?.should be_false end end @@ -244,12 +244,12 @@ describe Chef::Provider::Service::Systemd do end it "should return true if '/bin/systemctl is-enabled service_name' returns 0" do - @provider.should_receive(:shell_out_with_systems_locale).with('/bin/systemctl is-enabled rsyslog.service --quiet').and_return(@shell_out_success) + @provider.should_receive(:shell_out).with('/bin/systemctl is-enabled rsyslog.service --quiet').and_return(@shell_out_success) @provider.is_enabled?.should be_true end it "should return false if '/bin/systemctl is-enabled service_name' returns anything except 0" do - @provider.should_receive(:shell_out_with_systems_locale).with('/bin/systemctl is-enabled rsyslog.service --quiet').and_return(@shell_out_failure) + @provider.should_receive(:shell_out).with('/bin/systemctl is-enabled rsyslog.service --quiet').and_return(@shell_out_failure) @provider.is_enabled?.should be_false end end diff --git a/spec/unit/provider/service/upstart_service_spec.rb b/spec/unit/provider/service/upstart_service_spec.rb index efe4e0481f..499a794ff4 100644 --- a/spec/unit/provider/service/upstart_service_spec.rb +++ b/spec/unit/provider/service/upstart_service_spec.rb @@ -161,7 +161,6 @@ describe Chef::Provider::Service::Upstart do @provider.load_current_resource end - it "should track state when the upstart configuration file fails to load" do File.should_receive(:exists?).and_return false @provider.load_current_resource @@ -174,19 +173,19 @@ describe Chef::Provider::Service::Upstart do end it "should run the services status command if one has been specified" do - @provider.stub(:run_command_with_systems_locale).with({:command => "/bin/chefhasmonkeypants status"}).and_return(0) + @provider.stub(:shell_out!).with("/bin/chefhasmonkeypants status").and_return(0) @current_resource.should_receive(:running).with(true) @provider.load_current_resource end it "should track state when the user-provided status command fails" do - @provider.stub(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_raise(Chef::Exceptions::Exec) + @provider.stub(:shell_out!).and_raise(Errno::ENOENT) @provider.load_current_resource @provider.instance_variable_get("@command_success").should == false end it "should set running to false if it catches a Chef::Exceptions::Exec when using a status command" do - @provider.stub(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_raise(Chef::Exceptions::Exec) + @provider.stub(:shell_out!).and_raise(Errno::ENOENT) @current_resource.should_receive(:running).with(false) @provider.load_current_resource end @@ -202,7 +201,6 @@ describe Chef::Provider::Service::Upstart do @provider.load_current_resource.should eql(@current_resource) end - end describe "enable and disable service" do @@ -243,18 +241,18 @@ describe Chef::Provider::Service::Upstart do it "should call the start command if one is specified" do @new_resource.stub(:start_command).and_return("/sbin/rsyslog startyousillysally") - @provider.should_receive(:shell_out!).with("/sbin/rsyslog startyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog startyousillysally") @provider.start_service() end it "should call '/sbin/start service_name' if no start command is specified" do - @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/start #{@new_resource.service_name}"}).and_return(0) + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(0) @provider.start_service() end it "should not call '/sbin/start service_name' if it is already running" do @current_resource.stub(:running).and_return(true) - @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/sbin/start #{@new_resource.service_name}"}) + @provider.should_not_receive(:shell_out_with_systems_locale!) @provider.start_service() end @@ -263,58 +261,58 @@ describe Chef::Provider::Service::Upstart do @new_resource.parameters({ "OSD_ID" => "2" }) @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context) @provider.current_resource = @current_resource - @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/start rsyslog OSD_ID=2"}).and_return(0) + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/start rsyslog OSD_ID=2").and_return(0) @provider.start_service() end it "should call the restart command if one is specified" do @current_resource.stub(:running).and_return(true) @new_resource.stub(:restart_command).and_return("/sbin/rsyslog restartyousillysally") - @provider.should_receive(:shell_out!).with("/sbin/rsyslog restartyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog restartyousillysally") @provider.restart_service() end it "should call '/sbin/restart service_name' if no restart command is specified" do @current_resource.stub(:running).and_return(true) - @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/restart #{@new_resource.service_name}"}).and_return(0) + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/restart #{@new_resource.service_name}").and_return(0) @provider.restart_service() end it "should call '/sbin/start service_name' if restart_service is called for a stopped service" do @current_resource.stub(:running).and_return(false) - @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/start #{@new_resource.service_name}"}).and_return(0) + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(0) @provider.restart_service() end it "should call the reload command if one is specified" do @current_resource.stub(:running).and_return(true) @new_resource.stub(:reload_command).and_return("/sbin/rsyslog reloadyousillysally") - @provider.should_receive(:shell_out!).with("/sbin/rsyslog reloadyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog reloadyousillysally") @provider.reload_service() end it "should call '/sbin/reload service_name' if no reload command is specified" do @current_resource.stub(:running).and_return(true) - @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/reload #{@new_resource.service_name}"}).and_return(0) + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/reload #{@new_resource.service_name}").and_return(0) @provider.reload_service() end it "should call the stop command if one is specified" do @current_resource.stub(:running).and_return(true) @new_resource.stub(:stop_command).and_return("/sbin/rsyslog stopyousillysally") - @provider.should_receive(:shell_out!).with("/sbin/rsyslog stopyousillysally") + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog stopyousillysally") @provider.stop_service() end it "should call '/sbin/stop service_name' if no stop command is specified" do @current_resource.stub(:running).and_return(true) - @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/stop #{@new_resource.service_name}"}).and_return(0) + @provider.should_receive(:shell_out_with_systems_locale!).with("/sbin/stop #{@new_resource.service_name}").and_return(0) @provider.stop_service() end it "should not call '/sbin/stop service_name' if it is already stopped" do @current_resource.stub(:running).and_return(false) - @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/sbin/stop #{@new_resource.service_name}"}) + @provider.should_not_receive(:shell_out_with_systems_locale!).with("/sbin/stop #{@new_resource.service_name}") @provider.stop_service() end end diff --git a/spec/unit/provider/subversion_spec.rb b/spec/unit/provider/subversion_spec.rb index f37a42d235..5d9d1cec1e 100644 --- a/spec/unit/provider/subversion_spec.rb +++ b/spec/unit/provider/subversion_spec.rb @@ -16,7 +16,6 @@ # limitations under the License. # - require 'spec_helper' describe Chef::Provider::Subversion do @@ -199,7 +198,7 @@ describe Chef::Provider::Subversion do it "runs an export with the --force option" do ::File.stub(:directory?).with("/my/deploy").and_return(true) expected_cmd = "svn export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir" - @provider.should_receive(:run_command).with(:command => expected_cmd) + @provider.should_receive(:shell_out!).with(command: expected_cmd) @provider.run_action(:force_export) @resource.should be_updated end @@ -207,7 +206,7 @@ describe Chef::Provider::Subversion do it "runs the checkout command for action_checkout" do ::File.stub(:directory?).with("/my/deploy").and_return(true) expected_cmd = "svn checkout -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir" - @provider.should_receive(:run_command).with(:command => expected_cmd) + @provider.should_receive(:shell_out!).with(command: expected_cmd) @provider.run_action(:checkout) @resource.should be_updated end @@ -231,7 +230,7 @@ describe Chef::Provider::Subversion do @resource.user "whois" @resource.group "thisis" expected_cmd = "svn checkout -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir" - @provider.should_receive(:run_command).with(:command => expected_cmd, :user => "whois", :group => "thisis") + @provider.should_receive(:shell_out!).with(command: expected_cmd, user: "whois", group: "thisis") @provider.run_action(:checkout) @resource.should be_updated end @@ -256,7 +255,7 @@ describe Chef::Provider::Subversion do @provider.stub(:find_current_revision).and_return("11410") @provider.stub(:current_revision_matches_target_revision?).and_return(false) expected_cmd = "svn update -q -r12345 /my/deploy/dir" - @provider.should_receive(:run_command).with(:command => expected_cmd) + @provider.should_receive(:shell_out!).with(command: expected_cmd) @provider.run_action(:sync) @resource.should be_updated end @@ -273,7 +272,7 @@ describe Chef::Provider::Subversion do it "runs the export_command on action_export" do ::File.stub(:directory?).with("/my/deploy").and_return(true) expected_cmd = "svn export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir" - @provider.should_receive(:run_command).with(:command => expected_cmd) + @provider.should_receive(:shell_out!).with(command: expected_cmd) @provider.run_action(:export) @resource.should be_updated end diff --git a/spec/unit/provider/whyrun_safe_ruby_block_spec.rb b/spec/unit/provider/whyrun_safe_ruby_block_spec.rb index 5a17aacbd9..d5209248b3 100644 --- a/spec/unit/provider/whyrun_safe_ruby_block_spec.rb +++ b/spec/unit/provider/whyrun_safe_ruby_block_spec.rb @@ -30,14 +30,14 @@ describe Chef::Provider::WhyrunSafeRubyBlock, "initialize" do end it "should call the block and flag the resource as updated" do - @provider.run_action(:create) + @provider.run_action(:run) $evil_global_evil_laugh.should == :mwahahaha @new_resource.should be_updated end it "should call the block and flat the resource as updated - even in whyrun" do Chef::Config[:why_run] = true - @provider.run_action(:create) + @provider.run_action(:run) $evil_global_evil_laugh.should == :mwahahaha @new_resource.should be_updated Chef::Config[:why_run] = false diff --git a/spec/unit/resource/dsc_script_spec.rb b/spec/unit/resource/dsc_script_spec.rb new file mode 100644 index 0000000000..cbd502a61c --- /dev/null +++ b/spec/unit/resource/dsc_script_spec.rb @@ -0,0 +1,127 @@ +# +# Author:: Adam Edwards (<adamed@getchef.com>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +describe Chef::Resource::DscScript do + let(:dsc_test_resource_name) { 'DSCTest' } + + context 'when Powershell supports Dsc' do + let(:dsc_test_run_context) { + node = Chef::Node.new + node.automatic[:languages][:powershell][:version] = '4.0' + empty_events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, empty_events) + } + let(:dsc_test_resource) { + Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) + } + let(:configuration_code) {'echo "This is supposed to create a configuration document."'} + let(:configuration_path) {'c:/myconfigs/formatc.ps1'} + let(:configuration_name) { 'formatme' } + let(:configuration_data) { '@{AllNodes = @( @{ NodeName = "localhost"; PSDscAllowPlainTextPassword = $true })}' } + let(:configuration_data_script) { 'c:/myconfigs/data/safedata.psd1' } + + it "has a default action of `:run`" do + expect(dsc_test_resource.action).to eq(:run) + end + + it "has an allowed_actions attribute with only the `:run` and `:nothing` attributes" do + expect(dsc_test_resource.allowed_actions.to_set).to eq([:run,:nothing].to_set) + end + + it "allows the code attribute to be set" do + dsc_test_resource.code(configuration_code) + expect(dsc_test_resource.code).to eq(configuration_code) + end + + it "allows the command attribute to be set" do + dsc_test_resource.command(configuration_path) + expect(dsc_test_resource.command).to eq(configuration_path) + end + + it "allows the configuration_name attribute to be set" do + dsc_test_resource.configuration_name(configuration_name) + expect(dsc_test_resource.configuration_name).to eq(configuration_name) + end + + it "allows the configuration_data attribute to be set" do + dsc_test_resource.configuration_data(configuration_data) + expect(dsc_test_resource.configuration_data).to eq(configuration_data) + end + + it "allows the configuration_data_script attribute to be set" do + dsc_test_resource.configuration_data_script(configuration_data_script) + expect(dsc_test_resource.configuration_data_script).to eq(configuration_data_script) + end + + it "raises an ArgumentError exception if an attempt is made to set the code attribute when the command attribute is already set" do + dsc_test_resource.command(configuration_path) + expect { dsc_test_resource.code(configuration_code) }.to raise_error(ArgumentError) + end + + it "raises an ArgumentError exception if an attempt is made to set the command attribute when the code attribute is already set" do + dsc_test_resource.code(configuration_code) + expect { dsc_test_resource.command(configuration_path) }.to raise_error(ArgumentError) + end + + it "raises an ArgumentError exception if an attempt is made to set the configuration_name attribute when the code attribute is already set" do + dsc_test_resource.code(configuration_code) + expect { dsc_test_resource.configuration_name(configuration_name) }.to raise_error(ArgumentError) + end + + it "raises an ArgumentError exception if an attempt is made to set the configuration_data attribute when the configuration_data_script attribute is already set" do + dsc_test_resource.configuration_data_script(configuration_data_script) + expect { dsc_test_resource.configuration_data(configuration_data) }.to raise_error(ArgumentError) + end + + it "raises an ArgumentError exception if an attempt is made to set the configuration_data_script attribute when the configuration_data attribute is already set" do + dsc_test_resource.configuration_data(configuration_data) + expect { dsc_test_resource.configuration_data_script(configuration_data_script) }.to raise_error(ArgumentError) + end + end + + context 'when Powershell does not supported Dsc' do + ['1.0', '2.0', '3.0'].each do |version| + it "raises an exception for powershell version '#{version}'" do + node = Chef::Node.new + node.automatic[:languages][:powershell][:version] = version + empty_events = Chef::EventDispatch::Dispatcher.new + dsc_test_run_context = Chef::RunContext.new(node, {}, empty_events) + + expect { + Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) + }.to raise_error(Chef::Exceptions::NoProviderAvailable) + end + end + end + + context 'when Powershell is not present' do + let (:dsc_test_run_context) { + node = Chef::Node.new + empty_events = Chef::EventDispatch::Dispatcher.new + dsc_test_run_context = Chef::RunContext.new(node, {}, empty_events) + } + + it 'raises an exception if powershell is not present' do + expect { + Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) + }.to raise_error(Chef::Exceptions::NoProviderAvailable) + end + end +end diff --git a/spec/unit/resource/homebrew_package_spec.rb b/spec/unit/resource/homebrew_package_spec.rb new file mode 100644 index 0000000000..4b4f9afe5e --- /dev/null +++ b/spec/unit/resource/homebrew_package_spec.rb @@ -0,0 +1,36 @@ +# +# Author:: Joshua Timberman (<joshua@getchef.com>) +# Copyright (c) 2014, Chef Software, Inc. <legal@getchef.com> +# +# 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::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 + + it 'sets the provider to Chef::Provider::Package::Homebrew' do + expect(resource.provider).to eql(Chef::Provider::Package::Homebrew) + end + +end diff --git a/spec/unit/run_context_spec.rb b/spec/unit/run_context_spec.rb index 1def10faf5..21ece2abaa 100644 --- a/spec/unit/run_context_spec.rb +++ b/spec/unit/run_context_spec.rb @@ -134,4 +134,19 @@ describe Chef::RunContext do end end + describe "handling reboot requests" do + let(:expected) do + { :reason => "spec tests require a reboot" } + end + + it "stores and deletes the reboot request" do + @run_context.request_reboot(expected) + expect(@run_context.reboot_info).to eq(expected) + expect(@run_context.reboot_requested?).to be_true + + @run_context.cancel_reboot + expect(@run_context.reboot_info).to eq({}) + expect(@run_context.reboot_requested?).to be_false + end + end end diff --git a/spec/unit/util/dsc/configuration_generator_spec.rb b/spec/unit/util/dsc/configuration_generator_spec.rb new file mode 100644 index 0000000000..03f3ffe25c --- /dev/null +++ b/spec/unit/util/dsc/configuration_generator_spec.rb @@ -0,0 +1,171 @@ +# +# Author:: Jay Mundrawala <jmundrawala@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' +require 'chef/util/dsc/configuration_generator' + +describe Chef::Util::DSC::ConfigurationGenerator do + let(:conf_man) do + node = Chef::Node.new + Chef::Util::DSC::ConfigurationGenerator.new(node, 'tmp') + end + + describe '#validate_configuration_name!' do + it 'should not raise an error if a name contains all upper case letters' do + conf_man.send(:validate_configuration_name!, "HELLO") + end + + it 'should not raise an error if the name contains all lower case letters' do + conf_man.send(:validate_configuration_name!, "hello") + end + + it 'should not raise an error if no special characters are used except _' do + conf_man.send(:validate_configuration_name!, "hello_world") + end + + %w{! @ # $ % ^ & * & * ( ) - = + \{ \} . ? < > \\ /}.each do |sym| + it "raises an Argument error if it configuration name contains #{sym}" do + expect { + conf_man.send(:validate_configuration_name!, "Hello#{sym}") + }.to raise_error(ArgumentError) + end + end + end + + describe "#get_merged_configuration_flags" do + context 'when strings are used as switches' do + it 'should merge the hash if there are no restricted switches' do + merged = conf_man.send(:get_merged_configuration_flags!, {'flag' => 'a'}, 'hello') + merged.should include(:flag) + merged[:flag].should eql('a') + merged.should include(:outputpath) + end + + it 'should raise an ArgumentError if you try to override outputpath' do + expect { + conf_man.send(:get_merged_configuration_flags!, {'outputpath' => 'a'}, 'hello') + }.to raise_error(ArgumentError) + end + + it 'should be case insensitive for switches that are not allowed' do + expect { + conf_man.send(:get_merged_configuration_flags!, {'OutputPath' => 'a'}, 'hello') + }.to raise_error(ArgumentError) + end + + it 'should be case insensitive to switches that are allowed' do + merged = conf_man.send(:get_merged_configuration_flags!, {'FLAG' => 'a'}, 'hello') + merged.should include(:flag) + end + end + + context 'when symbols are used as switches' do + it 'should merge the hash if there are no restricted switches' do + merged = conf_man.send(:get_merged_configuration_flags!, {:flag => 'a'}, 'hello') + merged.should include(:flag) + merged[:flag].should eql('a') + merged.should include(:outputpath) + end + + it 'should raise an ArgumentError if you try to override outputpath' do + expect { + conf_man.send(:get_merged_configuration_flags!, {:outputpath => 'a'}, 'hello') + }.to raise_error(ArgumentError) + end + + it 'should be case insensitive for switches that are not allowed' do + expect { + conf_man.send(:get_merged_configuration_flags!, {:OutputPath => 'a'}, 'hello') + }.to raise_error(ArgumentError) + end + + it 'should be case insensitive to switches that are allowed' do + merged = conf_man.send(:get_merged_configuration_flags!, {:FLAG => 'a'}, 'hello') + merged.should include(:flag) + end + end + + context 'when there are no flags' do + it 'should supply an output path if configuration_flags is an empty hash' do + merged = conf_man.send(:get_merged_configuration_flags!, {}, 'hello') + merged.should include(:outputpath) + merged.length.should eql(1) + end + + it 'should supply an output path if configuration_flags is an empty hash' do + merged = conf_man.send(:get_merged_configuration_flags!, nil, 'hello') + merged.should include(:outputpath) + merged.length.should eql(1) + end + end + + # What should happen if configuration flags contains duplicates? + # flagA => 'a', flaga => 'a' + # or + # flagA => 'a', flaga => 'b' + # + end + + describe '#write_document_generation_script' do + let(:file_like_object) { double("file like object") } + + it "should write the input to a file" do + allow(File).to receive(:open).and_yield(file_like_object) + allow(File).to receive(:join) do |a, b| + [a,b].join("++") + end + allow(file_like_object).to receive(:write) + conf_man.send(:write_document_generation_script, 'file', 'hello') + expect(file_like_object).to have_received(:write) + end + end + + describe "#find_configuration_document" do + it "should find the mof file" do + # These tests seem way too implementation specific. Unfortunatly, File and Dir + # need to be mocked because they are OS specific + allow(File).to receive(:join) do |a, b| + [a,b].join("++") + end + + allow(Dir).to receive(:entries).with("tmp++hello") {['f1', 'f2', 'hello.mof', 'f3']} + expect(conf_man.send(:find_configuration_document, 'hello')).to eql('tmp++hello++hello.mof') + end + + it "should return nil if the mof file is not found" do + allow(File).to receive(:join) do |a, b| + [a,b].join("++") + end + allow(Dir).to receive(:entries).with("tmp++hello") {['f1', 'f2', 'f3']} + expect(conf_man.send(:find_configuration_document, 'hello')).to be_nil + end + end + + describe "#configuration_code" do + it "should build dsc" do + dsc = conf_man.send(:configuration_code, 'archive{}', 'hello') + found_configuration = false + dsc.split(';').each do |command| + if command.downcase =~ /\s*configuration\s+'hello'\s*\{\s*node\s+'localhost'\s*\{\s*archive\s*\{\s*\}\s*\}\s*\}\s*/ + found_configuration = true + end + end + expect(found_configuration).to be_true + end + end +end diff --git a/spec/unit/util/dsc/lcm_output_parser_spec.rb b/spec/unit/util/dsc/lcm_output_parser_spec.rb new file mode 100644 index 0000000000..23a3dbd3ec --- /dev/null +++ b/spec/unit/util/dsc/lcm_output_parser_spec.rb @@ -0,0 +1,169 @@ +# +# Author:: Bryan McLellan <btm@loftninjas.org> +# 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/util/dsc/lcm_output_parser' + +describe Chef::Util::DSC::LocalConfigurationManager::Parser do + context 'empty input parameter' do + it 'returns an empty array for a 0 length string' do + Chef::Util::DSC::LocalConfigurationManager::Parser::parse('').should be_empty + end + + it 'returns an empty array for a nil input' do + Chef::Util::DSC::LocalConfigurationManager::Parser::parse('').should be_empty + end + end + + context 'correctly formatted output from lcm' do + it 'returns an empty array for a log with no resources' do + str = <<EOF +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ End Set ] +EOF + Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str).should be_empty + end + + it 'returns a single resource when only 1 logged with the correct name' do + str = <<EOF +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ Start Resource ] [name] +logtype: [machinename]: LCM: [ End Resource ] [name] +logtype: [machinename]: LCM: [ End Set ] +EOF + resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str) + resources.length.should eq(1) + resources[0].name.should eq('[name]') + end + + it 'identifies when a resource changes the state of the system' do + str = <<EOF +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ Start Resource ] [name] +logtype: [machinename]: LCM: [ Start Set ] [name] +logtype: [machinename]: LCM: [ End Set ] [name] +logtype: [machinename]: LCM: [ End Resource ] [name] +logtype: [machinename]: LCM: [ End Set ] +EOF + resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str) + resources[0].changes_state?.should be_true + end + + it 'preserves the log provided for how the system changed the state' do + str = <<EOF +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ Start Resource ] [name] +logtype: [machinename]: LCM: [ Start Set ] [name] +logtype: [machinename]: [message] +logtype: [machinename]: LCM: [ End Set ] [name] +logtype: [machinename]: LCM: [ End Resource ] [name] +logtype: [machinename]: LCM: [ End Set ] +EOF + resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str) + resources[0].change_log.should match_array(["[name]","[message]","[name]"]) + end + + it 'should return false for changes_state?' do + str = <<EOF +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ Start Resource ] [name] +logtype: [machinename]: LCM: [ Skip Set ] [name] +logtype: [machinename]: LCM: [ End Resource ] [name] +logtype: [machinename]: LCM: [ End Set ] +EOF + resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str) + resources[0].changes_state?.should be_false + end + + it 'should return an empty array for change_log if changes_state? is false' do + str = <<EOF +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ Start Resource ] [name] +logtype: [machinename]: LCM: [ Skip Set ] [name] +logtype: [machinename]: LCM: [ End Resource ] [name] +logtype: [machinename]: LCM: [ End Set ] +EOF + resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str) + resources[0].change_log.should be_empty + end + end + + context 'Incorrectly formatted output from LCM' do + it 'should allow missing a [End Resource] when its the last one and still find all the resource' do + str = <<-EOF +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ Start Resource ] [name] +logtype: [machinename]: LCM: [ Start Test ] +logtype: [machinename]: LCM: [ End Test ] +logtype: [machinename]: LCM: [ Skip Set ] +logtype: [machinename]: LCM: [ End Resource ] +logtype: [machinename]: LCM: [ Start Resource ] [name2] +logtype: [machinename]: LCM: [ Start Test ] +logtype: [machinename]: LCM: [ End Test ] +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ End Set ] +logtype: [machinename]: LCM: [ End Set ] +EOF + + resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str) + resources[0].changes_state?.should be_false + resources[1].changes_state?.should be_true + end + + it 'should allow missing a [End Resource] when its the first one and still find all the resource' do + str = <<-EOF +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ Start Resource ] [name] +logtype: [machinename]: LCM: [ Start Test ] +logtype: [machinename]: LCM: [ End Test ] +logtype: [machinename]: LCM: [ Skip Set ] +logtype: [machinename]: LCM: [ Start Resource ] [name2] +logtype: [machinename]: LCM: [ Start Test ] +logtype: [machinename]: LCM: [ End Test ] +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ End Set ] +logtype: [machinename]: LCM: [ End Resource ] +logtype: [machinename]: LCM: [ End Set ] +EOF + + resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str) + resources[0].changes_state?.should be_false + resources[1].changes_state?.should be_true + end + + it 'should allow missing set and end resource and assume an unconverged resource in this case' do + str = <<-EOF +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ Start Resource ] [name] +logtype: [machinename]: LCM: [ Start Test ] +logtype: [machinename]: LCM: [ End Test ] +logtype: [machinename]: LCM: [ Start Resource ] [name2] +logtype: [machinename]: LCM: [ Start Test ] +logtype: [machinename]: LCM: [ End Test ] +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ End Set ] +logtype: [machinename]: LCM: [ End Resource ] +logtype: [machinename]: LCM: [ End Set ] +EOF + resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str) + resources[0].changes_state?.should be_true + resources[0].name.should eql('[name]') + resources[1].changes_state?.should be_true + resources[1].name.should eql('[name2]') + end + end +end diff --git a/spec/unit/util/dsc/local_configuration_manager_spec.rb b/spec/unit/util/dsc/local_configuration_manager_spec.rb new file mode 100644 index 0000000000..fb6664bd40 --- /dev/null +++ b/spec/unit/util/dsc/local_configuration_manager_spec.rb @@ -0,0 +1,134 @@ +# +# Author:: Adam Edwards <adamed@getchef.com> +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef' +require 'chef/util/dsc/local_configuration_manager' + +describe Chef::Util::DSC::LocalConfigurationManager do + + let(:lcm) { Chef::Util::DSC::LocalConfigurationManager.new(nil, 'tmp') } + + let(:normal_lcm_output) { <<-EOH +logtype: [machinename]: LCM: [ Start Set ] +logtype: [machinename]: LCM: [ Start Resource ] [name] +logtype: [machinename]: LCM: [ End Resource ] [name] +logtype: [machinename]: LCM: [ End Set ] +EOH + } + + let(:no_whatif_lcm_output) { <<-EOH +Start-DscConfiguration : A parameter cannot be found that matches parameter name 'whatif'. +At line:1 char:123 ++ run-somecommand -whatif ++ ~~~~~~~~ + + CategoryInfo : InvalidArgument: (:) [Start-DscConfiguration], ParameterBindingException + + FullyQualifiedErrorId : NamedParameterNotFound,SomeCompany.SomeAssembly.Commands.RunSomeCommand +EOH + } + + let(:dsc_resource_import_failure_output) { <<-EOH +PowerShell provider MSFT_xWebsite failed to execute Test-TargetResource functionality with error message: Please ensure that WebAdministration module is installed. + CategoryInfo : InvalidOperation: (:) [], CimException + FullyQualifiedErrorId : ProviderOperationExecutionFailure + PSComputerName : . PowerShell provider MSFT_xWebsite failed to execute Test-TargetResource functionality with error message: Please ensure that WebAdministration module is installed. + CategoryInfo : InvalidOperation: (:) [], CimException + FullyQualifiedErrorId : ProviderOperationExecutionFailure + PSComputerName : . The SendConfigurationApply function did not succeed. + CategoryInfo : NotSpecified: (root/Microsoft/...gurationManager:String) [], CimException + FullyQualifiedErrorId : MI RESULT 1 + PSComputerName : . +EOH + } + + let(:lcm_status) { + double("LCM cmdlet status", :stderr => lcm_standard_error, :return_value => lcm_standard_output, :succeeded? => lcm_cmdlet_success) + } + + describe 'test_configuration method invocation' do + context 'when interacting with the LCM using a PowerShell cmdlet' do + before(:each) do + allow(lcm).to receive(:run_configuration_cmdlet).and_return(lcm_status) + end + context 'that returns successfully' do + before(:each) do + allow(lcm).to receive(:run_configuration_cmdlet).and_return(lcm_status) + end + + let(:lcm_standard_output) { normal_lcm_output } + let(:lcm_standard_error) { nil } + let(:lcm_cmdlet_success) { true } + + it 'should successfully return resource information for normally formatted output when cmdlet the cmdlet succeeds' do + test_configuration_result = lcm.test_configuration('config') + expect(test_configuration_result.class).to be(Array) + expect(test_configuration_result.length).to be > 0 + expect(Chef::Log).not_to receive(:warn) + end + end + + context 'that fails due to missing what-if switch in DSC resource cmdlet implementation' do + let(:lcm_standard_output) { '' } + let(:lcm_standard_error) { no_whatif_lcm_output } + let(:lcm_cmdlet_success) { false } + + it 'should should return a (possibly empty) array of ResourceInfo instances' do + expect(Chef::Log).to receive(:warn) + test_configuration_result = nil + expect {test_configuration_result = lcm.test_configuration('config')}.not_to raise_error + expect(test_configuration_result.class).to be(Array) + end + end + + context 'that fails due to a DSC resource not being imported before StartDSCConfiguration -whatif is executed' do + let(:lcm_standard_output) { '' } + let(:lcm_standard_error) { dsc_resource_import_failure_output } + let(:lcm_cmdlet_success) { false } + + 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 + test_configuration_result = nil + expect {test_configuration_result = lcm.test_configuration('config')}.not_to raise_error + end + + it 'should return a (possibly empty) array of ResourceInfo instances' do + expect(Chef::Log).to receive(:warn) + test_configuration_result = nil + expect {test_configuration_result = lcm.test_configuration('config')}.not_to raise_error + expect(test_configuration_result.class).to be(Array) + end + end + + context 'that fails due to an PowerShell cmdlet error that cannot be handled' 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) + 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) + 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) + 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) + end + end +end + diff --git a/spec/unit/util/powershell/cmdlet_spec.rb b/spec/unit/util/powershell/cmdlet_spec.rb new file mode 100644 index 0000000000..a964f607c8 --- /dev/null +++ b/spec/unit/util/powershell/cmdlet_spec.rb @@ -0,0 +1,106 @@ +# +# Author:: Jay Mundrawala <jdm@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' +require 'chef/util/powershell/cmdlet' + +describe Chef::Util::Powershell::Cmdlet do + before (:all) do + @node = Chef::Node.new + @cmdlet = Chef::Util::Powershell::Cmdlet.new(@node, 'Some-Commandlet') + end + + describe '#validate_switch_name!' do + it 'should not raise an error if a name contains all upper case letters' do + @cmdlet.send(:validate_switch_name!, "HELLO") + end + + it 'should not raise an error if the name contains all lower case letters' do + @cmdlet.send(:validate_switch_name!, "hello") + end + + it 'should not raise an error if no special characters are used except _' do + @cmdlet.send(:validate_switch_name!, "hello_world") + end + + %w{! @ # $ % ^ & * & * ( ) - = + \{ \} . ? < > \\ /}.each do |sym| + it "raises an Argument error if it configuration name contains #{sym}" do + expect { + @cmdlet.send(:validate_switch_name!, "Hello#{sym}") + }.to raise_error(ArgumentError) + end + end + end + + describe '#escape_parameter_value' do + # Is this list really complete? + %w{` " # '}.each do |c| + it "escapse #{c}" do + @cmdlet.send(:escape_parameter_value, "stuff #{c}").should eql("stuff `#{c}") + end + end + + it 'does not do anything to a string without special characters' do + @cmdlet.send(:escape_parameter_value, 'stuff').should eql('stuff') + end + end + + describe '#escape_string_parameter_value' do + it "surrounds a string with ''" do + @cmdlet.send(:escape_string_parameter_value, 'stuff').should eql("'stuff'") + end + end + + describe '#command_switches_string' do + it 'raises an ArgumentError if the key is not a symbol' do + expect { + @cmdlet.send(:command_switches_string, {'foo' => 'bar'}) + }.to raise_error(ArgumentError) + end + + it 'does not allow invalid switch names' do + expect { + @cmdlet.send(:command_switches_string, {:foo! => 'bar'}) + }.to raise_error(ArgumentError) + end + + it 'ignores switches with a false value' do + @cmdlet.send(:command_switches_string, {foo: false}).should eql('') + end + + it 'should correctly handle a value type of string' do + @cmdlet.send(:command_switches_string, {foo: 'bar'}).should eql("-foo 'bar'") + end + + it 'should correctly handle a value type of string even when it is 0 length' do + @cmdlet.send(:command_switches_string, {foo: ''}).should eql("-foo ''") + end + + it 'should not quote integers' do + @cmdlet.send(:command_switches_string, {foo: 1}).should eql("-foo 1") + end + + it 'should not quote floats' do + @cmdlet.send(:command_switches_string, {foo: 1.0}).should eql("-foo 1.0") + end + + it 'has just the switch when the value is true' do + @cmdlet.send(:command_switches_string, {foo: true}).should eql("-foo") + end + end +end diff --git a/spec/unit/workstation_config_loader_spec.rb b/spec/unit/workstation_config_loader_spec.rb index 78313aec37..de108ff6d7 100644 --- a/spec/unit/workstation_config_loader_spec.rb +++ b/spec/unit/workstation_config_loader_spec.rb @@ -88,7 +88,12 @@ describe Chef::WorkstationConfigLoader do let(:env_pwd) { "/path/to/cwd" } before do - env["PWD"] = env_pwd + if Chef::Platform.windows? + env["CD"] = env_pwd + else + env["PWD"] = env_pwd + end + allow(config_loader).to receive(:path_exists?).with("#{env_pwd}/.chef/knife.rb").and_return(true) allow(File).to receive(:exist?).with("#{env_pwd}/.chef").and_return(true) allow(File).to receive(:directory?).with("#{env_pwd}/.chef").and_return(true) @@ -229,13 +234,14 @@ describe Chef::WorkstationConfigLoader do let(:config_content) { "" } let(:explicit_config_location) do - t = Tempfile.new("#{described_class}-rspec-test") + # could use described_class, but remove all ':' from the path if so. + t = Tempfile.new("Chef-WorkstationConfigLoader-rspec-test") t.print(config_content) t.close t.path end - after { File.unlink(explicit_config_location) } + after { File.unlink(explicit_config_location) if File.exists?(explicit_config_location) } context "and is valid" do |