summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNoah Kantrowitz <noah@coderanger.net>2015-05-29 18:03:37 -0700
committerNoah Kantrowitz <noah@coderanger.net>2015-05-29 18:03:37 -0700
commit0bf9fda2b7224db4005aeda9cf2efb00ba6fb51b (patch)
tree8a20227ac9cacb776af3eb14daaa0080f629271a
parentdec356f59e9aaa37e30aee49b0e23ce1e3f41a12 (diff)
parentbcb812deffae37b15513bfcff448b95c7be4b265 (diff)
downloadchef-0bf9fda2b7224db4005aeda9cf2efb00ba6fb51b.tar.gz
Merge branch 'master' into nameless
-rw-r--r--CHANGELOG.md35
-rw-r--r--Rakefile12
-rw-r--r--VERSION2
-rw-r--r--chef-config/Rakefile12
-rw-r--r--chef-config/lib/chef-config/version.rb2
-rw-r--r--lib/chef/application/client.rb6
-rw-r--r--lib/chef/audit/audit_reporter.rb15
-rw-r--r--lib/chef/client.rb791
-rw-r--r--lib/chef/config.rb1
-rw-r--r--lib/chef/dsl/recipe.rb2
-rw-r--r--lib/chef/event_dispatch/base.rb5
-rw-r--r--lib/chef/exceptions.rb2
-rw-r--r--lib/chef/formatters/doc.rb16
-rw-r--r--lib/chef/knife/core/generic_presenter.rb2
-rw-r--r--lib/chef/knife/core/subcommand_loader.rb2
-rw-r--r--lib/chef/mixin/deprecation.rb24
-rw-r--r--lib/chef/mixin/powershell_out.rb98
-rw-r--r--lib/chef/mixin/windows_architecture_helper.rb7
-rw-r--r--lib/chef/mixin/windows_env_helper.rb13
-rw-r--r--lib/chef/platform/query_helpers.rb5
-rw-r--r--lib/chef/policy_builder/policyfile.rb1
-rw-r--r--lib/chef/provider.rb2
-rw-r--r--lib/chef/provider/directory.rb3
-rw-r--r--lib/chef/provider/dsc_resource.rb9
-rw-r--r--lib/chef/provider/powershell_script.rb176
-rw-r--r--lib/chef/provider/service/freebsd.rb2
-rw-r--r--lib/chef/resource.rb6
-rw-r--r--lib/chef/resource_reporter.rb2
-rw-r--r--lib/chef/util/backup.rb10
-rw-r--r--lib/chef/version.rb2
-rw-r--r--spec/functional/mixin/powershell_out_spec.rb43
-rw-r--r--spec/functional/resource/execute_spec.rb11
-rw-r--r--spec/functional/resource/file_spec.rb25
-rw-r--r--spec/functional/resource/group_spec.rb5
-rw-r--r--spec/functional/resource/link_spec.rb16
-rw-r--r--spec/functional/resource/powershell_spec.rb45
-rw-r--r--spec/functional/resource/user/useradd_spec.rb28
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--spec/support/shared/context/client.rb275
-rw-r--r--spec/support/shared/examples/client.rb53
-rw-r--r--spec/support/shared/functional/file_resource.rb4
-rw-r--r--spec/support/shared/functional/securable_resource.rb24
-rw-r--r--spec/support/shared/functional/securable_resource_with_reporting.rb8
-rw-r--r--spec/support/shared/functional/windows_script.rb2
-rw-r--r--spec/unit/audit/audit_reporter_spec.rb64
-rw-r--r--spec/unit/chef_fs/file_pattern_spec.rb18
-rw-r--r--spec/unit/client_spec.rb432
-rw-r--r--spec/unit/cookbook_spec.rb9
-rw-r--r--spec/unit/cookbook_version_spec.rb20
-rw-r--r--spec/unit/deprecation_spec.rb55
-rw-r--r--spec/unit/exceptions_spec.rb4
-rw-r--r--spec/unit/formatters/doc_spec.rb46
-rw-r--r--spec/unit/knife/core/subcommand_loader_spec.rb22
-rw-r--r--spec/unit/knife/core/ui_spec.rb14
-rw-r--r--spec/unit/mixin/command_spec.rb3
-rw-r--r--spec/unit/mixin/powershell_out_spec.rb70
-rw-r--r--spec/unit/provider/directory_spec.rb334
-rw-r--r--spec/unit/provider/ifconfig/debian_spec.rb10
-rw-r--r--spec/unit/provider/package/openbsd_spec.rb21
-rw-r--r--spec/unit/provider/package/rubygems_spec.rb4
-rw-r--r--spec/unit/provider/powershell_spec.rb64
-rw-r--r--spec/unit/provider/remote_directory_spec.rb4
-rw-r--r--spec/unit/provider/service/freebsd_service_spec.rb12
-rw-r--r--spec/unit/provider/user_spec.rb6
-rw-r--r--spec/unit/resource/template_spec.rb2
-rw-r--r--spec/unit/resource_spec.rb12
66 files changed, 2045 insertions, 987 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6e94990631..29abb09ca5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,23 @@
## Unreleased
+* [**Yukihiko SAWANOBORI**](https://github.com/sawanoboly): Pass name by
+ knife cil attribute [pr#3195](https://github.com/chef/chef/pull/3195)
+* [**Torben Knerr**](https://github.com/tknerr):
+ Allow knife sub-command loader to match platform specific gems. [pr#3281](https://github.com/chef/chef/pull/3281)
+
+* [Issue #2247](https://github.com/chef/chef/issues/2247): powershell_script returns 0 for scripts with syntax errors
+* [pr#3080](https://github.com/chef/chef/pull/3080): Issue 2247: powershell_script exit status should be nonzero for syntax errors
+* [pr#3441](https://github.com/chef/chef/pull/3441): Add powershell_out mixin to core chef
+* [pr#3448](https://github.com/chef/chef/pull/3448): Fix dsc_resource to work with wmf5 april preview
+* [pr#3392](https://github.com/chef/chef/pull/3392): Comment up Chef::Client and privatize/deprecate unused things
+* [pr#3419](https://github.com/chef/chef/pull/3419): Fix cli issue with chef_repo_path when ENV variable is unset
+* [pr#3358](https://github.com/chef/chef/pull/3358): Separate audit and converge failures
+* [pr#3431](https://github.com/chef/chef/pull/3431): Fix backups on windows for the file resource
+* [pr#3397](https://github.com/chef/chef/pull/3397): Validate owner exists in directory resources
+* [pr#3418](https://github.com/chef/chef/pull/3418): Add `shell_out` mixin to Chef::Resource class for use in `not_if`/`only_if` conditionals, etc.
+* [pr#3406](https://github.com/chef/chef/pull/3406): Add wide-char 'Environment' to `broadcast_env_change` mixin for setting windows environment variables
+
+## 12.4.0
+
* [**Phil Dibowitz**](https://github.com/jaymzh):
Fix multipackage and architectures
* [**Igor Shpakov**](https://github.com/Igorshp):
@@ -28,24 +47,24 @@
* Add an integration test of chef-client with empty ENV. #3321
* Switch over Windows builds to universal builds. #3278
* Convert bootstrap template to use sh #2877
-* [Issue #3316](https://github.com/chef/chef/issues/3316) Fix idempotency issues with the `windows_package` resource
+* [Issue #3316](https://github.com/chef/chef/issues/3316): Fix idempotency issues with the `windows_package` resource
* [pr#3295](https://github.com/chef/chef/pull/3295): Stop mutating `new_resource.checksum` in file providers. Fixes some ChecksumMismatch exceptions like [issue#3168](https://github.com/chef/chef/issues/3168)
* [pr#3320] Sanitize non-UTF8 characters in the node data before doing node.save(). Works around many UTF8 exception issues reported on node.save().
* Implemented X-Ops-Server-API-Version with a API version of 0, as well as error handling when the Chef server does not support the API version that the client supports.
* [pr#3327](https://github.com/chef/chef/pull/3327): Fix unreliable AIX service group parsing mechanism.
* [pr#3333](https://github.com/chef/chef/pull/3333): Fix SSL errors when connecting to private Supermarkets
* [pr#3340](https://github.com/chef/chef/pull/3340): Allow Event dispatch subscribers to be inspected.
-* [Issue #3055](https://github.com/chef/chef/issues/3055) Fix regex parsing for recipe failures on Windows
-* [pr#3345](https://github.com/chef/chef/pull/3345) Windows Event log logger
-* [pr#3336](https://github.com/chef/chef/pull/3336) Remote file understands UNC paths
+* [Issue #3055](https://github.com/chef/chef/issues/3055): Fix regex parsing for recipe failures on Windows
+* [pr#3345](https://github.com/chef/chef/pull/3345): Windows Event log logger
+* [pr#3336](https://github.com/chef/chef/pull/3336): Remote file understands UNC paths
* [pr#3269](https://github.com/chef/chef/pull/3269): Deprecate automatic recipe DSL for classes in `Chef::Resource`
* [pr#3360](https://github.com/chef/chef/pull/3360): Add check_resource_semantics! lifecycle method to provider
* [pr#3344](https://github.com/chef/chef/pull/3344): Rewrite Windows user resouce code to use ffi instead of win32-api
-* [pr#3318](https://github.com/chef/chef/pull/3318) Modify Windows package provider to allow for url source
-* [pr#3381](https://github.com/chef/chef/pull/3381) warn on cookbook self-deps
+* [pr#3318](https://github.com/chef/chef/pull/3318): Modify Windows package provider to allow for url source
+* [pr#3381](https://github.com/chef/chef/pull/3381): warn on cookbook self-deps
* [pr#2312](https://github.com/chef/chef/pull/2312): fix `node[:recipes]` duplication, add `node[:cookbooks]` and `node[:expanded_run_list]`
-* [pr#3325](https://github.com/chef/chef/pull/3325) enforce passing a node name with validatorless bootstrapping
-* [pr#3398](https://github.com/chef/chef/pull/3398) Allow spaces in files for the `remote_file` resource
+* [pr#3325](https://github.com/chef/chef/pull/3325): enforce passing a node name with validatorless bootstrapping
+* [pr#3398](https://github.com/chef/chef/pull/3398): Allow spaces in files for the `remote_file` resource
## 12.3.0
diff --git a/Rakefile b/Rakefile
index d01a139c30..6a6e163a53 100644
--- a/Rakefile
+++ b/Rakefile
@@ -108,9 +108,19 @@ Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path|
Gem::PackageTask.new(gemspec).define
end
+def with_clean_env(&block)
+ if defined?(Bundler)
+ Bundler.with_clean_env(&block)
+ else
+ block.call
+ end
+end
+
desc "Build and install a chef gem"
task :install => [:package] do
- sh %{gem install pkg/#{GEM_NAME}-#{VERSION}.gem --no-rdoc --no-ri}
+ with_clean_env do
+ sh %{gem install pkg/#{GEM_NAME}-#{VERSION}.gem --no-rdoc --no-ri}
+ end
end
task :uninstall do
diff --git a/VERSION b/VERSION
index 1e37dfcda9..5765652ec7 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-12.4.0.dev.0
+12.4.0.rc.0
diff --git a/chef-config/Rakefile b/chef-config/Rakefile
index 6eb195f672..10b6010de3 100644
--- a/chef-config/Rakefile
+++ b/chef-config/Rakefile
@@ -8,9 +8,19 @@ Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path|
Gem::PackageTask.new(gemspec).define
end
+def with_clean_env(&block)
+ if defined?(Bundler)
+ Bundler.with_clean_env(&block)
+ else
+ block.call
+ end
+end
+
desc "Build and install a chef-config gem"
task :install => [:package] do
- sh %{gem install pkg/chef-config-#{ChefConfig::VERSION}.gem --no-rdoc --no-ri}
+ with_clean_env do
+ sh(%{gem install pkg/chef-config-#{ChefConfig::VERSION}.gem --no-rdoc --no-ri}, verbose: true)
+ end
end
task :default => :spec
diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb
index 2371f31d98..42f2b7b991 100644
--- a/chef-config/lib/chef-config/version.rb
+++ b/chef-config/lib/chef-config/version.rb
@@ -20,6 +20,6 @@
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
module ChefConfig
- VERSION = '12.4.0.dev.0'
+ VERSION = '12.4.0.rc.0'
end
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index a5faee9d35..551e26e303 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -279,6 +279,12 @@ class Chef::Application::Client < Chef::Application
Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url
Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode)
+
+ if Chef::Config.has_key?(:chef_repo_path) && Chef::Config.chef_repo_path.nil?
+ Chef::Config.delete(:chef_repo_path)
+ Chef::Log.warn "chef_repo_path was set in a config file but was empty. Assuming #{Chef::Config.chef_repo_path}"
+ end
+
if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
end
diff --git a/lib/chef/audit/audit_reporter.rb b/lib/chef/audit/audit_reporter.rb
index a4f84ed7eb..b74bf07b8b 100644
--- a/lib/chef/audit/audit_reporter.rb
+++ b/lib/chef/audit/audit_reporter.rb
@@ -34,6 +34,7 @@ class Chef
@rest_client = rest_client
# Ruby 1.9.3 and above "enumerate their values in the order that the corresponding keys were inserted."
@ordered_control_groups = Hash.new
+ @audit_phase_error = nil
end
def run_context
@@ -59,6 +60,7 @@ class Chef
# known control groups.
def audit_phase_failed(error)
# The stacktrace information has already been logged elsewhere
+ @audit_phase_error = error
Chef::Log.debug("Audit Reporter failed.")
ordered_control_groups.each do |name, control_group|
audit_data.add_control_group(control_group)
@@ -70,7 +72,9 @@ class Chef
end
def run_failed(error)
- post_auditing_data(error)
+ # Audit phase errors are captured when audit_phase_failed gets called.
+ # The error passed here isn't relevant to auditing, so we ignore it.
+ post_auditing_data
end
def control_group_started(name)
@@ -98,7 +102,7 @@ class Chef
private
- def post_auditing_data(error = nil)
+ def post_auditing_data
unless auditing_enabled?
Chef::Log.debug("Audit Reports are disabled. Skipping sending reports.")
return
@@ -116,8 +120,10 @@ class Chef
Chef::Log.debug("Sending audit report (run-id: #{audit_data.run_id})")
run_data = audit_data.to_hash
- if error
- run_data[:error] = "#{error.class.to_s}: #{error.message}\n#{error.backtrace.join("\n")}"
+ if @audit_phase_error
+ error_info = "#{@audit_phase_error.class}: #{@audit_phase_error.message}"
+ error_info << "\n#{@audit_phase_error.backtrace.join("\n")}" if @audit_phase_error.backtrace
+ run_data[:error] = error_info
end
Chef::Log.debug "Audit Report:\n#{Chef::JSONCompat.to_json_pretty(run_data)}"
@@ -163,7 +169,6 @@ class Chef
def iso8601ify(time)
time.utc.iso8601.to_s
end
-
end
end
end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 0764d3f3ba..51e78e60a9 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -50,6 +50,7 @@ require 'chef/run_lock'
require 'chef/policy_builder'
require 'chef/request_id'
require 'chef/platform/rebooter'
+require 'chef/mixin/deprecation'
require 'ohai'
require 'rbconfig'
@@ -60,108 +61,100 @@ class Chef
class Client
include Chef::Mixin::PathSanity
- # IO stream that will be used as 'STDOUT' for formatters. Formatters are
- # configured during `initialize`, so this provides a convenience for
- # setting alternative IO stream during tests.
- STDOUT_FD = STDOUT
-
- # IO stream that will be used as 'STDERR' for formatters. Formatters are
- # configured during `initialize`, so this provides a convenience for
- # setting alternative IO stream during tests.
- STDERR_FD = STDERR
+ extend Chef::Mixin::Deprecation
- # Clears all notifications for client run status events.
- # Primarily for testing purposes.
- def self.clear_notifications
- @run_start_notifications = nil
- @run_completed_successfully_notifications = nil
- @run_failed_notifications = nil
- end
-
- # The list of notifications to be run when the client run starts.
- def self.run_start_notifications
- @run_start_notifications ||= []
- end
-
- # The list of notifications to be run when the client run completes
- # successfully.
- def self.run_completed_successfully_notifications
- @run_completed_successfully_notifications ||= []
- end
-
- # The list of notifications to be run when the client run fails.
- def self.run_failed_notifications
- @run_failed_notifications ||= []
- end
-
- # Add a notification for the 'client run started' event. The notification
- # is provided as a block. The current Chef::RunStatus object will be passed
- # to the notification_block when the event is triggered.
- def self.when_run_starts(&notification_block)
- run_start_notifications << notification_block
- end
-
- # Add a notification for the 'client run success' event. The notification
- # is provided as a block. The current Chef::RunStatus object will be passed
- # to the notification_block when the event is triggered.
- def self.when_run_completes_successfully(&notification_block)
- run_completed_successfully_notifications << notification_block
- end
+ #
+ # The status of the Chef run.
+ #
+ # @return [Chef::RunStatus]
+ #
+ attr_reader :run_status
- # Add a notification for the 'client run failed' event. The notification
- # is provided as a block. The current Chef::RunStatus is passed to the
- # notification_block when the event is triggered.
- def self.when_run_fails(&notification_block)
- run_failed_notifications << notification_block
+ #
+ # The node represented by this client.
+ #
+ # @return [Chef::Node]
+ #
+ def node
+ run_status.node
end
-
- # Callback to fire notifications that the Chef run is starting
- def run_started
- self.class.run_start_notifications.each do |notification|
- notification.call(run_status)
- end
- @events.run_started(run_status)
+ def node=(value)
+ run_status.node = value
end
- # Callback to fire notifications that the run completed successfully
- def run_completed_successfully
- success_handlers = self.class.run_completed_successfully_notifications
- success_handlers.each do |notification|
- notification.call(run_status)
- end
- end
+ #
+ # The ohai system used by this client.
+ #
+ # @return [Ohai::System]
+ #
+ attr_reader :ohai
- # Callback to fire notifications that the Chef run failed
- def run_failed
- failure_handlers = self.class.run_failed_notifications
- failure_handlers.each do |notification|
- notification.call(run_status)
- end
- end
+ #
+ # The rest object used to communicate with the Chef server.
+ #
+ # @return [Chef::REST]
+ #
+ attr_reader :rest
- attr_accessor :node
- attr_accessor :ohai
- attr_accessor :rest
+ #
+ # The runner used to converge.
+ #
+ # @return [Chef::Runner]
+ #
attr_accessor :runner
+ #
+ # Extra node attributes that were applied to the node.
+ #
+ # @return [Hash]
+ #
attr_reader :json_attribs
- attr_reader :run_status
+
+ #
+ # The event dispatcher for the Chef run, including any configured output
+ # formatters and event loggers.
+ #
+ # @return [EventDispatch::Dispatcher]
+ #
+ # @see Chef::Formatters
+ # @see Chef::Config#formatters
+ # @see Chef::Config#stdout
+ # @see Chef::Config#stderr
+ # @see Chef::Config#force_logger
+ # @see Chef::Config#force_formatter
+ # TODO add stdout, stderr, and default formatters to Chef::Config so the
+ # defaults aren't calculated here. Remove force_logger and force_formatter
+ # from this code.
+ # @see Chef::EventLoggers
+ # @see Chef::Config#disable_event_logger
+ # @see Chef::Config#event_loggers
+ # @see Chef::Config#event_handlers
+ #
attr_reader :events
+ #
# Creates a new Chef::Client.
+ #
+ # @param json_attribs [Hash] Node attributes to layer into the node when it is
+ # fetched.
+ # @param args [Hash] Options:
+ # @option args [Array<RunList::RunListItem>] :override_runlist A runlist to
+ # use instead of the node's embedded run list.
+ # @option args [Array<String>] :specific_recipes A list of recipe file paths
+ # to load after the run list has been loaded.
+ #
def initialize(json_attribs=nil, args={})
@json_attribs = json_attribs || {}
- @node = nil
- @runner = nil
@ohai = Ohai::System.new
event_handlers = configure_formatters + configure_event_loggers
event_handlers += Array(Chef::Config[:event_handlers])
@events = EventDispatch::Dispatcher.new(*event_handlers)
+ # TODO it seems like a bad idea to be deletin' other peoples' hashes.
@override_runlist = args.delete(:override_runlist)
@specific_recipes = args.delete(:specific_recipes)
- @run_status = Chef::RunStatus.new(node, events)
+ @run_status = Chef::RunStatus.new(nil, events)
if new_runlist = args.delete(:runlist)
@json_attribs["run_list"] = new_runlist
@@ -175,6 +168,173 @@ class Chef
Chef.set_resource_priority_map(Chef::Platform::ResourcePriorityMap.instance)
end
+ #
+ # Do a full run for this Chef::Client.
+ #
+ # Locks the run while doing its job.
+ #
+ # Fires run_start before doing anything and fires run_completed or
+ # run_failed when finished. Also notifies client listeners of run_started
+ # at the beginning of Compile, and run_completed_successfully or run_failed
+ # when all is complete.
+ #
+ # Phase 1: Setup
+ # --------------
+ # Gets information about the system and the run we are doing.
+ #
+ # 1. Run ohai to collect system information.
+ # 2. Register / connect to the Chef server (unless in solo mode).
+ # 3. Retrieve the node (or create a new one).
+ # 4. Merge in json_attribs, Chef::Config.environment, and override_run_list.
+ #
+ # @see #run_ohai
+ # @see #load_node
+ # @see #build_node
+ # @see Chef::Config#lockfile
+ # @see Chef::RunLock#acquire
+ #
+ # Phase 2: Compile
+ # ----------------
+ # Decides *what* we plan to converge by compiling recipes.
+ #
+ # 1. Sync required cookbooks to the local cache.
+ # 2. Load libraries from all cookbooks.
+ # 3. Load attributes from all cookbooks.
+ # 4. Load LWRPs from all cookbooks.
+ # 5. Load resource definitions from all cookbooks.
+ # 6. Load recipes in the run list.
+ # 7. Load recipes from the command line.
+ #
+ # @see #setup_run_context Syncs and compiles cookbooks.
+ # @see Chef::CookbookCompiler#compile
+ #
+ # Phase 3: Converge
+ # -----------------
+ # Brings the system up to date.
+ #
+ # 1. Converge the resources built from recipes in Phase 2.
+ # 2. Save the node.
+ # 3. Reboot if we were asked to.
+ #
+ # @see #converge_and_save
+ # @see Chef::Runner
+ #
+ # Phase 4: Audit
+ # --------------
+ # Runs 'control_group' audits in recipes. This entire section can be enabled or disabled with config.
+ #
+ # 1. 'control_group' DSL collects audits during Phase 2
+ # 2. Audits are run using RSpec
+ # 3. Errors are collected and reported using the formatters
+ #
+ # @see #run_audits
+ # @see Chef::Audit::Runner#run
+ #
+ # @raise [Chef::Exceptions::RunFailedWrappingError] If converge or audit failed.
+ #
+ # @see Chef::Config#enforce_path_sanity
+ # @see Chef::Config#solo
+ # @see Chef::Config#audit_mode
+ #
+ # @return Always returns true.
+ #
+ def run
+ run_error = nil
+
+ runlock = RunLock.new(Chef::Config.lockfile)
+ # TODO feels like acquire should have its own block arg for this
+ runlock.acquire
+ # don't add code that may fail before entering this section to be sure to release lock
+ begin
+ runlock.save_pid
+
+ request_id = Chef::RequestID.instance.request_id
+ run_context = nil
+ events.run_start(Chef::VERSION)
+ Chef::Log.info("*** Chef #{Chef::VERSION} ***")
+ Chef::Log.info "Chef-client pid: #{Process.pid}"
+ Chef::Log.debug("Chef-client request_id: #{request_id}")
+ enforce_path_sanity
+ run_ohai
+
+ register unless Chef::Config[:solo]
+
+ load_node
+
+ build_node
+
+ run_status.run_id = request_id
+ run_status.start_clock
+ Chef::Log.info("Starting Chef Run for #{node.name}")
+ run_started
+
+ do_windows_admin_check
+
+ run_context = setup_run_context
+
+ if Chef::Config[:audit_mode] != :audit_only
+ converge_error = converge_and_save(run_context)
+ end
+
+ 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")
+ elsif Chef::Config[:audit_mode] != :disabled
+ audit_error = run_audits(run_context)
+ end
+
+ # Raise converge_error so run_failed reporters/events are processed.
+ raise converge_error if converge_error
+
+ run_status.stop_clock
+ Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
+ run_completed_successfully
+ events.run_completed(node)
+
+ # rebooting has to be the last thing we do, no exceptions.
+ Chef::Platform::Rebooter.reboot_if_needed!(node)
+ rescue Exception => run_error
+ # CHEF-3336: Send the error first in case something goes wrong below and we don't know why
+ Chef::Log.debug("Re-raising exception: #{run_error.class} - #{run_error.message}\n#{run_error.backtrace.join("\n ")}")
+ # If we failed really early, we may not have a run_status yet. Too early for these to be of much use.
+ if run_status
+ run_status.stop_clock
+ run_status.exception = run_error
+ run_failed
+ end
+ events.run_failed(run_error)
+ ensure
+ Chef::RequestID.instance.reset_request_id
+ request_id = nil
+ @run_status = nil
+ run_context = nil
+ runlock.release
+ GC.start
+ end
+
+ # Raise audit, converge, and other errors here so that we exit
+ # with the proper exit status code and everything gets raised
+ # as a RunFailedWrappingError
+ if run_error || converge_error || audit_error
+ error = if run_error == converge_error
+ Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
+ else
+ Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
+ end
+ error.fill_backtrace
+ Chef::Application.debug_stacktrace(error)
+ raise error
+ end
+
+ true
+ end
+
+ #
+ # Private API
+ # TODO make this stuff protected or private
+ #
+
+ # @api private
def configure_formatters
formatters_for_run.map do |formatter_name, output_path|
if output_path.nil?
@@ -187,6 +347,7 @@ class Chef
end
end
+ # @api private
def formatters_for_run
if Chef::Config.formatters.empty?
[default_formatter]
@@ -195,6 +356,7 @@ class Chef
end
end
+ # @api private
def default_formatter
if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter]
[:doc]
@@ -203,6 +365,7 @@ class Chef
end
end
+ # @api private
def configure_event_loggers
if Chef::Config.disable_event_logger
[]
@@ -219,8 +382,9 @@ class Chef
end
end
- # Resource repoters send event information back to the chef server for processing.
- # Can only be called after we have a @rest object
+ # Resource reporters send event information back to the chef server for
+ # processing. Can only be called after we have a @rest object
+ # @api private
def register_reporters
[
Chef::ResourceReporter.new(rest),
@@ -230,43 +394,123 @@ class Chef
end
end
+ #
+ # Callback to fire notifications that the Chef run is starting
+ #
+ # @api private
+ #
+ def run_started
+ self.class.run_start_notifications.each do |notification|
+ notification.call(run_status)
+ end
+ events.run_started(run_status)
+ end
+
+ #
+ # Callback to fire notifications that the run completed successfully
+ #
+ # @api private
+ #
+ def run_completed_successfully
+ success_handlers = self.class.run_completed_successfully_notifications
+ success_handlers.each do |notification|
+ notification.call(run_status)
+ end
+ end
+
+ #
+ # Callback to fire notifications that the Chef run failed
+ #
+ # @api private
+ #
+ def run_failed
+ failure_handlers = self.class.run_failed_notifications
+ failure_handlers.each do |notification|
+ notification.call(run_status)
+ end
+ end
+
+ #
# Instantiates a Chef::Node object, possibly loading the node's prior state
- # when using chef-client. Delegates to policy_builder. Injects the built node
- # into the Chef class.
+ # when using chef-client. Sets Chef.node to the new node.
#
# @return [Chef::Node] The node object for this Chef run
+ #
+ # @see Chef::PolicyBuilder#load_node
+ #
+ # @api private
+ #
def load_node
policy_builder.load_node
- @node = policy_builder.node
- Chef.set_node(@node)
+ run_status.node = policy_builder.node
+ Chef.set_node(policy_builder.node)
node
end
- # Mutates the `node` object to prepare it for the chef run. Delegates to
- # policy_builder
+ #
+ # Mutates the `node` object to prepare it for the chef run.
#
# @return [Chef::Node] The updated node object
+ #
+ # @see Chef::PolicyBuilder#build_node
+ #
+ # @api private
+ #
def build_node
policy_builder.build_node
- @run_status.node = node
+ run_status.node = node
node
end
+ #
+ # Sync cookbooks to local cache.
+ #
+ # TODO this appears to be unused.
+ #
+ # @see Chef::PolicyBuilder#sync_cookbooks
+ #
+ # @api private
+ #
+ def sync_cookbooks
+ policy_builder.sync_cookbooks
+ end
+
+ #
+ # Sets up the run context.
+ #
+ # @see Chef::PolicyBuilder#setup_run_context
+ #
+ # @return The newly set up run context
+ #
+ # @api private
def setup_run_context
- run_context = policy_builder.setup_run_context(@specific_recipes)
+ run_context = policy_builder.setup_run_context(specific_recipes)
assert_cookbook_path_not_empty(run_context)
run_status.run_context = run_context
run_context
end
- def sync_cookbooks
- policy_builder.sync_cookbooks
- end
-
+ #
+ # The PolicyBuilder strategy for figuring out run list and cookbooks.
+ #
+ # @return [Chef::PolicyBuilder::Policyfile, Chef::PolicyBuilder::ExpandNodeObject]
+ #
+ # @api private
+ #
def policy_builder
- @policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, @override_runlist, events)
+ @policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, override_runlist, events)
end
+ #
+ # Save the updated node to Chef.
+ #
+ # Does not save if we are in solo mode or using override_runlist.
+ #
+ # @see Chef::Node#save
+ # @see Chef::Config#solo
+ #
+ # @api private
+ #
def save_updated_node
if Chef::Config[:solo]
# nothing to do
@@ -274,16 +518,46 @@ class Chef
Chef::Log.warn("Skipping final node save because override_runlist was given")
else
Chef::Log.debug("Saving the current state of node #{node_name}")
- @node.save
+ node.save
end
end
+ #
+ # Run ohai plugins. Runs all ohai plugins unless minimal_ohai is specified.
+ #
+ # Sends the ohai_completed event when finished.
+ #
+ # @see Chef::EventDispatcher#
+ # @see Chef::Config#minimal_ohai
+ #
+ # @api private
+ #
def run_ohai
filter = Chef::Config[:minimal_ohai] ? %w[fqdn machinename hostname platform platform_version os os_version] : nil
ohai.all_plugins(filter)
- @events.ohai_completed(node)
+ events.ohai_completed(node)
end
+ #
+ # Figure out the node name we are working with.
+ #
+ # It tries these, in order:
+ # - Chef::Config.node_name
+ # - ohai[:fqdn]
+ # - ohai[:machinename]
+ # - ohai[:hostname]
+ #
+ # If we are running against a server with authentication protocol < 1.0, we
+ # *require* authentication protocol version 1.1.
+ #
+ # @raise [Chef::Exceptions::CannotDetermineNodeName] If the node name is not
+ # set and cannot be determined via ohai.
+ #
+ # @see Chef::Config#node_name
+ # @see Chef::Config#authentication_protocol_version
+ #
+ # @api private
+ #
def node_name
name = Chef::Config[:node_name] || ohai[:fqdn] || ohai[:machinename] || ohai[:hostname]
Chef::Config[:node_name] = name
@@ -292,6 +566,8 @@ class Chef
# node names > 90 bytes only work with authentication protocol >= 1.1
# see discussion in config.rb.
+ # TODO use a computed default in Chef::Config to determine this instead of
+ # setting it.
if name.bytesize > 90
Chef::Config[:authentication_protocol_version] = "1.1"
end
@@ -300,46 +576,86 @@ class Chef
end
#
- # === Returns
- # rest<Chef::REST>:: returns Chef::REST connection object
+ # Determine our private key and set up the connection to the Chef server.
+ #
+ # Skips registration and fires the `skipping_registration` event if
+ # Chef::Config.client_key is unspecified or already exists.
+ #
+ # If Chef::Config.client_key does not exist, we register the client with the
+ # Chef server and fire the registration_start and registration_completed events.
+ #
+ # @return [Chef::REST] The server connection object.
+ #
+ # @see Chef::Config#chef_server_url
+ # @see Chef::Config#client_key
+ # @see Chef::ApiClient::Registration#run
+ # @see Chef::EventDispatcher#skipping_registration
+ # @see Chef::EventDispatcher#registration_start
+ # @see Chef::EventDispatcher#registration_completed
+ # @see Chef::EventDispatcher#registration_failed
+ #
+ # @api private
+ #
def register(client_name=node_name, config=Chef::Config)
if !config[:client_key]
- @events.skipping_registration(client_name, config)
+ events.skipping_registration(client_name, config)
Chef::Log.debug("Client key is unspecified - skipping registration")
elsif File.exists?(config[:client_key])
- @events.skipping_registration(client_name, config)
+ events.skipping_registration(client_name, config)
Chef::Log.debug("Client key #{config[:client_key]} is present - skipping registration")
else
- @events.registration_start(node_name, config)
+ events.registration_start(node_name, config)
Chef::Log.info("Client key #{config[:client_key]} is not present - registering")
Chef::ApiClient::Registration.new(node_name, config[:client_key]).run
- @events.registration_completed
+ events.registration_completed
end
# We now have the client key, and should use it from now on.
@rest = Chef::REST.new(config[:chef_server_url], client_name, config[:client_key])
register_reporters
rescue Exception => e
+ # TODO this should probably only ever fire if we *started* registration.
+ # Move it to the block above.
# TODO: munge exception so a semantic failure message can be given to the
# user
- @events.registration_failed(client_name, e, config)
+ events.registration_failed(client_name, e, config)
raise
end
- # Converges the node.
#
- # === Returns
- # The thrown exception, if there was one. If this returns nil the converge was successful.
+ # Converges all compiled resources.
+ #
+ # Fires the converge_start, converge_complete and converge_failed events.
+ #
+ # If the exception `:end_client_run_early` is thrown during convergence, it
+ # does not mark the run complete *or* failed, and returns `nil`
+ #
+ # @param run_context The run context.
+ #
+ # @return The thrown exception, if we are in audit mode. `nil` means the
+ # converge was successful or ended early.
+ #
+ # @raise Any converge exception, unless we are in audit mode, in which case
+ # we *return* the exception.
+ #
+ # @see Chef::Runner#converge
+ # @see Chef::Config#audit_mode
+ # @see Chef::EventDispatch#converge_start
+ # @see Chef::EventDispatch#converge_complete
+ # @see Chef::EventDispatch#converge_failed
+ #
+ # @api private
+ #
def converge(run_context)
converge_exception = nil
catch(:end_client_run_early) do
begin
- @events.converge_start(run_context)
+ events.converge_start(run_context)
Chef::Log.debug("Converging node #{node_name}")
@runner = Chef::Runner.new(run_context)
- runner.converge
- @events.converge_complete
+ @runner.converge
+ events.converge_complete
rescue Exception => e
- @events.converge_failed(e)
+ events.converge_failed(e)
raise e if Chef::Config[:audit_mode] == :disabled
converge_exception = e
end
@@ -347,8 +663,28 @@ class Chef
converge_exception
end
+ #
+ # Converge the node via and then save it if successful.
+ #
+ # @param run_context The run context.
+ #
+ # @return The thrown exception, if we are in audit mode. `nil` means the
+ # converge was successful or ended early.
+ #
+ # @raise Any converge or node save exception, unless we are in audit mode,
+ # in which case we *return* the exception.
+ #
+ # @see #converge
+ # @see #save_updated_mode
+ # @see Chef::Config#audit_mode
+ #
+ # @api private
+ #
# We don't want to change the old API on the `converge` method to have it perform
# saving. So we wrap it in this method.
+ # TODO given this seems to be pretty internal stuff, how badly do we need to
+ # split this stuff up?
+ #
def converge_and_save(run_context)
converge_exception = converge(run_context)
unless converge_exception
@@ -362,37 +698,67 @@ class Chef
converge_exception
end
+ #
+ # Run the audit phase.
+ #
+ # Triggers the audit_phase_start, audit_phase_complete and
+ # audit_phase_failed events.
+ #
+ # @param run_context The run context.
+ #
+ # @return Any thrown exceptions. `nil` if successful.
+ #
+ # @see Chef::Audit::Runner#run
+ # @see Chef::EventDispatch#audit_phase_start
+ # @see Chef::EventDispatch#audit_phase_complete
+ # @see Chef::EventDispatch#audit_phase_failed
+ #
+ # @api private
+ #
def run_audits(run_context)
- audit_exception = nil
begin
- @events.audit_phase_start(run_status)
+ events.audit_phase_start(run_status)
Chef::Log.info("Starting audit phase")
auditor = Chef::Audit::Runner.new(run_context)
auditor.run
if auditor.failed?
- raise Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total)
+ audit_exception = Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total)
+ events.audit_phase_failed(audit_exception)
+ else
+ events.audit_phase_complete
end
- @events.audit_phase_complete
rescue Exception => e
Chef::Log.error("Audit phase failed with error message: #{e.message}")
- @events.audit_phase_failed(e)
+ events.audit_phase_failed(e)
audit_exception = e
end
audit_exception
end
- # Expands the run list. Delegates to the policy_builder.
#
- # Normally this does not need to be called from here, it will be called by
- # build_node. This is provided so external users (like the chefspec
- # project) can inject custom behavior into the run process.
+ # Expands the run list.
+ #
+ # @return [Chef::RunListExpansion] The expanded run list.
+ #
+ # @see Chef::PolicyBuilder#expand_run_list
#
- # === Returns
- # RunListExpansion: A RunListExpansion or API compatible object.
def expanded_run_list
policy_builder.expand_run_list
end
+ #
+ # Check if the user has Administrator privileges on windows.
+ #
+ # Throws an error if the user is not an admin, and
+ # `Chef::Config.fatal_windows_admin_check` is true.
+ #
+ # @raise [Chef::Exceptions::WindowsNotAdmin] If the user is not an admin.
+ #
+ # @see Chef::platform#windows?
+ # @see Chef::Config#fatal_windows_admin_check
+ #
+ # @api private
+ #
def do_windows_admin_check
if Chef::Platform.windows?
Chef::Log.debug("Checking for administrator privileges....")
@@ -412,99 +778,121 @@ class Chef
end
end
- # Do a full run for this Chef::Client. Calls:
- #
- # * run_ohai - Collect information about the system
- # * build_node - Get the last known state, merge with local changes
- # * register - If not in solo mode, make sure the server knows about this client
- # * sync_cookbooks - If not in solo mode, populate the local cache with the node's cookbooks
- # * converge - Bring this system up to date
- #
- # === Returns
- # true:: Always returns true.
- def run
- runlock = RunLock.new(Chef::Config.lockfile)
- runlock.acquire
- # don't add code that may fail before entering this section to be sure to release lock
- begin
- runlock.save_pid
-
- request_id = Chef::RequestID.instance.request_id
- run_context = nil
- @events.run_start(Chef::VERSION)
- Chef::Log.info("*** Chef #{Chef::VERSION} ***")
- Chef::Log.info "Chef-client pid: #{Process.pid}"
- Chef::Log.debug("Chef-client request_id: #{request_id}")
- enforce_path_sanity
- run_ohai
-
- register unless Chef::Config[:solo]
-
- load_node
-
- build_node
+ # Notification registration
+ class<<self
+ #
+ # Add a listener for the 'client run started' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_starts(&notification_block)
+ run_start_notifications << notification_block
+ end
- run_status.run_id = request_id
- run_status.start_clock
- Chef::Log.info("Starting Chef Run for #{node.name}")
- run_started
+ #
+ # Add a listener for the 'client run success' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_completes_successfully(&notification_block)
+ run_completed_successfully_notifications << notification_block
+ end
- do_windows_admin_check
+ #
+ # Add a listener for the 'client run failed' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_fails(&notification_block)
+ run_failed_notifications << notification_block
+ end
- run_context = setup_run_context
+ #
+ # Clears all listeners for client run status events.
+ #
+ # Primarily for testing purposes.
+ #
+ # @api private
+ #
+ def clear_notifications
+ @run_start_notifications = nil
+ @run_completed_successfully_notifications = nil
+ @run_failed_notifications = nil
+ end
- if Chef::Config[:audit_mode] != :audit_only
- converge_error = converge_and_save(run_context)
- end
+ #
+ # TODO These seem protected to me.
+ #
+
+ #
+ # Listeners to be run when the client run starts.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_start_notifications
+ @run_start_notifications ||= []
+ end
- 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")
- elsif Chef::Config[:audit_mode] != :disabled
- audit_error = run_audits(run_context)
- end
+ #
+ # Listeners to be run when the client run completes successfully.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_completed_successfully_notifications
+ @run_completed_successfully_notifications ||= []
+ end
- if converge_error || audit_error
- e = Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
- e.fill_backtrace
- raise e
- end
+ #
+ # Listeners to be run when the client run fails.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_failed_notifications
+ @run_failed_notifications ||= []
+ end
+ end
- run_status.stop_clock
- Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
- run_completed_successfully
- @events.run_completed(node)
+ #
+ # IO stream that will be used as 'STDOUT' for formatters. Formatters are
+ # configured during `initialize`, so this provides a convenience for
+ # setting alternative IO stream during tests.
+ #
+ # @api private
+ #
+ STDOUT_FD = STDOUT
- # rebooting has to be the last thing we do, no exceptions.
- Chef::Platform::Rebooter.reboot_if_needed!(node)
+ #
+ # IO stream that will be used as 'STDERR' for formatters. Formatters are
+ # configured during `initialize`, so this provides a convenience for
+ # setting alternative IO stream during tests.
+ #
+ # @api private
+ #
+ STDERR_FD = STDERR
- true
+ #
+ # Deprecated writers
+ #
- rescue Exception => e
- # CHEF-3336: Send the error first in case something goes wrong below and we don't know why
- Chef::Log.debug("Re-raising exception: #{e.class} - #{e.message}\n#{e.backtrace.join("\n ")}")
- # If we failed really early, we may not have a run_status yet. Too early for these to be of much use.
- if run_status
- run_status.stop_clock
- run_status.exception = e
- run_failed
- end
- Chef::Application.debug_stacktrace(e)
- @events.run_failed(e)
- raise
- ensure
- Chef::RequestID.instance.reset_request_id
- request_id = nil
- @run_status = nil
- run_context = nil
- runlock.release
- GC.start
- end
- true
- end
+ include Chef::Mixin::Deprecation
+ deprecated_attr_writer :ohai, "There is no alternative. Leave ohai alone!"
+ deprecated_attr_writer :rest, "There is no alternative. Leave rest alone!"
+ deprecated_attr :runner, "There is no alternative. Leave runner alone!"
private
+ attr_reader :override_runlist
+ attr_reader :specific_recipes
+
def empty_directory?(path)
!File.exists?(path) || (Dir.entries(path).size <= 2)
end
@@ -536,7 +924,6 @@ class Chef
Chef::ReservedNames::Win32::Security.has_admin_privileges?
end
-
end
end
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index 629553c8ab..9beb18b53e 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -51,4 +51,3 @@ class Chef
end
end
-
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index 97f5088b5d..77646376ba 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -21,6 +21,7 @@ require 'chef/mixin/convert_to_class_name'
require 'chef/exceptions'
require 'chef/resource_builder'
require 'chef/mixin/shell_out'
+require 'chef/mixin/powershell_out'
require 'chef/dsl/resources'
require 'chef/dsl/definitions'
@@ -33,6 +34,7 @@ class Chef
module Recipe
include Chef::Mixin::ShellOut
+ include Chef::Mixin::PowershellOut
# method_missing must live for backcompat purposes until Chef 13.
def method_missing(method_symbol, *args, &block)
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index 7274105802..93caa62a65 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -82,6 +82,11 @@ class Chef
def node_load_completed(node, expanded_run_list, config)
end
+ # Called after the Policyfile was loaded. This event only occurs when
+ # chef is in policyfile mode.
+ def policyfile_loaded(policy)
+ end
+
# Called before the cookbook collection is fetched from the server.
def cookbook_resolution_start(expanded_run_list)
end
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index da562e70f4..c0f4158db4 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -435,7 +435,7 @@ class Chef
wrapped_errors.each_with_index do |e,i|
backtrace << "#{i+1}) #{e.class} - #{e.message}"
backtrace += e.backtrace if e.backtrace
- backtrace << ""
+ backtrace << "" unless i == wrapped_errors.length - 1
end
set_backtrace(backtrace)
end
diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb
index 7144d00b5d..e63c764cbf 100644
--- a/lib/chef/formatters/doc.rb
+++ b/lib/chef/formatters/doc.rb
@@ -3,9 +3,9 @@ require 'chef/config'
class Chef
module Formatters
- #--
- # TODO: not sold on the name, but the output is similar to what rspec calls
- # "specdoc"
+
+ # Formatter similar to RSpec's documentation formatter. Uses indentation to
+ # show context.
class Doc < Formatters::Base
attr_reader :start_time, :end_time, :successful_audits, :failed_audits
@@ -93,6 +93,10 @@ class Chef
def node_load_completed(node, expanded_run_list, config)
end
+ def policyfile_loaded(policy)
+ puts_line "Using policy '#{policy["name"]}' at revision '#{policy["revision_id"]}'"
+ end
+
# Called before the cookbook collection is fetched from the server.
def cookbook_resolution_start(expanded_run_list)
puts_line "resolving cookbooks for run list: #{expanded_run_list.inspect}"
@@ -184,8 +188,10 @@ class Chef
puts_line "Audit phase exception:"
indent
puts_line "#{error.message}"
- error.backtrace.each do |l|
- puts_line l
+ if error.backtrace
+ error.backtrace.each do |l|
+ puts_line l
+ end
end
end
diff --git a/lib/chef/knife/core/generic_presenter.rb b/lib/chef/knife/core/generic_presenter.rb
index f3ea0f0d6c..2df9603faa 100644
--- a/lib/chef/knife/core/generic_presenter.rb
+++ b/lib/chef/knife/core/generic_presenter.rb
@@ -181,7 +181,7 @@ class Chef
# Must check :[] before attr because spec can include
# `keys` - want the key named `keys`, not a list of
# available keys.
- elsif data.respond_to?(:[])
+ elsif data.respond_to?(:[]) && data.has_key?(attr)
data = data[attr]
elsif data.respond_to?(attr.to_sym)
data = data.send(attr.to_sym)
diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb
index 1f59515f44..a8705c724f 100644
--- a/lib/chef/knife/core/subcommand_loader.rb
+++ b/lib/chef/knife/core/subcommand_loader.rb
@@ -23,7 +23,7 @@ class Chef
class SubcommandLoader
MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}.freeze
- MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}/}.freeze
+ MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/}.freeze
attr_reader :chef_config_dir
attr_reader :env
diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb
index 489f27c339..a3eacf75cb 100644
--- a/lib/chef/mixin/deprecation.rb
+++ b/lib/chef/mixin/deprecation.rb
@@ -95,6 +95,30 @@ class Chef
DeprecatedInstanceVariable.new(obj, name, level)
end
+ def deprecated_attr(name, alternative)
+ deprecated_attr_reader(name, alternative)
+ deprecated_attr_writer(name, alternative)
+ end
+
+ def deprecated_attr_reader(name, alternative, level=:warn)
+ define_method(name) do
+ Chef::Log.deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.")
+ Chef::Log.deprecation(alternative)
+ Chef::Log.deprecation("Called from:")
+ caller[0..3].each {|c| Chef::Log.deprecation(c)}
+ instance_variable_get("@#{name}")
+ end
+ end
+
+ def deprecated_attr_writer(name, alternative, level=:warn)
+ define_method("#{name}=") do |value|
+ Chef::Log.deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.")
+ Chef::Log.deprecation(alternative)
+ Chef::Log.deprecation("Called from:")
+ caller[0..3].each {|c| Chef::Log.deprecation(c)}
+ instance_variable_set("@#{name}", value)
+ end
+ end
end
end
end
diff --git a/lib/chef/mixin/powershell_out.rb b/lib/chef/mixin/powershell_out.rb
new file mode 100644
index 0000000000..e4f29c07c4
--- /dev/null
+++ b/lib/chef/mixin/powershell_out.rb
@@ -0,0 +1,98 @@
+#--
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'chef/mixin/shell_out'
+require 'chef/mixin/windows_architecture_helper'
+
+class Chef
+ module Mixin
+ module PowershellOut
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::WindowsArchitectureHelper
+
+ # Run a command under powershell with the same API as shell_out. The
+ # options hash is extended to take an "architecture" flag which
+ # can be set to :i386 or :x86_64 to force the windows architecture.
+ #
+ # @param script [String] script to run
+ # @param options [Hash] options hash
+ # @return [Mixlib::Shellout] mixlib-shellout object
+ def powershell_out(*command_args)
+ script = command_args.first
+ options = command_args.last.is_a?(Hash) ? command_args.last : nil
+
+ run_command_with_os_architecture(script, options)
+ end
+
+ # Run a command under powershell with the same API as shell_out!
+ # (raises exceptions on errors)
+ #
+ # @param script [String] script to run
+ # @param options [Hash] options hash
+ # @return [Mixlib::Shellout] mixlib-shellout object
+ def powershell_out!(*command_args)
+ cmd = powershell_out(*command_args)
+ cmd.error!
+ cmd
+ end
+
+ private
+
+ # Helper function to run shell_out and wrap it with the correct
+ # flags to possibly disable WOW64 redirection (which we often need
+ # because chef-client runs as a 32-bit app on 64-bit windows).
+ #
+ # @param script [String] script to run
+ # @param options [Hash] options hash
+ # @return [Mixlib::Shellout] mixlib-shellout object
+ def run_command_with_os_architecture(script, options)
+ options ||= {}
+ options = options.dup
+ arch = options.delete(:architecture)
+
+ with_os_architecture(nil, architecture: arch) do
+ shell_out(
+ build_powershell_command(script),
+ options
+ )
+ end
+ end
+
+ # Helper to build a powershell command around the script to run.
+ #
+ # @param script [String] script to run
+ # @retrurn [String] powershell command to execute
+ def build_powershell_command(script)
+ flags = [
+ # Hides the copyright banner at startup.
+ "-NoLogo",
+ # Does not present an interactive prompt to the user.
+ "-NonInteractive",
+ # Does not load the Windows PowerShell profile.
+ "-NoProfile",
+ # always set the ExecutionPolicy flag
+ # see http://technet.microsoft.com/en-us/library/ee176961.aspx
+ "-ExecutionPolicy Unrestricted",
+ # Powershell will hang if STDIN is redirected
+ # http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected
+ "-InputFormat None"
+ ]
+
+ "powershell.exe #{flags.join(' ')} -Command \"#{script}\""
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb
index a0ac34f627..c5f3e1bd79 100644
--- a/lib/chef/mixin/windows_architecture_helper.rb
+++ b/lib/chef/mixin/windows_architecture_helper.rb
@@ -42,7 +42,7 @@ class Chef
is_i386_process_on_x86_64_windows?
end
- def with_os_architecture(node)
+ def with_os_architecture(node, architecture: nil)
node ||= begin
os_arch = ENV['PROCESSOR_ARCHITEW6432'] ||
ENV['PROCESSOR_ARCHITECTURE']
@@ -51,9 +51,12 @@ class Chef
n[:kernel][:machine] = os_arch == 'AMD64' ? :x86_64 : :i386
end
end
+
+ architecture ||= node_windows_architecture(node)
+
wow64_redirection_state = nil
- if wow64_architecture_override_required?(node, node_windows_architecture(node))
+ if wow64_architecture_override_required?(node, architecture)
wow64_redirection_state = disable_wow64_file_redirection(node)
end
diff --git a/lib/chef/mixin/windows_env_helper.rb b/lib/chef/mixin/windows_env_helper.rb
index 490b235065..a126801a28 100644
--- a/lib/chef/mixin/windows_env_helper.rb
+++ b/lib/chef/mixin/windows_env_helper.rb
@@ -21,11 +21,11 @@ require 'chef/exceptions'
require 'chef/platform/query_helpers'
require 'chef/win32/error' if Chef::Platform.windows?
require 'chef/win32/api/system' if Chef::Platform.windows?
+require 'chef/win32/api/unicode' if Chef::Platform.windows?
class Chef
module Mixin
module WindowsEnvHelper
-
if Chef::Platform.windows?
include Chef::ReservedNames::Win32::API::System
end
@@ -39,7 +39,16 @@ class Chef
def broadcast_env_change
flags = SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG
- SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string('Environment').address, flags, 5000, nil)
+ # for why two calls, see:
+ # http://stackoverflow.com/questions/4968373/why-doesnt-sendmessagetimeout-update-the-environment-variables
+ if ( SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string('Environment').address, flags, 5000, nil) == 0 )
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ if ( SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string(
+ Chef::ReservedNames::Win32::Unicode.utf8_to_wide('Environment')
+ ).address, flags, 5000, nil) == 0 )
+ Chef::ReservedNames::Win32::Error.raise!
+ end
end
def expand_path(path)
diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb
index 03b1c9ca1e..b3948eac21 100644
--- a/lib/chef/platform/query_helpers.rb
+++ b/lib/chef/platform/query_helpers.rb
@@ -39,6 +39,11 @@ class Chef
is_server_2003
end
+ def supports_powershell_execution_bypass?(node)
+ node[:languages] && node[:languages][:powershell] &&
+ node[:languages][:powershell][:version].to_i >= 3
+ end
+
def supports_dsc?(node)
node[:languages] && node[:languages][:powershell] &&
node[:languages][:powershell][:version].to_i >= 4
diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb
index ac25b549be..5991e3ce10 100644
--- a/lib/chef/policy_builder/policyfile.rb
+++ b/lib/chef/policy_builder/policyfile.rb
@@ -119,6 +119,7 @@ class Chef
@node = Chef::Node.find_or_create(node_name)
validate_policyfile
+ events.policyfile_loaded(policy)
node
rescue Exception => e
events.node_load_failed(node_name, e, Chef::Config)
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 99d09d0507..42347a230e 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -22,6 +22,7 @@ require 'chef/mixin/convert_to_class_name'
require 'chef/mixin/enforce_ownership_and_permissions'
require 'chef/mixin/why_run'
require 'chef/mixin/shell_out'
+require 'chef/mixin/powershell_out'
require 'chef/mixin/provides'
require 'chef/platform/service_helpers'
require 'chef/node_map'
@@ -30,6 +31,7 @@ class Chef
class Provider
include Chef::Mixin::WhyRun
include Chef::Mixin::ShellOut
+ include Chef::Mixin::PowershellOut
extend Chef::Mixin::Provides
# supports the given resource and action (late binding)
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
index 416393ac60..4d5423d0e8 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -43,6 +43,9 @@ class Chef
end
def define_resource_requirements
+ # deep inside FAC we have to assert requirements, so call FACs hook to set that up
+ access_controls.define_resource_requirements
+
requirements.assert(:create) do |a|
# Make sure the parent dir exists, or else fail.
# for why run, print a message explaining the potential error.
diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb
index 2812c154c6..5fa84a21e9 100644
--- a/lib/chef/provider/dsc_resource.rb
+++ b/lib/chef/provider/dsc_resource.rb
@@ -121,7 +121,14 @@ class Chef
# however Invoke-DscResource is not correctly writing to that
# stream and instead just dumping to stdout
@converge_description = result.stdout
- result.return_value[0]["InDesiredState"]
+
+ if result.return_value.is_a?(Array)
+ # WMF Feb 2015 Preview
+ result.return_value[0]["InDesiredState"]
+ else
+ # WMF April 2015 Preview
+ result.return_value["InDesiredState"]
+ end
end
def set_resource
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
index f9dcd6d80c..ed44dee6ae 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -24,71 +24,153 @@ class Chef
provides :powershell_script, os: "windows"
+ def initialize (new_resource, run_context)
+ super(new_resource, run_context, '.ps1')
+ add_exit_status_wrapper
+ end
+
+ def action_run
+ valid_syntax = validate_script_syntax!
+ super if valid_syntax
+ 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(' ')
+
+ if ! (@new_resource.flags.nil?)
+ interpreter_flags = [@new_resource.flags, interpreter_flags].join(' ')
+ end
+
+ interpreter_flags
+ end
+
protected
- EXIT_STATUS_EXCEPTION_HANDLER = "\ntrap [Exception] {write-error -exception ($_.Exception.Message);exit 1}".freeze
- EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -ne $true) { if ( $LASTEXITCODE ) {exit $LASTEXITCODE} else { exit 1 }}".freeze
- EXIT_STATUS_RESET_SCRIPT = "\n$global:LASTEXITCODE=$null".freeze
- # Process exit codes are strange with PowerShell. Unless you
- # explicitly call exit in Powershell, the powershell.exe
- # interpreter returns only 0 for success or 1 for failure. Since
- # we'd like to get specific exit codes from executable tools run
- # with Powershell, we do some work using the automatic variables
- # $? and $LASTEXITCODE to return the process exit code of the
- # last process run in the script if it is the last command
- # executed, otherwise 0 or 1 based on whether $? is set to true
- # (success, where we return 0) or false (where we return 1).
- def normalize_script_exit_status( code )
- target_code = ( EXIT_STATUS_EXCEPTION_HANDLER +
- EXIT_STATUS_RESET_SCRIPT +
- "\n" +
- code.to_s +
- EXIT_STATUS_NORMALIZATION_SCRIPT )
- convert_boolean_return = @new_resource.convert_boolean_return
- self.code = <<EOH
-new-variable -name interpolatedexitcode -visibility private -value $#{convert_boolean_return}
-new-variable -name chefscriptresult -visibility private
-$chefscriptresult = {
-#{target_code}
-}.invokereturnasis()
-if ($interpolatedexitcode -and $chefscriptresult.gettype().name -eq 'boolean') { exit [int32](!$chefscriptresult) } else { exit 0 }
-EOH
- Chef::Log.debug("powershell_script provider called with script code:\n\n#{code}\n")
+ # Process exit codes are strange with PowerShell and require
+ # special handling to cover common use cases.
+ def add_exit_status_wrapper
+ self.code = wrapper_script
+ Chef::Log.debug("powershell_script provider called with script code:\n\n#{@new_resource.code}\n")
Chef::Log.debug("powershell_script provider will execute transformed code:\n\n#{self.code}\n")
end
- public
+ def validate_script_syntax!
+ interpreter_arguments = default_interpreter_flags.join(' ')
+ Tempfile.open(['chef_powershell_script-user-code', '.ps1']) do | user_script_file |
+ user_script_file.puts("{#{@new_resource.code}}")
+ user_script_file.close
- def initialize (new_resource, run_context)
- super(new_resource, run_context, '.ps1')
- normalize_script_exit_status(new_resource.code)
+ validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command #{user_script_file.path}"
+
+ # For consistency with other script resources, allow even syntax errors
+ # to be suppressed if the returns attribute would have suppressed it
+ # at converge.
+ valid_returns = [0]
+ specified_returns = @new_resource.returns.is_a?(Integer) ?
+ [@new_resource.returns] :
+ @new_resource.returns
+ valid_returns.concat([1]) if specified_returns.include?(1)
+
+ result = shell_out!(validation_command, {returns: valid_returns})
+ result.exitstatus == 0
+ end
end
- def flags
- default_flags = [
+ def default_interpreter_flags
+ # '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 prior to
+ # PowerShell 3.0, so the fallback is 'Unrestricted'
+ execution_policy = Chef::Platform.supports_powershell_execution_bypass?(run_context.node) ? 'Bypass' : 'Unrestricted'
+
+ [
"-NoLogo",
"-NonInteractive",
"-NoProfile",
- "-ExecutionPolicy Unrestricted",
+ "-ExecutionPolicy #{execution_policy}",
# Powershell will hang if STDIN is redirected
# http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected
- "-InputFormat None",
- # 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'.
- "-File"
+ "-InputFormat None"
]
+ end
- interpreter_flags = default_flags.join(' ')
+ # A wrapper script is used to launch user-supplied script while
+ # still obtaining useful process exit codes. Unless you
+ # explicitly call exit in Powershell, the powershell.exe
+ # interpreter returns only 0 for success or 1 for failure. Since
+ # we'd like to get specific exit codes from executable tools run
+ # with Powershell, we do some work using the automatic variables
+ # $? and $LASTEXITCODE to return the process exit code of the
+ # last process run in the script if it is the last command
+ # executed, otherwise 0 or 1 based on whether $? is set to true
+ # (success, where we return 0) or false (where we return 1).
+ def wrapper_script
+<<-EOH
+# Chef Client wrapper for powershell_script resources
- if ! (@new_resource.flags.nil?)
- interpreter_flags = [@new_resource.flags, interpreter_flags].join(' ')
- end
+# LASTEXITCODE can be uninitialized -- make it explictly 0
+# to avoid incorrect detection of failure (non-zero) codes
+$global:LASTEXITCODE = 0
- interpreter_flags
+# Catch any exceptions -- without this, exceptions will result
+# In a zero return code instead of the desired non-zero code
+# that indicates a failure
+trap [Exception] {write-error ($_.Exception.Message);exit 1}
+
+# Variable state that should not be accessible to the user code
+new-variable -name interpolatedexitcode -visibility private -value $#{@new_resource.convert_boolean_return}
+new-variable -name chefscriptresult -visibility private
+
+# Initialize a variable we use to capture $? inside a block
+$global:lastcmdlet = $null
+
+# Execute the user's code in a script block --
+$chefscriptresult =
+{
+ #{@new_resource.code}
+
+ # This assignment doesn't affect the block's return value
+ $global:lastcmdlet = $?
+}.invokereturnasis()
+
+# Assume failure status of 1 -- success cases
+# will have to override this
+$exitstatus = 1
+
+# If convert_boolean_return is enabled, the block's return value
+# gets precedence in determining our exit status
+if ($interpolatedexitcode -and $chefscriptresult -ne $null -and $chefscriptresult.gettype().name -eq 'boolean')
+{
+ $exitstatus = [int32](!$chefscriptresult)
+}
+elseif ($lastcmdlet)
+{
+ # Otherwise, a successful cmdlet execution defines the status
+ $exitstatus = 0
+}
+elseif ( $LASTEXITCODE -ne $null -and $LASTEXITCODE -ne 0 )
+{
+ # If the cmdlet status is failed, allow the Win32 status
+ # in $LASTEXITCODE to define exit status. This handles the case
+ # where no cmdlets, only Win32 processes have run since $?
+ # will be set to $false whenever a Win32 process returns a non-zero
+ # status.
+ $exitstatus = $LASTEXITCODE
+}
+
+# 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,
+# 1 (i.e. failed) otherwise.
+exit $exitstatus
+EOH
end
+
end
end
end
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 9204e3ef92..6c78f86fe0 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -147,7 +147,7 @@ class Chef
# some scripts support multiple instances through symlinks such as openvpn.
# We should get the service name from rcvar.
Chef::Log.debug("name=\"service\" not found at #{init_command}. falling back to rcvar")
- sn = shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
+ shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
else
# for why-run mode when the rcd_script is not there yet
new_resource.service_name
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 2f5c2b7798..18759b29f7 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -37,6 +37,8 @@ require 'chef/resource_resolver'
require 'chef/mixin/deprecation'
require 'chef/mixin/provides'
+require 'chef/mixin/shell_out'
+require 'chef/mixin/powershell_out'
class Chef
class Resource
@@ -51,6 +53,10 @@ class Chef
include Chef::DSL::RebootPending
extend Chef::Mixin::Provides
+ # This lets user code do things like `not_if { shell_out!("command") }`
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::PowershellOut
+
#
# The node the current Chef run is using.
#
diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb
index 7829bb4d70..7d13a5a5ce 100644
--- a/lib/chef/resource_reporter.rb
+++ b/lib/chef/resource_reporter.rb
@@ -213,7 +213,7 @@ class Chef
# If we failed before we received the run_started callback, there's not much we can do
# in terms of reporting
if @run_status
- post_reporting_data
+ post_reporting_data
end
end
diff --git a/lib/chef/util/backup.rb b/lib/chef/util/backup.rb
index 0cc009ca1f..6c95cedad7 100644
--- a/lib/chef/util/backup.rb
+++ b/lib/chef/util/backup.rb
@@ -78,8 +78,16 @@ class Chef
Chef::Log.info("#{@new_resource} removed backup at #{backup_file}")
end
+ def unsorted_backup_files
+ # If you replace this with Dir[], you will probably break Windows.
+ fn = Regexp.escape(::File.basename(path))
+ Dir.entries(::File.dirname(backup_path)).select do |f|
+ !!(f =~ /\A#{fn}.chef-[0-9.]*\B/)
+ end.map {|f| ::File.join(::File.dirname(backup_path), f)}
+ end
+
def sorted_backup_files
- Dir[Chef::Util::PathHelper.escape_glob(prefix, ".#{path}") + ".chef-*"].sort { |a,b| b <=> a }
+ unsorted_backup_files.sort { |a,b| b <=> a }
end
end
end
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index f7466084b6..f4cf71c002 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -21,7 +21,7 @@
class Chef
CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
- VERSION = '12.4.0.dev.0'
+ VERSION = '12.4.0.rc.0'
end
#
diff --git a/spec/functional/mixin/powershell_out_spec.rb b/spec/functional/mixin/powershell_out_spec.rb
new file mode 100644
index 0000000000..9cc8aeed7e
--- /dev/null
+++ b/spec/functional/mixin/powershell_out_spec.rb
@@ -0,0 +1,43 @@
+#
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/powershell_out'
+
+describe Chef::Mixin::PowershellOut, windows_only: true do
+ include Chef::Mixin::PowershellOut
+
+ describe "#powershell_out" do
+ it "runs a powershell command and collects stdout" do
+ expect(powershell_out("get-process").run_command.stdout).to match /Handles\s+NPM\(K\)\s+PM\(K\)\s+WS\(K\)\s+VM\(M\)\s+CPU\(s\)\s+Id\s+ProcessName/
+ end
+
+ it "does not raise exceptions when the command is invalid" do
+ powershell_out("this-is-not-a-valid-command").run_command
+ end
+ end
+
+ describe "#powershell_out!" do
+ it "runs a powershell command and collects stdout" do
+ expect(powershell_out!("get-process").run_command.stdout).to match /Handles\s+NPM\(K\)\s+PM\(K\)\s+WS\(K\)\s+VM\(M\)\s+CPU\(s\)\s+Id\s+ProcessName/
+ end
+
+ it "raises exceptions when the command is invalid" do
+ expect { powershell_out!("this-is-not-a-valid-command").run_command }.to raise_exception(Mixlib::ShellOut::ShellCommandFailed)
+ end
+ end
+end
diff --git a/spec/functional/resource/execute_spec.rb b/spec/functional/resource/execute_spec.rb
index ffa4628cb2..692ccfb796 100644
--- a/spec/functional/resource/execute_spec.rb
+++ b/spec/functional/resource/execute_spec.rb
@@ -137,9 +137,16 @@ describe Chef::Resource::Execute do
end
end
+ # Ensure that CommandTimeout is raised, and is caused by resource.timeout really expiring.
+ # https://github.com/chef/chef/issues/2985
+ #
+ # resource.timeout should be short, this is what we're testing
+ # resource.command ruby sleep timer should be longer than resource.timeout to give us something to timeout
+ # Timeout::timeout should be longer than resource.timeout, but less than the resource.command ruby sleep timer,
+ # so we fail if we finish on resource.command instead of resource.timeout, but raise CommandTimeout anyway (#2175).
it "times out when a timeout is set on the resource" do
- Timeout::timeout(5) do
- resource.command %{ruby -e 'sleep 600'}
+ Timeout::timeout(30) do
+ resource.command %{ruby -e 'sleep 300'}
resource.timeout 0.1
expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::CommandTimeout)
end
diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb
index f1a290dea4..9e30e62111 100644
--- a/spec/functional/resource/file_spec.rb
+++ b/spec/functional/resource/file_spec.rb
@@ -86,6 +86,31 @@ describe Chef::Resource::File do
end
end
+
+ describe "when using backup" do
+ before do
+ Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+ resource_without_content.backup(1)
+ resource_without_content.run_action(:create)
+ end
+
+ let(:backup_glob) { File.join(CHEF_SPEC_BACKUP_PATH, test_file_dir.sub(/^([A-Za-z]:)/, ""), "#{file_base}*") }
+
+ let(:path) do
+ # Use native system path
+ ChefConfig::PathHelper.canonical_path(File.join(test_file_dir, make_tmpname(file_base)), false)
+ end
+
+ it "only stores the number of requested backups" do
+ resource_without_content.content('foo')
+ resource_without_content.run_action(:create)
+ resource_without_content.content('bar')
+ resource_without_content.run_action(:create)
+ expect(Dir.glob(backup_glob).length).to eq(1)
+ end
+
+ end
+
# github issue 1842.
describe "when running action :create on a relative path" do
before do
diff --git a/spec/functional/resource/group_spec.rb b/spec/functional/resource/group_spec.rb
index 6676aa32e9..529af52d4e 100644
--- a/spec/functional/resource/group_spec.rb
+++ b/spec/functional/resource/group_spec.rb
@@ -372,6 +372,11 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" }
let(:tested_action) { :manage }
describe "when there is no group" do
+ before(:each) do
+ group_resource.run_action(:remove)
+ group_should_not_exist(group_name)
+ end
+
it "raises an error on modify" do
expect { group_resource.run_action(:modify) }.to raise_error
end
diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb
index d39a0c2ef6..7e903b30b4 100644
--- a/spec/functional/resource/link_spec.rb
+++ b/spec/functional/resource/link_spec.rb
@@ -348,8 +348,7 @@ describe Chef::Resource::Link do
end
it_behaves_like 'delete errors out'
end
- context 'and the link already exists and is not writeable to this user', :skip => true do
- end
+
it_behaves_like 'a securable resource without existing target' do
let(:path) { target_file }
def allowed_acl(sid, expected_perms)
@@ -360,7 +359,7 @@ describe Chef::Resource::Link do
end
def parent_inheritable_acls
dummy_file_path = File.join(test_file_dir, "dummy_file")
- dummy_file = FileUtils.touch(dummy_file_path)
+ FileUtils.touch(dummy_file_path)
dummy_desc = get_security_descriptor(dummy_file_path)
FileUtils.rm_rf(dummy_file_path)
dummy_desc
@@ -416,8 +415,6 @@ describe Chef::Resource::Link do
end
end
end
- context "when the link destination is not readable to this user", :skip => true do
- end
context "when the link destination does not exist" do
include_context 'create symbolic link succeeds'
include_context 'delete is noop'
@@ -518,8 +515,6 @@ describe Chef::Resource::Link do
end
it_behaves_like 'delete errors out'
end
- context "and the link already exists and is not writeable to this user", :skip => true do
- end
context "and specifies security attributes" do
before(:each) do
resource.owner(windows? ? 'Guest' : 'nobody')
@@ -559,10 +554,10 @@ describe Chef::Resource::Link do
end
context 'and the link does not yet exist' do
it 'links to the target file' do
+ skip('OS X/FreeBSD/AIX symlink? and readlink working on hard links to symlinks') if (os_x? or freebsd? or aix?)
resource.run_action(:create)
expect(File.exists?(target_file)).to be_truthy
# OS X gets angry about this sort of link. Bug in OS X, IMO.
- pending('OS X/FreeBSD/AIX symlink? and readlink working on hard links to symlinks') if (os_x? or freebsd? or aix?)
expect(symlink?(target_file)).to be_truthy
expect(readlink(target_file)).to eq(canonicalize(@other_target))
end
@@ -578,7 +573,7 @@ describe Chef::Resource::Link do
end
context 'and the link does not yet exist' do
it 'links to the target file' do
- pending('OS X/FreeBSD/AIX fails to create hardlinks to broken symlinks') if (os_x? or freebsd? or aix?)
+ skip('OS X/FreeBSD/AIX fails to create hardlinks to broken symlinks') if (os_x? or freebsd? or aix?)
resource.run_action(:create)
# Windows and Unix have different definitions of exists? here, and that's OK.
if windows?
@@ -593,8 +588,7 @@ describe Chef::Resource::Link do
end
end
end
- context "when the link destination is not readable to this user", :skip => true do
- end
+
context "when the link destination does not exist" do
context 'and the link does not yet exist' do
it 'create errors out' do
diff --git a/spec/functional/resource/powershell_spec.rb b/spec/functional/resource/powershell_spec.rb
index 56a905efe7..17ae8cbd2a 100644
--- a/spec/functional/resource/powershell_spec.rb
+++ b/spec/functional/resource/powershell_spec.rb
@@ -56,14 +56,13 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
resource.run_action(:run)
end
- it "returns the -27 for a powershell script that exits with -27", :windows_powershell_dsc_only do
- # This is broken on Powershell < 4.0
+ it "returns the exit status 27 for a powershell script that exits with 27" do
file = Tempfile.new(['foo', '.ps1'])
begin
- file.write "exit -27"
+ file.write "exit 27"
file.close
resource.code(". \"#{file.path}\"")
- resource.returns(-27)
+ resource.returns(27)
resource.run_action(:run)
ensure
file.close
@@ -71,6 +70,30 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
end
end
+ 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
+ # 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'])
+ begin
+ file.write "exit #{negative_exit_status.to_s}"
+ file.close
+ resource.code(". \"#{file.path}\"")
+
+ # PowerShell earlier than 4.0 takes negative exit codes
+ # and returns them as the underlying unsigned 16-bit
+ # 2's complement representation. We cover multiple versions
+ # of PowerShell in this example by including both the signed
+ # exit code and its converted counterpart as permitted return values.
+ # See http://support.microsoft.com/en-us/kb/2646183/zh-cn
+ resource.returns([negative_exit_status, unsigned_exit_status])
+ expect { resource.run_action(:run) }.not_to raise_error
+ ensure
+ file.close
+ file.unlink
+ end
+ end
it "returns the process exit code" do
resource.code(arbitrary_nonzero_process_exit_code_content)
@@ -99,7 +122,19 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
it "returns 1 if the last command was a cmdlet that failed and was preceded by a successfully executed non-cmdlet Windows binary" do
resource.code([windows_process_exit_code_success_content, cmdlet_exit_code_not_found_content].join(';'))
resource.returns(1)
- resource.run_action(:run)
+ expect { resource.run_action(:run) }.not_to raise_error
+ end
+
+ it "raises an error if the script is not syntactically correct and returns is not set to 1" do
+ resource.code('if({)')
+ resource.returns(0)
+ expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
+ end
+
+ it "returns 1 if the script provided to the code attribute is not syntactically correct" do
+ resource.code('if({)')
+ resource.returns(1)
+ expect { resource.run_action(:run) }.not_to raise_error
end
# This somewhat ambiguous case, two failures of different types,
diff --git a/spec/functional/resource/user/useradd_spec.rb b/spec/functional/resource/user/useradd_spec.rb
index 3e4e4e7604..474f6a4ecf 100644
--- a/spec/functional/resource/user/useradd_spec.rb
+++ b/spec/functional/resource/user/useradd_spec.rb
@@ -65,8 +65,12 @@ describe Chef::Provider::User::Useradd, metadata do
end
end
- def supports_quote_in_username?
- OHAI_SYSTEM["platform_family"] == "debian"
+ def self.quote_in_username_unsupported?
+ if OHAI_SYSTEM["platform_family"] == "debian"
+ false
+ else
+ "Only debian family systems support quotes in username"
+ end
end
def password_should_be_set
@@ -108,7 +112,7 @@ describe Chef::Provider::User::Useradd, metadata do
break if status.exitstatus != 8
sleep 1
- max_retries = max_retries -1
+ max_retries = max_retries - 1
rescue UserNotFound
break
end
@@ -162,15 +166,10 @@ describe Chef::Provider::User::Useradd, metadata do
end
end
- let(:skip) { false }
-
describe "action :create" do
context "when the user does not exist beforehand" do
before do
- if reason = skip
- pending(reason)
- end
user_resource.run_action(:create)
expect(user_resource).to be_updated_by_last_action
end
@@ -186,14 +185,7 @@ describe Chef::Provider::User::Useradd, metadata do
# tabulation: '\t', etc.). Note that using a slash ('/') may break the
# default algorithm for the definition of the user's home directory.
- context "and the username contains a single quote" do
- let(:skip) do
- if supports_quote_in_username?
- false
- else
- "Platform #{OHAI_SYSTEM["platform"]} not expected to support username w/ quote"
- end
- end
+ context "and the username contains a single quote", skip: quote_in_username_unsupported? do
let(:username) { "t'bilisi" }
@@ -342,7 +334,7 @@ describe Chef::Provider::User::Useradd, metadata do
before do
if reason = skip
- pending(reason)
+ skip(reason)
end
existing_user.run_action(:create)
expect(existing_user).to be_updated_by_last_action
@@ -535,7 +527,7 @@ describe Chef::Provider::User::Useradd, metadata do
def aix_user_lock_status
lock_info = shell_out!("lsuser -a account_locked #{username}")
- status = /\S+\s+account_locked=(\S+)/.match(lock_info.stdout)[1]
+ /\S+\s+account_locked=(\S+)/.match(lock_info.stdout)[1]
end
def user_account_should_be_locked
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 8b5e42e5b6..dcf244c3cc 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -151,7 +151,7 @@ RSpec.configure do |config|
config.filter_run_excluding :aes_256_gcm_only => true unless aes_256_gcm?
config.filter_run_excluding :broken => true
- running_platform_arch = `uname -m`.strip
+ running_platform_arch = `uname -m`.strip unless windows?
config.filter_run_excluding :arch => lambda {|target_arch|
running_platform_arch != target_arch
diff --git a/spec/support/shared/context/client.rb b/spec/support/shared/context/client.rb
new file mode 100644
index 0000000000..e625185f7c
--- /dev/null
+++ b/spec/support/shared/context/client.rb
@@ -0,0 +1,275 @@
+
+require 'spec_helper'
+
+# Stubs a basic client object
+shared_context "client" do
+ let(:fqdn) { "hostname.example.org" }
+ let(:hostname) { "hostname" }
+ let(:machinename) { "machinename.example.org" }
+ let(:platform) { "example-platform" }
+ let(:platform_version) { "example-platform-1.0" }
+
+ let(:ohai_data) do
+ {
+ :fqdn => fqdn,
+ :hostname => hostname,
+ :machinename => machinename,
+ :platform => platform,
+ :platform_version => platform_version
+ }
+ end
+
+ let(:ohai_system) do
+ ohai = instance_double("Ohai::System", :all_plugins => true, :data => ohai_data)
+ allow(ohai).to receive(:[]) do |k|
+ ohai_data[k]
+ end
+ ohai
+ end
+
+ let(:node) do
+ Chef::Node.new.tap do |n|
+ n.name(fqdn)
+ n.chef_environment("_default")
+ end
+ end
+
+ let(:json_attribs) { nil }
+ let(:client_opts) { {} }
+
+ let(:client) do
+ Chef::Config[:event_loggers] = []
+ Chef::Client.new(json_attribs, client_opts).tap do |c|
+ c.node = node
+ end
+ end
+
+ before do
+ Chef::Log.logger = Logger.new(StringIO.new)
+
+ # Node/Ohai data
+ #Chef::Config[:node_name] = fqdn
+ allow(Ohai::System).to receive(:new).and_return(ohai_system)
+ end
+end
+
+# Stubs a client for a client run.
+# Requires a client object be defined in the scope of this included context.
+# e.g.:
+# describe "some functionality" do
+# include_context "client"
+# include_context "a client run"
+# ...
+# end
+shared_context "a client run" do
+ let(:stdout) { StringIO.new }
+ let(:stderr) { StringIO.new }
+
+ let(:api_client_exists?) { false }
+ let(:enable_fork) { false }
+
+ let(:http_cookbook_sync) { double("Chef::REST (cookbook sync)") }
+ let(:http_node_load) { double("Chef::REST (node)") }
+ let(:http_node_save) { double("Chef::REST (node save)") }
+
+ let(:runner) { instance_double("Chef::Runner") }
+ let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => false) }
+
+ def stub_for_register
+ # --Client.register
+ # Make sure Client#register thinks the client key doesn't
+ # exist, so it tries to register and create one.
+ allow(File).to receive(:exists?).and_call_original
+ expect(File).to receive(:exists?).
+ with(Chef::Config[:client_key]).
+ exactly(:once).
+ and_return(api_client_exists?)
+
+ unless api_client_exists?
+ # Client.register will register with the validation client name.
+ expect_any_instance_of(Chef::ApiClient::Registration).to receive(:run)
+ end
+ end
+
+ def stub_for_node_load
+ # Client.register will then turn around create another
+ # Chef::REST object, this time with the client key it got from the
+ # previous step.
+ expect(Chef::REST).to receive(:new).
+ with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]).
+ exactly(:once).
+ and_return(http_node_load)
+
+ # --Client#build_node
+ # looks up the node, which we will return, then later saves it.
+ expect(Chef::Node).to receive(:find_or_create).with(fqdn).and_return(node)
+
+ # --ResourceReporter#node_load_completed
+ # gets a run id from the server for storing resource history
+ # (has its own tests, so stubbing it here.)
+ expect_any_instance_of(Chef::ResourceReporter).to receive(:node_load_completed)
+ end
+
+ def stub_for_sync_cookbooks
+ # --Client#setup_run_context
+ # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
+ #
+ expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks)
+ expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
+ expect(http_cookbook_sync).to receive(:post).
+ with("environments/_default/cookbook_versions", {:run_list => []}).
+ and_return({})
+ end
+
+ def stub_for_converge
+ # define me
+ end
+
+ def stub_for_audit
+ # define me
+ end
+
+ def stub_for_node_save
+ # define me
+ end
+
+ def stub_for_run
+ # define me
+ end
+
+ before do
+ Chef::Config[:client_fork] = enable_fork
+ Chef::Config[:cache_path] = windows? ? 'C:\chef' : '/var/chef'
+ Chef::Config[:why_run] = false
+ Chef::Config[:audit_mode] = :enabled
+
+ stub_const("Chef::Client::STDOUT_FD", stdout)
+ stub_const("Chef::Client::STDERR_FD", stderr)
+
+ stub_for_register
+ stub_for_node_load
+ stub_for_sync_cookbooks
+ stub_for_converge
+ stub_for_audit
+ stub_for_node_save
+
+ expect_any_instance_of(Chef::RunLock).to receive(:acquire)
+ expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
+ expect_any_instance_of(Chef::RunLock).to receive(:release)
+
+ # Post conditions: check that node has been filled in correctly
+ expect(client).to receive(:run_started)
+
+ stub_for_run
+ end
+end
+
+shared_context "converge completed" do
+ def stub_for_converge
+ # --Client#converge
+ expect(Chef::Runner).to receive(:new).and_return(runner)
+ expect(runner).to receive(:converge).and_return(true)
+ end
+
+ def stub_for_node_save
+ allow(node).to receive(:data_for_save).and_return(node.for_json)
+
+ # --Client#save_updated_node
+ expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key], validate_utf8: false).and_return(http_node_save)
+ expect(http_node_save).to receive(:put_rest).with("nodes/#{fqdn}", node.for_json).and_return(true)
+ end
+end
+
+shared_context "converge failed" do
+ let(:converge_error) do
+ err = Chef::Exceptions::UnsupportedAction.new("Action unsupported")
+ err.set_backtrace([ "/path/recipe.rb:15", "/path/recipe.rb:12" ])
+ err
+ end
+
+ def stub_for_converge
+ expect(Chef::Runner).to receive(:new).and_return(runner)
+ expect(runner).to receive(:converge).and_raise(converge_error)
+ end
+
+ def stub_for_node_save
+ expect(client).to_not receive(:save_updated_node)
+ end
+end
+
+shared_context "audit phase completed" do
+ def stub_for_audit
+ # -- Client#run_audits
+ expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
+ expect(audit_runner).to receive(:run).and_return(true)
+ expect(client.events).to receive(:audit_phase_complete)
+ end
+end
+
+shared_context "audit phase failed with error" do
+ let(:audit_error) do
+ err = RuntimeError.new("Unexpected audit error")
+ err.set_backtrace([ "/path/recipe.rb:57", "/path/recipe.rb:55" ])
+ err
+ end
+
+ def stub_for_audit
+ expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
+ expect(audit_runner).to receive(:run).and_raise(audit_error)
+ expect(client.events).to receive(:audit_phase_failed).with(audit_error)
+ end
+end
+
+shared_context "audit phase completed with failed controls" do
+ let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => true,
+ :num_failed => 1, :num_total => 3) }
+
+ let(:audit_error) do
+ err = Chef::Exceptions::AuditsFailed.new(audit_runner.num_failed, audit_runner.num_total)
+ err.set_backtrace([ "/path/recipe.rb:108", "/path/recipe.rb:103" ])
+ err
+ end
+
+ def stub_for_audit
+ expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
+ expect(audit_runner).to receive(:run)
+ expect(Chef::Exceptions::AuditsFailed).to receive(:new).with(
+ audit_runner.num_failed, audit_runner.num_total
+ ).and_return(audit_error)
+ expect(client.events).to receive(:audit_phase_failed).with(audit_error)
+ end
+end
+
+shared_context "run completed" do
+ def stub_for_run
+ expect(client).to receive(:run_completed_successfully)
+
+ # --ResourceReporter#run_completed
+ # updates the server with the resource history
+ # (has its own tests, so stubbing it here.)
+ expect_any_instance_of(Chef::ResourceReporter).to receive(:run_completed)
+ # --AuditReporter#run_completed
+ # posts the audit data to server.
+ # (has its own tests, so stubbing it here.)
+ expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_completed)
+ end
+end
+
+shared_context "run failed" do
+ def stub_for_run
+ expect(client).to receive(:run_failed)
+
+ # --ResourceReporter#run_completed
+ # updates the server with the resource history
+ # (has its own tests, so stubbing it here.)
+ expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
+ # --AuditReporter#run_completed
+ # posts the audit data to server.
+ # (has its own tests, so stubbing it here.)
+ expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
+ end
+
+ before do
+ expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
+ end
+end
diff --git a/spec/support/shared/examples/client.rb b/spec/support/shared/examples/client.rb
new file mode 100644
index 0000000000..330cb40ac6
--- /dev/null
+++ b/spec/support/shared/examples/client.rb
@@ -0,0 +1,53 @@
+
+require 'spec_helper'
+require 'spec/support/shared/context/client'
+
+# requires platform and platform_version be defined
+shared_examples "a completed run" do
+ include_context "run completed"
+
+ it "runs ohai, sets up authentication, loads node state, synchronizes policy, converges, and runs audits" do
+ # This is what we're testing.
+ expect(client.run).to be true
+
+ # fork is stubbed, so we can see the outcome of the run
+ expect(node.automatic_attrs[:platform]).to eq(platform)
+ expect(node.automatic_attrs[:platform_version]).to eq(platform_version)
+ end
+end
+
+shared_examples "a completed run with audit failure" do
+ include_context "run completed"
+
+ before do
+ expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
+ end
+
+ it "converges, runs audits, saves the node and raises the error in a wrapping error" do
+ expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
+ expect(error.wrapped_errors.size).to eq(run_errors.size)
+ run_errors.each do |run_error|
+ expect(error.wrapped_errors).to include(run_error)
+ expect(error.backtrace).to include(*run_error.backtrace)
+ end
+ end
+
+ # fork is stubbed, so we can see the outcome of the run
+ expect(node.automatic_attrs[:platform]).to eq(platform)
+ expect(node.automatic_attrs[:platform_version]).to eq(platform_version)
+ end
+end
+
+shared_examples "a failed run" do
+ include_context "run failed"
+
+ it "skips node save and raises the error in a wrapping error" do
+ expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
+ expect(error.wrapped_errors.size).to eq(run_errors.size)
+ run_errors.each do |run_error|
+ expect(error.wrapped_errors).to include(run_error)
+ expect(error.backtrace).to include(*run_error.backtrace)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared/functional/file_resource.rb b/spec/support/shared/functional/file_resource.rb
index 4f8e2f5b71..3ce3c9c94e 100644
--- a/spec/support/shared/functional/file_resource.rb
+++ b/spec/support/shared/functional/file_resource.rb
@@ -592,10 +592,6 @@ shared_examples_for "a configured file resource" do
File.open(path, "wb") { |f| f.write(wrong_content) }
end
- it "updates the source file content" do
- skip
- end
-
it "marks the resource as updated" do
resource.run_action(:create)
expect(resource).to be_updated_by_last_action
diff --git a/spec/support/shared/functional/securable_resource.rb b/spec/support/shared/functional/securable_resource.rb
index bde508b14d..b3c32356aa 100644
--- a/spec/support/shared/functional/securable_resource.rb
+++ b/spec/support/shared/functional/securable_resource.rb
@@ -163,9 +163,6 @@ shared_examples_for "a securable resource with existing target" do
let(:desired_gid) { 1337 }
let(:expected_gid) { 1337 }
- skip "should set an owner (Rerun specs under root)", :requires_unprivileged_user => true
- skip "should set a group (Rerun specs under root)", :requires_unprivileged_user => true
-
describe "when setting the owner", :requires_root do
before do
resource.owner expected_user_name
@@ -205,11 +202,6 @@ shared_examples_for "a securable resource with existing target" do
resource.run_action(:create)
end
- it "should set permissions as specified" do
- pending("Linux does not support lchmod")
- expect{ File.lstat(path).mode & 007777 }.to eq(@mode_string.oct & 007777)
- end
-
it "is marked as updated only if changes are made" do
expect(resource.updated_by_last_action?).to eq(expect_updated?)
end
@@ -222,11 +214,6 @@ shared_examples_for "a securable resource with existing target" do
resource.run_action(:create)
end
- it "should set permissions in numeric form as a ruby-interpreted octal" do
- pending('Linux does not support lchmod')
- expect{ File.lstat(path).mode & 007777 }.to eq(@mode_integer & 007777)
- end
-
it "is marked as updated only if changes are made" do
expect(resource.updated_by_last_action?).to eq(expect_updated?)
end
@@ -306,10 +293,6 @@ shared_examples_for "a securable resource without existing target" do
include_context "diff disabled"
- context "on Unix", :unix_only do
- skip "if we need any securable resource tests on Unix without existing target resource."
- end
-
context "on Windows", :windows_only do
include_context "use Windows permissions"
@@ -366,13 +349,6 @@ shared_examples_for "a securable resource without existing target" do
expect { resource.group 'Lance "The Nose" Glindenberry III' }.to raise_error(Chef::Exceptions::ValidationFailed)
end
- it "sets group when group is specified with a \\" do
- pending("Need to find a group containing a backslash that is on most peoples' machines")
- resource.group "#{ENV['COMPUTERNAME']}\\Administrators"
- resource.run_action(:create)
- expect{ descriptor.group }.to eq(SID.Everyone)
- end
-
it "leaves group alone if group is not specified and resource already exists" do
arbitrary_non_default_group = SID.Everyone
expect(arbitrary_non_default_group).not_to eq(SID.default_security_object_group)
diff --git a/spec/support/shared/functional/securable_resource_with_reporting.rb b/spec/support/shared/functional/securable_resource_with_reporting.rb
index 37fc538801..3176ebba0d 100644
--- a/spec/support/shared/functional/securable_resource_with_reporting.rb
+++ b/spec/support/shared/functional/securable_resource_with_reporting.rb
@@ -279,14 +279,14 @@ shared_examples_for "a securable resource with reporting" do
end
it "has empty values for file metadata in 'current_resource'" do
- pending "windows reporting not yet fully supported"
+ skip "windows reporting not yet fully supported"
expect(current_resource.owner).to be_nil
expect(current_resource.expanded_rights).to be_nil
end
context "and no security metadata is specified in new_resource" do
before do
- pending "windows reporting not yet fully supported"
+ skip "windows reporting not yet fully supported"
end
it "sets the metadata values on the new_resource as strings after creating" do
@@ -322,7 +322,7 @@ shared_examples_for "a securable resource with reporting" do
let(:expected_user_name) { 'domain\user' }
before do
- pending "windows reporting not yet fully supported"
+ skip "windows reporting not yet fully supported"
resource.owner(expected_user_name)
resource.run_action(:create)
end
@@ -336,7 +336,7 @@ shared_examples_for "a securable resource with reporting" do
context "when the target file exists" do
before do
- pending "windows reporting not yet fully supported"
+ skip "windows reporting not yet fully supported"
FileUtils.touch(resource.path)
resource.action(:create)
end
diff --git a/spec/support/shared/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb
index 35b86dc4e8..3499cc98ec 100644
--- a/spec/support/shared/functional/windows_script.rb
+++ b/spec/support/shared/functional/windows_script.rb
@@ -114,7 +114,7 @@ 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
diff --git a/spec/unit/audit/audit_reporter_spec.rb b/spec/unit/audit/audit_reporter_spec.rb
index 4bf889510a..75c96155da 100644
--- a/spec/unit/audit/audit_reporter_spec.rb
+++ b/spec/unit/audit/audit_reporter_spec.rb
@@ -88,6 +88,29 @@ describe Chef::Audit::AuditReporter do
reporter.run_completed(node)
end
+ context "when audit phase failed" do
+
+ let(:audit_error) { double("AuditError", :class => "Chef::Exceptions::AuditError",
+ :message => "Audit phase failed with error message: derpderpderp",
+ :backtrace => ["/path/recipe.rb:57", "/path/library.rb:106"]) }
+
+ before do
+ reporter.instance_variable_set(:@audit_phase_error, audit_error)
+ end
+
+ it "reports an error" do
+ reporter.run_completed(node)
+ expect(run_data).to have_key(:error)
+ expect(run_data).to have_key(:error)
+ expect(run_data[:error]).to eq <<-EOM.strip!
+Chef::Exceptions::AuditError: Audit phase failed with error message: derpderpderp
+/path/recipe.rb:57
+/path/library.rb:106
+EOM
+ end
+
+ end
+
context "when unable to post to server" do
let(:error) do
@@ -215,9 +238,13 @@ describe Chef::Audit::AuditReporter do
let(:audit_data) { Chef::Audit::AuditData.new(node.name, run_id) }
let(:run_data) { audit_data.to_hash }
- let(:error) { double("AuditError", :class => "Chef::Exception::AuditError",
- :message => "Well that certainly didn't work",
- :backtrace => ["line 0", "line 1", "line 2"]) }
+ let(:audit_error) { double("AuditError", :class => "Chef::Exceptions::AuditError",
+ :message => "Audit phase failed with error message: derpderpderp",
+ :backtrace => ["/path/recipe.rb:57", "/path/library.rb:106"]) }
+
+ let(:run_error) { double("RunError", :class => "Chef::Exceptions::RunError",
+ :message => "This error shouldn't be reported.",
+ :backtrace => ["fix it", "fix it", "fix it"]) }
before do
allow(reporter).to receive(:auditing_enabled?).and_return(true)
@@ -226,15 +253,32 @@ describe Chef::Audit::AuditReporter do
allow(audit_data).to receive(:to_hash).and_return(run_data)
end
- it "adds the error information to the reported data" do
- expect(rest).to receive(:create_url)
- expect(rest).to receive(:post)
- reporter.run_failed(error)
- expect(run_data).to have_key(:error)
- expect(run_data[:error]).to eq "Chef::Exception::AuditError: Well that certainly didn't work\n" +
- "line 0\nline 1\nline 2"
+ context "when no prior exception is stored" do
+ it "reports no error" do
+ expect(rest).to receive(:create_url)
+ expect(rest).to receive(:post)
+ reporter.run_failed(run_error)
+ expect(run_data).to_not have_key(:error)
+ end
end
+ context "when some prior exception is stored" do
+ before do
+ reporter.instance_variable_set(:@audit_phase_error, audit_error)
+ end
+
+ it "reports the prior error" do
+ expect(rest).to receive(:create_url)
+ expect(rest).to receive(:post)
+ reporter.run_failed(run_error)
+ expect(run_data).to have_key(:error)
+ expect(run_data[:error]).to eq <<-EOM.strip!
+Chef::Exceptions::AuditError: Audit phase failed with error message: derpderpderp
+/path/recipe.rb:57
+/path/library.rb:106
+EOM
+ end
+ end
end
shared_context "audit data" do
diff --git a/spec/unit/chef_fs/file_pattern_spec.rb b/spec/unit/chef_fs/file_pattern_spec.rb
index a9f06e8424..ed5f314605 100644
--- a/spec/unit/chef_fs/file_pattern_spec.rb
+++ b/spec/unit/chef_fs/file_pattern_spec.rb
@@ -157,7 +157,7 @@ describe Chef::ChefFS::FilePattern do
end
end
- context 'with simple pattern "a\*\b"', :pending => (Chef::Platform.windows?) do
+ context 'with simple pattern "a\*\b"', :skip => (Chef::Platform.windows?) do
let(:pattern) { Chef::ChefFS::FilePattern.new('a\*\b') }
it 'match?' do
expect(pattern.match?('a*b')).to be_truthy
@@ -264,7 +264,7 @@ describe Chef::ChefFS::FilePattern do
end
end
- context 'with star pattern "/abc/d[a-z][0-9]f/ghi"', :pending => (Chef::Platform.windows?) do
+ context 'with star pattern "/abc/d[a-z][0-9]f/ghi"', :skip => (Chef::Platform.windows?) do
let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/d[a-z][0-9]f/ghi') }
it 'match?' do
expect(pattern.match?('/abc/de1f/ghi')).to be_truthy
@@ -352,11 +352,7 @@ describe Chef::ChefFS::FilePattern do
expect(pattern.could_match_children?('/abc/def/ghi')).to be_truthy
expect(pattern.could_match_children?('abc')).to be_falsey
end
- it 'could_match_children? /abc** returns false for /xyz' do
- pending 'Make could_match_children? more rigorous'
- # At the moment, we return false for this, but in the end it would be nice to return true:
- expect(pattern.could_match_children?('/xyz')).to be_falsey
- end
+
it 'exact_child_name_under' do
expect(pattern.exact_child_name_under('/')).to eq(nil)
expect(pattern.exact_child_name_under('/abc')).to eq(nil)
@@ -440,14 +436,6 @@ describe Chef::ChefFS::FilePattern do
expect(p('/.').exact_path).to eq('/')
expect(p('/.').match?('/')).to be_truthy
end
- it 'handles dot by itself', :pending => "decide what to do with dot by itself" do
- expect(p('.').normalized_pattern).to eq('.')
- expect(p('.').exact_path).to eq('.')
- expect(p('.').match?('.')).to be_truthy
- expect(p('./').normalized_pattern).to eq('.')
- expect(p('./').exact_path).to eq('.')
- expect(p('./').match?('.')).to be_truthy
- end
it 'handles dotdot' do
expect(p('abc/../def').normalized_pattern).to eq('def')
expect(p('abc/../def').exact_path).to eq('def')
diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb
index d8c4ede796..1e4bbb5c56 100644
--- a/spec/unit/client_spec.rb
+++ b/spec/unit/client_spec.rb
@@ -19,6 +19,8 @@
#
require 'spec_helper'
+require 'spec/support/shared/context/client'
+require 'spec/support/shared/examples/client'
require 'chef/run_context'
require 'chef/rest'
@@ -28,55 +30,7 @@ class FooError < RuntimeError
end
describe Chef::Client do
-
- let(:hostname) { "hostname" }
- let(:machinename) { "machinename.example.org" }
- let(:fqdn) { "hostname.example.org" }
-
- let(:ohai_data) do
- { :fqdn => fqdn,
- :hostname => hostname,
- :machinename => machinename,
- :platform => 'example-platform',
- :platform_version => 'example-platform-1.0',
- :data => {}
- }
- end
-
- let(:ohai_system) do
- ohai_system = double( "Ohai::System",
- :all_plugins => true,
- :data => ohai_data)
- allow(ohai_system).to receive(:[]) do |key|
- ohai_data[key]
- end
- ohai_system
- end
-
- let(:node) do
- Chef::Node.new.tap do |n|
- n.name(fqdn)
- n.chef_environment("_default")
- end
- end
-
- let(:json_attribs) { nil }
- let(:client_opts) { {} }
-
- let(:client) do
- Chef::Config[:event_loggers] = []
- Chef::Client.new(json_attribs, client_opts).tap do |c|
- c.node = node
- end
- end
-
- before do
- Chef::Log.logger = Logger.new(StringIO.new)
-
- # Node/Ohai data
- #Chef::Config[:node_name] = fqdn
- allow(Ohai::System).to receive(:new).and_return(ohai_system)
- end
+ include_context "client"
context "when minimal ohai is configured" do
before do
@@ -88,7 +42,6 @@ describe Chef::Client do
expect(ohai_system).to receive(:all_plugins).with(expected_filter)
client.run_ohai
end
-
end
describe "authentication protocol selection" do
@@ -117,7 +70,6 @@ describe Chef::Client do
describe "configuring output formatters" do
context "when no formatter has been configured" do
-
context "and STDOUT is a TTY" do
before do
allow(STDOUT).to receive(:tty?).and_return(true)
@@ -203,135 +155,12 @@ describe Chef::Client do
end
describe "a full client run" do
- shared_context "a client run" do
- let(:http_node_load) { double("Chef::REST (node)") }
- let(:http_cookbook_sync) { double("Chef::REST (cookbook sync)") }
- let(:http_node_save) { double("Chef::REST (node save)") }
- let(:runner) { double("Chef::Runner") }
- let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => false) }
-
- let(:api_client_exists?) { false }
-
- let(:stdout) { StringIO.new }
- let(:stderr) { StringIO.new }
-
- let(:enable_fork) { false }
-
- def stub_for_register
- # --Client.register
- # Make sure Client#register thinks the client key doesn't
- # exist, so it tries to register and create one.
- allow(File).to receive(:exists?).and_call_original
- expect(File).to receive(:exists?).
- with(Chef::Config[:client_key]).
- exactly(:once).
- and_return(api_client_exists?)
-
- unless api_client_exists?
- # Client.register will register with the validation client name.
- expect_any_instance_of(Chef::ApiClient::Registration).to receive(:run)
- end
- end
-
- def stub_for_node_load
- # Client.register will then turn around create another
- # Chef::REST object, this time with the client key it got from the
- # previous step.
- expect(Chef::REST).to receive(:new).
- with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]).
- exactly(:once).
- and_return(http_node_load)
-
- # --Client#build_node
- # looks up the node, which we will return, then later saves it.
- expect(Chef::Node).to receive(:find_or_create).with(fqdn).and_return(node)
-
- # --ResourceReporter#node_load_completed
- # gets a run id from the server for storing resource history
- # (has its own tests, so stubbing it here.)
- expect_any_instance_of(Chef::ResourceReporter).to receive(:node_load_completed)
- end
-
- def stub_for_sync_cookbooks
- # --Client#setup_run_context
- # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
- #
- expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks)
- expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
- expect(http_cookbook_sync).to receive(:post).
- with("environments/_default/cookbook_versions", {:run_list => []}).
- and_return({})
- end
-
- def stub_for_converge
- # --Client#converge
- expect(Chef::Runner).to receive(:new).and_return(runner)
- expect(runner).to receive(:converge).and_return(true)
- end
-
- def stub_for_audit
- # -- Client#run_audits
- expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
- expect(audit_runner).to receive(:run).and_return(true)
- end
-
- def stub_for_node_save
- allow(node).to receive(:data_for_save).and_return(node.for_json)
-
- # --Client#save_updated_node
- expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key], validate_utf8: false).and_return(http_node_save)
- expect(http_node_save).to receive(:put_rest).with("nodes/#{fqdn}", node.for_json).and_return(true)
- end
-
- def stub_for_run
- expect_any_instance_of(Chef::RunLock).to receive(:acquire)
- expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
- expect_any_instance_of(Chef::RunLock).to receive(:release)
-
- # Post conditions: check that node has been filled in correctly
- expect(client).to receive(:run_started)
- expect(client).to receive(:run_completed_successfully)
-
- # --ResourceReporter#run_completed
- # updates the server with the resource history
- # (has its own tests, so stubbing it here.)
- expect_any_instance_of(Chef::ResourceReporter).to receive(:run_completed)
- # --AuditReporter#run_completed
- # posts the audit data to server.
- # (has its own tests, so stubbing it here.)
- expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_completed)
- end
-
- before do
- Chef::Config[:client_fork] = enable_fork
- Chef::Config[:cache_path] = windows? ? 'C:\chef' : '/var/chef'
- Chef::Config[:why_run] = false
- Chef::Config[:audit_mode] = :enabled
-
- stub_const("Chef::Client::STDOUT_FD", stdout)
- stub_const("Chef::Client::STDERR_FD", stderr)
-
- stub_for_register
- stub_for_node_load
- stub_for_sync_cookbooks
- stub_for_converge
- stub_for_audit
- stub_for_node_save
- stub_for_run
- end
- end
-
shared_examples_for "a successful client run" do
include_context "a client run"
+ include_context "converge completed"
+ include_context "audit phase completed"
- it "runs ohai, sets up authentication, loads node state, synchronizes policy, converges, and runs audits" do
- # This is what we're testing.
- client.run
-
- # fork is stubbed, so we can see the outcome of the run
- expect(node.automatic_attrs[:platform]).to eq("example-platform")
- expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0")
- end
+ include_examples "a completed run"
end
describe "when running chef-client without fork" do
@@ -339,24 +168,19 @@ describe Chef::Client do
end
describe "when the client key already exists" do
- let(:api_client_exists?) { true }
- include_examples "a successful client run"
+ include_examples "a successful client run" do
+ let(:api_client_exists?) { true }
+ end
end
- describe "when an override run list is given" do
- let(:client_opts) { {:override_runlist => "recipe[override_recipe]"} }
-
- it "should permit spaces in overriding run list" do
+ context "when an override run list is given" do
+ it "permits spaces in overriding run list" do
Chef::Client.new(nil, :override_runlist => 'role[a], role[b]')
end
- describe "when running the client" do
+ describe "calling run" do
include_examples "a successful client run" do
-
- before do
- # Client will try to compile and run override_recipe
- expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile)
- end
+ let(:client_opts) { {:override_runlist => "recipe[override_recipe]"} }
def stub_for_sync_cookbooks
# --Client#setup_run_context
@@ -373,13 +197,22 @@ describe Chef::Client do
# Expect NO node save
expect(node).not_to receive(:save)
end
+
+ before do
+ # Client will try to compile and run override_recipe
+ expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile)
+ end
end
end
end
describe "when a permanent run list is passed as an option" do
- include_examples "a successful client run" do
+ it "sets the new run list on the node" do
+ client.run
+ expect(node.run_list).to eq(Chef::RunList.new(new_runlist))
+ end
+ include_examples "a successful client run" do
let(:new_runlist) { "recipe[new_run_list_recipe]" }
let(:client_opts) { {:runlist => new_runlist} }
@@ -399,214 +232,61 @@ describe Chef::Client do
# do not create a fixture for this.
expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile)
end
-
- it "sets the new run list on the node" do
- client.run
- expect(node.run_list).to eq(Chef::RunList.new(new_runlist))
- end
end
end
- describe "when converge fails" do
- include_context "a client run" do
- let(:e) { Exception.new }
- def stub_for_converge
- expect(Chef::Runner).to receive(:new).and_return(runner)
- expect(runner).to receive(:converge).and_raise(e)
- expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
- end
-
- def stub_for_node_save
- expect(client).to_not receive(:save_updated_node)
- end
-
- def stub_for_run
- expect_any_instance_of(Chef::RunLock).to receive(:acquire)
- expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
- expect_any_instance_of(Chef::RunLock).to receive(:release)
-
- # Post conditions: check that node has been filled in correctly
- expect(client).to receive(:run_started)
- expect(client).to receive(:run_failed)
-
- expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
- expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
- end
- end
+ describe "when converge completes successfully" do
+ include_context "a client run"
+ include_context "converge completed"
- it "runs the audits and raises the error" do
- expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
- expect(error.wrapped_errors.size).to eq(1)
- expect(error.wrapped_errors[0]).to eq(e)
+ describe "when audit phase errors" do
+ include_context "audit phase failed with error"
+ include_examples "a completed run with audit failure" do
+ let(:run_errors) { [audit_error] }
end
end
- end
-
- describe "when the audit phase fails" do
- context "with an exception" do
- context "when audit mode is enabled" do
- include_context "a client run" do
- let(:e) { Exception.new }
- def stub_for_audit
- expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
- expect(audit_runner).to receive(:run).and_raise(e)
- expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
- end
-
- def stub_for_run
- expect_any_instance_of(Chef::RunLock).to receive(:acquire)
- expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
- expect_any_instance_of(Chef::RunLock).to receive(:release)
-
- # Post conditions: check that node has been filled in correctly
- expect(client).to receive(:run_started)
- expect(client).to receive(:run_failed)
-
- expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
- expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
- end
- end
-
- it "should save the node after converge and raise exception" do
- expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
- expect(error.wrapped_errors.size).to eq(1)
- expect(error.wrapped_errors[0]).to eq(e)
- end
- end
- end
-
- context "when audit mode is disabled" do
- include_context "a client run" do
- before do
- Chef::Config[:audit_mode] = :disabled
- end
-
- let(:e) { FooError.new }
-
- def stub_for_audit
- expect(Chef::Audit::Runner).to_not receive(:new)
- end
-
- def stub_for_converge
- expect(Chef::Runner).to receive(:new).and_return(runner)
- expect(runner).to receive(:converge).and_raise(e)
- expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(FooError)
- end
-
- def stub_for_node_save
- expect(client).to_not receive(:save_updated_node)
- end
-
- def stub_for_run
- expect_any_instance_of(Chef::RunLock).to receive(:acquire)
- expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
- expect_any_instance_of(Chef::RunLock).to receive(:release)
-
-
- # Post conditions: check that node has been filled in correctly
- expect(client).to receive(:run_started)
- expect(client).to receive(:run_failed)
-
- expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
-
- end
-
- it "re-raises an unwrapped exception" do
- expect { client.run }.to raise_error(FooError)
- end
- end
- end
-
+ describe "when audit phase completed" do
+ include_context "audit phase completed"
+ include_examples "a completed run"
end
- context "with failed audits" do
- include_context "a client run" do
- let(:audit_runner) do
- instance_double("Chef::Audit::Runner", :run => true, :failed? => true, :num_failed => 1, :num_total => 1)
- end
-
- def stub_for_audit
- expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
- expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
- end
-
- def stub_for_run
- expect_any_instance_of(Chef::RunLock).to receive(:acquire)
- expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
- expect_any_instance_of(Chef::RunLock).to receive(:release)
-
- # Post conditions: check that node has been filled in correctly
- expect(client).to receive(:run_started)
- expect(client).to receive(:run_failed)
-
- expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
- expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
- end
- end
-
- it "should save the node after converge and raise exception" do
- expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
- expect(error.wrapped_errors.size).to eq(1)
- expect(error.wrapped_errors[0]).to be_instance_of(Chef::Exceptions::AuditsFailed)
- end
+ describe "when audit phase completed with failed controls" do
+ include_context "audit phase completed with failed controls"
+ include_examples "a completed run with audit failure" do
+ let(:run_errors) { [audit_error] }
end
end
end
- describe "when why_run mode is enabled" do
- include_context "a client run" do
-
- before do
- Chef::Config[:why_run] = true
- end
-
- def stub_for_audit
- expect(Chef::Audit::Runner).to_not receive(:new)
- end
-
- def stub_for_node_save
- # This is how we should be mocking external calls - not letting it fall all the way through to the
- # REST call
- expect(node).to receive(:save)
- end
-
- it "runs successfully without enabling the audit runner" do
- client.run
+ describe "when converge errors" do
+ include_context "a client run"
+ include_context "converge failed"
- # fork is stubbed, so we can see the outcome of the run
- expect(node.automatic_attrs[:platform]).to eq("example-platform")
- expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0")
+ describe "when audit phase errors" do
+ include_context "audit phase failed with error"
+ include_examples "a failed run" do
+ let(:run_errors) { [converge_error, audit_error] }
end
end
- end
-
- describe "when audits are disabled" do
- include_context "a client run" do
-
- before do
- Chef::Config[:audit_mode] = :disabled
- end
- def stub_for_audit
- expect(Chef::Audit::Runner).to_not receive(:new)
+ describe "when audit phase completed" do
+ include_context "audit phase completed"
+ include_examples "a failed run" do
+ let(:run_errors) { [converge_error] }
end
+ end
- it "runs successfully without enabling the audit runner" do
- client.run
-
- # fork is stubbed, so we can see the outcome of the run
- expect(node.automatic_attrs[:platform]).to eq("example-platform")
- expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0")
+ describe "when audit phase completed with failed controls" do
+ include_context "audit phase completed with failed controls"
+ include_examples "a failed run" do
+ let(:run_errors) { [converge_error, audit_error] }
end
end
end
-
end
-
describe "when handling run failures" do
-
it "should remove the run_lock on failure of #load_node" do
@run_lock = double("Chef::RunLock", :acquire => true)
allow(Chef::RunLock).to receive(:new).and_return(@run_lock)
@@ -779,6 +459,7 @@ describe Chef::Client do
Chef::Config[:solo] = true
Chef::Config[:cookbook_path] = ["/path/to/invalid/cookbook_path"]
end
+
context "when any directory of cookbook_path contains no cookbook" do
it "raises CookbookNotFound error" do
expect do
@@ -833,7 +514,10 @@ describe Chef::Client do
it "should run exception handlers on early fail" do
expect(subject).to receive(:run_failed)
- expect { subject.run }.to raise_error(NoMethodError)
+ expect { subject.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
+ expect(error.wrapped_errors.size).to eq 1
+ expect(error.wrapped_errors).to include(NoMethodError)
+ end
end
end
end
diff --git a/spec/unit/cookbook_spec.rb b/spec/unit/cookbook_spec.rb
index 7b3cda2af1..f36b031309 100644
--- a/spec/unit/cookbook_spec.rb
+++ b/spec/unit/cookbook_spec.rb
@@ -59,15 +59,6 @@ describe Chef::CookbookVersion do
expect(@cookbook.fully_qualified_recipe_names.include?("openldap::three")).to eq(true)
end
- it "should find a preferred file" do
- skip
- end
-
- it "should not return an unchanged preferred file" do
- pending
- expect(@cookbook.preferred_filename(@node, :files, 'a-filename', 'the-checksum')).to be_nil
- end
-
it "should raise an ArgumentException if you try to load a bad recipe name" do
expect { @cookbook.load_recipe("doesnt_exist", @node) }.to raise_error(ArgumentError)
end
diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb
index 440dd9da6c..4990aef004 100644
--- a/spec/unit/cookbook_version_spec.rb
+++ b/spec/unit/cookbook_version_spec.rb
@@ -306,26 +306,6 @@ describe Chef::CookbookVersion do
subject(:cbv) { Chef::CookbookVersion.new("version validation", '/tmp/blah') }
- describe "HTTP Resource behaviors", pending: "will be deprected when CookbookManifest API is stablized" do
-
- it "errors on #save_url" do
- expect { cbv.save_url }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
- end
-
- it "errors on #force_save_url" do
- expect { cbv.force_save_url }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
- end
-
- it "errors on #to_hash" do
- expect { cbv.to_hash }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
- end
-
- it "errors on #to_json" do
- expect { cbv.to_json }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
- end
-
- end
-
it "errors on #status and #status=" do
expect { cbv.status = :wat }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
expect { cbv.status }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
diff --git a/spec/unit/deprecation_spec.rb b/spec/unit/deprecation_spec.rb
index f824cb7c76..2e1f3c39f3 100644
--- a/spec/unit/deprecation_spec.rb
+++ b/spec/unit/deprecation_spec.rb
@@ -95,4 +95,59 @@ describe Chef::Deprecation do
expect { test_instance.deprecated_method(10) }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
end
+ context "When a class has deprecated_attr, _reader and _writer" do
+ before(:context) do
+ class DeprecatedAttrTest
+ extend Chef::Mixin::Deprecation
+ def initialize
+ @a = @r = @w = 1
+ end
+ deprecated_attr :a, "a"
+ deprecated_attr_reader :r, "r"
+ deprecated_attr_writer :w, "w"
+ end
+ end
+
+ it "The deprecated_attr emits warnings" do
+ test = DeprecatedAttrTest.new
+ expect { test.a = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+ expect { test.a }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+ end
+
+ it "The deprecated_attr_writer emits warnings, and does not create a reader" do
+ test = DeprecatedAttrTest.new
+ expect { test.w = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+ expect { test.w }.to raise_error(NoMethodError)
+ end
+
+ it "The deprecated_attr_reader emits warnings, and does not create a writer" do
+ test = DeprecatedAttrTest.new
+ expect { test.r = 10 }.to raise_error(NoMethodError)
+ expect { test.r }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+ end
+
+ context "With deprecation warnings not throwing exceptions" do
+ before do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ end
+
+ it "The deprecated_attr can be written to and read from" do
+ test = DeprecatedAttrTest.new
+ test.a = 10
+ expect(test.a).to eq 10
+ end
+
+ it "The deprecated_attr_reader can be read from" do
+ test = DeprecatedAttrTest.new
+ expect(test.r).to eq 1
+ end
+
+ it "The deprecated_attr_writer can be written to" do
+ test = DeprecatedAttrTest.new
+ test.w = 10
+ expect(test.instance_eval { @w }).to eq 10
+ end
+ end
+ end
+
end
diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb
index d35ecc8ec8..fd90aeab71 100644
--- a/spec/unit/exceptions_spec.rb
+++ b/spec/unit/exceptions_spec.rb
@@ -113,7 +113,7 @@ describe Chef::Exceptions do
context "initialized with 1 error and nil" do
let(:e) { Chef::Exceptions::RunFailedWrappingError.new(RuntimeError.new("foo"), nil) }
let(:num_errors) { 1 }
- let(:backtrace) { ["1) RuntimeError - foo", ""] }
+ let(:backtrace) { ["1) RuntimeError - foo"] }
include_examples "RunFailedWrappingError expectations"
end
@@ -121,7 +121,7 @@ describe Chef::Exceptions do
context "initialized with 2 errors" do
let(:e) { Chef::Exceptions::RunFailedWrappingError.new(RuntimeError.new("foo"), RuntimeError.new("bar")) }
let(:num_errors) { 2 }
- let(:backtrace) { ["1) RuntimeError - foo", "", "2) RuntimeError - bar", ""] }
+ let(:backtrace) { ["1) RuntimeError - foo", "", "2) RuntimeError - bar"] }
include_examples "RunFailedWrappingError expectations"
end
diff --git a/spec/unit/formatters/doc_spec.rb b/spec/unit/formatters/doc_spec.rb
new file mode 100644
index 0000000000..d018207f49
--- /dev/null
+++ b/spec/unit/formatters/doc_spec.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Formatters::Base do
+
+ let(:out) { StringIO.new }
+ let(:err) { StringIO.new }
+
+ subject(:formatter) { Chef::Formatters::Doc.new(out, err) }
+
+ it "prints a policyfile's name and revision ID" do
+ minimal_policyfile = {
+ "revision_id"=> "613f803bdd035d574df7fa6da525b38df45a74ca82b38b79655efed8a189e073",
+ "name"=> "jenkins",
+ "run_list"=> [
+ "recipe[apt::default]",
+ "recipe[java::default]",
+ "recipe[jenkins::master]",
+ "recipe[policyfile_demo::default]"
+ ],
+ "cookbook_locks"=> { }
+ }
+
+ formatter.policyfile_loaded(minimal_policyfile)
+ expect(out.string).to include("Using policy 'jenkins' at revision '613f803bdd035d574df7fa6da525b38df45a74ca82b38b79655efed8a189e073'")
+ end
+
+end
diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb
index 76ebf154db..219a1f2906 100644
--- a/spec/unit/knife/core/subcommand_loader_spec.rb
+++ b/spec/unit/knife/core/subcommand_loader_spec.rb
@@ -22,14 +22,14 @@ describe Chef::Knife::SubcommandLoader do
let(:loader) { Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands')) }
let(:home) { File.join(CHEF_SPEC_DATA, 'knife-home') }
let(:plugin_dir) { File.join(home, '.chef', 'plugins', 'knife') }
-
+
before do
allow(ChefConfig).to receive(:windows?) { false }
- Chef::Util::PathHelper.class_variable_set(:@@home_dir, home)
+ Chef::Util::PathHelper.class_variable_set(:@@home_dir, home)
end
after do
- Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil)
+ Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil)
end
it "builds a list of the core subcommand file require paths" do
@@ -106,6 +106,18 @@ describe Chef::Knife::SubcommandLoader do
# Chef 12.0.0.rc.0 gem also:
"/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0/lib/chef/knife/thing.rb",
+ # Test that we ignore the platform suffix when checking for different
+ # gem versions.
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
+ # ...but don't ignore the .rc / .dev parts in the case when we have
+ # platform suffixes
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0-x86-mingw32/lib/chef/knife/invalid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev-mswin32/lib/chef/knife/invalid-too.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev.0-x86-mingw64/lib/chef/knife/still-invalid.rb",
+
# This command is "extra" compared to what's in the embedded/apps/chef install:
"/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/data_bag_secret_options.rb",
"/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
@@ -133,6 +145,10 @@ describe Chef::Knife::SubcommandLoader do
"/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
"/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
"/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
"/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
"/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
"/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb
index 4f48d4ff0d..ab420518a3 100644
--- a/spec/unit/knife/core/ui_spec.rb
+++ b/spec/unit/knife/core/ui_spec.rb
@@ -368,6 +368,20 @@ EOM
@ui.config[:attribute] = "keys.keys"
expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys.keys" => "values" } })
end
+
+ it "should return the name attribute" do
+ allow_any_instance_of(Chef::Node).to receive(:name).and_return("chef.localdomain")
+ input = Chef::Node.new
+ @ui.config[:attribute] = "name"
+ expect(@ui.format_for_display(input)).to eq( {"chef.localdomain"=>{"name"=>"chef.localdomain"} })
+ end
+
+ it "returns nil when given an attribute path that isn't a name or attribute" do
+ input = { "keys" => {"keys" => "values"}, "hi" => "ho", "id" => "sample-data-bag-item" }
+ non_existing_path = "nope.nada.nothingtoseehere"
+ @ui.config[:attribute] = non_existing_path
+ expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { non_existing_path => nil } })
+ end
end
describe "with --run-list passed" do
diff --git a/spec/unit/mixin/command_spec.rb b/spec/unit/mixin/command_spec.rb
index e198e3addd..050b261256 100644
--- a/spec/unit/mixin/command_spec.rb
+++ b/spec/unit/mixin/command_spec.rb
@@ -22,7 +22,7 @@ describe Chef::Mixin::Command, :volatile do
if windows?
- pending("TODO MOVE: this is a platform specific integration test.")
+ skip("TODO MOVE: this is a platform specific integration test.")
else
@@ -61,7 +61,6 @@ describe Chef::Mixin::Command, :volatile do
it "returns immediately after the first child process exits" do
expect {Timeout.timeout(10) do
- pid, stdin,stdout,stderr = nil,nil,nil,nil
evil_forker="exit if fork; 10.times { sleep 1}"
popen4("ruby -e '#{evil_forker}'") do |pid,stdin,stdout,stderr|
end
diff --git a/spec/unit/mixin/powershell_out_spec.rb b/spec/unit/mixin/powershell_out_spec.rb
new file mode 100644
index 0000000000..0fede582fa
--- /dev/null
+++ b/spec/unit/mixin/powershell_out_spec.rb
@@ -0,0 +1,70 @@
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/powershell_out'
+
+describe Chef::Mixin::PowershellOut do
+ let(:shell_out_class) { Class.new { include Chef::Mixin::PowershellOut } }
+ subject(:object) { shell_out_class.new }
+ let(:architecture) { "something" }
+ let(:flags) {
+ "-NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -InputFormat None"
+ }
+
+ describe "#powershell_out" do
+ it "runs a command and returns the shell_out object" do
+ ret = double("Mixlib::ShellOut")
+ expect(object).to receive(:shell_out).with(
+ "powershell.exe #{flags} -Command \"Get-Process\"",
+ {}
+ ).and_return(ret)
+ expect(object.powershell_out("Get-Process")).to eql(ret)
+ end
+
+ it "passes options" do
+ ret = double("Mixlib::ShellOut")
+ expect(object).to receive(:shell_out).with(
+ "powershell.exe #{flags} -Command \"Get-Process\"",
+ timeout: 600
+ ).and_return(ret)
+ expect(object.powershell_out("Get-Process", timeout: 600)).to eql(ret)
+ end
+ end
+
+ describe "#powershell_out!" do
+ it "runs a command and returns the shell_out object" do
+ mixlib_shellout = double("Mixlib::ShellOut")
+ expect(object).to receive(:shell_out).with(
+ "powershell.exe #{flags} -Command \"Get-Process\"",
+ {}
+ ).and_return(mixlib_shellout)
+ expect(mixlib_shellout).to receive(:error!)
+ expect(object.powershell_out!("Get-Process")).to eql(mixlib_shellout)
+ end
+
+ it "passes options" do
+ mixlib_shellout = double("Mixlib::ShellOut")
+ expect(object).to receive(:shell_out).with(
+ "powershell.exe #{flags} -Command \"Get-Process\"",
+ timeout: 600
+ ).and_return(mixlib_shellout)
+ expect(mixlib_shellout).to receive(:error!)
+ expect(object.powershell_out!("Get-Process", timeout: 600)).to eql(mixlib_shellout)
+ end
+ end
+end
diff --git a/spec/unit/provider/directory_spec.rb b/spec/unit/provider/directory_spec.rb
index 6489f2ca50..38d6db8320 100644
--- a/spec/unit/provider/directory_spec.rb
+++ b/spec/unit/provider/directory_spec.rb
@@ -16,173 +16,237 @@
# limitations under the License.
#
-require 'ostruct'
-
require 'spec_helper'
require 'tmpdir'
describe Chef::Provider::Directory do
- before(:each) do
- @new_resource = Chef::Resource::Directory.new(Dir.tmpdir)
- if !windows?
- @new_resource.owner(500)
- @new_resource.group(500)
- @new_resource.mode(0644)
+ let(:tmp_dir) { Dir.mktmpdir }
+ let(:new_resource) { Chef::Resource::Directory.new(tmp_dir) }
+ let(:node) { Chef::Node.new }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, {}, events) }
+ let(:directory) { Chef::Provider::Directory.new(new_resource, run_context) }
+
+ describe "#load_current_resource" do
+ describe "scanning file security metadata"
+ describe "on unix", unix_only: true do
+ describe "when the directory exists" do
+ let(:dir_stat) { File::Stat.new(tmp_dir) }
+ let(:expected_uid) { dir_stat.uid }
+ let(:expected_gid) { dir_stat.gid }
+ let(:expected_mode) { "0%o" % ( dir_stat.mode & 007777 ) }
+ let(:expected_pwnam) { Etc.getpwuid(expected_uid).name }
+ let(:expected_grnam) { Etc.getgrgid(expected_gid).name }
+
+ it "describes the access mode as a String of octal integers" do
+ directory.load_current_resource
+ expect(directory.current_resource.mode).to eq(expected_mode)
+ end
+
+ it "when the new_resource.owner is numeric, describes the owner as a numeric uid" do
+ new_resource.owner(500)
+ directory.load_current_resource
+ expect(directory.current_resource.owner).to eql(expected_uid)
+ end
+
+ it "when the new_resource.group is numeric, describes the group as a numeric gid" do
+ new_resource.group(500)
+ directory.load_current_resource
+ expect(directory.current_resource.group).to eql(expected_gid)
+ end
+
+ it "when the new_resource.owner is a string, describes the owner as a string" do
+ new_resource.owner("foo")
+ directory.load_current_resource
+ expect(directory.current_resource.owner).to eql(expected_pwnam)
+ end
+
+ it "when the new_resource.group is a string, describes the group as a string" do
+ new_resource.group("bar")
+ directory.load_current_resource
+ expect(directory.current_resource.group).to eql(expected_grnam)
+ end
+ end
end
- @node = Chef::Node.new
- @events = Chef::EventDispatch::Dispatcher.new
- @run_context = Chef::RunContext.new(@node, {}, @events)
- @directory = Chef::Provider::Directory.new(@new_resource, @run_context)
- end
+ describe "on windows", windows_only: true do
+ describe "when the directory exists" do
+ it "the mode is always nil" do
+ directory.load_current_resource
+ expect(directory.current_resource.mode).to be nil
+ end
+
+ it "the owner is always nil" do
+ directory.load_current_resource
+ expect(directory.current_resource.owner).to be nil
+ end
+
+ it "the group is always nil" do
+ directory.load_current_resource
+ expect(directory.current_resource.group).to be nil
+ end
+
+ it "rights are always nil (incorrectly)" do
+ directory.load_current_resource
+ expect(directory.current_resource.rights).to be nil
+ end
+
+ it "inherits is always nil (incorrectly)" do
+ directory.load_current_resource
+ expect(directory.current_resource.inherits).to be nil
+ end
+ end
+ end
+ describe "when the directory does not exist" do
+ before do
+ FileUtils.rmdir tmp_dir
+ end
- describe "scanning file security metadata on windows" do
- before do
+ it "sets the mode, group and owner to nil" do
+ directory.load_current_resource
+ expect(directory.current_resource.mode).to eq(nil)
+ expect(directory.current_resource.group).to eq(nil)
+ expect(directory.current_resource.owner).to eq(nil)
+ end
end
- it "describes the directory's access rights" do
- skip
- end
end
- describe "scanning file security metadata on unix" do
- before do
- allow(ChefConfig).to receive(:windows?).and_return(false)
- end
- let(:mock_stat) do
- cstats = double("stats")
- allow(cstats).to receive(:uid).and_return(500)
- allow(cstats).to receive(:gid).and_return(500)
- allow(cstats).to receive(:mode).and_return(0755)
- cstats
- end
+ describe "#define_resource_requirements" do
+ describe "on unix", unix_only: true do
+ it "raises an exception if the user does not exist" do
+ new_resource.owner("arglebargle_iv")
+ expect(Etc).to receive(:getpwnam).with("arglebargle_iv").and_raise(ArgumentError)
+ directory.action = :create
+ directory.load_current_resource
+ expect(directory.access_controls).to receive(:define_resource_requirements).and_call_original
+ directory.define_resource_requirements
+ expect { directory.process_resource_requirements }.to raise_error(ArgumentError)
+ end
- it "describes the access mode as a String of octal integers" do
- allow(File).to receive(:exists?).and_return(true)
- expect(File).to receive(:stat).and_return(mock_stat)
- @directory.load_current_resource
- expect(@directory.current_resource.mode).to eq("0755")
+ it "raises an exception if the group does not exist" do
+ new_resource.group("arglebargle_iv")
+ expect(Etc).to receive(:getgrnam).with("arglebargle_iv").and_raise(ArgumentError)
+ directory.action = :create
+ directory.load_current_resource
+ expect(directory.access_controls).to receive(:define_resource_requirements).and_call_original
+ directory.define_resource_requirements
+ expect { directory.process_resource_requirements }.to raise_error(ArgumentError)
+ end
end
+ end
- context "when user and group are specified with UID/GID" do
- it "describes the current owner and group as UID and GID" do
- allow(File).to receive(:exists?).and_return(true)
- expect(File).to receive(:stat).and_return(mock_stat)
- @directory.load_current_resource
- expect(@directory.current_resource.path).to eql(@new_resource.path)
- expect(@directory.current_resource.owner).to eql(500)
- expect(@directory.current_resource.group).to eql(500)
+ describe "#run_action(:create)" do
+ describe "when the directory exists" do
+ it "does not create the directory" do
+ expect(Dir).not_to receive(:mkdir).with(new_resource.path)
+ directory.run_action(:create)
+ end
+
+ it "should not set the resource as updated" do
+ directory.run_action(:create)
+ expect(new_resource).not_to be_updated
end
end
- context "when user/group are specified with user/group names" do
+ describe "when the directory does not exist" do
+ before do
+ FileUtils.rmdir tmp_dir
+ end
+
+ it "creates the directory" do
+ directory.run_action(:create)
+ expect(File.exist?(tmp_dir)).to be true
+ end
+
+ it "sets the new resource as updated" do
+ directory.run_action(:create)
+ expect(new_resource).to be_updated
+ end
end
- end
- # Unix only for now. While file security attribute reporting for windows is
- # disabled, unix and windows differ in the number of exists? calls that are
- # made by the provider.
- it "should create a new directory on create, setting updated to true", :unix_only do
- @new_resource.path "/tmp/foo"
+ describe "when the parent directory does not exist" do
+ before do
+ new_resource.path "#{tmp_dir}/foobar"
+ FileUtils.rmdir tmp_dir
+ end
- expect(File).to receive(:exists?).at_least(:once).and_return(false)
- expect(File).to receive(:directory?).with("/tmp").and_return(true)
- expect(Dir).to receive(:mkdir).with(@new_resource.path).once.and_return(true)
+ it "raises an exception when recursive is false" do
+ new_resource.recursive false
+ expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ end
- expect(@directory).to receive(:do_acl_changes)
- allow(@directory).to receive(:do_selinux)
- @directory.run_action(:create)
- expect(@directory.new_resource).to be_updated
- end
+ it "creates the directories when recursive is true" do
+ new_resource.recursive true
+ directory.run_action(:create)
+ expect(new_resource).to be_updated
+ expect(File.exist?("#{tmp_dir}/foobar")).to be true
+ end
- it "should raise an exception if the parent directory does not exist and recursive is false" do
- @new_resource.path "/tmp/some/dir"
- @new_resource.recursive false
- expect { @directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
- end
+ it "raises an exception when the parent directory is a file and recursive is true" do
+ FileUtils.touch tmp_dir
+ new_resource.recursive true
+ expect { directory.run_action(:create) }.to raise_error
+ end
- # Unix only for now. While file security attribute reporting for windows is
- # disabled, unix and windows differ in the number of exists? calls that are
- # made by the provider.
- it "should create a new directory when parent directory does not exist if recursive is true and permissions are correct", :unix_only do
- @new_resource.path "/path/to/dir"
- @new_resource.recursive true
- expect(File).to receive(:exists?).with(@new_resource.path).ordered.and_return(false)
-
- expect(File).to receive(:exists?).with('/path/to').ordered.and_return(false)
- expect(File).to receive(:exists?).with('/path').ordered.and_return(true)
- expect(Chef::FileAccessControl).to receive(:writable?).with('/path').ordered.and_return(true)
- expect(File).to receive(:exists?).with(@new_resource.path).ordered.and_return(false)
-
- expect(FileUtils).to receive(:mkdir_p).with(@new_resource.path).and_return(true)
- expect(@directory).to receive(:do_acl_changes)
- allow(@directory).to receive(:do_selinux)
- @directory.run_action(:create)
- expect(@new_resource).to be_updated
+ it "raises the right exception when the parent directory is a file and recursive is true" do
+ pending "this seems to return the wrong error" # FIXME
+ FileUtils.touch tmp_dir
+ new_resource.recursive true
+ expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ end
+ end
end
+ describe "#run_action(:create)" do
+ describe "when the directory exists" do
+ it "deletes the directory" do
+ directory.run_action(:delete)
+ expect(File.exist?(tmp_dir)).to be false
+ end
- it "should raise an error when creating a directory when parent directory is a file" do
- expect(File).to receive(:directory?).and_return(false)
- expect(Dir).not_to receive(:mkdir).with(@new_resource.path)
- expect { @directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
- expect(@directory.new_resource).not_to be_updated
- end
+ it "sets the new resource as updated" do
+ directory.run_action(:delete)
+ expect(new_resource).to be_updated
+ end
+ end
- # Unix only for now. While file security attribute reporting for windows is
- # disabled, unix and windows differ in the number of exists? calls that are
- # made by the provider.
- it "should not create the directory if it already exists", :unix_only do
- stub_file_cstats
- @new_resource.path "/tmp/foo"
- expect(File).to receive(:directory?).at_least(:once).and_return(true)
- expect(Chef::FileAccessControl).to receive(:writable?).with("/tmp").and_return(true)
- expect(File).to receive(:exists?).at_least(:once).and_return(true)
- expect(Dir).not_to receive(:mkdir).with(@new_resource.path)
- expect(@directory).to receive(:do_acl_changes)
- @directory.run_action(:create)
- end
+ describe "when the directory does not exist" do
+ before do
+ FileUtils.rmdir tmp_dir
+ end
- it "should delete the directory if it exists, and is writable with action_delete" do
- expect(File).to receive(:directory?).and_return(true)
- expect(Chef::FileAccessControl).to receive(:writable?).once.and_return(true)
- expect(Dir).to receive(:delete).with(@new_resource.path).once.and_return(true)
- @directory.run_action(:delete)
- end
+ it "does not delete the directory" do
+ expect(Dir).not_to receive(:delete).with(new_resource.path)
+ directory.run_action(:delete)
+ end
- it "should raise an exception if it cannot delete the directory due to bad permissions" do
- allow(File).to receive(:exists?).and_return(true)
- allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
- expect { @directory.run_action(:delete) }.to raise_error(RuntimeError)
- end
+ it "sets the new resource as updated" do
+ directory.run_action(:delete)
+ expect(new_resource).not_to be_updated
+ end
+ end
- it "should take no action when deleting a target directory that does not exist" do
- @new_resource.path "/an/invalid/path"
- allow(File).to receive(:exists?).and_return(false)
- expect(Dir).not_to receive(:delete).with(@new_resource.path)
- @directory.run_action(:delete)
- expect(@directory.new_resource).not_to be_updated
- end
+ describe "when the directory is not writable" do
+ before do
+ allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
+ end
- it "should raise an exception when deleting a directory when target directory is a file" do
- stub_file_cstats
- @new_resource.path "/an/invalid/path"
- allow(File).to receive(:exists?).and_return(true)
- expect(File).to receive(:directory?).and_return(false)
- expect(Dir).not_to receive(:delete).with(@new_resource.path)
- expect { @directory.run_action(:delete) }.to raise_error(RuntimeError)
- expect(@directory.new_resource).not_to be_updated
- end
+ it "cannot delete it and raises an exception" do
+ expect { directory.run_action(:delete) }.to raise_error(RuntimeError)
+ end
+ end
+
+ describe "when the target directory is a file" do
+ before do
+ FileUtils.rmdir tmp_dir
+ FileUtils.touch tmp_dir
+ end
- def stub_file_cstats
- cstats = double("stats")
- allow(cstats).to receive(:uid).and_return(500)
- allow(cstats).to receive(:gid).and_return(500)
- allow(cstats).to receive(:mode).and_return(0755)
- # File.stat is called in:
- # - Chef::Provider::File.load_current_resource_attrs
- # - Chef::ScanAccessControl via Chef::Provider::File.setup_acl
- allow(File).to receive(:stat).and_return(cstats)
+ it "cannot delete it and raises an exception" do
+ expect { directory.run_action(:delete) }.to raise_error(RuntimeError)
+ end
+ end
end
end
diff --git a/spec/unit/provider/ifconfig/debian_spec.rb b/spec/unit/provider/ifconfig/debian_spec.rb
index 351e734040..0c02ae9cd4 100644
--- a/spec/unit/provider/ifconfig/debian_spec.rb
+++ b/spec/unit/provider/ifconfig/debian_spec.rb
@@ -144,11 +144,6 @@ EOF
expect(IO.read(tempfile.path)).to eq(expected_string)
end
- it "should not mark the resource as updated" do
- provider.run_action(:add)
- pending "superclass ifconfig provider is not idempotent"
- expect(new_resource.updated_by_last_action?).to be_falsey
- end
end
context "when the /etc/network/interfaces file does not have the source line" do
@@ -280,11 +275,6 @@ another line
expect(IO.read(tempfile.path)).to eq(expected_string)
end
- it "should not mark the resource as updated" do
- provider.run_action(:add)
- pending "superclass ifconfig provider is not idempotent"
- expect(new_resource.updated_by_last_action?).to be_falsey
- end
end
context "when the /etc/network/interfaces file does not have the source line" do
diff --git a/spec/unit/provider/package/openbsd_spec.rb b/spec/unit/provider/package/openbsd_spec.rb
index b0cdb9969d..abe94474a4 100644
--- a/spec/unit/provider/package/openbsd_spec.rb
+++ b/spec/unit/provider/package/openbsd_spec.rb
@@ -50,18 +50,6 @@ describe Chef::Provider::Package::Openbsd do
context 'when there is a single candidate' do
- context 'when installing from source' do
- it 'should run the installation command' do
- pending('Installing from source is not supported yet')
- # This is a consequence of load_current_resource being called before define_resource_requirements
- # It can be deleted once an implementation is provided
- allow(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return(
- instance_double('shellout', :stdout => "#{name}-#{version}\n"))
- new_resource.source('/some/path/on/disk.tgz')
- provider.run_action(:install)
- end
- end
-
context 'when source is not provided' do
it 'should run the installation command' do
expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return(
@@ -106,15 +94,6 @@ describe Chef::Provider::Package::Openbsd do
end
end
- context 'if a version is specified' do
- it 'runs the installation command' do
- pending('Specifying both a version and flavor is not supported')
- new_resource.version(version)
- allow(provider).to receive(:shell_out!).with(/pkg_info -e/, anything()).and_return(instance_double('shellout', :stdout => ''))
- allow(provider).to receive(:candidate_version).and_return("#{package_name}-#{version}-#{flavor}")
- provider.run_action(:install)
- end
- end
end
context 'if a version is specified' do
diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb
index 380572499c..3ccc69652f 100644
--- a/spec/unit/provider/package/rubygems_spec.rb
+++ b/spec/unit/provider/package/rubygems_spec.rb
@@ -222,8 +222,6 @@ describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do
end
it "uses the cached result for gem paths when available" do
- gem_env_output = ['/path/to/gems', '/another/path/to/gems'].join(File::PATH_SEPARATOR)
- shell_out_result = OpenStruct.new(:stdout => gem_env_output)
expect(@gem_env).not_to receive(:shell_out!)
expected = ['/path/to/gems', '/another/path/to/gems']
Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache['/usr/weird/bin/gem']= expected
@@ -261,7 +259,7 @@ describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do
else
`which gem`.strip
end
- pending("cant find your gem executable") if path_to_gem.empty?
+ skip("cant find your gem executable") if path_to_gem.empty?
gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new(path_to_gem)
expected = ['rspec-core', Gem::Version.new(RSpec::Core::Version::STRING)]
actual = gem_env.installed_versions(Gem::Dependency.new('rspec-core', nil)).map { |s| [s.name, s.version] }
diff --git a/spec/unit/provider/powershell_spec.rb b/spec/unit/provider/powershell_spec.rb
index 60dbcf80b0..855c18af9b 100644
--- a/spec/unit/provider/powershell_spec.rb
+++ b/spec/unit/provider/powershell_spec.rb
@@ -19,20 +19,62 @@
require 'spec_helper'
describe Chef::Provider::PowershellScript, "action_run" do
- before(:each) do
- @node = Chef::Node.new
+ let(:powershell_version) { nil }
+ let(:node) {
+ node = Chef::Node.new
+ node.default["kernel"] = Hash.new
+ node.default["kernel"][:machine] = :x86_64.to_s
+ if ! powershell_version.nil?
+ node.default[:languages] = { :powershell => { :version => powershell_version } }
+ end
+ node
+ }
- @node.default["kernel"] = Hash.new
- @node.default["kernel"][:machine] = :x86_64.to_s
+ let(:provider) {
+ empty_events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, {}, empty_events)
+ new_resource = Chef::Resource::PowershellScript.new('run some powershell code', run_context)
+ Chef::Provider::PowershellScript.new(new_resource, run_context)
+ }
- @run_context = Chef::RunContext.new(@node, {}, @events)
- @new_resource = Chef::Resource::PowershellScript.new('run some powershell code', @run_context)
+ 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")
+ end
- @provider = Chef::Provider::PowershellScript.new(@new_resource, @run_context)
- end
+ let(:execution_policy_flag) do
+ execution_policy_index = 0
+ provider_flags = provider.flags.split(' ')
+ execution_policy_specified = false
- it "should set the -File flag as the last flag" do
- expect(@provider.flags.split(' ').pop).to eq("-File")
- end
+ 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 "should set the -ExecutionPolicy flag to 'Unrestricted' by default" do
+ expect(execution_policy_flag.downcase).to eq('unrestricted'.downcase)
+ 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
+ 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)
+ end
+ end
+ end
+ end
end
diff --git a/spec/unit/provider/remote_directory_spec.rb b/spec/unit/provider/remote_directory_spec.rb
index 4434714ebc..99e2fe285c 100644
--- a/spec/unit/provider/remote_directory_spec.rb
+++ b/spec/unit/provider/remote_directory_spec.rb
@@ -194,8 +194,8 @@ describe Chef::Provider::RemoteDirectory do
expect(::File.exist?(symlinked_dir_path)).to be_falsey
expect(::File.exist?(tmp_dir)).to be_truthy
- rescue Chef::Exceptions::Win32APIError => e
- pending "This must be run as an Administrator to create symlinks"
+ rescue Chef::Exceptions::Win32APIError
+ skip "This must be run as an Administrator to create symlinks"
end
end
end
diff --git a/spec/unit/provider/service/freebsd_service_spec.rb b/spec/unit/provider/service/freebsd_service_spec.rb
index 5a55425d87..cfc28c94d5 100644
--- a/spec/unit/provider/service/freebsd_service_spec.rb
+++ b/spec/unit/provider/service/freebsd_service_spec.rb
@@ -189,18 +189,6 @@ PS_SAMPLE
expect(provider.status_load_success).to be_nil
end
- context "when ps command is nil" do
- before do
- node.automatic_attrs[:command] = {:ps => nil}
- end
-
- it "should set running to nil" do
- pending "superclass raises no conversion of nil to string which seems broken"
- provider.determine_current_status!
- expect(current_resource.running).to be_nil
- end
- end
-
context "when ps is empty string" do
before do
node.automatic_attrs[:command] = {:ps => ""}
diff --git a/spec/unit/provider/user_spec.rb b/spec/unit/provider/user_spec.rb
index 381168647b..2345ce18fb 100644
--- a/spec/unit/provider/user_spec.rb
+++ b/spec/unit/provider/user_spec.rb
@@ -143,8 +143,8 @@ describe Chef::Provider::User do
begin
require 'rubygems'
require 'shadow'
- rescue LoadError => e
- pending "ruby-shadow gem not installed for dynamic load test"
+ rescue LoadError
+ skip "ruby-shadow gem not installed for dynamic load test"
true
else
false
@@ -161,7 +161,7 @@ describe Chef::Provider::User do
unless shadow_lib_unavail?
context "and we have the ruby-shadow gem" do
- pending "and we are not root (rerun this again as root)", :requires_unprivileged_user => true
+ skip "and we are not root (rerun this again as root)", :requires_unprivileged_user => true
context "and we are root", :requires_root => true do
it "should pass assertions when ruby-shadow can be loaded" do
diff --git a/spec/unit/resource/template_spec.rb b/spec/unit/resource/template_spec.rb
index df5ca94b8a..2fd951b72d 100644
--- a/spec/unit/resource/template_spec.rb
+++ b/spec/unit/resource/template_spec.rb
@@ -98,7 +98,7 @@ describe Chef::Resource::Template do
context "on windows", :windows_only do
# according to Chef::Resource::File, windows state attributes are rights + deny_rights
- pending "it describes its state"
+ skip "it describes its state"
end
it "returns the file path as its identity" do
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index 0479778f55..3bfd63f5ab 100644
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -35,6 +35,18 @@ describe Chef::Resource do
@resource = Chef::Resource.new("funk", @run_context)
end
+ it "should mixin shell_out" do
+ expect(@resource.respond_to?(:shell_out)).to be true
+ end
+
+ it "should mixin shell_out!" do
+ expect(@resource.respond_to?(:shell_out!)).to be true
+ end
+
+ it "should mixin shell_out_with_systems_locale" do
+ expect(@resource.respond_to?(:shell_out_with_systems_locale)).to be true
+ end
+
describe "when inherited" do
it "adds an entry to a list of subclasses" do