diff options
author | Claire McQuin <claire@getchef.com> | 2014-10-31 16:46:35 -0700 |
---|---|---|
committer | Claire McQuin <claire@getchef.com> | 2014-10-31 16:46:35 -0700 |
commit | 1aa6737e000e1261f49a8ff0d6960f9d482e2023 (patch) | |
tree | 41886fa682d9068e9ba723c3bc4aff5cdee12cfc | |
parent | d24a4a16a6e1d571c6f70e11340b0e1695ff92d9 (diff) | |
parent | e5c35f74b8451139605fc5c986eb51405d53d4c2 (diff) | |
download | chef-1aa6737e000e1261f49a8ff0d6960f9d482e2023.tar.gz |
Merge branch 'audit-mode' into runner
-rw-r--r-- | chef.gemspec | 4 | ||||
-rw-r--r-- | lib/chef/audit/chef_example_group.rb | 10 | ||||
-rw-r--r-- | lib/chef/client.rb | 71 | ||||
-rw-r--r-- | lib/chef/dsl/audit.rb | 38 | ||||
-rw-r--r-- | lib/chef/event_dispatch/base.rb | 16 | ||||
-rw-r--r-- | lib/chef/exceptions.rb | 29 | ||||
-rw-r--r-- | lib/chef/formatters/doc.rb | 18 | ||||
-rw-r--r-- | lib/chef/recipe.rb | 2 | ||||
-rw-r--r-- | spec/unit/exceptions_spec.rb | 46 |
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 |