diff options
author | Mike Dodge <mikedodge04@fb.com> | 2015-10-27 11:15:21 -0700 |
---|---|---|
committer | Mike Dodge <mikedodge04@fb.com> | 2015-10-27 11:15:21 -0700 |
commit | 1130d5472a2da541a9c03c4aeb0eac52c71db8fc (patch) | |
tree | 0be91e774d58712ff7ae163b144de88d95442aad | |
parent | 8c042622fbf94acca813bd6d84d967d2ea08ec43 (diff) | |
parent | 30ff430b33dc15cf5d2b17bc0a2115b5bf68f52d (diff) | |
download | chef-1130d5472a2da541a9c03c4aeb0eac52c71db8fc.tar.gz |
Merge remote-tracking branch 'chef/master'
119 files changed, 1798 insertions, 773 deletions
diff --git a/.gitignore b/.gitignore index 39962d2f5f..953f58919f 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ Vagrantfile # Kitchen Tests Local Mode Data kitchen-tests/nodes/* + +# Temporary files present during spec runs +spec/data/test-dir diff --git a/CHANGELOG.md b/CHANGELOG.md index 251b68e50e..00041ca584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,59 @@ + ## Unreleased + +* [**Dave Eddy**](https://github.com/bahamas10) + [pr#3187](https://github.com/chef/chef/pull/3187) overhaul solaris SMF service provider +* [**Mikhail Zholobov**](https://github.com/legal90) + [pr#3192](https://github.com/chef/chef/pull/3192) provider/user/dscl: Set default gid to 20 +* [**Mikhail Zholobov**](https://github.com/legal90) + [pr#3193](https://github.com/chef/chef/pull/3193) provider/user/dscl: Set "comment" default value +* [**Jordan Evans**](https://github.com/jordane) + [pr#3263](https://github.com/chef/chef/pull/3263) `value_for_platform` should use `Chef::VersionConstraint::Platform` +* [**Scott McGillivray**](https://github.com/thechile) + [pr#3450](https://github.com/chef/chef/pull/3450) Fix 'knife cookbook show' to work on root files +* [**Aubrey Holland**](https://github.com/aub) + [pr#3986](https://github.com/chef/chef/pull/3986) fix errors when files go away during chown +* [**James Michael DuPont**](https://github.com/h4ck3rm1k3) + [pr#3973](https://github.com/chef/chef/pull/3973) better error reporting +* [**Michael Pereira**](https://github.com/MichaelPereira) + [pr#3968](https://github.com/chef/chef/pull/3968) Fix cookbook installation from supermarket on windows +* [**Yukihiko SAWANOBORI**](https://github.com/sawanoboly) + [pr#3941](https://github.com/chef/chef/pull/3941) allow reboot by reboot resource with chef-apply +* [**permyakovsv**](https://github.com/permyakovsv) + [pr#3901](https://github.com/chef/chef/pull/3901) Add tmux-split parameter to knife ssh +* [**Yukihiko SAWANOBORI**](https://github.com/sawanoboly) + [pr#3900](https://github.com/chef/chef/pull/3900) Add new option json attributes file to bootstraping +* [**Evan Gilman**](https://github.com/evan2645) + [pr#3864](https://github.com/chef/chef/pull/3864) Knife `bootstrap_environment` should use Explicit config before Implicit +* [**Ranjib Dey**](https://github.com/ranjib) + [pr#3834](https://github.com/chef/chef/pull/3834) Dont spit out stdout and stderr for execute resource failure, if its declared sensitive +* [**Jeff Blaine**](https://github.com/jblaine) + [pr#3776](https://github.com/chef/chef/pull/3776) Changes --hide-healthy to --hide-by-mins MINS +* [**dbresson**](https://github.com/dbresson) + [pr#3650](https://github.com/chef/chef/pull/3650) Define == for node objects +* [**Jordan Evans**](https://github.com/jordane) + [pr#3633](https://github.com/chef/chef/pull/3633) add the word group to `converge_by` call for group provider +* [**Patrick Connolly**](https://github.com/patcon) + [pr#3529](https://github.com/chef/chef/pull/3529) Allow user@hostname format for knife-bootstrap + +* [pr#3530](https://github.com/chef/chef/pull/3530) Allow using --sudo option with user's home folder in knife bootstrap +* [pr#3858](https://github.com/chef/chef/pull/3858) Remove duplicate 'Accept' header in spec +* [pr#3911](https://github.com/chef/chef/pull/3911) Avoid subclassing Struct.new +* [pr#3990](https://github.com/chef/chef/pull/3990) Use SHA256 instead of MD5 for `registry_key` when data is not displayable +* [pr#4034](https://github.com/chef/chef/pull/4034) add optional ruby-profiling with --profile-ruby +* [pr#3119](https://github.com/chef/chef/pull/3119) allow removing user, even if their GID isn't resolvable +* [pr#4068](https://github.com/chef/chef/pull/4068) update messaging from LWRP to Custom Resource in logging and spec +* [pr#4021](https://github.com/chef/chef/pull/4021) add missing requires for Chef::DSL::Recipe to LWRPBase +* [pr#3597](https://github.com/chef/chef/pull/3597) print STDOUT from the powershell_script +* [pr#4091](https://github.com/chef/chef/pull/4091) Allow downloading of root_files in a chef repository + +## 12.5.1 + +* [**Ranjib Dey**](https://github.com/ranjib): + [pr#3588](https://github.com/chef/chef/pull/3588) Count skipped resources among total resources in doc formatter + +## 12.5.1 + * [**Ranjib Dey**](https://github.com/ranjib): [pr#3588](https://github.com/chef/chef/pull/3588) Count skipped resources among total resources in doc formatter * [**John Kerry**](https://github.com/jkerry): diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e7f2e05f06..b2f9ece975 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ We utilize **Github Issues** for issue tracking and contributions. You can contr We have a 3 step process that utilizes **Github Issues**: -1. Sign or be added to an existing [Contributor License Agreement (CLA)](https://supermarket.getchef.com/become-a-contributor). +1. Sign or be added to an existing [Contributor License Agreement (CLA)](https://supermarket.chef.io/become-a-contributor). 2. Create a Github Pull Request. 3. Do [Code Review](#cr) with the **Chef Engineering Team** or **Chef Core Committers** on the pull request. @@ -21,7 +21,7 @@ Chef is built to last. We strive to ensure high quality throughout the Chef expe this, we require a couple of things for all pull requests to Chef: 1. **Tests:** To ensure high quality code and protect against future regressions, we require all the - code in Chef to have at least unit test coverage. See the [spec/unit](https://github.com/opscode/chef/tree/master/spec/unit) + code in Chef to have at least unit test coverage. See the [spec/unit](https://github.com/chef/chef/tree/master/spec/unit) directory for the existing tests and use ```bundle exec rake spec``` to run them. 2. **Green Travis Run:** We use [Travis CI](https://travis-ci.org/) in order to run our tests continuously on all the pull requests. We require the Travis runs to succeed on every pull @@ -63,7 +63,7 @@ You can watch the recordings of the old Code Review hangouts on the [opscodebtm] Licensing is very important to open source projects. It helps ensure the software continues to be available under the terms that the author desired. -Chef uses [the Apache 2.0 license](https://github.com/opscode/chef/blob/master/LICENSE) +Chef uses [the Apache 2.0 license](https://github.com/chef/chef/blob/master/LICENSE) to strike a balance between open contribution and allowing you to use the software however you would like to. @@ -81,10 +81,10 @@ To make a good faith effort to ensure these criteria are met, Chef requires an I It only takes a few minutes to complete a CLA, and you retain the copyright to your contribution. You can complete our - [Individual CLA](https://supermarket.getchef.com/icla-signatures/new) online. + [Individual CLA](https://supermarket.chef.io/icla-signatures/new) online. If you're contributing on behalf of your employer and they retain the copyright for your works, have your employer fill out our - [Corporate CLA](https://supermarket.getchef.com/ccla-signatures/new) instead. + [Corporate CLA](https://supermarket.chef.io/ccla-signatures/new) instead. ### Chef Obvious Fix Policy @@ -109,7 +109,7 @@ As a rule of thumb, changes are obvious fixes if they do not introduce any new f ``` ------------------------------------------------------------------------ commit 370adb3f82d55d912b0cf9c1d1e99b132a8ed3b5 -Author: danielsdeleo <dan@opscode.com> +Author: danielsdeleo <dan@chef.io> Date: Wed Sep 18 11:44:40 2013 -0700 Fix typo in config file docs. @@ -126,12 +126,12 @@ Chef Issue Tracking is handled using Github Issues. If you are familiar with Chef and know the component that is causing you a problem or if you have a feature request on a specific component you can file an issue in the corresponding Github project. All of our Open Source Software can be found in our - [Github organization](https://github.com/opscode/). + [Github organization](https://github.com/chef/). There is also a listing of the various Chef products and where to file issues that can be found in the Chef docs in the [community contributions section](https://docs.chef.io/community_contributions.html#issues-and-bug-reports). -Otherwise you can file your issue in the [Chef project](https://github.com/opscode/chef/issues) +Otherwise you can file your issue in the [Chef project](https://github.com/chef/chef/issues) and we will make sure it gets filed against the appropriate project. In order to decrease the back and forth in issues, and to help us get to the bottom of them quickly @@ -160,17 +160,17 @@ In order to decrease the back and forth in issues, and to help us get to the bot ### Useful Github Queries -Contributions go through a review process to improve code quality and avoid regressions. Managing a large number of contributions requires a workflow to provide queues for work such as triage, code review, and merging. A semi-formal process has evolved over the life of the project. Chef maintains this process pending community development and acceptance of an [RFC](https://github.com/opscode/chef-rfc). These queries will help track contributions through this process: +Contributions go through a review process to improve code quality and avoid regressions. Managing a large number of contributions requires a workflow to provide queues for work such as triage, code review, and merging. A semi-formal process has evolved over the life of the project. Chef maintains this process pending community development and acceptance of an [RFC](https://github.com/chef/chef-rfc). These queries will help track contributions through this process: -* [Issues that are not assigned to a team](https://github.com/opscode/chef/issues?q=is%3Aopen+-label%3AAIX+-label%3ABSD+-label%3Awindows+-label%3A%22Chef+Core%22++-label%3A%22Dev+Tools%22+-label%3AUbuntu+-label%3A%22Enterprise+Linux%22+-label%3A%22Ready+For+Merge%22+-label%3AMac+-label%3ASolaris+) -* [Untriaged Issues](https://github.com/opscode/chef/issues?q=is%3Aopen+is%3Aissue+-label%3ABug+-label%3AEnhancement+-label%3A%22Tech+Cleanup%22+-label%3A%22Ready+For+Merge%22) -* [PRs to be Reviewed](https://github.com/opscode/chef/labels/Pending%20Maintainer%20Review) -* [Suitable for First Contribution](https://github.com/opscode/chef/labels/Easy) +* [Issues that are not assigned to a team](https://github.com/chef/chef/issues?q=is%3Aopen+-label%3AAIX+-label%3ABSD+-label%3Awindows+-label%3A%22Chef+Core%22++-label%3A%22Dev+Tools%22+-label%3AUbuntu+-label%3A%22Enterprise+Linux%22+-label%3A%22Ready+For+Merge%22+-label%3AMac+-label%3ASolaris+) +* [Untriaged Issues](https://github.com/chef/chef/issues?q=is%3Aopen+is%3Aissue+-label%3ABug+-label%3AEnhancement+-label%3A%22Tech+Cleanup%22+-label%3A%22Ready+For+Merge%22) +* [PRs to be Reviewed](https://github.com/chef/chef/labels/Pending%20Maintainer%20Review) +* [Suitable for First Contribution](https://github.com/chef/chef/labels/Easy) ## <a name="release"></a> Chef Release Cycles Our primary shipping vehicle is operating system specific packages that includes - all the requirements of Chef. We call these [Omnibus packages](https://github.com/opscode/omnibus-ruby) + all the requirements of Chef. We call these [Omnibus packages](https://github.com/chef/omnibus) We also release our software as gems to [Rubygems](https://rubygems.org/) but we strongly recommend using Chef packages since they are the only combination of native libraries & @@ -190,7 +190,7 @@ We frequently make `alpha` and `beta` releases with version numbers that look li We do a `Minor` release approximately every 3 months and `Patch` releases on a when-needed basis for regressions, significant bugs, and security issues. -Announcements of releases are available on [Chef Blog](http://www.getchef.com/blog) when they are +Announcements of releases are available on [Chef Blog](http://www.chef.io/blog) when they are available. ## Chef Community @@ -203,6 +203,6 @@ Chef is made possible by a strong community of developers and system administrat Also here are some additional pointers to some awesome Chef content: -* [Chef Docs](http://docs.opscode.com/) -* [Learn Chef](https://learnchef.opscode.com/) -* [Chef Inc](http://www.getchef.com/) +* [Chef Docs](https://docs.chef.io/) +* [Learn Chef](https://learn.chef.io/) +* [Chef Inc](https://www.chef.io/) @@ -3,7 +3,7 @@ gemspec :name => "chef" gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby" -gem 'chef-config', path: "chef-config" +gem 'chef-config', path: "chef-config" if File.exists?(__FILE__ + '../chef-config') group(:docgen) do gem "yard" @@ -18,6 +18,8 @@ group(:maintenance) do end group(:development, :test) do + # for profiling + gem "ruby-prof" gem "simplecov" gem 'rack', "~> 1.5.1" diff --git a/MAINTAINERS.md b/MAINTAINERS.md index d270a7c7ac..0023cb91bf 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -197,6 +197,7 @@ To mention the team, use @chef/client-freebsd * [Cory Stephenson](https://github.com/Aevin1387) * [David Aronsohn](https://github.com/tbunnyman) +* [Bryant Lippert](https://github.com/agentmeerkat) ## OpenBSD diff --git a/MAINTAINERS.toml b/MAINTAINERS.toml index ca0a2e67bc..12eb536fa3 100644 --- a/MAINTAINERS.toml +++ b/MAINTAINERS.toml @@ -193,7 +193,8 @@ The specific components of Chef related to a given platform - including (but not maintainers = [ "Aevin1387", - "tBunnyMan" + "tBunnyMan", + "agentmeerkat" ] [Org.Components.Subsystems.OpenBSD] @@ -236,6 +237,10 @@ The specific components of Chef related to a given platform - including (but not Name = "Cory Stephenson" GitHub = "Aevin1387" + [people.agentmeerkat] + Name = "Bryant Lippert" + GitHub = "agentmeerkat" + [people.btm] Name = "Bryan McLellan" GitHub = "btm" @@ -1,5 +1,5 @@ # Chef -[![Code Climate](https://codeclimate.com/github/opscode/chef.png)](https://codeclimate.com/github/opscode/chef) +[![Code Climate](https://codeclimate.com/github/chef/chef.png)](https://codeclimate.com/github/chef/chef) [![Build Status Master](https://travis-ci.org/chef/chef.svg?branch=master)](https://travis-ci.org/chef/chef) [![Build Status Master](https://ci.appveyor.com/api/projects/status/github/chef/chef?branch=master&svg=true&passingText=master%20-%20Ok&pendingText=master%20-%20Pending&failingText=master%20-%20Failing)](https://ci.appveyor.com/project/Chef/chef/branch/master) @@ -9,7 +9,7 @@ Want to try Chef? Get started with [learnchef](https://learn.chef.io) * Source: [http://github.com/chef/chef/tree/master](http://github.com/chef/chef/tree/master) * Tickets/Issues: [https://github.com/chef/chef/issues](https://github.com/chef/chef/issues) * IRC: `#chef` and `#chef-hacking` on Freenode -* Mailing list: [http://lists.opscode.com](http://lists.opscode.com) +* Mailing list: [https://discourse.chef.io](https://discourse.chef.io) Chef is a configuration management tool designed to bring automation to your entire infrastructure. @@ -1 +1 @@ -12.5.0 +12.5.1 diff --git a/chef-config/VERSION b/chef-config/VERSION index b7d7205d7e..2b4b4d7cb5 100644 --- a/chef-config/VERSION +++ b/chef-config/VERSION @@ -1 +1 @@ -12.5.0 +12.5.1 diff --git a/chef-config/chef-config.gemspec b/chef-config/chef-config.gemspec index 475bd0f2d2..6619f04169 100644 --- a/chef-config/chef-config.gemspec +++ b/chef-config/chef-config.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency(rspec, "~> 3.2") end - spec.files = %w(Rakefile LICENSE README.md) + + spec.files = %w(Rakefile LICENSE README.md) + Dir.glob("*.gemspec") + Dir.glob("{lib,spec}/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } spec.bindir = "bin" diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb index ca9e132f7c..654a18eac1 100644 --- a/chef-config/lib/chef-config/version.rb +++ b/chef-config/lib/chef-config/version.rb @@ -21,7 +21,7 @@ module ChefConfig CHEFCONFIG_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__))) - VERSION = '12.5.0' + VERSION = '12.5.1' end # diff --git a/chef-windows.gemspec b/chef-windows.gemspec index 2a7ad86f92..1c72d9102a 100644 --- a/chef-windows.gemspec +++ b/chef-windows.gemspec @@ -9,7 +9,7 @@ gemspec.add_dependency "win32-event", "~> 0.6.1" gemspec.add_dependency "win32-eventlog", "~> 0.6.2" gemspec.add_dependency "win32-mmap", "~> 0.4.1" gemspec.add_dependency "win32-mutex", "~> 0.4.2" -gemspec.add_dependency "win32-process", "~> 0.7.5" +gemspec.add_dependency "win32-process", "~> 0.8.2" gemspec.add_dependency "win32-service", "~> 0.8.7" gemspec.add_dependency "windows-api", "~> 0.4.4" gemspec.add_dependency "wmi-lite", "~> 1.0" diff --git a/chef.gemspec b/chef.gemspec index b1abe7670a..96673f391a 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -52,5 +52,5 @@ Gem::Specification.new do |s| s.executables = %w( chef-client chef-solo knife chef-shell chef-apply ) s.require_path = 'lib' - s.files = %w(Gemfile Rakefile LICENSE README.md CONTRIBUTING.md) + Dir.glob("{distro,lib,tasks,spec}/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } + s.files = %w(Gemfile Rakefile LICENSE README.md CONTRIBUTING.md) + Dir.glob("{distro,lib,tasks,spec}/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } + Dir.glob("*.gemspec") end diff --git a/lib/chef/api_client/registration.rb b/lib/chef/api_client/registration.rb index de5fc7ac3d..4882323293 100644 --- a/lib/chef/api_client/registration.rb +++ b/lib/chef/api_client/registration.rb @@ -68,7 +68,8 @@ class Chef def assert_destination_writable! if (File.exists?(destination) && !File.writable?(destination)) or !File.writable?(File.dirname(destination)) - raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination} - check permissions?" + abs_path = File.expand_path(destination) + raise Chef::Exceptions::CannotWritePrivateKey, "I can't write your private key to #{abs_path} - check permissions?" end end diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb index 243b441119..4c559542f1 100644 --- a/lib/chef/application/apply.rb +++ b/lib/chef/application/apply.rb @@ -29,7 +29,7 @@ require 'chef/resources' class Chef::Application::Apply < Chef::Application - banner "Usage: chef-apply [RECIPE_FILE] [-e RECIPE_TEXT] [-s]" + banner "Usage: chef-apply [RECIPE_FILE | -e RECIPE_TEXT | -s] [OPTIONS]" option :execute, :short => "-e RECIPE_TEXT", @@ -97,10 +97,16 @@ class Chef::Application::Apply < Chef::Application :description => 'Enable whyrun mode', :boolean => true + option :profile_ruby, + :long => "--[no-]profile-ruby", + :description => "Dump complete Ruby call graph stack of entire Chef run (expert only)", + :boolean => true, + :default => false + option :color, :long => '--[no-]color', :boolean => true, - :default => !Chef::Platform.windows?, + :default => true, :description => "Use colored output, defaults to enabled" option :minimal_ohai, @@ -190,6 +196,7 @@ class Chef::Application::Apply < Chef::Application ensure @recipe_fh.close end + Chef::Platform::Rebooter.reboot_if_needed!(runner) end def run_application diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb index 148257ab89..5fac34196d 100644 --- a/lib/chef/application/client.rb +++ b/lib/chef/application/client.rb @@ -2,7 +2,7 @@ # Author:: AJ Christensen (<aj@opscode.com) # Author:: Christopher Brown (<cb@opscode.com>) # Author:: Mark Mzyk (mmzyk@opscode.com) -# Copyright:: Copyright (c) 2008 Opscode, Inc. +# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -55,11 +55,17 @@ class Chef::Application::Client < Chef::Application :boolean => true, :default => false + option :profile_ruby, + :long => "--[no-]profile-ruby", + :description => "Dump complete Ruby call graph stack of entire Chef run (expert only)", + :boolean => true, + :default => false + option :color, :long => '--[no-]color', :boolean => true, - :default => !Chef::Platform.windows?, - :description => "Use colored output, defaults to false on Windows, true otherwise" + :default => true, + :description => "Use colored output, defaults to enabled" option :log_level, :short => "-l LEVEL", diff --git a/lib/chef/application/knife.rb b/lib/chef/application/knife.rb index af5216ae00..d169a5dab5 100644 --- a/lib/chef/application/knife.rb +++ b/lib/chef/application/knife.rb @@ -44,8 +44,8 @@ class Chef::Application::Knife < Chef::Application option :color, :long => '--[no-]color', :boolean => true, - :default => !Chef::Platform.windows?, - :description => "Use colored output, defaults to false on Windows, true otherwise" + :default => true, + :description => "Use colored output, defaults to enabled" option :environment, :short => "-E ENVIRONMENT", diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb index 5bb2a1ceb0..4b472e9662 100644 --- a/lib/chef/application/solo.rb +++ b/lib/chef/application/solo.rb @@ -1,7 +1,7 @@ # # Author:: AJ Christensen (<aj@opscode.com>) # Author:: Mark Mzyk (mmzyk@opscode.com) -# Copyright:: Copyright (c) 2008 Opscode, Inc. +# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,6 +52,12 @@ class Chef::Application::Solo < Chef::Application :boolean => true, :default => false + option :profile_ruby, + :long => "--[no-]profile-ruby", + :description => "Dump complete Ruby call graph stack of entire Chef run (expert only)", + :boolean => true, + :default => false + option :color, :long => '--[no-]color', :boolean => true, diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb index b42a01cfdb..2551582c3a 100644 --- a/lib/chef/application/windows_service.rb +++ b/lib/chef/application/windows_service.rb @@ -189,7 +189,11 @@ class Chef config_params += " -c #{Chef::Config[:config_file]}" unless Chef::Config[:config_file].nil? config_params += " -L #{Chef::Config[:log_location]}" unless Chef::Config[:log_location] == STDOUT # Starts a new process and waits till the process exits - result = shell_out("chef-client #{config_params}", :timeout => Chef::Config[:windows_service][:watchdog_timeout]) + result = shell_out( + "chef-client #{config_params}", + :timeout => Chef::Config[:windows_service][:watchdog_timeout], + :logger => Chef::Log + ) Chef::Log.debug "#{result.stdout}" Chef::Log.debug "#{result.stderr}" rescue Mixlib::ShellOut::CommandTimeout => e diff --git a/lib/chef/chef_fs/file_system/file_system_entry.rb b/lib/chef/chef_fs/file_system/file_system_entry.rb index 8611aa2e0f..478631eac2 100644 --- a/lib/chef/chef_fs/file_system/file_system_entry.rb +++ b/lib/chef/chef_fs/file_system/file_system_entry.rb @@ -83,7 +83,7 @@ class Chef end def exists? - File.exists?(file_path) && parent.can_have_child?(name, dir?) + File.exists?(file_path) && (parent.nil? || parent.can_have_child?(name, dir?)) end def read diff --git a/lib/chef/client.rb b/lib/chef/client.rb index 7d5d463242..b2a00a7d01 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -232,6 +232,8 @@ class Chef # @return Always returns true. # def run + start_profiling + run_error = nil runlock = RunLock.new(Chef::Config.lockfile) @@ -271,7 +273,7 @@ class Chef if Chef::Config[:why_run] == true # why_run should probably be renamed to why_converge - Chef::Log.debug("Not running controls in 'why_run' mode - this mode is used to see potential converge changes") + Chef::Log.debug("Not running controls in 'why-run' mode - this mode is used to see potential converge changes") elsif Chef::Config[:audit_mode] != :disabled audit_error = run_audits(run_context) end @@ -284,6 +286,9 @@ class Chef run_completed_successfully events.run_completed(node) + # keep this inside the main loop to get exception backtraces + end_profiling + # rebooting has to be the last thing we do, no exceptions. Chef::Platform::Rebooter.reboot_if_needed!(node) rescue Exception => run_error @@ -891,6 +896,28 @@ class Chef attr_reader :override_runlist attr_reader :specific_recipes + def profiling_prereqs! + require 'ruby-prof' + rescue LoadError + raise "You must have the ruby-prof gem installed in order to use --profile-ruby" + end + + def start_profiling + return unless Chef::Config[:profile_ruby] + profiling_prereqs! + RubyProf.start + end + + def end_profiling + return unless Chef::Config[:profile_ruby] + profiling_prereqs! + path = Chef::FileCache.create_cache_path("graph_profile.out", false) + File.open(path, "w+") do |file| + RubyProf::GraphPrinter.new(RubyProf.stop).print(file, {}) + end + Chef::Log.warn("Ruby execution profile dumped to #{path}") + end + def empty_directory?(path) !File.exists?(path) || (Dir.entries(path).size <= 2) end diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb index bcbfcbeec8..dbccdbc0a8 100644 --- a/lib/chef/cookbook/cookbook_version_loader.rb +++ b/lib/chef/cookbook/cookbook_version_loader.rb @@ -91,7 +91,7 @@ class Chef remove_ignored_files if empty? - Chef::Log.warn "found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping." + Chef::Log.warn "Found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping." end @cookbook_settings end diff --git a/lib/chef/cookbook/remote_file_vendor.rb b/lib/chef/cookbook/remote_file_vendor.rb index 9d895b168e..7868430227 100644 --- a/lib/chef/cookbook/remote_file_vendor.rb +++ b/lib/chef/cookbook/remote_file_vendor.rb @@ -69,7 +69,7 @@ class Chef Chef::FileCache.move_to(raw_file.path, cache_filename) else Chef::Log.debug("Not fetching #{cache_filename}, as the cache is up to date.") - Chef::Log.debug("current checksum: #{current_checksum}; manifest checksum: #{found_manifest_record['checksum']})") + Chef::Log.debug("Current checksum: #{current_checksum}; manifest checksum: #{found_manifest_record['checksum']})") end full_path_cache_filename = Chef::FileCache.load(cache_filename, false) diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 3cdfd8c10b..0e9617f98c 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -4,7 +4,7 @@ # Author:: Tim Hinderliter (<tim@opscode.com>) # Author:: Seth Falcon (<seth@opscode.com>) # Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright 2008-2011 Opscode, Inc. +# Copyright:: Copyright 2008-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -450,7 +450,11 @@ class Chef end relative_search_path.map {|relative_path| File.join(segment.to_s, relative_path)} else - [File.join(segment, path)] + if segment.to_sym == :root_files + [path] + else + [File.join(segment, path)] + end end end private :preferences_for_path diff --git a/lib/chef/data_bag.rb b/lib/chef/data_bag.rb index 8475774fa1..401ba6f63f 100644 --- a/lib/chef/data_bag.rb +++ b/lib/chef/data_bag.rb @@ -144,7 +144,7 @@ class Chef def save begin if Chef::Config[:why_run] - Chef::Log.warn("In whyrun mode, so NOT performing data bag save.") + Chef::Log.warn("In why-run mode, so NOT performing data bag save.") else create end diff --git a/lib/chef/data_bag_item.rb b/lib/chef/data_bag_item.rb index 9f92e26c50..31c9b69330 100644 --- a/lib/chef/data_bag_item.rb +++ b/lib/chef/data_bag_item.rb @@ -170,7 +170,7 @@ class Chef r = chef_server_rest begin if Chef::Config[:why_run] - Chef::Log.warn("In whyrun mode, so NOT performing data bag item save.") + Chef::Log.warn("In why-run mode, so NOT performing data bag item save.") else r.put_rest("data/#{data_bag}/#{item_id}", self) end diff --git a/lib/chef/digester.rb b/lib/chef/digester.rb index 75c4e76859..f2b496b785 100644 --- a/lib/chef/digester.rb +++ b/lib/chef/digester.rb @@ -38,7 +38,11 @@ class Chef end def generate_checksum(file) - checksum_file(file, OpenSSL::Digest::SHA256.new) + if file.is_a?(StringIO) + checksum_io(file, OpenSSL::Digest::SHA256.new) + else + checksum_file(file, OpenSSL::Digest::SHA256.new) + end end def self.generate_md5_checksum_for_file(*args) diff --git a/lib/chef/dsl/platform_introspection.rb b/lib/chef/dsl/platform_introspection.rb index 2a52010a70..a6bd12d2ef 100644 --- a/lib/chef/dsl/platform_introspection.rb +++ b/lib/chef/dsl/platform_introspection.rb @@ -50,7 +50,7 @@ class Chef def value_for_node(node) platform, version = node[:platform].to_s, node[:platform_version].to_s - # Check if we match a version constraint via Chef::VersionConstraint and Chef::Version::Platform + # Check if we match a version constraint via Chef::VersionConstraint::Platform and Chef::Version::Platform matched_value = match_versions(node) if @values.key?(platform) && @values[platform].key?(version) @values[platform][version] @@ -76,11 +76,11 @@ class Chef keys = @values[platform].keys keys.each do |k| begin - if Chef::VersionConstraint.new(k).include?(node_version) + if Chef::VersionConstraint::Platform.new(k).include?(node_version) key_matches << k end rescue Chef::Exceptions::InvalidVersionConstraint => e - Chef::Log.debug "Caught InvalidVersionConstraint. This means that a key in value_for_platform cannot be interpreted as a Chef::VersionConstraint." + Chef::Log.debug "Caught InvalidVersionConstraint. This means that a key in value_for_platform cannot be interpreted as a Chef::VersionConstraint::Platform." Chef::Log.debug(e) end end diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb index 1c9a58be23..585a3db174 100644 --- a/lib/chef/event_dispatch/base.rb +++ b/lib/chef/event_dispatch/base.rb @@ -384,6 +384,9 @@ class Chef def deprecation(message, location=caller(2..2)[0]) end + def run_list_expanded(run_list_expansion) + end + # An uncategorized message. This supports the case that a user needs to # pass output that doesn't fit into one of the callbacks above. Note that # there's no semantic information about the content or importance of the diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb index 966a3f32ec..f3e55539a9 100644 --- a/lib/chef/event_dispatch/dispatcher.rb +++ b/lib/chef/event_dispatch/dispatcher.rb @@ -32,8 +32,11 @@ class Chef mth = s.method(method_name) # Trim arguments to match what the subscriber expects to allow # adding new arguments without breaking compat. - args = args.take(mth.arity) if mth.arity < args.size && mth.arity >= 0 - mth.call(*args) + if mth.arity < args.size && mth.arity >= 0 + mth.call(*args.take(mth.arity)) + else + mth.call(*args) + end end end diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 6e7ff2e24a..855c86d9cc 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -74,6 +74,11 @@ class Chef class InvalidPrivateKey < ArgumentError; end class MissingKeyAttribute < ArgumentError; end class KeyCommandInputError < ArgumentError; end + class BootstrapCommandInputError < ArgumentError + def initialize + super "You cannot pass both --json-attributes and --json-attribute-file. Please pass one or none." + end + end class InvalidKeyArgument < ArgumentError; end class InvalidKeyAttribute < ArgumentError; end class InvalidUserAttribute < ArgumentError; end diff --git a/lib/chef/file_access_control/unix.rb b/lib/chef/file_access_control/unix.rb index c53d832414..8178d5fbf2 100644 --- a/lib/chef/file_access_control/unix.rb +++ b/lib/chef/file_access_control/unix.rb @@ -79,18 +79,18 @@ class Chef def should_update_owner? if target_uid.nil? # the user has not specified a permission on the new resource, so we never manage it with FAC - Chef::Log.debug("found target_uid == nil, so no owner was specified on resource, not managing owner") + Chef::Log.debug("Found target_uid == nil, so no owner was specified on resource, not managing owner") return false elsif current_uid.nil? # the user has specified a permission, and we are creating a file, so always enforce permissions - Chef::Log.debug("found current_uid == nil, so we are creating a new file, updating owner") + Chef::Log.debug("Found current_uid == nil, so we are creating a new file, updating owner") return true elsif target_uid != current_uid # the user has specified a permission, and it does not match the file, so fix the permission - Chef::Log.debug("found target_uid != current_uid, updating owner") + Chef::Log.debug("Found target_uid != current_uid, updating owner") return true else - Chef::Log.debug("found target_uid == current_uid, not updating owner") + Chef::Log.debug("Found target_uid == current_uid, not updating owner") # the user has specified a permission, but it matches the file, so behave idempotently return false end @@ -138,18 +138,18 @@ class Chef def should_update_group? if target_gid.nil? # the user has not specified a permission on the new resource, so we never manage it with FAC - Chef::Log.debug("found target_gid == nil, so no group was specified on resource, not managing group") + Chef::Log.debug("Found target_gid == nil, so no group was specified on resource, not managing group") return false elsif current_gid.nil? # the user has specified a permission, and we are creating a file, so always enforce permissions - Chef::Log.debug("found current_gid == nil, so we are creating a new file, updating group") + Chef::Log.debug("Found current_gid == nil, so we are creating a new file, updating group") return true elsif target_gid != current_gid # the user has specified a permission, and it does not match the file, so fix the permission - Chef::Log.debug("found target_gid != current_gid, updating group") + Chef::Log.debug("Found target_gid != current_gid, updating group") return true else - Chef::Log.debug("found target_gid == current_gid, not updating group") + Chef::Log.debug("Found target_gid == current_gid, not updating group") # the user has specified a permission, but it matches the file, so behave idempotently return false end @@ -187,20 +187,20 @@ class Chef def should_update_mode? if target_mode.nil? # the user has not specified a permission on the new resource, so we never manage it with FAC - Chef::Log.debug("found target_mode == nil, so no mode was specified on resource, not managing mode") + Chef::Log.debug("Found target_mode == nil, so no mode was specified on resource, not managing mode") return false elsif current_mode.nil? # the user has specified a permission, and we are creating a file, so always enforce permissions - Chef::Log.debug("found current_mode == nil, so we are creating a new file, updating mode") + Chef::Log.debug("Found current_mode == nil, so we are creating a new file, updating mode") return true elsif target_mode != current_mode # the user has specified a permission, and it does not match the file, so fix the permission - Chef::Log.debug("found target_mode != current_mode, updating mode") + Chef::Log.debug("Found target_mode != current_mode, updating mode") return true elsif suid_bit_set? and (should_update_group? or should_update_owner?) return true else - Chef::Log.debug("found target_mode == current_mode, not updating mode") + Chef::Log.debug("Found target_mode == current_mode, not updating mode") # the user has specified a permission, but it matches the file, so behave idempotently return false end diff --git a/lib/chef/file_content_management/deploy/cp.rb b/lib/chef/file_content_management/deploy/cp.rb index c6b1d6cd11..ea378c2e5d 100644 --- a/lib/chef/file_content_management/deploy/cp.rb +++ b/lib/chef/file_content_management/deploy/cp.rb @@ -34,12 +34,12 @@ class Chef # class Cp def create(file) - Chef::Log.debug("touching #{file} to create it") + Chef::Log.debug("Touching #{file} to create it") FileUtils.touch(file) end def deploy(src, dst) - Chef::Log.debug("copying temporary file #{src} into place at #{dst}") + Chef::Log.debug("Copying temporary file #{src} into place at #{dst}") FileUtils.cp(src, dst) end end diff --git a/lib/chef/file_content_management/deploy/mv_unix.rb b/lib/chef/file_content_management/deploy/mv_unix.rb index 758c594e50..9712486424 100644 --- a/lib/chef/file_content_management/deploy/mv_unix.rb +++ b/lib/chef/file_content_management/deploy/mv_unix.rb @@ -30,19 +30,19 @@ class Chef def create(file) # this is very simple, but it ensures that ownership and file modes take # good defaults, in particular mode needs to obey umask on create - Chef::Log.debug("touching #{file} to create it") + Chef::Log.debug("Touching #{file} to create it") FileUtils.touch(file) end def deploy(src, dst) # we are only responsible for content so restore the dst files perms - Chef::Log.debug("reading modes from #{dst} file") + Chef::Log.debug("Reading modes from #{dst} file") stat = ::File.stat(dst) mode = stat.mode & 07777 uid = stat.uid gid = stat.gid - Chef::Log.debug("applying mode = #{mode.to_s(8)}, uid = #{uid}, gid = #{gid} to #{src}") + Chef::Log.debug("Applying mode = #{mode.to_s(8)}, uid = #{uid}, gid = #{gid} to #{src}") # i own the inode, so should be able to at least chmod it ::File.chmod(mode, src) @@ -67,7 +67,7 @@ class Chef Chef::Log.warn("Could not set gid = #{gid} on #{src}, file modes not preserved") end - Chef::Log.debug("moving temporary file #{src} into place at #{dst}") + Chef::Log.debug("Moving temporary file #{src} into place at #{dst}") FileUtils.mv(src, dst) end end diff --git a/lib/chef/file_content_management/deploy/mv_windows.rb b/lib/chef/file_content_management/deploy/mv_windows.rb index 0d16da9717..e2951dba4c 100644 --- a/lib/chef/file_content_management/deploy/mv_windows.rb +++ b/lib/chef/file_content_management/deploy/mv_windows.rb @@ -35,7 +35,7 @@ class Chef ACL = Security::ACL def create(file) - Chef::Log.debug("touching #{file} to create it") + Chef::Log.debug("Touching #{file} to create it") FileUtils.touch(file) end diff --git a/lib/chef/file_content_management/tempfile.rb b/lib/chef/file_content_management/tempfile.rb index 2dde0ce21b..6e1624f9a4 100644 --- a/lib/chef/file_content_management/tempfile.rb +++ b/lib/chef/file_content_management/tempfile.rb @@ -49,7 +49,7 @@ class Chef end end - raise Chef::Exceptions::FileContentStagingError(errors) if tf.nil? + raise Chef::Exceptions::FileContentStagingError, errors if tf.nil? # We always process the tempfile in binmode so that we # preserve the line endings of the content. diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb index 3c22d2e763..621fadce40 100644 --- a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb +++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb @@ -108,7 +108,7 @@ class Chef def culprit_backtrace_entry @culprit_backtrace_entry ||= begin bt_entry = filtered_bt.first - Chef::Log.debug("backtrace entry for compile error: '#{bt_entry}'") + Chef::Log.debug("Backtrace entry for compile error: '#{bt_entry}'") bt_entry end end @@ -138,7 +138,7 @@ class Chef begin filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/i } r = exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }} - Chef::Log.debug("filtered backtrace of compile error: #{r.join(",")}") + Chef::Log.debug("Filtered backtrace of compile error: #{r.join(",")}") r end end diff --git a/lib/chef/http/decompressor.rb b/lib/chef/http/decompressor.rb index e1d776da60..a61f510e7d 100644 --- a/lib/chef/http/decompressor.rb +++ b/lib/chef/http/decompressor.rb @@ -79,10 +79,10 @@ class Chef else case response[CONTENT_ENCODING] when GZIP - Chef::Log.debug "decompressing gzip response" + Chef::Log.debug "Decompressing gzip response" Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body) when DEFLATE - Chef::Log.debug "decompressing deflate response" + Chef::Log.debug "Decompressing deflate response" Zlib::Inflate.inflate(response.body) else response.body diff --git a/lib/chef/json_compat.rb b/lib/chef/json_compat.rb index d0b3b4c7f8..5e9f29a60b 100644 --- a/lib/chef/json_compat.rb +++ b/lib/chef/json_compat.rb @@ -41,6 +41,7 @@ class Chef CHEF_RESOURCECOLLECTION = "Chef::ResourceCollection".freeze CHEF_RESOURCESET = "Chef::ResourceCollection::ResourceSet".freeze CHEF_RESOURCELIST = "Chef::ResourceCollection::ResourceList".freeze + CHEF_RUNLISTEXPANSION = "Chef::RunListExpansion".freeze class <<self diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index 93236225a2..8502ccb8eb 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -42,7 +42,7 @@ class Chef Chef::Knife::Ssh.load_deps end - banner "knife bootstrap FQDN (options)" + banner "knife bootstrap [SSH_USER@]FQDN (options)" option :ssh_user, :short => "-x USERNAME", @@ -122,6 +122,11 @@ class Chef :description => "Execute the bootstrap via sudo", :boolean => true + option :preserve_home, + :long => "--sudo-preserve-home", + :description => "Preserve non-root user HOME environment variable with sudo", + :boolean => true + option :use_sudo_password, :long => "--use-sudo-password", :description => "Execute the bootstrap via sudo with password", @@ -164,7 +169,13 @@ class Chef :long => "--json-attributes", :description => "A JSON string to be added to the first run of chef-client", :proc => lambda { |o| Chef::JSONCompat.parse(o) }, - :default => {} + :default => nil + + option :first_boot_attributes_from_file, + :long => "--json-attribute-file FILE", + :description => "A JSON file to be used to the first run of chef-client", + :proc => lambda { |o| Chef::JSONCompat.parse(File.read(o)) }, + :default => nil option :host_key_verify, :long => "--[no-]host-key-verify", @@ -256,13 +267,25 @@ class Chef "chef-full" end + def host_descriptor + Array(@name_args).first + end + # The server_name is the DNS or IP we are going to connect to, it is not necessarily # the node name, the fqdn, or the hostname of the server. This is a public API hook # which knife plugins use or inherit and override. # # @return [String] The DNS or IP that bootstrap will connect to def server_name - Array(@name_args).first + if host_descriptor + @server_name ||= host_descriptor.split('@').reverse[0] + end + end + + def user_name + if host_descriptor + @user_name ||= host_descriptor.split('@').reverse[1] + end end def bootstrap_template @@ -317,13 +340,22 @@ class Chef ) end + def first_boot_attributes + @config[:first_boot_attributes] || @config[:first_boot_attributes_from_file] || {} + end + def render_template + @config[:first_boot_attributes] = first_boot_attributes template_file = find_template template = IO.read(template_file).chomp Erubis::Eruby.new(template).evaluate(bootstrap_context) end def run + if @config[:first_boot_attributes] && @config[:first_boot_attributes_from_file] + raise Chef::Exceptions::BootstrapCommandInputError + end + validate_name_args! validate_options! @@ -358,7 +390,7 @@ class Chef if config[:ssh_password] raise else - ui.info("Failed to authenticate #{config[:ssh_user]} - trying password auth") + ui.info("Failed to authenticate #{knife_ssh.config[:ssh_user]} - trying password auth") knife_ssh_with_password_auth.run end end @@ -389,7 +421,7 @@ class Chef ssh = Chef::Knife::Ssh.new ssh.ui = ui ssh.name_args = [ server_name, ssh_command ] - ssh.config[:ssh_user] = config[:ssh_user] + ssh.config[:ssh_user] = user_name || config[:ssh_user] ssh.config[:ssh_password] = config[:ssh_password] ssh.config[:ssh_port] = config[:ssh_port] ssh.config[:ssh_gateway] = config[:ssh_gateway] @@ -412,7 +444,8 @@ class Chef command = render_template if config[:use_sudo] - command = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -SH #{command}" : "sudo -H #{command}" + sudo_prefix = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -S " : "sudo " + command = config[:preserve_home] ? "#{sudo_prefix} #{command}" : "#{sudo_prefix} -H #{command}" end command diff --git a/lib/chef/knife/cookbook_site_install.rb b/lib/chef/knife/cookbook_site_install.rb index aee8b7fa94..cc68fe7897 100644 --- a/lib/chef/knife/cookbook_site_install.rb +++ b/lib/chef/knife/cookbook_site_install.rb @@ -142,7 +142,11 @@ class Chef def extract_cookbook(upstream_file, version) ui.info("Uncompressing #{@cookbook_name} version #{version}.") # FIXME: Detect if we have the bad tar from git on Windows: https://github.com/opscode/chef/issues/1753 - shell_out!("tar zxvf #{convert_path upstream_file}", :cwd => @install_path) + extract_command="tar zxvf \"#{convert_path upstream_file}\"" + if Chef::Platform.windows? + extract_command << " --force-local" + end + shell_out!(extract_command, :cwd => @install_path) end def clear_existing_files(cookbook_path) diff --git a/lib/chef/knife/cookbook_site_unshare.rb b/lib/chef/knife/cookbook_site_unshare.rb index b34282ec32..77bb18322c 100644 --- a/lib/chef/knife/cookbook_site_unshare.rb +++ b/lib/chef/knife/cookbook_site_unshare.rb @@ -38,7 +38,7 @@ class Chef exit 1 end - confirm "Do you really want to unshare the cookbook #{@cookbook_name}" + confirm "Do you really want to unshare all versions of the cookbook #{@cookbook_name}" begin rest.delete_rest "https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}" @@ -48,7 +48,7 @@ class Chef exit 1 end - ui.info "Unshared cookbook #{@cookbook_name}" + ui.info "Unshared all versions of the cookbook #{@cookbook_name}" end end diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index d210b9418f..8613a50cb4 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -40,7 +40,7 @@ class Chef end def bootstrap_environment - @chef_config[:environment] + @config[:environment] end def validation_key diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index 62af853e88..89a9608c60 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -111,6 +111,12 @@ class Chef :boolean => true, :proc => Proc.new { :raise } + option :tmux_split, + :long => "--tmux-split", + :description => "Split tmux window.", + :boolean => true, + :default => false + def session config[:on_error] ||= :skip ssh_error_handler = Proc.new do |server| @@ -225,6 +231,8 @@ class Chef if config[:identity_file] opts[:keys] = File.expand_path(config[:identity_file]) opts[:keys_only] = true + elsif config[:ssh_password] + opts[:password] = config[:ssh_password] end # Don't set the keys to nil if we don't have them. forward_agent = config[:forward_agent] || ssh_config[:forward_agent] @@ -402,7 +410,11 @@ class Chef new_window_cmds = lambda do if session.servers_for.size > 1 [""] + session.servers_for[1..-1].map do |server| - "new-window -a -n '#{server.host}' #{ssh_dest.call(server)}" + if config[:tmux_split] + "split-window #{ssh_dest.call(server)}; tmux select-layout tiled" + else + "new-window -a -n '#{server.host}' #{ssh_dest.call(server)}" + end end else [] @@ -465,7 +477,7 @@ class Chef session.servers_for.each do |server| cssh_cmd << " #{server.user ? "#{server.user}@#{server.host}" : server.host}" end - Chef::Log.debug("starting cssh session with command: #{cssh_cmd}") + Chef::Log.debug("Starting cssh session with command: #{cssh_cmd}") exec(cssh_cmd) end diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb index 95f2c724ff..1a61b035cb 100644 --- a/lib/chef/knife/status.rb +++ b/lib/chef/knife/status.rb @@ -44,7 +44,11 @@ class Chef option :hide_healthy, :short => "-H", :long => "--hide-healthy", - :description => "Hide nodes that have run chef in the last hour" + :description => "Hide nodes that have run chef in the last hour. [DEPRECATED] Use --hide-by-mins MINS instead" + + option :hide_by_mins, + :long => "--hide-by-mins MINS", + :description => "Hide nodes that have run chef in the last MINS minutes" def append_to_query(term) @query << " AND " unless @query.empty? @@ -68,12 +72,21 @@ class Chef append_to_query("chef_environment:#{config[:environment]}") if config[:environment] if config[:hide_healthy] + ui.warn("-H / --hide-healthy is deprecated. Use --hide-by-mins MINS instead") time = Time.now.to_i # AND NOT is not valid lucene syntax, so don't use append_to_query @query << " " unless @query.empty? @query << "NOT ohai_time:[#{(time - 60*60).to_s} TO #{time.to_s}]" end + if config[:hide_by_mins] + hidemins = config[:hide_by_mins].to_i + time = Time.now.to_i + # AND NOT is not valid lucene syntax, so don't use append_to_query + @query << " " unless @query.empty? + @query << "NOT ohai_time:[#{(time - hidemins*60).to_s} TO #{time.to_s}]" + end + @query = @query.empty? ? "*:*" : @query all_nodes = [] diff --git a/lib/chef/mixin/properties.rb b/lib/chef/mixin/properties.rb new file mode 100644 index 0000000000..85abe4427e --- /dev/null +++ b/lib/chef/mixin/properties.rb @@ -0,0 +1,302 @@ +require 'chef/delayed_evaluator' +require 'chef/mixin/params_validate' +require 'chef/property' + +class Chef + module Mixin + module Properties + module ClassMethods + # + # The list of properties defined on this resource. + # + # Everything defined with `property` is in this list. + # + # @param include_superclass [Boolean] `true` to include properties defined + # on superclasses; `false` or `nil` to return the list of properties + # directly on this class. + # + # @return [Hash<Symbol,Property>] The list of property names and types. + # + def properties(include_superclass=true) + if include_superclass + result = {} + ancestors.reverse_each { |c| result.merge!(c.properties(false)) if c.respond_to?(:properties) } + result + else + @properties ||= {} + end + end + + # + # Create a property on this resource class. + # + # If a superclass has this property, or if this property has already been + # defined by this resource, this will *override* the previous value. + # + # @param name [Symbol] The name of the property. + # @param type [Object,Array<Object>] The type(s) of this property. + # If present, this is prepended to the `is` validation option. + # @param options [Hash<Symbol,Object>] Validation options. + # @option options [Object,Array] :is An object, or list of + # objects, that must match the value using Ruby's `===` operator + # (`options[:is].any? { |v| v === value }`). + # @option options [Object,Array] :equal_to An object, or list + # of objects, that must be equal to the value using Ruby's `==` + # operator (`options[:is].any? { |v| v == value }`) + # @option options [Regexp,Array<Regexp>] :regex An object, or + # list of objects, that must match the value with `regex.match(value)`. + # @option options [Class,Array<Class>] :kind_of A class, or + # list of classes, that the value must be an instance of. + # @option options [Hash<String,Proc>] :callbacks A hash of + # messages -> procs, all of which match the value. The proc must + # return a truthy or falsey value (true means it matches). + # @option options [Symbol,Array<Symbol>] :respond_to A method + # name, or list of method names, the value must respond to. + # @option options [Symbol,Array<Symbol>] :cannot_be A property, + # or a list of properties, that the value cannot have (such as `:nil` or + # `:empty`). The method with a questionmark at the end is called on the + # value (e.g. `value.empty?`). If the value does not have this method, + # it is considered valid (i.e. if you don't respond to `empty?` we + # assume you are not empty). + # @option options [Proc] :coerce A proc which will be called to + # transform the user input to canonical form. The value is passed in, + # and the transformed value returned as output. Lazy values will *not* + # be passed to this method until after they are evaluated. Called in the + # context of the resource (meaning you can access other properties). + # @option options [Boolean] :required `true` if this property + # must be present; `false` otherwise. This is checked after the resource + # is fully initialized. + # @option options [Boolean] :name_property `true` if this + # property defaults to the same value as `name`. Equivalent to + # `default: lazy { name }`, except that #property_is_set? will + # return `true` if the property is set *or* if `name` is set. + # @option options [Boolean] :name_attribute Same as `name_property`. + # @option options [Object] :default The value this property + # will return if the user does not set one. If this is `lazy`, it will + # be run in the context of the instance (and able to access other + # properties). + # @option options [Boolean] :desired_state `true` if this property is + # part of desired state. Defaults to `true`. + # @option options [Boolean] :identity `true` if this property + # is part of object identity. Defaults to `false`. + # + # @example Bare property + # property :x + # + # @example With just a type + # property :x, String + # + # @example With just options + # property :x, default: 'hi' + # + # @example With type and options + # property :x, String, default: 'hi' + # + def property(name, type=NOT_PASSED, **options) + name = name.to_sym + + options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) } + + options[:instance_variable_name] = :"@#{name}" if !options.has_key?(:instance_variable_name) + options.merge!(name: name, declared_in: self) + + if type == NOT_PASSED + # If a type is not passed, the property derives from the + # superclass property (if any) + if properties.has_key?(name) + property = properties[name].derive(**options) + else + property = property_type(**options) + end + + # If a Property is specified, derive a new one from that. + elsif type.is_a?(Property) || (type.is_a?(Class) && type <= Property) + property = type.derive(**options) + + # If a primitive type was passed, combine it with "is" + else + if options[:is] + options[:is] = ([ type ] + [ options[:is] ]).flatten(1) + else + options[:is] = type + end + property = property_type(**options) + end + + local_properties = properties(false) + local_properties[name] = property + + property.emit_dsl + end + + # + # Create a reusable property type that can be used in multiple properties + # in different resources. + # + # @param options [Hash<Symbol,Object>] Validation options. see #property for + # the list of options. + # + # @example + # property_type(default: 'hi') + # + def property_type(**options) + Property.derive(**options) + end + + # + # Create a lazy value for assignment to a default value. + # + # @param block The block to run when the value is retrieved. + # + # @return [Chef::DelayedEvaluator] The lazy value + # + def lazy(&block) + DelayedEvaluator.new(&block) + end + + # + # Get or set the list of desired state properties for this resource. + # + # State properties are properties that describe the desired state + # of the system, such as file permissions or ownership. + # In general, state properties are properties that could be populated by + # examining the state of the system (e.g., File.stat can tell you the + # permissions on an existing file). Contrarily, properties that are not + # "state properties" usually modify the way Chef itself behaves, for example + # by providing additional options for a package manager to use when + # installing a package. + # + # This list is used by the Chef client auditing system to extract + # information from resources to describe changes made to the system. + # + # This method is unnecessary when declaring properties with `property`; + # properties are added to state_properties by default, and can be turned off + # with `desired_state: false`. + # + # ```ruby + # property :x # part of desired state + # property :y, desired_state: false # not part of desired state + # ``` + # + # @param names [Array<Symbol>] A list of property names to set as desired + # state. + # + # @return [Array<Property>] All properties in desired state. + # + def state_properties(*names) + if !names.empty? + names = names.map { |name| name.to_sym }.uniq + + local_properties = properties(false) + # Add new properties to the list. + names.each do |name| + property = properties[name] + if !property + self.property name, instance_variable_name: false, desired_state: true + elsif !property.desired_state? + self.property name, desired_state: true + end + end + + # If state_attrs *excludes* something which is currently desired state, + # mark it as desired_state: false. + local_properties.each do |name,property| + if property.desired_state? && !names.include?(name) + self.property name, desired_state: false + end + end + end + + properties.values.select { |property| property.desired_state? } + end + + # + # Set the identity of this resource to a particular set of properties. + # + # This drives #identity, which returns data that uniquely refers to a given + # resource on the given node (in such a way that it can be correlated + # across Chef runs). + # + # This method is unnecessary when declaring properties with `property`; + # properties can be added to identity during declaration with + # `identity: true`. + # + # ```ruby + # property :x, identity: true # part of identity + # property :y # not part of identity + # ``` + # + # If no properties are marked as identity, "name" is considered the identity. + # + # @param names [Array<Symbol>] A list of property names to set as the identity. + # + # @return [Array<Property>] All identity properties. + # + def identity_properties(*names) + if !names.empty? + names = names.map { |name| name.to_sym } + + # Add or change properties that are not part of the identity. + names.each do |name| + property = properties[name] + if !property + self.property name, instance_variable_name: false, identity: true + elsif !property.identity? + self.property name, identity: true + end + end + + # If identity_properties *excludes* something which is currently part of + # the identity, mark it as identity: false. + properties.each do |name,property| + if property.identity? && !names.include?(name) + + self.property name, identity: false + end + end + end + + result = properties.values.select { |property| property.identity? } + result = [ properties[:name] ] if result.empty? + result + end + + def included(other) + other.extend ClassMethods + end + end + + def self.included(other) + other.extend ClassMethods + end + + include Chef::Mixin::ParamsValidate + + # + # Whether this property has been set (or whether it has a default that has + # been retrieved). + # + # @param name [Symbol] The name of the property. + # @return [Boolean] `true` if the property has been set. + # + def property_is_set?(name) + property = self.class.properties[name.to_sym] + raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property + property.is_set?(self) + end + + # + # Clear this property as if it had never been set. It will thereafter return + # the default. + # been retrieved). + # + # @param name [Symbol] The name of the property. + # + def reset_property(name) + property = self.class.properties[name.to_sym] + raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property + property.reset(self) + end + end + end +end diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 668ddbdc35..83ec7e2550 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -440,7 +440,7 @@ class Chef self.tags # make sure they're defined - automatic_attrs[:recipes] = expansion.recipes.with_fully_qualified_names_and_version_constraints + automatic_attrs[:recipes] = expansion.recipes.with_duplicate_names automatic_attrs[:expanded_run_list] = expansion.recipes.with_fully_qualified_names_and_version_constraints automatic_attrs[:roles] = expansion.roles @@ -609,7 +609,7 @@ class Chef # so then POST to create. begin if Chef::Config[:why_run] - Chef::Log.warn("In whyrun mode, so NOT performing node save.") + Chef::Log.warn("In why-run mode, so NOT performing node save.") else chef_server_rest.put_rest("nodes/#{name}", data_for_save) end @@ -647,6 +647,14 @@ class Chef "node[#{name}]" end + def ==(other) + if other.kind_of?(self.class) + self.name == other.name + else + false + end + end + def <=>(other_node) self.name <=> other_node.name end diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb index 26b98afa52..2c6d644e42 100644 --- a/lib/chef/policy_builder/expand_node_object.rb +++ b/lib/chef/policy_builder/expand_node_object.rb @@ -33,6 +33,9 @@ class Chef # expands the run_list on a node object and then queries the chef-server # to find the correct set of cookbooks, given version constraints of the # node's environment. + # + # Note that this class should only be used via PolicyBuilder::Dynamic and + # not instantiated directly. class ExpandNodeObject attr_reader :events @@ -94,6 +97,33 @@ class Chef run_context end + # DEPRECATED: As of Chef 12.5, chef selects either policyfile mode or + # "expand node" mode dynamically, based on the content of the node + # object, first boot JSON, and config. This happens in + # PolicyBuilder::Dynamic, which selects the implementation during + # #load_node and then delegates to either ExpandNodeObject or Policyfile + # implementations as appropriate. Tools authors should update their code + # to create a PolicyBuilder::Dynamc policy builder and allow it to select + # the proper implementation. + def load_node + Chef.log_deprecation("ExpandNodeObject#load_node is deprecated. Please use Chef::PolicyBuilder::Dynamic instead of using ExpandNodeObject directly") + + events.node_load_start(node_name, config) + Chef::Log.debug("Building node object for #{node_name}") + + @node = + if Chef::Config[:solo] + Chef::Node.build(node_name) + else + Chef::Node.find_or_create(node_name) + end + finish_load_node(node) + node + rescue Exception => e + events.node_load_failed(node_name, e, config) + raise + end + def finish_load_node(node) @node = node end @@ -124,6 +154,7 @@ class Chef Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]") events.node_load_completed(node, @expanded_run_list_with_versions, Chef::Config) + events.run_list_expanded(@run_list_expansion) node end diff --git a/lib/chef/property.rb b/lib/chef/property.rb index c1207d9132..e97d8f9607 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -479,6 +479,8 @@ class Chef # A type accepts nil explicitly if "is" allows nil, it validates as nil, *and* is not simply # an empty type. # + # A type is presumed to accept nil if it does coercion (which must handle nil). + # # These examples accept nil explicitly: # ```ruby # property :a, [ String, nil ] @@ -510,7 +512,8 @@ class Chef # # @api private def explicitly_accepts_nil?(resource) - options.has_key?(:is) && resource.send(:_pv_is, { name => nil }, name, options[:is], raise_error: false) + options.has_key?(:coerce) || + (options.has_key?(:is) && resource.send(:_pv_is, { name => nil }, name, options[:is], raise_error: false)) end def get_value(resource) diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index 3138704a55..e22f11d9be 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -265,7 +265,7 @@ class Chef provider_class = self @included_resource_dsl_module = Module.new do extend Forwardable - define_singleton_method(:to_s) { "#{resource_class} forwarder module" } + define_singleton_method(:to_s) { "forwarder module for #{provider_class}" } define_singleton_method(:inspect) { to_s } # Add a delegator for each explicit property that will get the *current* value # of the property by default instead of the *actual* value. diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb index 77a0410593..c59200e717 100644 --- a/lib/chef/provider/deploy.rb +++ b/lib/chef/provider/deploy.rb @@ -276,7 +276,7 @@ class Chef def enforce_ownership converge_by("force ownership of #{@new_resource.deploy_to} to #{@new_resource.group}:#{@new_resource.user}") do - FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to) + FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to, :force => true) Chef::Log.info("#{@new_resource} set user to #{@new_resource.user}") if @new_resource.user Chef::Log.info("#{@new_resource} set group to #{@new_resource.group}") if @new_resource.group end diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb index b44112c19e..30de0d3b9e 100644 --- a/lib/chef/provider/execute.rb +++ b/lib/chef/provider/execute.rb @@ -41,7 +41,7 @@ class Chef def define_resource_requirements # @todo: this should change to raise in some appropriate major version bump. if creates && creates_relative? && !cwd - Chef::Log.warn "Providing a relative path for the creates attribute without the cwd is deprecated and will be changed to fail (CHEF-3819)" + Chef::Log.warn "Providing a relative path for the creates attribute without the cwd is deprecated and will be changed to fail in the future (CHEF-3819)" end end @@ -58,7 +58,16 @@ class Chef end converge_by("execute #{description}") do - result = shell_out!(command, opts) + begin + shell_out!(command, opts) + rescue Mixlib::ShellOut::ShellCommandFailed + if sensitive? + raise Mixlib::ShellOut::ShellCommandFailed, + "Command execution failed. STDOUT/STDERR suppressed for sensitive resource" + else + raise + end + end Chef::Log.info("#{new_resource} ran successfully") end end diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb index a802758dce..a1cf92058d 100644 --- a/lib/chef/provider/group.rb +++ b/lib/chef/provider/group.rb @@ -125,7 +125,7 @@ class Chef def action_create case @group_exists when false - converge_by("create #{@new_resource.group_name}") do + converge_by("create group #{@new_resource.group_name}") do create_group Chef::Log.info("#{@new_resource} created") end diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb index a96c382a01..9c7cd15bbf 100644 --- a/lib/chef/provider/lwrp_base.rb +++ b/lib/chef/provider/lwrp_base.rb @@ -19,6 +19,7 @@ # require 'chef/provider' +require 'chef/dsl/recipe' require 'chef/dsl/include_recipe' class Chef diff --git a/lib/chef/provider/package/openbsd.rb b/lib/chef/provider/package/openbsd.rb index 83fc09c8ae..7a6582363e 100644 --- a/lib/chef/provider/package/openbsd.rb +++ b/lib/chef/provider/package/openbsd.rb @@ -111,7 +111,7 @@ class Chef end end results = results.reject(&:nil?) - Chef::Log.debug("candidate versions of '#{new_resource.package_name}' are '#{results}'") + Chef::Log.debug("Candidate versions of '#{new_resource.package_name}' are '#{results}'") case results.length when 0 [] diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb index 81454380a3..aff8dc9326 100644 --- a/lib/chef/provider/package/yum.rb +++ b/lib/chef/provider/package/yum.rb @@ -791,7 +791,7 @@ class Chef "/usr/bin/python" end rescue StandardError => e - Chef::Log.warn("An error occured attempting to determine correct python executable. Using default.") + Chef::Log.warn("An error occurred attempting to determine correct python executable. Using default.") Chef::Log.debug(e) "/usr/bin/python" end diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb index b876b6d8ee..e04efb6b42 100644 --- a/lib/chef/provider/powershell_script.rb +++ b/lib/chef/provider/powershell_script.rb @@ -16,6 +16,7 @@ # limitations under the License. # +require 'chef/platform/query_helpers' require 'chef/provider/windows_script' class Chef @@ -40,16 +41,24 @@ class Chef # Powershell.exe is always in "v1.0" folder (for backwards compatibility) interpreter_path = Chef::Util::PathHelper.join(basepath, "WindowsPowerShell", "v1.0", interpreter) - "\"#{interpreter_path}\" #{flags} \"#{script_file.path}\"" - end - - def flags # Must use -File rather than -Command to launch the script # file created by the base class that contains the script # code -- otherwise, powershell.exe does not propagate the # error status of a failed Windows process that ran at the # end of the script, it gets changed to '1'. - interpreter_flags = [default_interpreter_flags, '-File'].join(' ') + # + # Nano only supports -Command + cmd = "\"#{interpreter_path}\" #{flags}" + if Chef::Platform.windows_nano_server? + cmd << " -Command \". '#{script_file.path}'\"" + else + cmd << " -File \"#{script_file.path}\"" + end + cmd + end + + def flags + interpreter_flags = [*default_interpreter_flags].join(' ') if ! (@new_resource.flags.nil?) interpreter_flags = [@new_resource.flags, interpreter_flags].join(' ') @@ -87,7 +96,7 @@ EOH # written to the file system at this point, which is required since # the intent is to execute the code just written to it. user_script_file.close - validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command #{user_script_file.path}" + validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command \". '#{user_script_file.path}'\"" # Note that other script providers like bash allow syntax errors # to be suppressed by setting 'returns' to a value that the @@ -107,6 +116,8 @@ EOH end def default_interpreter_flags + return [] if Chef::Platform.windows_nano_server? + # Execution policy 'Bypass' is preferable since it doesn't require # user input confirmation for files such as PowerShell modules # downloaded from the Internet. However, 'Bypass' is not supported @@ -188,6 +199,9 @@ elseif ( $LASTEXITCODE -ne $null -and $LASTEXITCODE -ne 0 ) $exitstatus = $LASTEXITCODE } +# Print STDOUT for the script execution +Write-Output $chefscriptresult + # If this script is launched with -File, the process exit # status of PowerShell.exe will be $exitstatus. If it was # launched with -Command, it will be 0 if $exitstatus was 0, diff --git a/lib/chef/provider/remote_file/http.rb b/lib/chef/provider/remote_file/http.rb index f17ab5a56d..e1f1cb2da7 100644 --- a/lib/chef/provider/remote_file/http.rb +++ b/lib/chef/provider/remote_file/http.rb @@ -105,7 +105,7 @@ class Chef # case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz, # which is not what you wanted. if uri.to_s =~ /gz$/ - Chef::Log.debug("turning gzip compression off due to filename ending in gz") + Chef::Log.debug("Turning gzip compression off due to filename ending in gz") opts[:disable_gzip] = true end opts diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb index 33a9778715..3ad11a7672 100644 --- a/lib/chef/provider/service/redhat.rb +++ b/lib/chef/provider/service/redhat.rb @@ -61,8 +61,10 @@ class Chef end requirements.assert(:start, :enable, :reload, :restart) do |a| - a.assertion { !@service_missing } - a.failure_message Chef::Exceptions::Service, "#{new_resource}: unable to locate the init.d script!" + a.assertion do + custom_command_for_action?(action) || !@service_missing + end + a.failure_message Chef::Exceptions::Service, "#{new_resource}: No custom command for #{action} specified and unable to locate the init.d script!" a.whyrun "Assuming service would be disabled. The init script is not presently installed." end end diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb index eaea6bb1ab..7040503c6b 100644 --- a/lib/chef/provider/service/solaris.rb +++ b/lib/chef/provider/service/solaris.rb @@ -30,35 +30,39 @@ class Chef def initialize(new_resource, run_context=nil) super - @init_command = "/usr/sbin/svcadm" - @status_command = "/bin/svcs -l" + @init_command = "/usr/sbin/svcadm" + @status_command = "/bin/svcs" @maintenace = false end def load_current_resource @current_resource = Chef::Resource::Service.new(@new_resource.name) @current_resource.service_name(@new_resource.service_name) - unless ::File.exists? "/bin/svcs" - raise Chef::Exceptions::Service, "/bin/svcs does not exist!" + + [@init_command, @status_command].each do |cmd| + unless ::File.executable? cmd then + raise Chef::Exceptions::Service, "#{cmd} not executable!" + end end @status = service_status.enabled + @current_resource end def enable_service - shell_out!("#{default_init_command} clear #{@new_resource.service_name}") if @maintenance - shell_out!("#{default_init_command} enable -s #{@new_resource.service_name}") + shell_out!(default_init_command, "clear", @new_resource.service_name) if @maintenance + shell_out!(default_init_command, "enable", "-s", @new_resource.service_name) end def disable_service - shell_out!("#{default_init_command} disable -s #{@new_resource.service_name}") + shell_out!(default_init_command, "disable", "-s", @new_resource.service_name) end alias_method :stop_service, :disable_service alias_method :start_service, :enable_service def reload_service - shell_out_with_systems_locale!("#{default_init_command} refresh #{@new_resource.service_name}") + shell_out!(default_init_command, "refresh", @new_resource.service_name) end def restart_service @@ -68,16 +72,38 @@ class Chef end def service_status - status = shell_out!("#{@status_command} #{@current_resource.service_name}", :returns => [0, 1]) - status.stdout.each_line do |line| - case line - when /state\s+online/ - @current_resource.enabled(true) - @current_resource.running(true) - when /state\s+maintenance/ - @maintenance = true - end + cmd = shell_out!(@status_command, "-l", @current_resource.service_name, :returns => [0, 1]) + # Example output + # $ svcs -l rsyslog + # fmri svc:/application/rsyslog:default + # name rsyslog logging utility + # enabled true + # state online + # next_state none + # state_time April 2, 2015 04:25:19 PM EDT + # logfile /var/svc/log/application-rsyslog:default.log + # restarter svc:/system/svc/restarter:default + # contract_id 1115271 + # dependency require_all/error svc:/milestone/multi-user:default (online) + # $ + + # load output into hash + status = {} + cmd.stdout.each_line do |line| + key, value = line.strip.split(/\s+/, 2) + status[key] = value + end + + # check service state + @maintenance = false + case status['state'] + when 'online' + @current_resource.enabled(true) + @current_resource.running(true) + when 'maintenance' + @maintenance = true end + unless @current_resource.enabled @current_resource.enabled(false) @current_resource.running(false) diff --git a/lib/chef/provider/template/content.rb b/lib/chef/provider/template/content.rb index a231bd509e..693b19a8c6 100644 --- a/lib/chef/provider/template/content.rb +++ b/lib/chef/provider/template/content.rb @@ -29,30 +29,30 @@ class Chef def template_location @template_file_cache_location ||= begin - template_finder.find(@new_resource.source, :local => @new_resource.local, :cookbook => @new_resource.cookbook) + template_finder.find(new_resource.source, :local => new_resource.local, :cookbook => new_resource.cookbook) end end private def file_for_provider - context = TemplateContext.new(@new_resource.variables) - context[:node] = @run_context.node + context = TemplateContext.new(new_resource.variables) + context[:node] = run_context.node context[:template_finder] = template_finder # helper variables - context[:cookbook_name] = @new_resource.cookbook_name unless context.keys.include?(:coookbook_name) - context[:recipe_name] = @new_resource.recipe_name unless context.keys.include?(:recipe_name) - context[:recipe_line_string] = @new_resource.source_line unless context.keys.include?(:recipe_line_string) - context[:recipe_path] = @new_resource.source_line_file unless context.keys.include?(:recipe_path) - context[:recipe_line] = @new_resource.source_line_number unless context.keys.include?(:recipe_line) - context[:template_name] = @new_resource.source unless context.keys.include?(:template_name) + context[:cookbook_name] = new_resource.cookbook_name unless context.keys.include?(:coookbook_name) + context[:recipe_name] = new_resource.recipe_name unless context.keys.include?(:recipe_name) + context[:recipe_line_string] = new_resource.source_line unless context.keys.include?(:recipe_line_string) + context[:recipe_path] = new_resource.source_line_file unless context.keys.include?(:recipe_path) + context[:recipe_line] = new_resource.source_line_number unless context.keys.include?(:recipe_line) + context[:template_name] = new_resource.source unless context.keys.include?(:template_name) context[:template_path] = template_location unless context.keys.include?(:template_path) - context._extend_modules(@new_resource.helper_modules) + context._extend_modules(new_resource.helper_modules) output = context.render_template(template_location) - tempfile = Tempfile.open("chef-rendered-template") + tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile tempfile.binmode tempfile.write(output) tempfile.close @@ -61,7 +61,7 @@ class Chef def template_finder @template_finder ||= begin - TemplateFinder.new(run_context, @new_resource.cookbook_name, @run_context.node) + TemplateFinder.new(run_context, new_resource.cookbook_name, run_context.node) end end end diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb index 244b11db98..76aefbf1c8 100644 --- a/lib/chef/provider/user.rb +++ b/lib/chef/provider/user.rb @@ -89,7 +89,7 @@ class Chef end def define_resource_requirements - requirements.assert(:all_actions) do |a| + requirements.assert(:create, :modify, :manage, :lock, :unlock) do |a| a.assertion { @group_name_resolved } a.failure_message Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{@new_resource.gid}" a.whyrun "group name #{@new_resource.gid} does not exist. This will cause group assignment to fail. Assuming this group will have been created previously." diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb index 0c0c85e18b..1faf2438ac 100644 --- a/lib/chef/provider/user/dscl.rb +++ b/lib/chef/provider/user/dscl.rb @@ -44,6 +44,10 @@ class Chef # This provider only supports Mac OSX versions 10.7 and above class Dscl < Chef::Provider::User + attr_accessor :user_info + attr_accessor :authentication_authority + attr_accessor :password_shadow_conversion_algorithm + provides :user, os: "darwin" def define_resource_requirements @@ -56,19 +60,19 @@ class Chef requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?("/usr/bin/dscl") } - a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{@new_resource}!") + a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{new_resource}!") end requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?("/usr/bin/plutil") } - a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{@new_resource}!") + a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{new_resource}!") end requirements.assert(:create, :modify, :manage) do |a| a.assertion do - if @new_resource.password && mac_osx_version_greater_than_10_7? + if new_resource.password && mac_osx_version_greater_than_10_7? # SALTED-SHA512 password shadow hashes are not supported on 10.8 and above. - !salted_sha512?(@new_resource.password) + !salted_sha512?(new_resource.password) else true end @@ -80,10 +84,10 @@ in 'password', with the associated 'salt' and 'iterations'.") requirements.assert(:create, :modify, :manage) do |a| a.assertion do - if @new_resource.password && mac_osx_version_greater_than_10_7? && salted_sha512_pbkdf2?(@new_resource.password) + if new_resource.password && mac_osx_version_greater_than_10_7? && salted_sha512_pbkdf2?(new_resource.password) # salt and iterations should be specified when # SALTED-SHA512-PBKDF2 password shadow hash is given - !@new_resource.salt.nil? && !@new_resource.iterations.nil? + !new_resource.salt.nil? && !new_resource.iterations.nil? else true end @@ -94,9 +98,9 @@ in 'password', with the associated 'salt' and 'iterations'.") requirements.assert(:create, :modify, :manage) do |a| a.assertion do - if @new_resource.password && !mac_osx_version_greater_than_10_7? + if new_resource.password && !mac_osx_version_greater_than_10_7? # On 10.7 SALTED-SHA512-PBKDF2 is not supported - !salted_sha512_pbkdf2?(@new_resource.password) + !salted_sha512_pbkdf2?(new_resource.password) else true end @@ -109,21 +113,21 @@ user password using shadow hash.") end def load_current_resource - @current_resource = Chef::Resource::User.new(@new_resource.username) - @current_resource.username(@new_resource.username) + @current_resource = Chef::Resource::User.new(new_resource.username) + current_resource.username(new_resource.username) @user_info = read_user_info - if @user_info - @current_resource.uid(dscl_get(@user_info, :uid)) - @current_resource.gid(dscl_get(@user_info, :gid)) - @current_resource.home(dscl_get(@user_info, :home)) - @current_resource.shell(dscl_get(@user_info, :shell)) - @current_resource.comment(dscl_get(@user_info, :comment)) - @authentication_authority = dscl_get(@user_info, :auth_authority) - - if @new_resource.password && dscl_get(@user_info, :password) == "********" + if user_info + current_resource.uid(dscl_get(user_info, :uid)) + current_resource.gid(dscl_get(user_info, :gid)) + current_resource.home(dscl_get(user_info, :home)) + current_resource.shell(dscl_get(user_info, :shell)) + current_resource.comment(dscl_get(user_info, :comment)) + @authentication_authority = dscl_get(user_info, :auth_authority) + + if new_resource.password && dscl_get(user_info, :password) == "********" # A password is set. Let's get the password information from shadow file - shadow_hash_binary = dscl_get(@user_info, :shadow_hash) + shadow_hash_binary = dscl_get(user_info, :shadow_hash) # Calling shell_out directly since we want to give an input stream shadow_hash_xml = convert_binary_plist_to_xml(shadow_hash_binary.string) @@ -132,26 +136,26 @@ user password using shadow hash.") if shadow_hash["SALTED-SHA512"] # Convert the shadow value from Base64 encoding to hex before consuming them @password_shadow_conversion_algorithm = "SALTED-SHA512" - @current_resource.password(shadow_hash["SALTED-SHA512"].string.unpack('H*').first) + current_resource.password(shadow_hash["SALTED-SHA512"].string.unpack('H*').first) elsif shadow_hash["SALTED-SHA512-PBKDF2"] @password_shadow_conversion_algorithm = "SALTED-SHA512-PBKDF2" # Convert the entropy from Base64 encoding to hex before consuming them - @current_resource.password(shadow_hash["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first) - @current_resource.iterations(shadow_hash["SALTED-SHA512-PBKDF2"]["iterations"]) + current_resource.password(shadow_hash["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first) + current_resource.iterations(shadow_hash["SALTED-SHA512-PBKDF2"]["iterations"]) # Convert the salt from Base64 encoding to hex before consuming them - @current_resource.salt(shadow_hash["SALTED-SHA512-PBKDF2"]["salt"].string.unpack('H*').first) + current_resource.salt(shadow_hash["SALTED-SHA512-PBKDF2"]["salt"].string.unpack('H*').first) else raise(Chef::Exceptions::User,"Unknown shadow_hash format: #{shadow_hash.keys.join(' ')}") end end - convert_group_name if @new_resource.gid + convert_group_name if new_resource.gid else @user_exists = false - Chef::Log.debug("#{@new_resource} user does not exist") + Chef::Log.debug("#{new_resource} user does not exist") end - @current_resource + current_resource end # @@ -190,15 +194,16 @@ user password using shadow hash.") # Create a user using dscl # def dscl_create_user - run_dscl("create /Users/#{@new_resource.username}") + run_dscl("create /Users/#{new_resource.username}") end # # Saves the specified Chef user `comment` into RealName attribute - # of Mac user. + # of Mac user. If `comment` is not specified, it takes `username` value. # def dscl_create_comment - run_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'") + comment = new_resource.comment || new_resource.username + run_dscl("create /Users/#{new_resource.username} RealName '#{comment}'") end # @@ -207,13 +212,14 @@ user password using shadow hash.") # from 200 if `system` is set, 500 otherwise. # def dscl_set_uid - @new_resource.uid(get_free_uid) if (@new_resource.uid.nil? || @new_resource.uid == '') + # XXX: mutates the new resource + new_resource.uid(get_free_uid) if (new_resource.uid.nil? || new_resource.uid == '') - if uid_used?(@new_resource.uid) - raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{@new_resource.uid} is already in use") + if uid_used?(new_resource.uid) + raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{new_resource.uid} is already in use") end - run_dscl("create /Users/#{@new_resource.username} UniqueID #{@new_resource.uid}") + run_dscl("create /Users/#{new_resource.username} UniqueID #{new_resource.uid}") end # @@ -222,7 +228,7 @@ user password using shadow hash.") # def get_free_uid(search_limit=1000) uid = nil - base_uid = @new_resource.system ? 200 : 500 + base_uid = new_resource.system ? 200 : 500 next_uid_guess = base_uid users_uids = run_dscl("list /Users uid") while(next_uid_guess < search_limit + base_uid) @@ -248,7 +254,7 @@ user password using shadow hash.") tmap end if uid_map[uid.to_s] - unless uid_map[uid.to_s] == @new_resource.username.to_s + unless uid_map[uid.to_s] == new_resource.username.to_s return true end end @@ -257,18 +263,23 @@ user password using shadow hash.") # # Sets the group id for the user using dscl. Fails if a group doesn't - # exist on the system with given group id. + # exist on the system with given group id. If `gid` is not specified, it + # sets a default Mac user group "staff", with id 20. # def dscl_set_gid - unless @new_resource.gid && @new_resource.gid.to_s.match(/^\d+$/) + if new_resource.gid.nil? + # XXX: mutates the new resource + new_resource.gid(20) + elsif !new_resource.gid.to_s.match(/^\d+$/) begin - possible_gid = run_dscl("read /Groups/#{@new_resource.gid} PrimaryGroupID").split(" ").last + possible_gid = run_dscl("read /Groups/#{new_resource.gid} PrimaryGroupID").split(" ").last rescue Chef::Exceptions::DsclCommandFailed => e - raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{@new_resource.gid} when creating user #{@new_resource.username}") + raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{new_resource.gid} when creating user #{new_resource.username}") end - @new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/) + # XXX: mutates the new resource + new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/) end - run_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'") + run_dscl("create /Users/#{new_resource.username} PrimaryGroupID '#{new_resource.gid}'") end # @@ -276,15 +287,15 @@ user password using shadow hash.") # directory is managed (moved / created) for the user. # def dscl_set_home - if @new_resource.home.nil? || @new_resource.home.empty? - run_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory") + if new_resource.home.nil? || new_resource.home.empty? + run_dscl("delete /Users/#{new_resource.username} NFSHomeDirectory") return end - if @new_resource.supports[:manage_home] + if new_resource.supports[:manage_home] validate_home_dir_specification! - if (@current_resource.home == @new_resource.home) && !new_home_exists? + if (current_resource.home == new_resource.home) && !new_home_exists? ditto_home elsif !current_home_exists? && !new_home_exists? ditto_home @@ -292,49 +303,49 @@ user password using shadow hash.") move_home end end - run_dscl("create /Users/#{@new_resource.username} NFSHomeDirectory '#{@new_resource.home}'") + run_dscl("create /Users/#{new_resource.username} NFSHomeDirectory '#{new_resource.home}'") end def validate_home_dir_specification! - unless @new_resource.home =~ /^\// - raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'") + unless new_resource.home =~ /^\// + raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{new_resource.username}', home directory: '#{new_resource.home}'") end end def current_home_exists? - ::File.exist?("#{@current_resource.home}") + ::File.exist?("#{current_resource.home}") end def new_home_exists? - ::File.exist?("#{@new_resource.home}") + ::File.exist?("#{new_resource.home}") end def ditto_home skel = "/System/Library/User Template/English.lproj" raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel) - shell_out! "ditto '#{skel}' '#{@new_resource.home}'" - ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home) + shell_out! "ditto '#{skel}' '#{new_resource.home}'" + ::FileUtils.chown_R(new_resource.username,new_resource.gid.to_s,new_resource.home) end def move_home - Chef::Log.debug("#{@new_resource} moving #{self} home from #{@current_resource.home} to #{@new_resource.home}") + Chef::Log.debug("#{new_resource} moving #{self} home from #{current_resource.home} to #{new_resource.home}") - src = @current_resource.home - FileUtils.mkdir_p(@new_resource.home) + src = current_resource.home + FileUtils.mkdir_p(new_resource.home) files = ::Dir.glob("#{Chef::Util::PathHelper.escape_glob(src)}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."] - ::FileUtils.mv(files,@new_resource.home, :force => true) + ::FileUtils.mv(files,new_resource.home, :force => true) ::FileUtils.rmdir(src) - ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home) + ::FileUtils.chown_R(new_resource.username,new_resource.gid.to_s,new_resource.home) end # # Sets the shell for the user using dscl. # def dscl_set_shell - if @new_resource.shell || ::File.exists?("#{@new_resource.shell}") - run_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'") + if new_resource.shell || ::File.exists?("#{new_resource.shell}") + run_dscl("create /Users/#{new_resource.username} UserShell '#{new_resource.shell}'") else - run_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'") + run_dscl("create /Users/#{new_resource.username} UserShell '/usr/bin/false'") end end @@ -345,7 +356,7 @@ user password using shadow hash.") # def set_password # Return if there is no password to set - return if @new_resource.password.nil? + return if new_resource.password.nil? shadow_info = prepare_password_shadow_info @@ -355,7 +366,7 @@ user password using shadow hash.") :input => shadow_info.to_plist, :live_stream => shadow_info_binary) command.run_command - if @user_info.nil? + if user_info.nil? # User is just created. read_user_info() will read the fresh information # for the user with a cache flush. However with experimentation we've seen # that dscl cache is not immediately updated after the creation of the user @@ -365,8 +376,8 @@ user password using shadow hash.") end # Replace the shadow info in user's plist - dscl_set(@user_info, :shadow_hash, shadow_info_binary) - save_user_info(@user_info) + dscl_set(user_info, :shadow_hash, shadow_info_binary) + save_user_info(user_info) end # @@ -379,12 +390,12 @@ user password using shadow hash.") iterations = nil if mac_osx_version_10_7? - hash_value = if salted_sha512?(@new_resource.password) - @new_resource.password + hash_value = if salted_sha512?(new_resource.password) + new_resource.password else # Create a random 4 byte salt salt = OpenSSL::Random.random_bytes(4) - encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + @new_resource.password) + encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + new_resource.password) hash_value = salt.unpack('H*').first + encoded_password end @@ -392,16 +403,16 @@ user password using shadow hash.") shadow_info["SALTED-SHA512"].string = convert_to_binary(hash_value) shadow_info else - if salted_sha512_pbkdf2?(@new_resource.password) - entropy = convert_to_binary(@new_resource.password) - salt = convert_to_binary(@new_resource.salt) - iterations = @new_resource.iterations + if salted_sha512_pbkdf2?(new_resource.password) + entropy = convert_to_binary(new_resource.password) + salt = convert_to_binary(new_resource.salt) + iterations = new_resource.iterations else salt = OpenSSL::Random.random_bytes(32) - iterations = @new_resource.iterations # Use the default if not specified by the user + iterations = new_resource.iterations # Use the default if not specified by the user entropy = OpenSSL::PKCS5::pbkdf2_hmac( - @new_resource.password, + new_resource.password, salt, iterations, 128, @@ -427,43 +438,43 @@ user password using shadow hash.") # and deleting home directory if needed. # def remove_user - if @new_resource.supports[:manage_home] + if new_resource.supports[:manage_home] # Remove home directory - FileUtils.rm_rf(@current_resource.home) + FileUtils.rm_rf(current_resource.home) end # Remove the user from its groups run_dscl("list /Groups").each_line do |group| if member_of_group?(group.chomp) - run_dscl("delete /Groups/#{group.chomp} GroupMembership '#{@new_resource.username}'") + run_dscl("delete /Groups/#{group.chomp} GroupMembership '#{new_resource.username}'") end end # Remove user account - run_dscl("delete /Users/#{@new_resource.username}") + run_dscl("delete /Users/#{new_resource.username}") end # # Locks the user. # def lock_user - run_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'") + run_dscl("append /Users/#{new_resource.username} AuthenticationAuthority ';DisabledUser;'") end # # Unlocks the user # def unlock_user - auth_string = @authentication_authority.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip - run_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'") + auth_string = authentication_authority.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip + run_dscl("create /Users/#{new_resource.username} AuthenticationAuthority '#{auth_string}'") end # # Returns true if the user is locked, false otherwise. # def locked? - if @authentication_authority - !!(@authentication_authority =~ /DisabledUser/ ) + if authentication_authority + !!(authentication_authority =~ /DisabledUser/ ) else false end @@ -485,11 +496,11 @@ user password using shadow hash.") # given attribute. # def diverged?(parameter) - parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?) + parameter_updated?(parameter) && (not new_resource.send(parameter).nil?) end def parameter_updated?(parameter) - not (@new_resource.send(parameter) == @current_resource.send(parameter)) + not (new_resource.send(parameter) == current_resource.send(parameter)) end # @@ -500,11 +511,11 @@ user password using shadow hash.") # type of the password specified. # def diverged_password? - return false if @new_resource.password.nil? + return false if new_resource.password.nil? # Dscl provider supports both plain text passwords and shadow hashes. if mac_osx_version_10_7? - if salted_sha512?(@new_resource.password) + if salted_sha512?(new_resource.password) diverged?(:password) else !salted_sha512_password_match? @@ -514,14 +525,14 @@ user password using shadow hash.") # will be updated when the user logs in. So it's possible that we will have # SALTED-SHA512 password in the current_resource. In that case we will force # password to be updated. - return true if salted_sha512?(@current_resource.password) + return true if salted_sha512?(current_resource.password) # Some system users don't have salts; this can happen if the system is # upgraded and the user hasn't logged in yet. In this case, we will force # the password to be updated. - return true if @current_resource.salt.nil? + return true if current_resource.salt.nil? - if salted_sha512_pbkdf2?(@new_resource.password) + if salted_sha512_pbkdf2?(new_resource.password) diverged?(:password) || diverged?(:salt) || diverged?(:iterations) else !salted_sha512_pbkdf2_password_match? @@ -543,7 +554,7 @@ user password using shadow hash.") # GroupMembership: root admin etc members = membership_info.split(" ") members.shift # Get rid of GroupMembership: string - members.include?(@new_resource.username) + members.include?(new_resource.username) end # @@ -577,7 +588,7 @@ user password using shadow hash.") shell_out("dscacheutil '-flushcache'") begin - user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist" + user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist" user_plist_info = run_plutil("convert xml1 -o - #{user_plist_file}") user_info = Plist::parse_xml(user_plist_info) rescue Chef::Exceptions::PlistUtilCommandFailed @@ -591,7 +602,7 @@ user password using shadow hash.") # in DSCL_PROPERTY_MAP to the disk. # def save_user_info(user_info) - user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist" + user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist" Plist::Emit.save_plist(user_info, user_plist_file) run_plutil("convert binary1 #{user_plist_file}") end @@ -673,9 +684,9 @@ user password using shadow hash.") def salted_sha512_password_match? # Salt is included in the first 4 bytes of shadow data - salt = @current_resource.password.slice(0,8) - shadow = OpenSSL::Digest::SHA512.hexdigest(convert_to_binary(salt) + @new_resource.password) - @current_resource.password == salt + shadow + salt = current_resource.password.slice(0,8) + shadow = OpenSSL::Digest::SHA512.hexdigest(convert_to_binary(salt) + new_resource.password) + current_resource.password == salt + shadow end def salted_sha512_pbkdf2?(string) @@ -683,15 +694,15 @@ user password using shadow hash.") end def salted_sha512_pbkdf2_password_match? - salt = convert_to_binary(@current_resource.salt) + salt = convert_to_binary(current_resource.salt) OpenSSL::PKCS5::pbkdf2_hmac( - @new_resource.password, + new_resource.password, salt, - @current_resource.iterations, + current_resource.iterations, 128, OpenSSL::Digest::SHA512.new - ).unpack('H*').first == @current_resource.password + ).unpack('H*').first == current_resource.password end end diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb index e282a11d45..76519bb498 100644 --- a/lib/chef/provider/user/windows.rb +++ b/lib/chef/provider/user/windows.rb @@ -35,6 +35,10 @@ class Chef end def load_current_resource + if @new_resource.gid + Chef::Log.warn("The 'gid' attribute is not implemented by the Windows platform. Please use the 'group' resource to assign a user to a group.") + end + @current_resource = Chef::Resource::User.new(@new_resource.name) @current_resource.username(@new_resource.username) user_info = nil @@ -42,7 +46,6 @@ class Chef user_info = @net_user.get_info @current_resource.uid(user_info[:user_id]) - @current_resource.gid(user_info[:primary_group_id]) @current_resource.comment(user_info[:full_name]) @current_resource.home(user_info[:home_dir]) @current_resource.shell(user_info[:script_path]) @@ -65,7 +68,7 @@ class Chef Chef::Log.debug("#{@new_resource} password has changed") return true end - [ :uid, :gid, :comment, :home, :shell ].any? do |user_attrib| + [ :uid, :comment, :home, :shell ].any? do |user_attrib| !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib) end end @@ -100,7 +103,6 @@ class Chef field_list = { 'comment' => 'full_name', 'home' => 'home_dir', - 'gid' => 'primary_group_id', 'uid' => 'user_id', 'shell' => 'script_path', 'password' => 'password' diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 9e40fdccfd..90453bd00e 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -19,7 +19,6 @@ # require 'chef/exceptions' -require 'chef/mixin/params_validate' require 'chef/dsl/platform_introspection' require 'chef/dsl/data_query' require 'chef/dsl/registry_helper' @@ -40,6 +39,7 @@ require 'chef/resource_resolver' require 'set' require 'chef/mixin/deprecation' +require 'chef/mixin/properties' require 'chef/mixin/provides' require 'chef/mixin/shell_out' require 'chef/mixin/powershell_out' @@ -61,6 +61,34 @@ class Chef include Chef::Mixin::ShellOut include Chef::Mixin::PowershellOut + # Bring in `property` and `property_type` + include Chef::Mixin::Properties + + # + # The name of this particular resource. + # + # This special resource attribute is set automatically from the declaration + # of the resource, e.g. + # + # execute 'Vitruvius' do + # command 'ls' + # end + # + # Will set the name to "Vitruvius". + # + # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`. + # + # This is also used for resource notifications and subscribes in the same manner. + # + # This will coerce any object into a string via #to_s. Arrays are a special case + # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more + # awkward `package[["foo", "bar"]]` that #to_s would produce. + # + # @param name [Object] The name to set, typically a String or Array + # @return [String] The name of this Resource. + # + property :name, String, coerce: proc { |v| v.is_a?(Array) ? v.join(', ') : v.to_s }, desired_state: false + # # The node the current Chef run is using. # @@ -133,30 +161,6 @@ class Chef end # - # The list of properties defined on this resource. - # - # Everything defined with `property` is in this list. - # - # @param include_superclass [Boolean] `true` to include properties defined - # on superclasses; `false` or `nil` to return the list of properties - # directly on this class. - # - # @return [Hash<Symbol,Property>] The list of property names and types. - # - def self.properties(include_superclass=true) - @properties ||= {} - if include_superclass - if superclass.respond_to?(:properties) - superclass.properties.merge(@properties) - else - @properties.dup - end - else - @properties - end - end - - # # The action or actions that will be taken when this resource is run. # # @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`) @@ -681,7 +685,6 @@ class Chef # Resource Definition Interface (for resource developers) # - include Chef::Mixin::ParamsValidate include Chef::Mixin::Deprecation # @@ -715,240 +718,6 @@ class Chef end # - # Create a property on this resource class. - # - # If a superclass has this property, or if this property has already been - # defined by this resource, this will *override* the previous value. - # - # @param name [Symbol] The name of the property. - # @param type [Object,Array<Object>] The type(s) of this property. - # If present, this is prepended to the `is` validation option. - # @param options [Hash<Symbol,Object>] Validation options. - # @option options [Object,Array] :is An object, or list of - # objects, that must match the value using Ruby's `===` operator - # (`options[:is].any? { |v| v === value }`). - # @option options [Object,Array] :equal_to An object, or list - # of objects, that must be equal to the value using Ruby's `==` - # operator (`options[:is].any? { |v| v == value }`) - # @option options [Regexp,Array<Regexp>] :regex An object, or - # list of objects, that must match the value with `regex.match(value)`. - # @option options [Class,Array<Class>] :kind_of A class, or - # list of classes, that the value must be an instance of. - # @option options [Hash<String,Proc>] :callbacks A hash of - # messages -> procs, all of which match the value. The proc must - # return a truthy or falsey value (true means it matches). - # @option options [Symbol,Array<Symbol>] :respond_to A method - # name, or list of method names, the value must respond to. - # @option options [Symbol,Array<Symbol>] :cannot_be A property, - # or a list of properties, that the value cannot have (such as `:nil` or - # `:empty`). The method with a questionmark at the end is called on the - # value (e.g. `value.empty?`). If the value does not have this method, - # it is considered valid (i.e. if you don't respond to `empty?` we - # assume you are not empty). - # @option options [Proc] :coerce A proc which will be called to - # transform the user input to canonical form. The value is passed in, - # and the transformed value returned as output. Lazy values will *not* - # be passed to this method until after they are evaluated. Called in the - # context of the resource (meaning you can access other properties). - # @option options [Boolean] :required `true` if this property - # must be present; `false` otherwise. This is checked after the resource - # is fully initialized. - # @option options [Boolean] :name_property `true` if this - # property defaults to the same value as `name`. Equivalent to - # `default: lazy { name }`, except that #property_is_set? will - # return `true` if the property is set *or* if `name` is set. - # @option options [Boolean] :name_attribute Same as `name_property`. - # @option options [Object] :default The value this property - # will return if the user does not set one. If this is `lazy`, it will - # be run in the context of the instance (and able to access other - # properties). - # @option options [Boolean] :desired_state `true` if this property is - # part of desired state. Defaults to `true`. - # @option options [Boolean] :identity `true` if this property - # is part of object identity. Defaults to `false`. - # - # @example Bare property - # property :x - # - # @example With just a type - # property :x, String - # - # @example With just options - # property :x, default: 'hi' - # - # @example With type and options - # property :x, String, default: 'hi' - # - def self.property(name, type=NOT_PASSED, **options) - name = name.to_sym - - options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) } - - options[:instance_variable_name] = :"@#{name}" if !options.has_key?(:instance_variable_name) - options.merge!(name: name, declared_in: self) - - if type == NOT_PASSED - # If a type is not passed, the property derives from the - # superclass property (if any) - if properties.has_key?(name) - property = properties[name].derive(**options) - else - property = property_type(**options) - end - - # If a Property is specified, derive a new one from that. - elsif type.is_a?(Property) || (type.is_a?(Class) && type <= Property) - property = type.derive(**options) - - # If a primitive type was passed, combine it with "is" - else - if options[:is] - options[:is] = ([ type ] + [ options[:is] ]).flatten(1) - else - options[:is] = type - end - property = property_type(**options) - end - - local_properties = properties(false) - local_properties[name] = property - - property.emit_dsl - end - - # - # Create a reusable property type that can be used in multiple properties - # in different resources. - # - # @param options [Hash<Symbol,Object>] Validation options. see #property for - # the list of options. - # - # @example - # property_type(default: 'hi') - # - def self.property_type(**options) - Property.derive(**options) - end - - # - # The name of this particular resource. - # - # This special resource attribute is set automatically from the declaration - # of the resource, e.g. - # - # execute 'Vitruvius' do - # command 'ls' - # end - # - # Will set the name to "Vitruvius". - # - # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`. - # - # This is also used for resource notifications and subscribes in the same manner. - # - # This will coerce any object into a string via #to_s. Arrays are a special case - # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more - # awkward `package[["foo", "bar"]]` that #to_s would produce. - # - # @param name [Object] The name to set, typically a String or Array - # @return [String] The name of this Resource. - # - property :name, String, coerce: proc { |v| v.is_a?(Array) ? v.join(', ') : v.to_s }, desired_state: false - - # - # Whether this property has been set (or whether it has a default that has - # been retrieved). - # - # @param name [Symbol] The name of the property. - # @return [Boolean] `true` if the property has been set. - # - def property_is_set?(name) - property = self.class.properties[name.to_sym] - raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property - property.is_set?(self) - end - - # - # Clear this property as if it had never been set. It will thereafter return - # the default. - # been retrieved). - # - # @param name [Symbol] The name of the property. - # - def reset_property(name) - property = self.class.properties[name.to_sym] - raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property - property.reset(self) - end - - # - # Create a lazy value for assignment to a default value. - # - # @param block The block to run when the value is retrieved. - # - # @return [Chef::DelayedEvaluator] The lazy value - # - def self.lazy(&block) - DelayedEvaluator.new(&block) - end - - # - # Get or set the list of desired state properties for this resource. - # - # State properties are properties that describe the desired state - # of the system, such as file permissions or ownership. - # In general, state properties are properties that could be populated by - # examining the state of the system (e.g., File.stat can tell you the - # permissions on an existing file). Contrarily, properties that are not - # "state properties" usually modify the way Chef itself behaves, for example - # by providing additional options for a package manager to use when - # installing a package. - # - # This list is used by the Chef client auditing system to extract - # information from resources to describe changes made to the system. - # - # This method is unnecessary when declaring properties with `property`; - # properties are added to state_properties by default, and can be turned off - # with `desired_state: false`. - # - # ```ruby - # property :x # part of desired state - # property :y, desired_state: false # not part of desired state - # ``` - # - # @param names [Array<Symbol>] A list of property names to set as desired - # state. - # - # @return [Array<Property>] All properties in desired state. - # - def self.state_properties(*names) - if !names.empty? - names = names.map { |name| name.to_sym }.uniq - - local_properties = properties(false) - # Add new properties to the list. - names.each do |name| - property = properties[name] - if !property - self.property name, instance_variable_name: false, desired_state: true - elsif !property.desired_state? - self.property name, desired_state: true - end - end - - # If state_attrs *excludes* something which is currently desired state, - # mark it as desired_state: false. - local_properties.each do |name,property| - if property.desired_state? && !names.include?(name) - self.property name, desired_state: false - end - end - end - - properties.values.select { |property| property.desired_state? } - end - - # # Set or return the list of "state properties" implemented by the Resource # subclass. # @@ -973,56 +742,6 @@ class Chef end # - # Set the identity of this resource to a particular set of properties. - # - # This drives #identity, which returns data that uniquely refers to a given - # resource on the given node (in such a way that it can be correlated - # across Chef runs). - # - # This method is unnecessary when declaring properties with `property`; - # properties can be added to identity during declaration with - # `identity: true`. - # - # ```ruby - # property :x, identity: true # part of identity - # property :y # not part of identity - # ``` - # - # If no properties are marked as identity, "name" is considered the identity. - # - # @param names [Array<Symbol>] A list of property names to set as the identity. - # - # @return [Array<Property>] All identity properties. - # - def self.identity_properties(*names) - if !names.empty? - names = names.map { |name| name.to_sym } - - # Add or change properties that are not part of the identity. - names.each do |name| - property = properties[name] - if !property - self.property name, instance_variable_name: false, identity: true - elsif !property.identity? - self.property name, identity: true - end - end - - # If identity_properties *excludes* something which is currently part of - # the identity, mark it as identity: false. - properties.each do |name,property| - if property.identity? && !names.include?(name) - self.property name, identity: false - end - end - end - - result = properties.values.select { |property| property.identity? } - result = [ properties[:name] ] if result.empty? - result - end - - # # Set the identity of this resource to a particular property. # # This drives #identity, which returns data that uniquely refers to a given diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb index ec669a75d3..11c4ae045c 100644 --- a/lib/chef/resource/execute.rb +++ b/lib/chef/resource/execute.rb @@ -102,7 +102,7 @@ class Chef end def path(arg=nil) - Chef::Log.warn "'path' attribute of 'execute' is not used by any provider in Chef 11 and Chef 12. Use 'environment' attribute to configure 'PATH'. This attribute will be removed in Chef 13." + Chef::Log.warn "The 'path' attribute of 'execute' is not used by any provider in Chef 11 or Chef 12. Use 'environment' attribute to configure 'PATH'. This attribute will be removed in Chef 13." set_or_return( :path, diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb index 443e0ed819..a9a669f18c 100644 --- a/lib/chef/resource/lwrp_base.rb +++ b/lib/chef/resource/lwrp_base.rb @@ -1,8 +1,8 @@ # -# Author:: Adam Jacob (<adam@opscode.com>) -# Author:: Christopher Walters (<cw@opscode.com>) -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2008-2012 Opscode, Inc. +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Christopher Walters (<cw@chef.io>) +# Author:: Daniel DeLeo (<dan@chef.io>) +# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,7 +45,7 @@ class Chef def build_from_file(cookbook_name, filename, run_context) if LWRPBase.loaded_lwrps[filename] - Chef::Log.info("LWRP resource #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.") + Chef::Log.info("Custom resource #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.") return loaded_lwrps[filename] end @@ -60,7 +60,7 @@ class Chef # Make a useful string for the class (rather than <Class:312894723894>) resource_class.instance_eval do define_singleton_method(:to_s) do - "LWRP resource #{resource_name} from cookbook #{cookbook_name}" + "Custom resource #{resource_name} from cookbook #{cookbook_name}" end define_singleton_method(:inspect) { to_s } end diff --git a/lib/chef/resource/registry_key.rb b/lib/chef/resource/registry_key.rb index d2e5c4b94c..f1bf7954ce 100644 --- a/lib/chef/resource/registry_key.rb +++ b/lib/chef/resource/registry_key.rb @@ -125,7 +125,7 @@ class Chef scrubbed_value = value.dup if needs_checksum?(scrubbed_value) data_io = StringIO.new(scrubbed_value[:data].to_s) - scrubbed_value[:data] = Chef::Digester.instance.generate_md5_checksum(data_io) + scrubbed_value[:data] = Chef::Digester.instance.generate_checksum(data_io) end scrubbed << scrubbed_value end diff --git a/lib/chef/resource/resource_notification.rb b/lib/chef/resource/resource_notification.rb index a27ed961c7..4fd61ad1f8 100644 --- a/lib/chef/resource/resource_notification.rb +++ b/lib/chef/resource/resource_notification.rb @@ -20,7 +20,15 @@ require 'chef/resource' class Chef class Resource - class Notification < Struct.new(:resource, :action, :notifying_resource) + class Notification + + attr_accessor :resource, :action, :notifying_resource + + def initialize(resource, action, notifying_resource) + @resource = resource + @action = action + @notifying_resource = notifying_resource + end def duplicates?(other_notification) unless other_notification.respond_to?(:resource) && other_notification.respond_to?(:action) @@ -104,6 +112,11 @@ is defined near #{resource.source_line} raise err end + def ==(other) + return false unless other.is_a?(self.class) + other.resource == resource && other.action == action && other.notifying_resource == notifying_resource + end + end end end diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb index 30bed367cb..5081adf918 100644 --- a/lib/chef/resource/script.rb +++ b/lib/chef/resource/script.rb @@ -40,7 +40,7 @@ class Chef unless arg.nil? # Chef-13: change this to raise if the user is trying to set a value here Chef::Log.warn "Specifying command attribute on a script resource is a coding error, use the 'code' attribute, or the execute resource" - Chef::Log.warn "This attribute is deprecated and must be fixed or this code will fail on Chef-13" + Chef::Log.warn "This attribute is deprecated and must be fixed or this code will fail on Chef 13" end super end diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb index 48e2b535a8..2bbd01d5aa 100644 --- a/lib/chef/resource/windows_script.rb +++ b/lib/chef/resource/windows_script.rb @@ -16,6 +16,7 @@ # limitations under the License. # +require 'chef/platform/query_helpers' require 'chef/resource/script' require 'chef/mixin/windows_architecture_helper' @@ -51,9 +52,12 @@ class Chef protected def assert_architecture_compatible!(desired_architecture) - if ! node_supports_windows_architecture?(node, desired_architecture) + if desired_architecture == :i386 && Chef::Platform.windows_nano_server? raise Chef::Exceptions::Win32ArchitectureIncorrect, - "cannot execute script with requested architecture '#{desired_architecture.to_s}' on a system with architecture '#{node_windows_architecture(node)}'" + "cannot execute script with requested architecture 'i386' on Windows Nano Server" + elsif ! node_supports_windows_architecture?(node, desired_architecture) + raise Chef::Exceptions::Win32ArchitectureIncorrect, + "cannot execute script with requested architecture '#{desired_architecture.to_s}' on a system with architecture '#{node_windows_architecture(node)}'" end end end diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb index 7d13a5a5ce..1175b0afb3 100644 --- a/lib/chef/resource_reporter.rb +++ b/lib/chef/resource_reporter.rb @@ -112,6 +112,7 @@ class Chef @exception = nil @rest_client = rest_client @error_descriptions = {} + @expanded_run_list = {} end def run_started(run_status) @@ -217,6 +218,10 @@ class Chef end end + def run_list_expanded(run_list_expansion) + @expanded_run_list = run_list_expansion + end + def post_reporting_data if reporting_enabled? run_data = prepare_run_data @@ -271,6 +276,7 @@ class Chef run_data["data"] = {} run_data["start_time"] = start_time.to_s run_data["end_time"] = end_time.to_s + run_data["expanded_run_list"] = Chef::JSONCompat.to_json(@expanded_run_list) if exception exception_data = {} diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb index f87cec9b76..4106a01077 100644 --- a/lib/chef/rest.rb +++ b/lib/chef/rest.rb @@ -166,7 +166,7 @@ class Chef def retriable_http_request(method, url, req_body, headers) rest_request = Chef::HTTP::HTTPRequest.new(method, url, req_body, headers) - Chef::Log.debug("Sending HTTP Request via #{method} to #{url.host}:#{url.port}#{rest_request.path}") + Chef::Log.debug("Sending HTTP request via #{method} to #{url.host}:#{url.port}#{rest_request.path}") retrying_http_errors(url) do yield rest_request diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index b2a4b13ea4..f7ab88f7e0 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -268,7 +268,7 @@ class Chef # @see DSL::IncludeRecipe#load_recipe # def load_recipe(recipe_name, current_cookbook: nil) - Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe") + Chef::Log.debug("Loading recipe #{recipe_name} via include_recipe") cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name, current_cookbook: current_cookbook) @@ -308,7 +308,7 @@ ERROR_MESSAGE raise Chef::Exceptions::RecipeNotFound, "could not find recipe file #{recipe_file}" end - Chef::Log.debug("Loading Recipe File #{recipe_file}") + Chef::Log.debug("Loading recipe file #{recipe_file}") recipe = Chef::Recipe.new('@recipe_files', recipe_file, self) recipe.from_file(recipe_file) recipe diff --git a/lib/chef/run_list/run_list_expansion.rb b/lib/chef/run_list/run_list_expansion.rb index 46b45f1d9e..64e4326fb8 100644 --- a/lib/chef/run_list/run_list_expansion.rb +++ b/lib/chef/run_list/run_list_expansion.rb @@ -22,6 +22,7 @@ require 'chef/mixin/deep_merge' require 'chef/role' require 'chef/rest' +require 'chef/json_compat' class Chef class RunList @@ -54,6 +55,13 @@ class Chef # * Duplicate roles are not shown. attr_reader :run_list_trace + # Like run list trace but instead of saving the entries as strings it saves their objects + # The to_json method uses this list to construct json. + attr_reader :better_run_list_trace + + attr_reader :all_missing_roles + attr_reader :role_errors + def initialize(environment, run_list_items, source=nil) @environment = environment @missing_roles_with_including_role = Array.new @@ -68,6 +76,9 @@ class Chef @applied_roles = {} @run_list_trace = Hash.new {|h, key| h[key] = [] } + @better_run_list_trace = Hash.new {|h, key| h[key] = [] } + @all_missing_roles = {} + @role_errors = {} end # Did we find any errors (expanding roles)? @@ -124,6 +135,7 @@ class Chef def role_not_found(name, included_by) Chef::Log.error("Role #{name} (included by '#{included_by}') is in the runlist but does not exist. Skipping expand.") @missing_roles_with_including_role << [name, included_by] + @all_missing_roles[name] = true nil end @@ -131,6 +143,15 @@ class Chef @missing_roles_with_including_role.map {|item| item.first } end + def to_json(*a) + Chef::JSONCompat.to_json(to_hash, *a) + end + + def to_hash + seen_items = {:recipe => {}, :role => {}} + {:id => @environment, :run_list => convert_run_list_trace('top level', seen_items)} + end + private # these methods modifies internal state based on arguments, so hide it. @@ -140,8 +161,10 @@ class Chef end def expand_run_list_items(items, included_by="top level") + if entry = items.shift @run_list_trace[included_by.to_s] << entry.to_s + @better_run_list_trace[included_by.to_s] << entry case entry.type when :recipe @@ -156,8 +179,26 @@ class Chef end end + # Recursive helper to decode the non-nested hash form back into a tree + def convert_run_list_trace(base, seen_items) + @better_run_list_trace[base].map do |item| + skipped = seen_items[item.type][item.name] + seen_items[item.type][item.name] = true + case item.type + when :recipe + {:type => 'recipe', :name => item.name, :version => item.version, :skipped => !!skipped} + when :role + error = @role_errors[item.name] + missing = @all_missing_roles[item.name] + {:type => :role, :name => item.name, :children => (missing || error || skipped) ? [] : convert_run_list_trace(item.to_s, seen_items), + :missing => missing, :error => error, :skipped => skipped} + end + end + end + end + # Expand a run list from disk. Suitable for chef-solo class RunListExpansionFromDisk < RunListExpansion @@ -184,8 +225,14 @@ class Chef else raise end + rescue Exception => e + @role_errors[name] = e.to_s + raise end + end end end + + diff --git a/lib/chef/run_list/versioned_recipe_list.rb b/lib/chef/run_list/versioned_recipe_list.rb index 2824f08f31..803156aef9 100644 --- a/lib/chef/run_list/versioned_recipe_list.rb +++ b/lib/chef/run_list/versioned_recipe_list.rb @@ -82,6 +82,21 @@ class Chef qualified_recipe end end + + # Get an array of strings of both fully-qualified and unexpanded recipe names + # in response to chef/chef#3767 + # Chef-13 will revert to the behaviour of just including the fully-qualified name + # + # @return [Array] Array of strings with fully-qualified and unexpanded recipe names + def with_duplicate_names + self.map do |recipe_name| + if recipe_name.include?('::') + recipe_name + else + [recipe_name, "#{recipe_name}::default"] + end + end.flatten + end end end end diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb index 6469a18c49..658af8779c 100644 --- a/lib/chef/search/query.rb +++ b/lib/chef/search/query.rb @@ -88,8 +88,21 @@ WARNDEP if block response["rows"].each { |row| block.call(row) if row } - unless (response["start"] + response["rows"].length) >= response["total"] - args_h[:start] = response["start"] + response["rows"].length + # + # args_h[:rows] and args_h[:start] are the page size and + # start position requested of the search index backing the + # search API. + # + # The response may contain fewer rows than arg_h[:rows] if + # the page of index results included deleted nodes which + # have been filtered from the returned data. In this case, + # we still want to start the next page at start + + # args_h[:rows] to avoid asking the search backend for + # overlapping pages (which could result in duplicates). + # + next_start = response["start"] + (args_h[:rows] || response["rows"].length) + unless next_start >= response["total"] + args_h[:start] = next_start search(type, query, args_h, &block) end true @@ -99,6 +112,7 @@ WARNDEP end private + def validate_type(t) unless t.kind_of?(String) || t.kind_of?(Symbol) msg = "Invalid search object type #{t.inspect} (#{t.class}), must be a String or Symbol." + diff --git a/lib/chef/util/diff.rb b/lib/chef/util/diff.rb index c2dc6e045c..b8336b5135 100644 --- a/lib/chef/util/diff.rb +++ b/lib/chef/util/diff.rb @@ -64,7 +64,7 @@ class Chef def use_tempfile_if_missing(file) tempfile = nil unless File.exists?(file) - Chef::Log.debug("file #{file} does not exist to diff against, using empty tempfile") + Chef::Log.debug("File #{file} does not exist to diff against, using empty tempfile") tempfile = Tempfile.new("chef-diff") file = tempfile.path end @@ -139,7 +139,7 @@ class Chef return "(new content is binary, diff output suppressed)" if is_binary?(new_file) begin - Chef::Log.debug("running: diff -u #{old_file} #{new_file}") + Chef::Log.debug("Running: diff -u #{old_file} #{new_file}") diff_str = udiff(old_file, new_file) rescue Exception => e diff --git a/lib/chef/version.rb b/lib/chef/version.rb index c0ff754873..c769533aa6 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -21,7 +21,7 @@ class Chef CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__))) - VERSION = '12.5.0' + VERSION = '12.5.1' end # diff --git a/lib/chef/win32/mutex.rb b/lib/chef/win32/mutex.rb index f4755e9019..0d8eba1b3c 100644 --- a/lib/chef/win32/mutex.rb +++ b/lib/chef/win32/mutex.rb @@ -79,7 +79,7 @@ class Chef # of the process goes away and this class is only being used # to synchronize chef-clients runs on a node. Chef::Log.error("Can not release mutex '#{name}'. This might cause issues \ -if the mutex is attempted to be acquired by other threads.") +if other threads attempt to acquire the mutex.") Chef::ReservedNames::Win32::Error.raise! end end diff --git a/spec/functional/resource/powershell_script_spec.rb b/spec/functional/resource/powershell_script_spec.rb index be744e748b..91b74fd752 100644 --- a/spec/functional/resource/powershell_script_spec.rb +++ b/spec/functional/resource/powershell_script_spec.rb @@ -16,6 +16,7 @@ # limitations under the License. # +require 'chef/platform/query_helpers' require 'spec_helper' describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do @@ -27,7 +28,6 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do it_behaves_like "a Windows script running on Windows" - let(:successful_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe $env:systemroot" } let(:failed_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe /badargument" } let(:processor_architecture_script_content) { "echo $env:PROCESSOR_ARCHITECTURE" } @@ -57,6 +57,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end it "returns the exit status 27 for a powershell script that exits with 27" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + file = Tempfile.new(['foo', '.ps1']) begin file.write "exit 27" @@ -73,6 +75,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do let (:negative_exit_status) { -27 } let (:unsigned_exit_status) { (-negative_exit_status ^ 65535) + 1 } it "returns the exit status -27 as a signed integer or an unsigned 16-bit 2's complement value of 65509 for a powershell script that exits with -27" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + # Versions of PowerShell prior to 4.0 return a 16-bit unsigned value -- # PowerShell 4.0 and later versions return a 32-bit signed value. file = Tempfile.new(['foo', '.ps1']) @@ -96,6 +100,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end it "returns the process exit code" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.code(arbitrary_nonzero_process_exit_code_content) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) @@ -114,24 +120,34 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end it "returns 1 if the last command was a cmdlet that failed" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.code(cmdlet_exit_code_not_found_content) resource.returns(1) resource.run_action(:run) end it "returns 1 if the last command was a cmdlet that failed and was preceded by a successfully executed non-cmdlet Windows binary" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.code([windows_process_exit_code_success_content, cmdlet_exit_code_not_found_content].join(';')) resource.returns(1) expect { resource.run_action(:run) }.not_to raise_error end it "raises a Mixlib::ShellOut::ShellCommandFailed error if the script is not syntactically correct" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.code('if({)') resource.returns(0) expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end it "raises an error if the script is not syntactically correct even if returns is set to 1 which is what powershell.exe returns for syntactically invalid scripts" do + # This test fails because shell_out expects the exit status to be 1, but it is actually 0 + # The error is a false-positive. + skip "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.code('if({)') resource.returns(1) expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) @@ -146,24 +162,32 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do # errors than 0 or 1, we return that instead, which is acceptable # since callers can test for nonzero rather than testing for 1. it "returns 1 if the last command was a cmdlet that failed and was preceded by an unsuccessfully executed non-cmdlet Windows binary" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.code([arbitrary_nonzero_process_exit_code_content,cmdlet_exit_code_not_found_content].join(';')) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) end it "returns 0 if the last command was a non-cmdlet Windows binary that succeeded and was preceded by a failed cmdlet" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.code([cmdlet_exit_code_success_content, arbitrary_nonzero_process_exit_code_content].join(';')) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) end it "returns a specific error code if the last command was a non-cmdlet Windows binary that failed and was preceded by cmdlet that succeeded" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.code([cmdlet_exit_code_success_content, arbitrary_nonzero_process_exit_code_content].join(';')) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) end it "returns a specific error code if the last command was a non-cmdlet Windows binary that failed and was preceded by cmdlet that failed" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.code([cmdlet_exit_code_not_found_content, arbitrary_nonzero_process_exit_code_content].join(';')) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) @@ -182,6 +206,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end it "returns 1 for $false as the last line of the script when convert_boolean_return is true" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.convert_boolean_return true resource.code "$false" resource.returns(1) @@ -208,6 +234,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end it "returns 1 if an invalid flag is passed to the interpreter" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.code(cmdlet_exit_code_success_content) resource.flags(invalid_powershell_interpreter_flag) resource.returns(1) @@ -286,7 +314,7 @@ configuration LCM expect(source_contains_case_insensitive_content?( get_script_output, 'AMD64' )).to eq(true) end - it "executes a script with a 32-bit process if :i386 arch is specified" do + it "executes a script with a 32-bit process if :i386 arch is specified", :not_supported_on_nano do resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.architecture(:i386) resource.returns(0) @@ -294,6 +322,12 @@ configuration LCM expect(source_contains_case_insensitive_content?( get_script_output, 'x86' )).to eq(true) end + + it "raises an error when executing a script with a 32-bit process on Windows Nano Server", :windows_nano_only do + resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") + expect{ resource.architecture(:i386) }.to raise_error(Chef::Exceptions::Win32ArchitectureIncorrect, + "cannot execute script with requested architecture 'i386' on Windows Nano Server") + end end describe "when executing guards" do @@ -347,6 +381,8 @@ configuration LCM end it "evaluates a powershell $false for a not_if block as true" do + pending "powershell.exe always exits with $true on nano" if Chef::Platform.windows_nano_server? + resource.not_if "$false" expect(resource.should_skip?(:run)).to be_falsey end @@ -357,6 +393,8 @@ configuration LCM end it "evaluates a powershell $false for an only_if block as false" do + pending "powershell.exe always exits with $true on nano" if Chef::Platform.windows_nano_server? + resource.only_if "$false" expect(resource.should_skip?(:run)).to be_truthy end @@ -377,6 +415,8 @@ configuration LCM end it "evaluates a non-zero powershell exit status for not_if as true" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.not_if "exit 37" expect(resource.should_skip?(:run)).to be_falsey end @@ -387,6 +427,8 @@ configuration LCM end it "evaluates a failed executable exit status for not_if as false" do + pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? + resource.not_if windows_process_exit_code_not_found_content expect(resource.should_skip?(:run)).to be_falsey end @@ -397,6 +439,8 @@ configuration LCM end it "evaluates a failed executable exit status for only_if as false" do + pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? + resource.only_if windows_process_exit_code_not_found_content expect(resource.should_skip?(:run)).to be_truthy end @@ -407,6 +451,8 @@ configuration LCM end it "evaluates a failed cmdlet exit status for not_if as true" do + pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? + resource.not_if "throw 'up'" expect(resource.should_skip?(:run)).to be_falsey end @@ -417,6 +463,8 @@ configuration LCM end it "evaluates a failed cmdlet exit status for only_if as false" do + pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? + resource.only_if "throw 'up'" expect(resource.should_skip?(:run)).to be_truthy end @@ -459,30 +507,36 @@ configuration LCM end it "evaluates a 64-bit resource with a 64-bit guard and interprets boolean true as nonzero status code", :windows64_only do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.architecture :x86_64 resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'AMD64')" expect(resource.should_skip?(:run)).to be_truthy end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code" do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code", :not_supported_on_nano do resource.architecture :i386 resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -ne 'X86')" expect(resource.should_skip?(:run)).to be_falsey end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code" do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code", :not_supported_on_nano do resource.architecture :i386 resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'X86')" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for only_if" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.convert_boolean_return true resource.only_if "$false" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for not_if" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + resource.convert_boolean_return true resource.not_if "$false" expect(resource.should_skip?(:run)).to be_falsey @@ -500,33 +554,40 @@ configuration LCM expect(resource.should_skip?(:run)).to be_truthy end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for only_if" do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for only_if", :not_supported_on_nano do resource.convert_boolean_return true resource.architecture :i386 resource.only_if "$env:PROCESSOR_ARCHITECTURE -eq 'X86'" expect(resource.should_skip?(:run)).to be_falsey end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for not_if" do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for not_if", :not_supported_on_nano do resource.convert_boolean_return true resource.architecture :i386 resource.not_if "$env:PROCESSOR_ARCHITECTURE -ne 'X86'" expect(resource.should_skip?(:run)).to be_falsey end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for only_if" do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for only_if", :not_supported_on_nano do resource.convert_boolean_return true resource.architecture :i386 resource.only_if "$env:PROCESSOR_ARCHITECTURE -ne 'X86'" expect(resource.should_skip?(:run)).to be_truthy end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for not_if" do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for not_if", :not_supported_on_nano do resource.convert_boolean_return true resource.architecture :i386 resource.not_if "$env:PROCESSOR_ARCHITECTURE -eq 'X86'" expect(resource.should_skip?(:run)).to be_truthy end + + it "raises an error when a 32-bit guard is used on Windows Nano Server", :windows_nano_only do + resource.only_if "$true", :architecture => :i386 + expect{resource.run_action(:run)}.to raise_error( + Chef::Exceptions::Win32ArchitectureIncorrect, + /cannot execute script with requested architecture 'i386' on Windows Nano Server/) + end end end diff --git a/spec/functional/resource/user/windows_spec.rb b/spec/functional/resource/user/windows_spec.rb index 5e68478b34..5e3a9090d4 100644 --- a/spec/functional/resource/user/windows_spec.rb +++ b/spec/functional/resource/user/windows_spec.rb @@ -66,6 +66,14 @@ describe Chef::Provider::User::Windows, :windows_only do new_resource.run_action(:create) expect(new_resource).to be_updated_by_last_action end + + context 'with a gid specified' do + it 'warns unsupported' do + expect(Chef::Log).to receive(:warn).with(/not implemented/) + new_resource.gid('agroup') + new_resource.run_action(:create) + end + end end describe 'action :remove' do diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb index 5b235e2720..314a9310be 100644 --- a/spec/integration/client/client_spec.rb +++ b/spec/integration/client/client_spec.rb @@ -301,6 +301,23 @@ EOM result.error! end + it "should complete with success when using --profile-ruby and output a profile file" do + file 'config/client.rb', <<EOM +local_mode true +cookbook_path "#{path_to('cookbooks')}" +EOM + result = shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' -z --profile-ruby", :cwd => chef_dir) + expect(File.exist?(path_to("config/local-mode-cache/cache/graph_profile.out"))).to be true + end + + it "doesn't produce a profile when --profile-ruby is not present" do + file 'config/client.rb', <<EOM +local_mode true +cookbook_path "#{path_to('cookbooks')}" +EOM + result = shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' -z", :cwd => chef_dir) + expect(File.exist?(path_to("config/local-mode-cache/cache/graph_profile.out"))).to be false + end end when_the_repository "has a cookbook that generates deprecation warnings" do diff --git a/spec/integration/knife/download_spec.rb b/spec/integration/knife/download_spec.rb index 8842ed5ac4..b8a19061b7 100644 --- a/spec/integration/knife/download_spec.rb +++ b/spec/integration/knife/download_spec.rb @@ -1103,6 +1103,15 @@ EOM before :each do Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, '/organizations/foo') end + when_the_repository 'has existing top level files' do + before do + file 'invitations.json', {} + end + + it "can still download top level files" do + knife('download /invitations.json').should_succeed + end + end when_the_repository 'is empty' do it 'knife download / downloads everything' do diff --git a/spec/integration/recipes/resource_action_spec.rb b/spec/integration/recipes/resource_action_spec.rb index 1c84b986cc..6f3f5ab47e 100644 --- a/spec/integration/recipes/resource_action_spec.rb +++ b/spec/integration/recipes/resource_action_spec.rb @@ -202,6 +202,11 @@ describe "Resource.action" do let(:resource_dsl) { :action_jackson } end + it "Can retrieve ancestors of action class without crashing" do + converge { action_jackson 'hi' } + expect { ActionJackson.action_class.ancestors.join(",") }.not_to raise_error + end + context "And 'action_jackgrandson' inheriting from ActionJackson and changing nothing" do before(:context) { class ActionJackgrandson < ActionJackson diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 92a4daf6d5..4f7fde8eaf 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -128,10 +128,12 @@ RSpec.configure do |config| config.filter_run_excluding :mac_osx_only=> true if !mac_osx? 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 :not_supported_on_nano => true if windows_nano_server? 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_nano_only => true unless windows_nano_server? config.filter_run_excluding :ruby64_only => true unless ruby_64bit? config.filter_run_excluding :ruby32_only => true unless ruby_32bit? config.filter_run_excluding :windows_powershell_dsc_only => true unless windows_powershell_dsc? diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb index 1cfad05172..9c6c3fdf72 100644 --- a/spec/support/platform_helpers.rb +++ b/spec/support/platform_helpers.rb @@ -83,6 +83,11 @@ def windows_powershell_dsc? supports_dsc end +def windows_nano_server? + require 'chef/platform/query_helpers' + Chef::Platform.windows_nano_server? +end + def mac_osx_106? if File.exists? "/usr/bin/sw_vers" result = ShellHelpers.shell_out("/usr/bin/sw_vers") diff --git a/spec/support/shared/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb index 3499cc98ec..d84c06c86b 100644 --- a/spec/support/shared/functional/windows_script.rb +++ b/spec/support/shared/functional/windows_script.rb @@ -19,14 +19,15 @@ # Shared context used by both Powershell and Batch script provider # tests. +require 'chef/platform/query_helpers' + shared_context Chef::Resource::WindowsScript do before(:all) do - - ohai_reader = Ohai::System.new - ohai_reader.all_plugins("platform") + @ohai_reader = Ohai::System.new + @ohai_reader.all_plugins(["platform", "kernel"]) new_node = Chef::Node.new - new_node.consume_external_attrs(ohai_reader.data,{}) + new_node.consume_external_attrs(@ohai_reader.data,{}) events = Chef::EventDispatch::Dispatcher.new @@ -51,12 +52,11 @@ shared_context Chef::Resource::WindowsScript do shared_examples_for "a script resource with architecture attribute" do context "with the given architecture attribute value" do - let(:resource_architecture) { architecture } let(:expected_architecture) do - if architecture - expected_architecture = architecture + if resource_architecture + expected_architecture = resource_architecture else - expected_architecture = :i386 + expected_architecture = @ohai_reader.data['kernel']['machine'].to_sym end end let(:expected_architecture_output) do @@ -77,16 +77,16 @@ shared_context Chef::Resource::WindowsScript do before(:each) do resource.code resource_command - (resource.architecture architecture) if architecture + (resource.architecture resource_architecture) if resource_architecture resource.returns(0) end - it "should create a process with the expected architecture" do + it "creates a process with the expected architecture" do resource.run_action(:run) expect(get_process_architecture).to eq(expected_architecture_output.downcase) end - it "should execute guards with the same architecture as the resource" do + it "executes guards with the same architecture as the resource" do resource.only_if resource_guard_command resource.run_action(:run) expect(get_process_architecture).to eq(expected_architecture_output.downcase) @@ -94,18 +94,32 @@ shared_context Chef::Resource::WindowsScript do expect(get_guard_process_architecture).to eq(get_process_architecture) end - let (:architecture) { :x86_64 } - it "should execute a 64-bit guard if the guard's architecture is specified as 64-bit", :windows64_only do - resource.only_if resource_guard_command, :architecture => :x86_64 - resource.run_action(:run) - expect(get_guard_process_architecture).to eq('amd64') + context "when the guard's architecture is specified as 64-bit" do + let (:guard_architecture) { :x86_64 } + it "executes a 64-bit guard", :windows64_only do + resource.only_if resource_guard_command, :architecture => guard_architecture + resource.run_action(:run) + expect(get_guard_process_architecture).to eq('amd64') + end end - let (:architecture) { :i386 } - it "should execute a 32-bit guard if the guard's architecture is specified as 32-bit" do - resource.only_if resource_guard_command, :architecture => :i386 - resource.run_action(:run) - expect(get_guard_process_architecture).to eq('x86') + context "when the guard's architecture is specified as 32-bit", :not_supported_on_nano do + let (:guard_architecture) { :i386 } + it "executes a 32-bit guard" do + resource.only_if resource_guard_command, :architecture => guard_architecture + resource.run_action(:run) + expect(get_guard_process_architecture).to eq('x86') + end + end + + context "when the guard's architecture is specified as 32-bit", :windows_nano_only do + let (:guard_architecture) { :i386 } + it "raises an error" do + resource.only_if resource_guard_command, :architecture => guard_architecture + expect{ resource.run_action(:run) }.to raise_error( + Chef::Exceptions::Win32ArchitectureIncorrect, + /cannot execute script with requested architecture 'i386' on Windows Nano Server/) + end end end end @@ -114,7 +128,28 @@ shared_context Chef::Resource::WindowsScript do describe "when the run action is invoked on Windows" do it "executes the script code" do - resource.code("whoami > #{script_output_path}") + resource.code("whoami > \"#{script_output_path}\"") + resource.returns(0) + resource.run_action(:run) + end + end + + context "when $env:TMP has a space" do + before(:each) do + @dir = Dir.mktmpdir("Jerry Smith") + @original_env = ENV.to_hash.dup + ENV.delete('TMP') + ENV['TMP'] = @dir + end + + after(:each) do + FileUtils.remove_entry_secure(@dir) + ENV.clear + ENV.update(@original_env) + end + + it "executes the script code" do + resource.code("whoami > \"#{script_output_path}\"") resource.returns(0) resource.run_action(:run) end @@ -122,6 +157,8 @@ shared_context Chef::Resource::WindowsScript do context "when evaluating guards" do it "has a guard_interpreter attribute set to the short name of the resource" do + pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? + expect(resource.guard_interpreter).to eq(resource.resource_name) resource.not_if "findstr.exe /thiscommandhasnonzeroexitstatus" expect(Chef::Resource).to receive(:resource_for_node).and_call_original @@ -131,17 +168,17 @@ shared_context Chef::Resource::WindowsScript do end context "when the architecture attribute is not set" do - let(:architecture) { nil } + let(:resource_architecture) { nil } it_behaves_like "a script resource with architecture attribute" end - context "when the architecture attribute is :i386" do - let(:architecture) { :i386 } + context "when the architecture attribute is :i386", :not_supported_on_nano do + let(:resource_architecture) { :i386 } it_behaves_like "a script resource with architecture attribute" end context "when the architecture attribute is :x86_64" do - let(:architecture) { :x86_64 } + let(:resource_architecture) { :x86_64 } it_behaves_like "a script resource with architecture attribute" end end diff --git a/spec/support/shared/unit/platform_introspector.rb b/spec/support/shared/unit/platform_introspector.rb index 9f42c985f8..df24370fde 100644 --- a/spec/support/shared/unit/platform_introspector.rb +++ b/spec/support/shared/unit/platform_introspector.rb @@ -32,6 +32,7 @@ shared_examples_for "a platform introspector" do # The following @platform_hash keys are used for testing version constraints @platform_hash['exact_match'] = { '1.2.3' => 'exact', '>= 1.0' => 'not exact'} @platform_hash['multiple_matches'] = { '~> 2.3.4' => 'matched ~> 2.3.4', '>= 2.3' => 'matched >=2.3' } + @platform_hash['invalid_cookbook_version'] = {'>= 21' => 'Matches a single number'} @platform_hash['successful_matches'] = { '< 3.0' => 'matched < 3.0', '>= 3.0' => 'matched >= 3.0' } @platform_family_hash = { @@ -95,6 +96,12 @@ shared_examples_for "a platform introspector" do expect {platform_introspector.value_for_platform(@platform_hash)}.to raise_error(RuntimeError) end + it 'should not require .0 to match >= 21.0' do + node.automatic_attrs[:platform] = 'invalid_cookbook_version' + node.automatic_attrs[:platform_version] = '21' + expect(platform_introspector.value_for_platform(@platform_hash)).to eq('Matches a single number') + end + it 'should return the value for that match' do node.automatic_attrs[:platform] = 'successful_matches' node.automatic_attrs[:platform_version] = '2.9' diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb index 831a3fc19e..727536f1f8 100644 --- a/spec/unit/application/client_spec.rb +++ b/spec/unit/application/client_spec.rb @@ -288,9 +288,9 @@ describe Chef::Application::Client, "configure_chef" do ARGV.replace(@original_argv) end - it "should set the colored output to false by default on windows and true otherwise" do + it "should set the colored output to true by default on windows and true on all other platforms as well" do if windows? - expect(Chef::Config[:color]).to be_falsey + expect(Chef::Config[:color]).to be_truthy else expect(Chef::Config[:color]).to be_truthy end diff --git a/spec/unit/application/knife_spec.rb b/spec/unit/application/knife_spec.rb index 3c215eac7f..8894e86240 100644 --- a/spec/unit/application/knife_spec.rb +++ b/spec/unit/application/knife_spec.rb @@ -70,13 +70,13 @@ describe Chef::Application::Knife do end end - it "should set the colored output to false by default on windows and true otherwise" do + it "should set the colored output to true by default on windows and true on all other platforms as well" do with_argv(*%w{noop knife command}) do expect(@knife).to receive(:exit).with(0) @knife.run end if windows? - expect(Chef::Config[:color]).to be_falsey + expect(Chef::Config[:color]).to be_truthy else expect(Chef::Config[:color]).to be_truthy end diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index f736c38859..8fbf56844e 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -375,7 +375,8 @@ describe Chef::Client do expect(node[:roles].length).to eq(1) expect(node[:roles]).to include("role_containing_cookbook1") expect(node[:recipes]).not_to be_nil - expect(node[:recipes].length).to eq(1) + expect(node[:recipes].length).to eq(2) + expect(node[:recipes]).to include("cookbook1") expect(node[:recipes]).to include("cookbook1::default") expect(node[:expanded_run_list]).not_to be_nil expect(node[:expanded_run_list].length).to eq(1) diff --git a/spec/unit/event_dispatch/dispatcher_spec.rb b/spec/unit/event_dispatch/dispatcher_spec.rb index 1014feea89..5a06e1d6d1 100644 --- a/spec/unit/event_dispatch/dispatcher_spec.rb +++ b/spec/unit/event_dispatch/dispatcher_spec.rb @@ -73,8 +73,51 @@ describe Chef::EventDispatch::Dispatcher do expect(event_sink.synchronized_cookbook_args).to eq ["apache2"] end end - end -end + context "when two event sinks have different arguments for an event" do + let(:event_sink_1) do + Class.new(Chef::EventDispatch::Base) do + attr_reader :synchronized_cookbook_args + def synchronized_cookbook(cookbook_name) + @synchronized_cookbook_args = [cookbook_name] + end + end.new + end + let(:event_sink_2) do + Class.new(Chef::EventDispatch::Base) do + attr_reader :synchronized_cookbook_args + def synchronized_cookbook(cookbook_name, cookbook) + @synchronized_cookbook_args = [cookbook_name, cookbook] + end + end.new + end + + context "and the one with fewer arguments comes first" do + before do + dispatcher.register(event_sink_1) + dispatcher.register(event_sink_2) + end + it "trims the arugment list" do + cookbook_version = double("cookbook_version") + dispatcher.synchronized_cookbook("apache2", cookbook_version) + expect(event_sink_1.synchronized_cookbook_args).to eq ["apache2"] + expect(event_sink_2.synchronized_cookbook_args).to eq ["apache2", cookbook_version] + end + end + + context "and the one with fewer arguments comes last" do + before do + dispatcher.register(event_sink_2) + dispatcher.register(event_sink_1) + end + it "trims the arugment list" do + cookbook_version = double("cookbook_version") + dispatcher.synchronized_cookbook("apache2", cookbook_version) + expect(event_sink_1.synchronized_cookbook_args).to eq ["apache2"] + expect(event_sink_2.synchronized_cookbook_args).to eq ["apache2", cookbook_version] + end + end + end +end diff --git a/spec/unit/http_spec.rb b/spec/unit/http_spec.rb index 4d851df951..a654d14aa2 100644 --- a/spec/unit/http_spec.rb +++ b/spec/unit/http_spec.rb @@ -89,4 +89,4 @@ describe Chef::HTTP do end # head -end +end
\ No newline at end of file diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb index 48aae3e61b..342c1878f1 100644 --- a/spec/unit/knife/bootstrap_spec.rb +++ b/spec/unit/knife/bootstrap_spec.rb @@ -235,12 +235,39 @@ describe Chef::Knife::Bootstrap do expect(knife.render_template).to eq('{"run_list":["role[base]","recipe[cupcakes]"]}') end - it "should have foo => {bar => baz} in the first_boot" do - knife.parse_options(["-j", '{"foo":{"bar":"baz"}}']) - knife.merge_configs - expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}') - actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template) - expect(actual_hash).to eq(expected_hash) + context "with bootstrap_attribute options" do + let(:jsonfile) { + file = Tempfile.new (['node', '.json']) + File.open(file.path, "w") {|f| f.puts '{"foo":{"bar":"baz"}}' } + file + } + + it "should have foo => {bar => baz} in the first_boot from cli" do + knife.parse_options(["-j", '{"foo":{"bar":"baz"}}']) + knife.merge_configs + expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}') + actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template) + expect(actual_hash).to eq(expected_hash) + end + + it "should have foo => {bar => baz} in the first_boot from file" do + knife.parse_options(["--json-attribute-file", jsonfile.path]) + knife.merge_configs + expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}') + actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template) + expect(actual_hash).to eq(expected_hash) + jsonfile.close + end + + context "when --json-attributes and --json-attribute-file were both passed" do + it "raises a Chef::Exceptions::BootstrapCommandInputError with the proper error message" do + knife.parse_options(["-j", '{"foo":{"bar":"baz"}}']) + knife.parse_options(["--json-attribute-file", jsonfile.path]) + knife.merge_configs + expect{ knife.run }.to raise_error(Chef::Exceptions::BootstrapCommandInputError) + jsonfile.close + end + end end end diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb index 0433ef9983..4d69d6300a 100644 --- a/spec/unit/knife/core/bootstrap_context_spec.rb +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -91,7 +91,7 @@ EXPECTED end describe "when bootstrapping into a specific environment" do - let(:chef_config){ {:environment => "prodtastic"} } + let(:config){ {:environment => "prodtastic"} } it "starts chef in the configured environment" do expect(bootstrap_context.start_chef).to eq('chef-client -j /etc/chef/first-boot.json -E prodtastic') end diff --git a/spec/unit/knife/status_spec.rb b/spec/unit/knife/status_spec.rb index ee44f3b3fd..11728a6f9b 100644 --- a/spec/unit/knife/status_spec.rb +++ b/spec/unit/knife/status_spec.rb @@ -44,9 +44,9 @@ describe Chef::Knife::Status do @knife.run end - it "should filter healthy nodes" do - @knife.config[:hide_healthy] = true - expect(@query).to receive(:search).with(:node, "NOT ohai_time:[1428569820 TO 1428573420]", opts) + it "should filter by nodes older than some mins" do + @knife.config[:hide_by_mins] = 59 + expect(@query).to receive(:search).with(:node, "NOT ohai_time:[1428569880 TO 1428573420]", opts) @knife.run end @@ -56,10 +56,10 @@ describe Chef::Knife::Status do @knife.run end - it "should filter by environment and health" do + it "should filter by environment and nodes older than some mins" do @knife.config[:environment] = "production" - @knife.config[:hide_healthy] = true - expect(@query).to receive(:search).with(:node, "chef_environment:production NOT ohai_time:[1428569820 TO 1428573420]", opts) + @knife.config[:hide_by_mins] = 59 + expect(@query).to receive(:search).with(:node, "chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts) @knife.run end @@ -79,22 +79,22 @@ describe Chef::Knife::Status do @knife.run end - it "should filter healthy nodes" do - @knife.config[:hide_healthy] = true - expect(@query).to receive(:search).with(:node, "name:my_custom_name NOT ohai_time:[1428569820 TO 1428573420]", opts) + it "should filter by nodes older than some mins with nodename specified" do + @knife.config[:hide_by_mins] = 59 + expect(@query).to receive(:search).with(:node, "name:my_custom_name NOT ohai_time:[1428569880 TO 1428573420]", opts) @knife.run end - it "should filter by environment" do + it "should filter by environment with nodename specified" do @knife.config[:environment] = "production" expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production", opts) @knife.run end - it "should filter by environment and health" do + it "should filter by environment and nodes older than some mins with nodename specified" do @knife.config[:environment] = "production" - @knife.config[:hide_healthy] = true - expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production NOT ohai_time:[1428569820 TO 1428573420]", opts) + @knife.config[:hide_by_mins] = 59 + expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts) @knife.run end end diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb index bcb64cb21e..7f6d315bbb 100644 --- a/spec/unit/lwrp_spec.rb +++ b/spec/unit/lwrp_spec.rb @@ -190,7 +190,7 @@ describe "LWRP" do end it "should have a class that outputs a reasonable string" do - expect(get_lwrp(:lwrp_foo).to_s).to eq "LWRP resource lwrp_foo from cookbook lwrp" + expect(get_lwrp(:lwrp_foo).to_s).to eq "Custom resource lwrp_foo from cookbook lwrp" end it "should add the specified actions to the allowed_actions array" do diff --git a/spec/unit/mixin/properties_spec.rb b/spec/unit/mixin/properties_spec.rb new file mode 100644 index 0000000000..18178619e4 --- /dev/null +++ b/spec/unit/mixin/properties_spec.rb @@ -0,0 +1,97 @@ +require 'support/shared/integration/integration_helper' +require 'chef/mixin/properties' + +module ChefMixinPropertiesSpec + describe "Chef::Resource.property" do + include IntegrationSupport + + context "with a base class A with properties a, ab, and ac" do + class A + include Chef::Mixin::Properties + property :a, 'a', default: 'a' + property :ab, ['a', 'b'], default: 'a' + property :ac, ['a', 'c'], default: 'a' + end + + context "and a module B with properties b, ab and bc" do + module B + include Chef::Mixin::Properties + property :b, 'b', default: 'b' + property :ab, default: 'b' + property :bc, ['b', 'c'], default: 'c' + end + + context "and a derived class C < A with properties c, ac and bc" do + class C < A + include B + property :c, 'c', default: 'c' + property :ac, default: 'c' + property :bc, default: 'c' + end + + it "A.properties has a, ab, and ac with types 'a', ['a', 'b'], and ['b', 'c']" do + expect(A.properties.keys).to eq [ :a, :ab, :ac ] + expect(A.properties[:a].validation_options[:is]).to eq 'a' + expect(A.properties[:ab].validation_options[:is]).to eq [ 'a', 'b' ] + expect(A.properties[:ac].validation_options[:is]).to eq [ 'a', 'c' ] + end + it "B.properties has b, ab, and bc with types 'b', nil and ['b', 'c']" do + expect(B.properties.keys).to eq [ :b, :ab, :bc ] + expect(B.properties[:b].validation_options[:is]).to eq 'b' + expect(B.properties[:ab].validation_options[:is]).to be_nil + expect(B.properties[:bc].validation_options[:is]).to eq [ 'b', 'c' ] + end + it "C.properties has a, b, c, ac and bc with merged types" do + expect(C.properties.keys).to eq [ :a, :ab, :ac, :b, :bc, :c ] + expect(C.properties[:a].validation_options[:is]).to eq 'a' + expect(C.properties[:b].validation_options[:is]).to eq 'b' + expect(C.properties[:c].validation_options[:is]).to eq 'c' + expect(C.properties[:ac].validation_options[:is]).to eq [ 'a', 'c' ] + expect(C.properties[:bc].validation_options[:is]).to eq [ 'b', 'c' ] + end + it "C.properties has ab with a non-merged type (from B)" do + expect(C.properties[:ab].validation_options[:is]).to be_nil + end + + context "and an instance of C" do + let(:c) { C.new } + + it "all properties can be retrieved and merged properties default to ab->b, ac->c, bc->c" do + expect(c.a).to eq('a') + expect(c.b).to eq('b') + expect(c.c).to eq('c') + expect(c.ab).to eq('b') + expect(c.ac).to eq('c') + expect(c.bc).to eq('c') + end + end + end + end + end + end + + context "with an Inner module" do + module Inner + include Chef::Mixin::Properties + property :inner + end + + context "and an Outer module including it" do + module Outer + include Inner + property :outer + end + + context "and an Outerest class including that" do + class Outerest + include Outer + property :outerest + end + + it "Outerest.properties.validation_options[:is] inner, outer, outerest" do + expect(Outerest.properties.keys).to eq [:inner, :outer, :outerest] + end + end + end + end +end diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 5f3bed2833..17e085a465 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -42,6 +42,14 @@ describe Chef::Node do expect([n3, n1, n2].sort).to eq([n1, n2, n3]) end + it "should share identity only with others of the same name" do + n1 = Chef::Node.build('foo') + n2 = Chef::Node.build('foo') + n3 = Chef::Node.build('bar') + expect(n1).to eq(n2) + expect(n1).not_to eq(n3) + end + describe "when the node does not exist on the server" do before do response = OpenStruct.new(:code => '404') diff --git a/spec/unit/policy_builder/expand_node_object_spec.rb b/spec/unit/policy_builder/expand_node_object_spec.rb index 815926ffb7..306d677108 100644 --- a/spec/unit/policy_builder/expand_node_object_spec.rb +++ b/spec/unit/policy_builder/expand_node_object_spec.rb @@ -34,6 +34,14 @@ describe Chef::PolicyBuilder::ExpandNodeObject do expect(policy_builder).to respond_to(:node) end + it "implements a load_node method for backwards compatibility until Chef 13" do + expect(policy_builder).to respond_to(:load_node) + end + + it "has removed the deprecated #load_node method", :chef_gte_13_only do + expect(policy_builder).to_not respond_to(:load_node) + end + it "implements a finish_load_node method" do expect(policy_builder).to respond_to(:finish_load_node) end @@ -98,6 +106,27 @@ describe Chef::PolicyBuilder::ExpandNodeObject do end + context "deprecated #load_node method" do + + let(:node) do + node = Chef::Node.new + node.name(node_name) + node.run_list(["recipe[a::default]", "recipe[b::server]"]) + node + end + + before do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) + policy_builder.load_node + end + + it "loads the node" do + expect(policy_builder.node).to eq(node) + end + + end + context "once the node has been loaded" do let(:node) do node = Chef::Node.new diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 9ecf7a4cde..dc06cb3326 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -921,6 +921,9 @@ describe "Chef::Resource.property" do expect(resource.x 10).to eq "101" expect(Namer.current_index).to eq 1 end + it "does not emit a deprecation warning if set to nil" do + expect(resource.x nil).to eq "1" + end it "coercion sets the value (and coercion does not run on get)" do expect(resource.x 10).to eq "101" expect(resource.x).to eq "101" @@ -933,6 +936,11 @@ describe "Chef::Resource.property" do expect(Namer.current_index).to eq 2 end end + with_property ':x, coerce: proc { |x| x }' do + it "does not emit a deprecation warning if set to nil" do + expect(resource.x nil).to be_nil + end + end with_property ':x, coerce: proc { |x| Namer.next_index; raise "hi" if x == 10; x }, is: proc { |x| Namer.next_index; x != 10 }' do it "failed coercion fails to set the value" do resource.x 20 diff --git a/spec/unit/provider/deploy_spec.rb b/spec/unit/provider/deploy_spec.rb index e6a7125e32..adcb9431eb 100644 --- a/spec/unit/provider/deploy_spec.rb +++ b/spec/unit/provider/deploy_spec.rb @@ -356,7 +356,7 @@ describe Chef::Provider::Deploy do it "chowns the whole release dir to user and group specified in the resource" do @resource.user "foo" @resource.group "bar" - expect(FileUtils).to receive(:chown_R).with("foo", "bar", "/my/deploy/dir") + expect(FileUtils).to receive(:chown_R).with("foo", "bar", "/my/deploy/dir", { :force => true }) @provider.enforce_ownership end diff --git a/spec/unit/provider/execute_spec.rb b/spec/unit/provider/execute_spec.rb index 1274203ce3..e7607d9417 100644 --- a/spec/unit/provider/execute_spec.rb +++ b/spec/unit/provider/execute_spec.rb @@ -172,5 +172,14 @@ describe Chef::Provider::Execute do provider.run_action(:run) expect(new_resource).to be_updated end + + it "should not include stdout/stderr in failure exception for sensitive resource" do + opts.delete(:live_stream) + new_resource.sensitive true + expect(provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed) + expect do + provider.run_action(:run) + end.to raise_error(Mixlib::ShellOut::ShellCommandFailed, /suppressed for sensitive resource/) + end end end diff --git a/spec/unit/provider/powershell_script_spec.rb b/spec/unit/provider/powershell_script_spec.rb index 855c18af9b..121973763d 100644 --- a/spec/unit/provider/powershell_script_spec.rb +++ b/spec/unit/provider/powershell_script_spec.rb @@ -38,41 +38,67 @@ describe Chef::Provider::PowershellScript, "action_run" do } context 'when setting interpreter flags' do - it "should set the -File flag as the last flag" do - expect(provider.flags.split(' ').pop).to eq("-File") + context 'on nano' do + before(:each) do + allow(Chef::Platform).to receive(:windows_nano_server?).and_return(true) + allow(provider).to receive(:is_forced_32bit).and_return(false) + os_info_double = double("os_info") + allow(provider.run_context.node.kernel).to receive(:os_info).and_return(os_info_double) + allow(os_info_double).to receive(:system_directory).and_return("C:\\Windows\\system32") + end + + it "sets the -Command flag as the last flag" do + flags = provider.command.split(' ').keep_if { |flag| flag =~ /^-/ } + expect(flags.pop).to eq("-Command") + end end - let(:execution_policy_flag) do - execution_policy_index = 0 - provider_flags = provider.flags.split(' ') - execution_policy_specified = false + context 'not on nano' do + before(:each) do + allow(Chef::Platform).to receive(:windows_nano_server?).and_return(false) + allow(provider).to receive(:is_forced_32bit).and_return(false) + os_info_double = double("os_info") + allow(provider.run_context.node.kernel).to receive(:os_info).and_return(os_info_double) + allow(os_info_double).to receive(:system_directory).and_return("C:\\Windows\\system32") + end - provider_flags.find do | value | - execution_policy_index += 1 - execution_policy_specified = value.downcase == '-ExecutionPolicy'.downcase + it "sets the -File flag as the last flag" do + flags = provider.command.split(' ').keep_if { |flag| flag =~ /^-/ } + expect(flags.pop).to eq("-File") end - execution_policy = execution_policy_specified ? provider_flags[execution_policy_index] : nil - end + let(:execution_policy_flag) do + execution_policy_index = 0 + provider_flags = provider.flags.split(' ') + execution_policy_specified = false - context 'when running with an unspecified PowerShell version' do - let(:powershell_version) { nil } - it "should set the -ExecutionPolicy flag to 'Unrestricted' by default" do - expect(execution_policy_flag.downcase).to eq('unrestricted'.downcase) + provider_flags.find do | value | + execution_policy_index += 1 + execution_policy_specified = value.downcase == '-ExecutionPolicy'.downcase + end + + execution_policy = execution_policy_specified ? provider_flags[execution_policy_index] : nil + end + + context 'when running with an unspecified PowerShell version' do + let(:powershell_version) { nil } + it "sets the -ExecutionPolicy flag to 'Unrestricted' by default" do + expect(execution_policy_flag.downcase).to eq('unrestricted'.downcase) + end end - end - { '2.0' => 'Unrestricted', - '2.5' => 'Unrestricted', - '3.0' => 'Bypass', - '3.6' => 'Bypass', - '4.0' => 'Bypass', - '5.0' => 'Bypass' }.each do | version_policy | - let(:powershell_version) { version_policy[0].to_f } - context "when running PowerShell version #{version_policy[0]}" do + { '2.0' => 'Unrestricted', + '2.5' => 'Unrestricted', + '3.0' => 'Bypass', + '3.6' => 'Bypass', + '4.0' => 'Bypass', + '5.0' => 'Bypass' }.each do | version_policy | let(:powershell_version) { version_policy[0].to_f } - it "should set the -ExecutionPolicy flag to '#{version_policy[1]}'" do - expect(execution_policy_flag.downcase).to eq(version_policy[1].downcase) + context "when running PowerShell version #{version_policy[0]}" do + let(:powershell_version) { version_policy[0].to_f } + it "sets the -ExecutionPolicy flag to '#{version_policy[1]}'" do + expect(execution_policy_flag.downcase).to eq(version_policy[1].downcase) + end end 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 2039408914..62c3ac6c6e 100644 --- a/spec/unit/provider/service/solaris_smf_service_spec.rb +++ b/spec/unit/provider/service/solaris_smf_service_spec.rb @@ -31,66 +31,126 @@ describe Chef::Provider::Service::Solaris do @provider = Chef::Provider::Service::Solaris.new(@new_resource, @run_context) allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) - @stdin = StringIO.new - @stdout = StringIO.new - @stderr = StringIO.new - @pid = 2342 - @stdout_string = "state disabled" - allow(@stdout).to receive(:gets).and_return(@stdout_string) - @status = double("Status", :exitstatus => 0, :stdout => @stdout) - allow(@provider).to receive(:shell_out!).and_return(@status) + # enabled / started service (svcs -l chef) + enabled_svc_stdout = [ + 'fmri svc:/application/chef:default', + 'name chef service', + 'enabled true', + 'state online', + 'next_state none', + 'state_time April 2, 2015 04:25:19 PM EDT', + 'logfile /var/svc/log/application-chef:default.log', + 'restarter svc:/system/svc/restarter:default', + 'contract_id 1115271', + 'dependency require_all/error svc:/milestone/multi-user:default (online)' + ].join("\n") + + # disabled / stopped service (svcs -l chef) + disabled_svc_stdout = [ + 'fmri svc:/application/chef:default', + 'name chef service', + 'enabled false', + 'state disabled', + 'next_state none', + 'state_time April 2, 2015 04:25:19 PM EDT', + 'logfile /var/svc/log/application-chef:default.log', + 'restarter svc:/system/svc/restarter:default', + 'contract_id 1115271', + 'dependency require_all/error svc:/milestone/multi-user:default (online)' + ].join("\n") + + # disabled / stopped service (svcs -l chef) + maintenance_svc_stdout = [ + 'fmri svc:/application/chef:default', + 'name chef service', + 'enabled true', + 'state maintenance', + 'next_state none', + 'state_time April 2, 2015 04:25:19 PM EDT', + 'logfile /var/svc/log/application-chef:default.log', + 'restarter svc:/system/svc/restarter:default', + 'contract_id 1115271', + 'dependency require_all/error svc:/milestone/multi-user:default (online)' + ].join("\n") + + # shell_out! return value for a service that is running + @enabled_svc_status = double("Status", :exitstatus => 0, :stdout => enabled_svc_stdout, :stdin => '', :stderr => '') + + # shell_out! return value for a service that is disabled + @disabled_svc_status = double("Status", :exitstatus => 0, :stdout => disabled_svc_stdout, :stdin => '', :stderr => '') + + # shell_out! return value for a service that is in maintenance mode + @maintenance_svc_status = double("Status", :exitstatus => 0, :stdout => maintenance_svc_stdout, :stdin => '', :stderr => '') + + # shell_out! return value for a service that does not exist + @no_svc_status = double("Status", :exitstatus => 1, :stdout => '', :stdin => '', :stderr => "svcs: Pattern 'chef' doesn't match any instances\n") + + # shell_out! return value for a successful execution + @success = double("clear", :exitstatus => 0, :stdout => '', :stdin => '', :stderr => '') end - it "should raise an error if /bin/svcs does not exist" do - expect(File).to receive(:exists?).with("/bin/svcs").and_return(false) + it "should raise an error if /bin/svcs and /usr/sbin/svcadm are not executable" do + allow(File).to receive(:executable?).with("/bin/svcs").and_return(false) + allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(false) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Service) end - describe "on a host with /bin/svcs" do + it "should raise an error if /bin/svcs is not executable" do + allow(File).to receive(:executable?).with("/bin/svcs").and_return(false) + allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(true) + expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Service) + end + + it "should raise an error if /usr/sbin/svcadm is not executable" do + allow(File).to receive(:executable?).with("/bin/svcs").and_return(true) + allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(false) + expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Service) + end + + describe "on a host with /bin/svcs and /usr/sbin/svcadm" do before do - allow(File).to receive(:exists?).with('/bin/svcs').and_return(true) + allow(File).to receive(:executable?).with("/bin/svcs").and_return(true) + allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(true) end describe "when discovering the current service state" do it "should create a current resource with the name of the new resource" do - allow(@provider).to receive(:shell_out!).with("/bin/svcs -l chef").and_return(@status) + expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status) expect(Chef::Resource::Service).to receive(:new).and_return(@current_resource) @provider.load_current_resource end it "should return the current resource" do - allow(@provider).to receive(:shell_out!).with("/bin/svcs -l chef").and_return(@status) + expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status) expect(@provider.load_current_resource).to eql(@current_resource) end it "should call '/bin/svcs -l service_name'" do - expect(@provider).to receive(:shell_out!).with("/bin/svcs -l chef", {:returns=>[0, 1]}).and_return(@status) + expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status) @provider.load_current_resource end it "should mark service as not running" do - allow(@provider).to receive(:shell_out!).and_return(@status) + expect(@provider).to receive(:shell_out!).and_return(@disabled_svc_status) expect(@current_resource).to receive(:running).with(false) @provider.load_current_resource end it "should mark service as running" do - @status = double("Status", :exitstatus => 0, :stdout => 'state online') - allow(@provider).to receive(:shell_out!).and_return(@status) + expect(@provider).to receive(:shell_out!).and_return(@enabled_svc_status) expect(@current_resource).to receive(:running).with(true) @provider.load_current_resource end it "should not mark service as maintenance" do - allow(@provider).to receive(:shell_out!).and_return(@status) + expect(@provider).to receive(:shell_out!).and_return(@enabled_svc_status) @provider.load_current_resource expect(@provider.maintenance).to be_falsey end it "should mark service as maintenance" do - @status = double("Status", :exitstatus => 0, :stdout => 'state maintenance') - allow(@provider).to receive(:shell_out!).and_return(@status) + expect(@provider).to receive(:shell_out!).and_return(@maintenance_svc_status) @provider.load_current_resource expect(@provider.maintenance).to be_truthy end @@ -99,30 +159,41 @@ describe Chef::Provider::Service::Solaris do describe "when enabling the service" do before(:each) do @provider.current_resource = @current_resource - @current_resource.enabled(true) end it "should call svcadm enable -s chef" do - expect(@provider).not_to receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}") - expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status) + expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status) + expect(@provider).not_to receive(:shell_out!).with("/usr/sbin/svcadm", "clear", @current_resource.service_name) + expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "enable", "-s", @current_resource.service_name).and_return(@success) + @provider.load_current_resource + expect(@provider.enable_service).to be_truthy expect(@current_resource.enabled).to be_truthy end it "should call svcadm enable -s chef for start_service" do - expect(@provider).not_to receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}") - expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status) + expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status) + expect(@provider).not_to receive(:shell_out!).with("/usr/sbin/svcadm", "clear", @current_resource.service_name) + expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "enable", "-s", @current_resource.service_name).and_return(@success) + @provider.load_current_resource expect(@provider.start_service).to be_truthy expect(@current_resource.enabled).to be_truthy end it "should call svcadm clear chef for start_service when state maintenance" do - @status = double("Status", :exitstatus => 0, :stdout => 'state maintenance') - allow(@provider).to receive(:shell_out!).and_return(@status) + # we are in maint mode + expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@maintenance_svc_status) + expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "clear", @current_resource.service_name).and_return(@success) + expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "enable", "-s", @current_resource.service_name).and_return(@success) + + # load the resource, then enable it @provider.load_current_resource - expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}").and_return(@status) - expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status) expect(@provider.enable_service).to be_truthy + + # now we are enabled + expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status) + @provider.load_current_resource + expect(@current_resource.enabled).to be_truthy end end @@ -130,17 +201,20 @@ describe Chef::Provider::Service::Solaris do describe "when disabling the service" do before(:each) do @provider.current_resource = @current_resource - @current_resource.enabled(false) end it "should call svcadm disable -s chef" do - expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm disable -s chef").and_return(@status) + expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@disabled_svc_status) + expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "disable", "-s", "chef").and_return(@success) + @provider.load_current_resource expect(@provider.disable_service).to be_truthy expect(@current_resource.enabled).to be_falsey end it "should call svcadm disable -s chef for stop_service" do - expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm disable -s chef").and_return(@status) + expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@disabled_svc_status) + expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "disable", "-s", "chef").and_return(@success) + @provider.load_current_resource expect(@provider.stop_service).to be_truthy expect(@current_resource.enabled).to be_falsey end @@ -149,12 +223,12 @@ describe Chef::Provider::Service::Solaris do describe "when reloading the service" do before(:each) do - @status = double("Process::Status", :exitstatus => 0) @provider.current_resource = @current_resource + allow(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@enabled_svc_status) end it "should call svcadm refresh chef" do - expect(@provider).to receive(:shell_out_with_systems_locale!).with("/usr/sbin/svcadm refresh chef").and_return(@status) + expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "refresh", "chef") @provider.reload_service end @@ -162,19 +236,16 @@ describe Chef::Provider::Service::Solaris do describe "when the service doesn't exist" do before(:each) do - @stdout_string = "" - @status = double("Status", :exitstatus => 1, :stdout => @stdout) @provider.current_resource = @current_resource + expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", {:returns=>[0, 1]}).and_return(@no_svc_status) end it "should be marked not running" do - expect(@provider).to receive(:shell_out!).with("/bin/svcs -l chef", {:returns=>[0, 1]}).and_return(@status) @provider.service_status expect(@current_resource.running).to be_falsey end it "should be marked not enabled" do - expect(@provider).to receive(:shell_out!).with("/bin/svcs -l chef", {:returns=>[0, 1]}).and_return(@status) @provider.service_status expect(@current_resource.enabled).to be_falsey end diff --git a/spec/unit/provider/template/content_spec.rb b/spec/unit/provider/template/content_spec.rb index 3d6e822c00..509c8cf33b 100644 --- a/spec/unit/provider/template/content_spec.rb +++ b/spec/unit/provider/template/content_spec.rb @@ -20,6 +20,14 @@ require 'spec_helper' describe Chef::Provider::Template::Content do + let(:enclosing_directory) { + canonicalize_path(Dir.mktmpdir) + } + + let(:resource_path) { + canonicalize_path(File.expand_path(File.join(enclosing_directory, "openldap_stuff.conf"))) + } + let(:new_resource) do double("Chef::Resource::Template (new)", :cookbook_name => 'openldap', @@ -28,6 +36,8 @@ describe Chef::Provider::Template::Content do :source_line_file => "/Users/lamont/solo/cookbooks/openldap/recipes/default.rb", :source_line_number => "2", :source => 'openldap_stuff.conf.erb', + :name => 'openldap_stuff.conf', + :path => resource_path, :local => false, :cookbook => nil, :variables => {}, @@ -36,7 +46,10 @@ describe Chef::Provider::Template::Content do :helper_modules => []) end - let(:rendered_file_location) { Dir.tmpdir + '/openldap_stuff.conf' } + let(:rendered_file_locations) { + [Dir.tmpdir + '/openldap_stuff.conf', + enclosing_directory + '/openldap_stuff.conf'] + } let(:run_context) do cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) @@ -54,7 +67,9 @@ describe Chef::Provider::Template::Content do end after do - FileUtils.rm(rendered_file_location) if ::File.exist?(rendered_file_location) + rendered_file_locations.each do |file| + FileUtils.rm(file) if ::File.exist?(file) + end end it "finds the template file in the cookbook cache if it isn't local" do @@ -74,6 +89,39 @@ describe Chef::Provider::Template::Content do expect(content.template_location).to eq(CHEF_SPEC_DATA + '/cookbooks/openldap/templates/default/test.erb') end + it "returns a tempfile in the tempdir when :file_staging_uses_destdir is not set" do + Chef::Config[:file_staging_uses_destdir] = false + expect(content.tempfile.path.start_with?(Dir::tmpdir)).to be true + expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be false + end + + it "returns a tempfile in the destdir when :file_staging_uses_destdir is set" do + Chef::Config[:file_staging_uses_destdir] = true + expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be true + end + + context "when creating a tempfile in destdir fails" do + let(:enclosing_directory) { + canonicalize_path("/nonexisting/path") + } + + it "returns a tempfile in the tempdir when :file_deployment_uses_destdir is set to :auto" do + Chef::Config[:file_staging_uses_destdir] = :auto + expect(content.tempfile.path.start_with?(Dir::tmpdir)).to be true + expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be false + end + + it "fails when :file_desployment_uses_destdir is set" do + Chef::Config[:file_staging_uses_destdir] = true + expect{content.tempfile}.to raise_error(Chef::Exceptions::FileContentStagingError) + end + + it "returns a tempfile in the tempdir when :file_desployment_uses_destdir is not set" do + expect(content.tempfile.path.start_with?(Dir::tmpdir)).to be true + expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be false + end + end + it "creates the template with the rendered content" do run_context.node.normal[:slappiness] = "a warm gun" expect(IO.read(content.tempfile.path)).to eq("slappiness is a warm gun") @@ -88,6 +136,8 @@ describe Chef::Provider::Template::Content do :source_line_file => CHEF_SPEC_DATA + "/cookbooks/openldap/recipes/default.rb", :source_line_number => "2", :source => 'helpers.erb', + :name => 'helpers.erb', + :path => CHEF_SPEC_DATA + '/cookbooks/openldap/templates/default/helpers.erb', :local => false, :cookbook => nil, :variables => {}, diff --git a/spec/unit/provider/user/dscl_spec.rb b/spec/unit/provider/user/dscl_spec.rb index 32d0812d8c..a9407a4d7e 100644 --- a/spec/unit/provider/user/dscl_spec.rb +++ b/spec/unit/provider/user/dscl_spec.rb @@ -760,6 +760,13 @@ ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30") provider.dscl_create_comment end + it "sets the comment field to username" do + new_resource.comment nil + expect(provider).to receive(:run_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true) + provider.dscl_create_comment + expect(new_resource.comment).to eq("#mockssuck") + end + it "should run run_dscl with create /Users/user PrimaryGroupID to set the users primary group" do expect(provider).to receive(:run_dscl).with("create /Users/toor PrimaryGroupID '1001'").and_return(true) provider.dscl_set_gid @@ -789,6 +796,13 @@ ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30") expect { provider.dscl_set_gid }.to raise_error(Chef::Exceptions::GroupIDNotFound) end end + + it "should set group ID to 20 if it's not specified" do + new_resource.gid nil + expect(provider).to receive(:run_dscl).with("create /Users/toor PrimaryGroupID '20'").ordered.and_return(true) + provider.dscl_set_gid + expect(new_resource.gid).to eq(20) + end end describe "when the user exists and chef is managing it" do diff --git a/spec/unit/provider/user/windows_spec.rb b/spec/unit/provider/user/windows_spec.rb index e51e20a68f..7e08f971a9 100644 --- a/spec/unit/provider/user/windows_spec.rb +++ b/spec/unit/provider/user/windows_spec.rb @@ -107,8 +107,8 @@ describe Chef::Provider::User::Windows do expect(@provider.set_options[:home_dir]).to eq('/home/adam') end - it "marks the primary_group_id attribute to be updated" do - expect(@provider.set_options[:primary_group_id]).to eq(1000) + it "ignores the primary_group_id attribute" do + expect(@provider.set_options[:primary_group_id]).to eq(nil) end it "marks the user_id attribute to be updated" do diff --git a/spec/unit/provider/user_spec.rb b/spec/unit/provider/user_spec.rb index 2345ce18fb..bd24a6a01e 100644 --- a/spec/unit/provider/user_spec.rb +++ b/spec/unit/provider/user_spec.rb @@ -452,11 +452,20 @@ describe Chef::Provider::User do it "should raise an error if we can't translate the group name during resource assertions" do expect(Etc).to receive(:getgrnam).and_raise(ArgumentError) + @provider.action = :create @provider.define_resource_requirements @provider.convert_group_name expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::User) end + it "does not raise an error if we can't translate the group name during resource assertions if we are removing the user" do + expect(Etc).to receive(:getgrnam).and_raise(ArgumentError) + @provider.action = :remove + @provider.define_resource_requirements + @provider.convert_group_name + expect { @provider.process_resource_requirements }.not_to raise_error + end + it "should set the new resources gid to the integerized version if available" do expect(Etc).to receive(:getgrnam).with("999").and_return(@group) @provider.convert_group_name diff --git a/spec/unit/resource/powershell_script_spec.rb b/spec/unit/resource/powershell_script_spec.rb index 2505c4a3d7..42fcd61a58 100644 --- a/spec/unit/resource/powershell_script_spec.rb +++ b/spec/unit/resource/powershell_script_spec.rb @@ -30,24 +30,28 @@ describe Chef::Resource::PowershellScript do run_context = Chef::RunContext.new(node, nil, nil) @resource = Chef::Resource::PowershellScript.new("powershell_unit_test", run_context) - end - it "should create a new Chef::Resource::PowershellScript" do + it "creates a new Chef::Resource::PowershellScript" do expect(@resource).to be_a_kind_of(Chef::Resource::PowershellScript) end - it "should set convert_boolean_return to false by default" do + it "sets convert_boolean_return to false by default" do expect(@resource.convert_boolean_return).to eq(false) end - it "should return the value for convert_boolean_return that was set" do + it "returns the value for convert_boolean_return that was set" do @resource.convert_boolean_return true expect(@resource.convert_boolean_return).to eq(true) @resource.convert_boolean_return false expect(@resource.convert_boolean_return).to eq(false) end + it "raises an error when architecture is i386 on Windows Nano Server" do + allow(Chef::Platform).to receive(:windows_nano_server?).and_return(true) + expect{@resource.architecture(:i386)}.to raise_error(Chef::Exceptions::Win32ArchitectureIncorrect, "cannot execute script with requested architecture 'i386' on Windows Nano Server") + end + context "when using guards" do let(:resource) { @resource } before(:each) do @@ -62,32 +66,32 @@ describe Chef::Resource::PowershellScript do expect(inherited_difference).to eq([]) end - it "should allow guard interpreter to be set to Chef::Resource::Script" do + it "allows guard interpreter to be set to Chef::Resource::Script" do resource.guard_interpreter(:script) allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false) resource.only_if("echo hi") end - it "should allow guard interpreter to be set to Chef::Resource::Bash derived from Chef::Resource::Script" do + it "allows guard interpreter to be set to Chef::Resource::Bash derived from Chef::Resource::Script" do resource.guard_interpreter(:bash) allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false) resource.only_if("echo hi") end - it "should allow guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do + it "allows guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do resource.guard_interpreter(:powershell_script) allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false) resource.only_if("echo hi") end - it "should enable convert_boolean_return by default for guards in the context of powershell_script when no guard params are specified" do + it "enables convert_boolean_return by default for guards in the context of powershell_script when no guard params are specified" do allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(true) allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( {:convert_boolean_return => true, :code => "$true"}).and_return(Proc.new {}) resource.only_if("$true") end - it "should enable convert_boolean_return by default for guards in non-Chef::Resource::Script derived resources when no guard params are specified" do + it "enables convert_boolean_return by default for guards in non-Chef::Resource::Script derived resources when no guard params are specified" do node = Chef::Node.new run_context = Chef::RunContext.new(node, nil, nil) file_resource = Chef::Resource::File.new('idontexist', run_context) @@ -98,21 +102,21 @@ describe Chef::Resource::PowershellScript do resource.only_if("$true") end - it "should enable convert_boolean_return by default for guards in the context of powershell_script when guard params are specified" do + it "enables convert_boolean_return by default for guards in the context of powershell_script when guard params are specified" do guard_parameters = {:cwd => '/etc/chef', :architecture => :x86_64} allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( {:convert_boolean_return => true, :code => "$true"}.merge(guard_parameters)).and_return(Proc.new {}) resource.only_if("$true", guard_parameters) end - it "should pass convert_boolean_return as true if it was specified as true in a guard parameter" do + it "passes convert_boolean_return as true if it was specified as true in a guard parameter" do guard_parameters = {:cwd => '/etc/chef', :convert_boolean_return => true, :architecture => :x86_64} allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( {:convert_boolean_return => true, :code => "$true"}.merge(guard_parameters)).and_return(Proc.new {}) resource.only_if("$true", guard_parameters) end - it "should pass convert_boolean_return as false if it was specified as true in a guard parameter" do + it "passes convert_boolean_return as false if it was specified as true in a guard parameter" do other_guard_parameters = {:cwd => '/etc/chef', :architecture => :x86_64} parameters_with_boolean_disabled = other_guard_parameters.merge({:convert_boolean_return => false, :code => "$true"}) allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( @@ -127,6 +131,6 @@ describe Chef::Resource::PowershellScript do let(:resource_name) { :powershell_script } let(:interpreter_file_name) { 'powershell.exe' } - it_should_behave_like "a Windows script resource" + it_behaves_like "a Windows script resource" end end diff --git a/spec/unit/resource/registry_key_spec.rb b/spec/unit/resource/registry_key_spec.rb index 2e2811d026..2d82f1a51c 100644 --- a/spec/unit/resource/registry_key_spec.rb +++ b/spec/unit/resource/registry_key_spec.rb @@ -91,7 +91,7 @@ describe Chef::Resource::RegistryKey, "values" do it "should return checksummed data if the type is unsafe" do @resource.values( { :name => 'poosh', :type => :binary, :data => 255.chr * 1 }) - expect(@resource.values).to eql([ { :name => 'poosh', :type => :binary, :data => "00594fd4f42ba43fc1ca0427a0576295" } ]) + expect(@resource.values).to eql([ { :name => 'poosh', :type => :binary, :data => 'a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89' } ]) end it "should throw an exception if the name field is missing" do @@ -194,6 +194,6 @@ describe Chef::Resource::RegistryKey, "state" do it "should return scrubbed values" do @resource.values([ { :name => 'poosh', :type => :binary, :data => 255.chr * 1 } ]) - expect(@resource.state).to eql( { :values => [{ :name => 'poosh', :type => :binary, :data => "00594fd4f42ba43fc1ca0427a0576295" }] } ) + expect(@resource.state).to eql( { :values => [{ :name => 'poosh', :type => :binary, :data => 'a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89'}] } ) end end diff --git a/spec/unit/resource/resource_notification_spec.rb b/spec/unit/resource/resource_notification_spec.rb index 7f6b124d4d..024b6f93f7 100644 --- a/spec/unit/resource/resource_notification_spec.rb +++ b/spec/unit/resource/resource_notification_spec.rb @@ -19,149 +19,148 @@ require 'spec_helper' require 'chef/resource/resource_notification' describe Chef::Resource::Notification do - before do - @notification = Chef::Resource::Notification.new(:service_apache, :restart, :template_httpd_conf) - end + + let(:notification) { Chef::Resource::Notification.new(:service_apache, :restart, :template_httpd_conf) } it "has a resource to be notified" do - expect(@notification.resource).to eq(:service_apache) + expect(notification.resource).to eq(:service_apache) end it "has an action to take on the service" do - expect(@notification.action).to eq(:restart) + expect(notification.action).to eq(:restart) end it "has a notifying resource" do - expect(@notification.notifying_resource).to eq(:template_httpd_conf) + expect(notification.notifying_resource).to eq(:template_httpd_conf) end it "is a duplicate of another notification with the same target resource and action" do other = Chef::Resource::Notification.new(:service_apache, :restart, :sync_web_app_code) - expect(@notification.duplicates?(other)).to be_truthy + expect(notification.duplicates?(other)).to be_truthy end it "is not a duplicate of another notification if the actions differ" do other = Chef::Resource::Notification.new(:service_apache, :enable, :install_apache) - expect(@notification.duplicates?(other)).to be_falsey + expect(notification.duplicates?(other)).to be_falsey end it "is not a duplicate of another notification if the target resources differ" do other = Chef::Resource::Notification.new(:service_sshd, :restart, :template_httpd_conf) - expect(@notification.duplicates?(other)).to be_falsey + expect(notification.duplicates?(other)).to be_falsey end it "raises an ArgumentError if you try to check a non-ducktype object for duplication" do - expect {@notification.duplicates?(:not_a_notification)}.to raise_error(ArgumentError) + expect {notification.duplicates?(:not_a_notification)}.to raise_error(ArgumentError) end it "takes no action to resolve a resource reference that doesn't need to be resolved" do @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") - @notification.resource = @keyboard_cat + notification.resource = @keyboard_cat @long_cat = Chef::Resource::Cat.new("long_cat") - @notification.notifying_resource = @long_cat + notification.notifying_resource = @long_cat @resource_collection = Chef::ResourceCollection.new # would raise an error since the resource is not in the collection - @notification.resolve_resource_reference(@resource_collection) - expect(@notification.resource).to eq(@keyboard_cat) + notification.resolve_resource_reference(@resource_collection) + expect(notification.resource).to eq(@keyboard_cat) end it "resolves a lazy reference to a resource" do - @notification.resource = {:cat => "keyboard_cat"} + notification.resource = {:cat => "keyboard_cat"} @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @keyboard_cat @long_cat = Chef::Resource::Cat.new("long_cat") - @notification.notifying_resource = @long_cat - @notification.resolve_resource_reference(@resource_collection) - expect(@notification.resource).to eq(@keyboard_cat) + notification.notifying_resource = @long_cat + notification.resolve_resource_reference(@resource_collection) + expect(notification.resource).to eq(@keyboard_cat) end it "resolves a lazy reference to its notifying resource" do @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") - @notification.resource = @keyboard_cat - @notification.notifying_resource = {:cat => "long_cat"} + notification.resource = @keyboard_cat + notification.notifying_resource = {:cat => "long_cat"} @long_cat = Chef::Resource::Cat.new("long_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @long_cat - @notification.resolve_resource_reference(@resource_collection) - expect(@notification.notifying_resource).to eq(@long_cat) + notification.resolve_resource_reference(@resource_collection) + expect(notification.notifying_resource).to eq(@long_cat) end it "resolves lazy references to both its resource and its notifying resource" do - @notification.resource = {:cat => "keyboard_cat"} + notification.resource = {:cat => "keyboard_cat"} @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @keyboard_cat - @notification.notifying_resource = {:cat => "long_cat"} + notification.notifying_resource = {:cat => "long_cat"} @long_cat = Chef::Resource::Cat.new("long_cat") @resource_collection << @long_cat - @notification.resolve_resource_reference(@resource_collection) - expect(@notification.resource).to eq(@keyboard_cat) - expect(@notification.notifying_resource).to eq(@long_cat) + notification.resolve_resource_reference(@resource_collection) + expect(notification.resource).to eq(@keyboard_cat) + expect(notification.notifying_resource).to eq(@long_cat) end it "raises a RuntimeError if you try to reference multiple resources" do - @notification.resource = {:cat => ["keyboard_cat", "cheez_cat"]} + notification.resource = {:cat => ["keyboard_cat", "cheez_cat"]} @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") @cheez_cat = Chef::Resource::Cat.new("cheez_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @keyboard_cat @resource_collection << @cheez_cat @long_cat = Chef::Resource::Cat.new("long_cat") - @notification.notifying_resource = @long_cat - expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError) + notification.notifying_resource = @long_cat + expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError) end it "raises a RuntimeError if you try to reference multiple notifying resources" do - @notification.notifying_resource = {:cat => ["long_cat", "cheez_cat"]} + notification.notifying_resource = {:cat => ["long_cat", "cheez_cat"]} @long_cat = Chef::Resource::Cat.new("long_cat") @cheez_cat = Chef::Resource::Cat.new("cheez_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @long_cat @resource_collection << @cheez_cat @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") - @notification.resource = @keyboard_cat - expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError) + notification.resource = @keyboard_cat + expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError) end it "raises a RuntimeError if it can't find a resource in the resource collection when resolving a lazy reference" do - @notification.resource = {:cat => "keyboard_cat"} + notification.resource = {:cat => "keyboard_cat"} @cheez_cat = Chef::Resource::Cat.new("cheez_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @cheez_cat @long_cat = Chef::Resource::Cat.new("long_cat") - @notification.notifying_resource = @long_cat - expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError) + notification.notifying_resource = @long_cat + expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError) end it "raises a RuntimeError if it can't find a notifying resource in the resource collection when resolving a lazy reference" do - @notification.notifying_resource = {:cat => "long_cat"} + notification.notifying_resource = {:cat => "long_cat"} @cheez_cat = Chef::Resource::Cat.new("cheez_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @cheez_cat @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") - @notification.resource = @keyboard_cat - expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError) + notification.resource = @keyboard_cat + expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(RuntimeError) end it "raises an ArgumentError if improper syntax is used in the lazy reference to its resource" do - @notification.resource = "cat => keyboard_cat" + notification.resource = "cat => keyboard_cat" @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @keyboard_cat @long_cat = Chef::Resource::Cat.new("long_cat") - @notification.notifying_resource = @long_cat - expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(ArgumentError) + notification.notifying_resource = @long_cat + expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(ArgumentError) end it "raises an ArgumentError if improper syntax is used in the lazy reference to its notifying resource" do - @notification.notifying_resource = "cat => long_cat" + notification.notifying_resource = "cat => long_cat" @long_cat = Chef::Resource::Cat.new("long_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @long_cat @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") - @notification.resource = @keyboard_cat - expect {@notification.resolve_resource_reference(@resource_collection)}.to raise_error(ArgumentError) + notification.resource = @keyboard_cat + expect {notification.resolve_resource_reference(@resource_collection)}.to raise_error(ArgumentError) end # Create test to resolve lazy references to both notifying resource and dest. resource diff --git a/spec/unit/resource_reporter_spec.rb b/spec/unit/resource_reporter_spec.rb index 4f3a085584..f2c0b8fd8b 100644 --- a/spec/unit/resource_reporter_spec.rb +++ b/spec/unit/resource_reporter_spec.rb @@ -50,6 +50,9 @@ describe Chef::ResourceReporter do @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @run_status = Chef::RunStatus.new(@node, @events) + @run_list = Chef::RunList.new + @run_list << 'recipe[lobster]' << 'role[rage]' << 'recipe[fist]' + @expansion = Chef::RunList::RunListExpansion.new("_default", @run_list.run_list_items) @run_id = @run_status.run_id allow(Time).to receive(:now).and_return(@start_time, @end_time) end @@ -424,6 +427,10 @@ describe Chef::ResourceReporter do expect(@report["run_list"]).to eq(Chef::JSONCompat.to_json(@run_status.node.run_list)) end + it "includes the expanded_run_list" do + expect(@report).to have_key("expanded_run_list") + end + it "includes the end_time" do expect(@report).to have_key("end_time") expect(@report["end_time"]).to eq(@run_status.end_time.to_s) diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb index 3b04981610..3eee997c50 100644 --- a/spec/unit/rest_spec.rb +++ b/spec/unit/rest_spec.rb @@ -69,8 +69,8 @@ describe Chef::REST do rest end - let(:standard_read_headers) {{"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}} - let(:standard_write_headers) {{"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}} + let(:standard_read_headers) {{"Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}} + let(:standard_write_headers) {{"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}} before(:each) do Chef::Log.init(log_stringio) diff --git a/spec/unit/run_list/run_list_expansion_spec.rb b/spec/unit/run_list/run_list_expansion_spec.rb index 859219d346..a7df9e749b 100644 --- a/spec/unit/run_list/run_list_expansion_spec.rb +++ b/spec/unit/run_list/run_list_expansion_spec.rb @@ -21,7 +21,7 @@ require 'spec_helper' describe Chef::RunList::RunListExpansion do before do @run_list = Chef::RunList.new - @run_list << 'recipe[lobster]' << 'role[rage]' << 'recipe[fist]' + @run_list << 'recipe[lobster::mastercookbook@0.1.0]' << 'role[rage]' << 'recipe[fist@0.1]' @expansion = Chef::RunList::RunListExpansion.new("_default", @run_list.run_list_items) end @@ -59,7 +59,7 @@ describe Chef::RunList::RunListExpansion do end it "has the correct list of recipes for the given environment" do - expect(@expansion.recipes).to eq(["lobster", "prod-only", "fist"]) + expect(@expansion.recipes).to eq(["lobster::mastercookbook", "prod-only", "fist"]) end end @@ -82,19 +82,34 @@ describe Chef::RunList::RunListExpansion do describe "after expanding a run list" do before do @first_role = Chef::Role.new + @first_role.name('rage') @first_role.run_list('role[mollusk]') @first_role.default_attributes({'foo' => 'bar'}) @first_role.override_attributes({'baz' => 'qux'}) @second_role = Chef::Role.new + @second_role.name('rage') @second_role.run_list('recipe[crabrevenge]') @second_role.default_attributes({'foo' => 'boo'}) @second_role.override_attributes({'baz' => 'bux'}) allow(@expansion).to receive(:fetch_role).and_return(@first_role, @second_role) @expansion.expand + @json = '{"id":"_default","run_list":[{"type":"recipe","name":"lobster::mastercookbook","version":"0.1.0",' + .concat( +'"skipped":false},{"type":"role","name":"rage","children":[{"type":"role","name":"mollusk","children":[],"missing":null,' + .concat( +'"error":null,"skipped":null},{"type":"recipe","name":"crabrevenge","version":null,"skipped":false}],"missing":null,' + .concat( +'"error":null,"skipped":null},{"type":"recipe","name":"fist","version":"0.1","skipped":false}]}'))) + + end + + it "produces json tree upon tracing expansion" do + jsonRunList = @expansion.to_json + expect(jsonRunList).to eq(@json) end it "has the ordered list of recipes" do - expect(@expansion.recipes).to eq(['lobster', 'crabrevenge', 'fist']) + expect(@expansion.recipes).to eq(['lobster::mastercookbook', 'crabrevenge', 'fist']) end it "has the merged attributes from the roles with outer roles overriding inner" do diff --git a/spec/unit/run_list/versioned_recipe_list_spec.rb b/spec/unit/run_list/versioned_recipe_list_spec.rb index 9c3ecaa0dd..be57d6c944 100644 --- a/spec/unit/run_list/versioned_recipe_list_spec.rb +++ b/spec/unit/run_list/versioned_recipe_list_spec.rb @@ -187,4 +187,9 @@ describe Chef::RunList::VersionedRecipeList do end end + context "with duplicated names", :chef_gte_13_only do + it "should fail in Chef 13" do + expect(list).to_not respond_to(:with_duplicate_names) + end + end end diff --git a/spec/unit/search/query_spec.rb b/spec/unit/search/query_spec.rb index 59ac80f228..f85b1760d4 100644 --- a/spec/unit/search/query_spec.rb +++ b/spec/unit/search/query_spec.rb @@ -83,6 +83,8 @@ describe Chef::Search::Query do describe "search" do let(:query_string) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0" } let(:query_string_continue) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=4" } + let(:query_string_with_rows) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=4" } + let(:query_string_continue_with_rows) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=4&rows=4" } let(:response) { { "rows" => [ @@ -149,6 +151,14 @@ describe Chef::Search::Query do r } + let(:big_response_empty) { + { + "start" => 0, + "total" => 8, + "rows" => [] + } + } + let(:big_response_end) { r = response.dup r["start"] = 4 @@ -208,7 +218,7 @@ describe Chef::Search::Query do it "pages through the responses" do @call_me = double("blocky") response["rows"].each { |r| expect(@call_me).to receive(:do).with(r) } - query.search(:node, "*:*", sort: nil, start: 0, rows: 1) { |r| @call_me.do(r) } + query.search(:node, "*:*", sort: nil, start: 0, rows: 4) { |r| @call_me.do(r) } end it "sends multiple API requests when the server indicates there is more data" do @@ -219,6 +229,14 @@ describe Chef::Search::Query do end end + it "paginates correctly in the face of filtered nodes" do + expect(rest).to receive(:get_rest).with(query_string_with_rows).and_return(big_response_empty) + expect(rest).to receive(:get_rest).with(query_string_continue_with_rows).and_return(big_response_end) + query.search(:node, "platform:rhel", rows: 4) do |r| + nil + end + end + context "when :filter_result is provided as a result" do include_context "filtered search" do let(:filter_key) { :filter_result } diff --git a/spec/unit/windows_service_spec.rb b/spec/unit/windows_service_spec.rb index bc5e781c03..396584716d 100644 --- a/spec/unit/windows_service_spec.rb +++ b/spec/unit/windows_service_spec.rb @@ -49,7 +49,11 @@ describe "Chef::Application::WindowsService", :windows_only do allow(instance.instance_variable_get(:@service_signal)).to receive(:wait) allow(instance).to receive(:state).and_return(4) expect(instance).to receive(:run_chef_client).and_call_original - expect(instance).to receive(:shell_out).with("chef-client --no-fork -c test_config_file -L #{tempfile.path}", {:timeout => 7200}).and_return(shell_out_result) + expect(instance).to receive(:shell_out).with("chef-client --no-fork -c test_config_file -L #{tempfile.path}", + { + :timeout => 7200, + :logger => Chef::Log + }).and_return(shell_out_result) instance.service_main tempfile.unlink end @@ -63,7 +67,11 @@ describe "Chef::Application::WindowsService", :windows_only do allow(instance.instance_variable_get(:@service_signal)).to receive(:wait) allow(instance).to receive(:state).and_return(4) expect(instance).to receive(:run_chef_client).and_call_original - expect(instance).to receive(:shell_out).with("chef-client --no-fork -c test_config_file -L #{tempfile.path}", {:timeout => 10}).and_return(shell_out_result) + expect(instance).to receive(:shell_out).with("chef-client --no-fork -c test_config_file -L #{tempfile.path}", + { + :timeout => 10, + :logger => Chef::Log + }).and_return(shell_out_result) instance.service_main tempfile.unlink end |