summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaire McQuin <claire@getchef.com>2014-10-31 16:46:35 -0700
committerClaire McQuin <claire@getchef.com>2014-10-31 16:46:35 -0700
commit1aa6737e000e1261f49a8ff0d6960f9d482e2023 (patch)
tree41886fa682d9068e9ba723c3bc4aff5cdee12cfc
parentd24a4a16a6e1d571c6f70e11340b0e1695ff92d9 (diff)
parente5c35f74b8451139605fc5c986eb51405d53d4c2 (diff)
downloadchef-1aa6737e000e1261f49a8ff0d6960f9d482e2023.tar.gz
Merge branch 'audit-mode' into runner
-rw-r--r--chef.gemspec4
-rw-r--r--lib/chef/audit/chef_example_group.rb10
-rw-r--r--lib/chef/client.rb71
-rw-r--r--lib/chef/dsl/audit.rb38
-rw-r--r--lib/chef/event_dispatch/base.rb16
-rw-r--r--lib/chef/exceptions.rb29
-rw-r--r--lib/chef/formatters/doc.rb18
-rw-r--r--lib/chef/recipe.rb2
-rw-r--r--spec/unit/exceptions_spec.rb46
9 files changed, 214 insertions, 20 deletions
diff --git a/chef.gemspec b/chef.gemspec
index 427657f678..16d11d39e5 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -41,9 +41,9 @@ Gem::Specification.new do |s|
s.add_development_dependency "rake", "~> 10.1.0"
# rspec_junit_formatter 0.2.0 drops ruby 1.8.7 support
- s.add_development_dependency "rspec_junit_formatter", "~> 0.1.0"
+ s.add_development_dependency "rspec_junit_formatter", "~> 0.2.0"
- %w(rspec-core rspec-expectations rspec-mocks).each { |gem| s.add_development_dependency gem, "~> 2.14.0" }
+ %w(rspec-core rspec-expectations rspec-mocks).each { |gem| s.add_development_dependency gem, "~> 3.1" }
s.bindir = "bin"
# chef-service-manager is a windows only executable.
diff --git a/lib/chef/audit/chef_example_group.rb b/lib/chef/audit/chef_example_group.rb
new file mode 100644
index 0000000000..cd874d57b7
--- /dev/null
+++ b/lib/chef/audit/chef_example_group.rb
@@ -0,0 +1,10 @@
+require 'rspec/core'
+
+class Chef
+ class Audit
+ class ChefExampleGroup < ::RSpec::Core::ExampleGroup
+ # Can encompass tests in a `control` block or `describe` block
+ define_example_group_method :control
+ end
+ end
+end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 4f37bd0ee3..6c83639ec3 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -246,7 +246,6 @@ class Chef
@policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, @override_runlist, events)
end
-
def save_updated_node
if Chef::Config[:solo]
# nothing to do
@@ -260,6 +259,7 @@ class Chef
def run_ohai
ohai.all_plugins
+ @events.ohai_completed(node)
end
def node_name
@@ -295,6 +295,7 @@ class Chef
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])
+ # TODO register this where we register all other event listeners
@resource_reporter = Chef::ResourceReporter.new(@rest)
@events.register(@resource_reporter)
rescue Exception => e
@@ -307,18 +308,49 @@ class Chef
# Converges the node.
#
# === Returns
- # true:: Always returns true
+ # The thrown exception, if there was one. If this returns nil the converge was successful.
def converge(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
- true
- rescue Exception
- # TODO: should this be a separate #converge_failed(exception) method?
- @events.converge_complete
- raise
+ converge_exception = nil
+ catch(:end_client_run_early) do
+ begin
+ @events.converge_start(run_context)
+ Chef::Log.debug("Converging node #{node_name}")
+ @runner = Chef::Runner.new(run_context)
+ runner.converge
+ @events.converge_complete
+ rescue Exception => e
+ @events.converge_failed(e)
+ converge_exception = e
+ end
+ end
+ converge_exception
+ end
+
+ # TODO don't want to change old API
+ def converge_and_save(run_context)
+ converge_exception = converge(run_context)
+ unless converge_exception
+ begin
+ save_updated_node
+ rescue Exception => e
+ converge_exception = e
+ end
+ end
+ converge_exception
+ end
+
+ # TODO are failed audits going to raise exceptions, or only be handled by the reporters?
+ def run_audits(run_context)
+ audit_exception = nil
+ begin
+ @events.audit_start(run_context)
+ # TODO
+ @events.audit_complete
+ rescue Exception => e
+ @events.audit_failed(e)
+ audit_exception = e
+ end
+ audit_exception
end
# Expands the run list. Delegates to the policy_builder.
@@ -333,7 +365,6 @@ class Chef
policy_builder.expand_run_list
end
-
def do_windows_admin_check
if Chef::Platform.windows?
Chef::Log.debug("Checking for administrator privileges....")
@@ -380,7 +411,7 @@ class Chef
Chef::Log.debug("Chef-client request_id: #{request_id}")
enforce_path_sanity
run_ohai
- @events.ohai_completed(node)
+
register unless Chef::Config[:solo]
load_node
@@ -396,11 +427,14 @@ class Chef
run_context = setup_run_context
- catch(:end_client_run_early) do
- converge(run_context)
- end
+ converge_error = converge_and_save(run_context)
+ audit_error = run_audits(run_context)
- save_updated_node
+ if converge_error || audit_error
+ e = Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
+ e.fill_backtrace
+ raise e
+ end
run_status.stop_clock
Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
@@ -411,6 +445,7 @@ class Chef
Chef::Platform::Rebooter.reboot_if_needed!(node)
true
+
rescue Exception => e
# CHEF-3336: Send the error first in case something goes wrong below and we don't know why
Chef::Log.debug("Re-raising exception: #{e.class} - #{e.message}\n#{e.backtrace.join("\n ")}")
diff --git a/lib/chef/dsl/audit.rb b/lib/chef/dsl/audit.rb
new file mode 100644
index 0000000000..7adcecbf14
--- /dev/null
+++ b/lib/chef/dsl/audit.rb
@@ -0,0 +1,38 @@
+#
+# Author:: Tyler Ball (<tball@getchef.com>)
+# Copyright:: Copyright (c) 2014 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#require 'chef/audit'
+require 'chef/audit/chef_example_group'
+
+class Chef
+ module DSL
+ module Audit
+
+ # Adds the controls group and block (containing controls to execute) to the runner's list of pending examples
+ def controls(group_name, &group_block)
+ raise ::Chef::Exceptions::NoAuditsProvided unless group_block
+
+ # TODO add the @example_groups list to the runner for later execution
+ # run_context.audit_runner.register
+ ::Chef::Audit::ChefExampleGroup.describe(group_name, &group_block)
+ end
+
+ end
+ end
+end
+
+
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index bfd4503097..57152e9b8c 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -225,6 +225,22 @@ class Chef
def converge_complete
end
+ # Called if the converge phase fails
+ def converge_failed(exception)
+ end
+
+ # Called before audit phase starts
+ def audit_start(run_context)
+ end
+
+ # Called when the audit phase is finished.
+ def audit_complete
+ end
+
+ # Called if the audit phase fails
+ def audit_failed(exception)
+ end
+
# TODO: need events for notification resolve?
# def notifications_resolved
# end
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index 25f08455fc..ddfa574973 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -366,5 +366,34 @@ class Chef
super "Found more than one provider for #{resource.resource_name} resource: #{classes}"
end
end
+
+ class NoAuditsProvided < RuntimeError
+ def initialize
+ super "You must provide a block with audits"
+ end
+ end
+
+ # If a converge or audit fails, we want to wrap the output from those errors into 1 error so we can
+ # see both issues in the output. It is possible that nil will be provided. You must call `fill_backtrace`
+ # to correctly populate the backtrace with the wrapped backtraces.
+ class RunFailedWrappingError < RuntimeError
+ attr_reader :wrapped_errors
+ def initialize(*errors)
+ errors = errors.select {|e| !e.nil?}
+ output = "Found #{errors.size} errors, they are stored in the backtrace\n"
+ @wrapped_errors = errors
+ super output
+ end
+
+ def fill_backtrace
+ backtrace = []
+ wrapped_errors.each_with_index do |e,i|
+ backtrace << "#{i+1}) #{e.class} - #{e.message}"
+ backtrace += e.backtrace if e.backtrace
+ backtrace << ""
+ end
+ set_backtrace(backtrace)
+ end
+ end
end
end
diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb
index 4a08b9d095..66ee6ccabe 100644
--- a/lib/chef/formatters/doc.rb
+++ b/lib/chef/formatters/doc.rb
@@ -151,6 +151,24 @@ class Chef
unindent if @current_recipe
end
+ def converge_failed(e)
+ # TODO do we want to do anything else in here?
+ converge_complete
+ end
+
+ def audit_start(run_context)
+ # TODO read the number of `controls` blocks to run from the run_context.audit_runner
+ puts_line "Running collected audits"
+ end
+
+ def audit_complete
+ # TODO
+ end
+
+ def audit_failed(exception)
+ # TODO
+ end
+
# Called before action is executed on a resource.
def resource_action_start(resource, action, notification_type=nil, notifier=nil)
if resource.cookbook_name && resource.recipe_name
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index de72a8d0c4..b4046e4f16 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -24,6 +24,7 @@ require 'chef/dsl/platform_introspection'
require 'chef/dsl/include_recipe'
require 'chef/dsl/registry_helper'
require 'chef/dsl/reboot_pending'
+require 'chef/dsl/audit'
require 'chef/mixin/from_file'
@@ -40,6 +41,7 @@ class Chef
include Chef::DSL::Recipe
include Chef::DSL::RegistryHelper
include Chef::DSL::RebootPending
+ include Chef::DSL::Audit
include Chef::Mixin::FromFile
include Chef::Mixin::Deprecation
diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb
index 21b0abb9bf..59e507f7a8 100644
--- a/spec/unit/exceptions_spec.rb
+++ b/spec/unit/exceptions_spec.rb
@@ -81,4 +81,50 @@ describe Chef::Exceptions do
end
end
end
+
+ describe Chef::Exceptions::RunFailedWrappingError do
+ shared_examples "RunFailedWrappingError expectations" do
+ it "should initialize with a default message" do
+ expect(e.message).to eq("Found #{num_errors} errors, they are stored in the backtrace\n")
+ end
+
+ it "should provide a modified backtrace when requested" do
+ e.fill_backtrace
+ expect(e.backtrace).to eq(backtrace)
+ end
+ end
+
+ context "initialized with nothing" do
+ let(:e) { Chef::Exceptions::RunFailedWrappingError.new }
+ let(:num_errors) { 0 }
+ let(:backtrace) { [] }
+
+ include_examples "RunFailedWrappingError expectations"
+ end
+
+ context "initialized with nil" do
+ let(:e) { Chef::Exceptions::RunFailedWrappingError.new(nil, nil) }
+ let(:num_errors) { 0 }
+ let(:backtrace) { [] }
+
+ include_examples "RunFailedWrappingError expectations"
+ end
+
+ 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", ""] }
+
+ include_examples "RunFailedWrappingError expectations"
+ end
+
+ 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", ""] }
+
+ include_examples "RunFailedWrappingError expectations"
+ end
+
+ end
end