summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJosh Cooper <josh@puppetlabs.com>2011-06-10 14:21:56 -0700
committerJosh Cooper <josh@puppetlabs.com>2011-06-10 14:21:56 -0700
commitf8c11329f1e0e0fb5648e26c7a5c58fc2341319e (patch)
tree2fad134e944ba296304912caf6c38a64792fb3bd
parent01c11424b61163fae71de3611a5166c894601937 (diff)
parent98ba4071f424932173b450d1a94a9ae39f33a6c7 (diff)
downloadpuppet-f8c11329f1e0e0fb5648e26c7a5c58fc2341319e.tar.gz
puppet-f8c11329f1e0e0fb5648e26c7a5c58fc2341319e.tar.xz
puppet-f8c11329f1e0e0fb5648e26c7a5c58fc2341319e.zip
Merge branch 'ticket/2.6.x/7127-prerun-command-failures-dont-stop-puppet' into 2.6.x
* ticket/2.6.x/7127-prerun-command-failures-dont-stop-puppet: (#7127) Stop puppet if a prerun command fails Do not needlessly create multiple reports when creating a transaction
-rw-r--r--lib/puppet/application/apply.rb8
-rw-r--r--lib/puppet/configurer.rb107
-rw-r--r--lib/puppet/resource/catalog.rb12
-rw-r--r--lib/puppet/transaction.rb41
-rw-r--r--lib/puppet/transaction/report.rb4
-rwxr-xr-xspec/unit/configurer_spec.rb157
-rwxr-xr-xspec/unit/resource/catalog_spec.rb2
-rwxr-xr-xspec/unit/transaction_spec.rb10
8 files changed, 210 insertions, 131 deletions
diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb
index 717935640..f2bbcb99b 100644
--- a/lib/puppet/application/apply.rb
+++ b/lib/puppet/application/apply.rb
@@ -130,7 +130,13 @@ class Puppet::Application::Apply < Puppet::Application
configurer = Puppet::Configurer.new
report = configurer.run(:skip_plugin_download => true, :catalog => catalog)
- exit( options[:detailed_exitcodes] ? report.exit_status : 0 )
+ if not report
+ exit(1)
+ elsif options[:detailed_exitcodes] then
+ exit(report.exit_status)
+ else
+ exit(0)
+ end
rescue => detail
puts detail.backtrace if Puppet[:trace]
$stderr.puts detail.message
diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb
index 596d2dc53..7f39a3853 100644
--- a/lib/puppet/configurer.rb
+++ b/lib/puppet/configurer.rb
@@ -5,8 +5,6 @@ require 'puppet/network/http_pool'
require 'puppet/util'
class Puppet::Configurer
- class CommandHookError < RuntimeError; end
-
require 'puppet/configurer/fact_handler'
require 'puppet/configurer/plugin_handler'
@@ -79,8 +77,6 @@ class Puppet::Configurer
download_plugins unless options[:skip_plugin_download]
download_fact_plugins unless options[:skip_plugin_download]
-
- execute_prerun_command
end
# Get the remote catalog, yo. Returns nil if no catalog can be found.
@@ -109,67 +105,73 @@ class Puppet::Configurer
catalog
end
- # The code that actually runs the catalog.
- # This just passes any options on to the catalog,
- # which accepts :tags and :ignoreschedules.
- def run(options = {})
- begin
- prepare(options)
- rescue SystemExit,NoMemoryError
- raise
- rescue Exception => detail
- puts detail.backtrace if Puppet[:trace]
- Puppet.err "Failed to prepare catalog: #{detail}"
+ # Retrieve (optionally) and apply a catalog. If a catalog is passed in
+ # the options, then apply that one, otherwise retrieve it.
+ def retrieve_and_apply_catalog(options, fact_options)
+ unless catalog = (options.delete(:catalog) || retrieve_catalog(fact_options))
+ Puppet.err "Could not retrieve catalog; skipping run"
+ return
end
- if Puppet::Resource::Catalog.indirection.terminus_class == :rest
- # This is a bit complicated. We need the serialized and escaped facts,
- # and we need to know which format they're encoded in. Thus, we
- # get a hash with both of these pieces of information.
- fact_options = facts_for_uploading
+ report = options[:report]
+ report.configuration_version = catalog.version
+
+ benchmark(:notice, "Finished catalog run") do
+ catalog.apply(options)
end
+ report.finalize_report
+ report
+ end
+
+ # The code that actually runs the catalog.
+ # This just passes any options on to the catalog,
+ # which accepts :tags and :ignoreschedules.
+ def run(options = {})
options[:report] ||= Puppet::Transaction::Report.new("apply")
report = options[:report]
- Puppet::Util::Log.newdestination(report)
- if catalog = options[:catalog]
- options.delete(:catalog)
- elsif ! catalog = retrieve_catalog(fact_options)
- Puppet.err "Could not retrieve catalog; skipping run"
- return
- end
+ Puppet::Util::Log.newdestination(report)
+ begin
+ prepare(options)
- report.configuration_version = catalog.version
+ if Puppet::Resource::Catalog.indirection.terminus_class == :rest
+ # This is a bit complicated. We need the serialized and escaped facts,
+ # and we need to know which format they're encoded in. Thus, we
+ # get a hash with both of these pieces of information.
+ fact_options = facts_for_uploading
+ end
- transaction = nil
+ # set report host name now that we have the fact
+ report.host = Puppet[:node_name_value]
- begin
- benchmark(:notice, "Finished catalog run") do
- transaction = catalog.apply(options)
+ begin
+ execute_prerun_command or return nil
+ retrieve_and_apply_catalog(options, fact_options)
+ rescue SystemExit,NoMemoryError
+ raise
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Failed to apply catalog: #{detail}"
+ return nil
+ ensure
+ execute_postrun_command or return nil
end
- report
- rescue => detail
- puts detail.backtrace if Puppet[:trace]
- Puppet.err "Failed to apply catalog: #{detail}"
- return
+ ensure
+ # Make sure we forget the retained module_directories of any autoload
+ # we might have used.
+ Thread.current[:env_module_directories] = nil
+
+ # Now close all of our existing http connections, since there's no
+ # reason to leave them lying open.
+ Puppet::Network::HttpPool.clear_http_instances
end
ensure
- # Make sure we forget the retained module_directories of any autoload
- # we might have used.
- Thread.current[:env_module_directories] = nil
-
- # Now close all of our existing http connections, since there's no
- # reason to leave them lying open.
- Puppet::Network::HttpPool.clear_http_instances
- execute_postrun_command
-
Puppet::Util::Log.close(report)
- send_report(report, transaction)
+ send_report(report)
end
- def send_report(report, trans)
- report.finalize_report if trans
+ def send_report(report)
puts report.summary if Puppet[:summarize]
save_last_run_summary(report)
report.save if Puppet[:report]
@@ -207,12 +209,15 @@ class Puppet::Configurer
end
def execute_from_setting(setting)
- return if (command = Puppet[setting]) == ""
+ return true if (command = Puppet[setting]) == ""
begin
Puppet::Util.execute([command])
+ true
rescue => detail
- raise CommandHookError, "Could not run command from #{setting}: #{detail}"
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Could not run command from #{setting}: #{detail}"
+ false
end
end
diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb
index a8668d844..0a63ff172 100644
--- a/lib/puppet/resource/catalog.rb
+++ b/lib/puppet/resource/catalog.rb
@@ -132,16 +132,22 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
expire
Puppet::Util::Storage.load if host_config?
- transaction = Puppet::Transaction.new(self)
- transaction.report = options[:report] if options[:report]
+ transaction = Puppet::Transaction.new(self, options[:report])
+ register_report = options[:report].nil?
+
transaction.tags = options[:tags] if options[:tags]
transaction.ignoreschedules = true if options[:ignoreschedules]
transaction.add_times :config_retrieval => self.retrieval_duration || 0
begin
- transaction.evaluate
+ Puppet::Util::Log.newdestination(transaction.report) if register_report
+ begin
+ transaction.evaluate
+ ensure
+ Puppet::Util::Log.close(transaction.report) if register_report
+ end
rescue Puppet::Error => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not apply complete catalog: #{detail}"
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index 48154ad6f..d6d50d410 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -15,7 +15,7 @@ class Puppet::Transaction
attr_accessor :sorted_resources, :configurator
# The report, once generated.
- attr_accessor :report
+ attr_reader :report
# Routes and stores any events and subscriptions.
attr_reader :event_manager
@@ -123,31 +123,23 @@ class Puppet::Transaction
# collects all of the changes, executes them, and responds to any
# necessary events.
def evaluate
- # Start logging.
- Puppet::Util::Log.newdestination(@report)
-
prepare
Puppet.info "Applying configuration version '#{catalog.version}'" if catalog.version
- begin
- @sorted_resources.each do |resource|
- next if stop_processing?
- if resource.is_a?(Puppet::Type::Component)
- Puppet.warning "Somehow left a component in the relationship graph"
- next
- end
- ret = nil
- seconds = thinmark do
- ret = eval_resource(resource)
- end
-
- resource.info "Evaluated in %0.2f seconds" % seconds if Puppet[:evaltrace] and @catalog.host_config?
- ret
+ @sorted_resources.each do |resource|
+ next if stop_processing?
+ if resource.is_a?(Puppet::Type::Component)
+ Puppet.warning "Somehow left a component in the relationship graph"
+ next
end
- ensure
- # And then close the transaction log.
- Puppet::Util::Log.close(@report)
+ ret = nil
+ seconds = thinmark do
+ ret = eval_resource(resource)
+ end
+
+ resource.info "valuated in %0.2f seconds" % seconds if Puppet[:evaltrace] and @catalog.host_config?
+ ret
end
Puppet.debug "Finishing transaction #{object_id}"
@@ -228,13 +220,10 @@ class Puppet::Transaction
# this should only be called by a Puppet::Type::Component resource now
# and it should only receive an array
- def initialize(catalog)
+ def initialize(catalog, report = nil)
@catalog = catalog
-
- @report = Report.new("apply", catalog.version)
-
+ @report = report || Report.new("apply", catalog.version)
@event_manager = Puppet::Transaction::EventManager.new(self)
-
@resource_harness = Puppet::Transaction::ResourceHarness.new(self)
end
diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb
index 841c56968..77b9da833 100644
--- a/lib/puppet/transaction/report.rb
+++ b/lib/puppet/transaction/report.rb
@@ -10,8 +10,8 @@ class Puppet::Transaction::Report
indirects :report, :terminus_class => :processor
- attr_accessor :configuration_version
- attr_reader :resource_statuses, :logs, :metrics, :host, :time, :kind, :status
+ attr_accessor :configuration_version, :host
+ attr_reader :resource_statuses, :logs, :metrics, :time, :kind, :status
# This is necessary since Marshall doesn't know how to
# dump hash with default proc (see below @records)
diff --git a/spec/unit/configurer_spec.rb b/spec/unit/configurer_spec.rb
index b825e3404..f96876b58 100755
--- a/spec/unit/configurer_spec.rb
+++ b/spec/unit/configurer_spec.rb
@@ -36,16 +36,16 @@ describe Puppet::Configurer do
it "should execute any pre-run command provided via the 'prerun_command' setting" do
Puppet.settings[:prerun_command] = "/my/command"
- Puppet::Util.expects(:execute).with { |args| args[0] == "/my/command" }
+ Puppet::Util.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed")
@agent.execute_prerun_command
end
it "should fail if the command fails" do
Puppet.settings[:prerun_command] = "/my/command"
- Puppet::Util.expects(:execute).raises Puppet::ExecutionFailure
+ Puppet::Util.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed")
- lambda { @agent.execute_prerun_command }.should raise_error(Puppet::Configurer::CommandHookError)
+ @agent.execute_prerun_command.should be_false
end
end
@@ -59,16 +59,16 @@ describe Puppet::Configurer do
it "should execute any post-run command provided via the 'postrun_command' setting" do
Puppet.settings[:postrun_command] = "/my/command"
- Puppet::Util.expects(:execute).with { |args| args[0] == "/my/command" }
+ Puppet::Util.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed")
@agent.execute_postrun_command
end
it "should fail if the command fails" do
Puppet.settings[:postrun_command] = "/my/command"
- Puppet::Util.expects(:execute).raises Puppet::ExecutionFailure
+ Puppet::Util.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed")
- lambda { @agent.execute_postrun_command }.should raise_error(Puppet::Configurer::CommandHookError)
+ @agent.execute_postrun_command.should be_false
end
end
@@ -85,8 +85,8 @@ describe Puppet::Configurer do
Puppet::Resource::Catalog.terminus_class = :rest
Puppet::Resource::Catalog.stubs(:find).returns(@catalog)
@agent.stubs(:send_report)
+ @agent.stubs(:save_last_run_summary)
- Puppet::Util::Log.stubs(:newdestination)
Puppet::Util::Log.stubs(:close)
end
@@ -98,7 +98,7 @@ describe Puppet::Configurer do
it "should initialize a transaction report if one is not provided" do
report = Puppet::Transaction::Report.new("apply")
- Puppet::Transaction::Report.expects(:new).at_least_once.returns report
+ Puppet::Transaction::Report.expects(:new).returns report
@agent.run
end
@@ -128,9 +128,11 @@ describe Puppet::Configurer do
it "should set the report as a log destination" do
report = Puppet::Transaction::Report.new("apply")
- Puppet::Transaction::Report.expects(:new).at_least_once.returns report
+ Puppet::Transaction::Report.expects(:new).returns report
Puppet::Util::Log.expects(:newdestination).with(report)
+ Puppet::Util::Log.expects(:close).with(report)
+ Puppet::Util::Log.expects(:close).with([])
@agent.run
end
@@ -180,22 +182,10 @@ describe Puppet::Configurer do
it "should send the report" do
report = Puppet::Transaction::Report.new("apply")
- Puppet::Transaction::Report.expects(:new).at_least_once.returns(report)
- @agent.expects(:send_report).with { |r, trans| r == report }
-
- @agent.run
- end
-
- it "should send the transaction report with a reference to the transaction if a run was actually made" do
- report = Puppet::Transaction::Report.new("apply")
Puppet::Transaction::Report.expects(:new).returns(report)
+ @agent.expects(:send_report).with(report)
- trans = stub 'transaction'
- @catalog.expects(:apply).returns trans
-
- @agent.expects(:send_report).with { |r, t| t == trans }
-
- @agent.run :catalog => @catalog
+ @agent.run
end
it "should send the transaction report even if the catalog could not be retrieved" do
@@ -215,12 +205,12 @@ describe Puppet::Configurer do
Puppet::Transaction::Report.expects(:new).returns(report)
@agent.expects(:send_report)
- lambda { @agent.run }.should raise_error
+ @agent.run.should be_nil
end
it "should remove the report as a log destination when the run is finished" do
report = Puppet::Transaction::Report.new("apply")
- Puppet::Transaction::Report.expects(:new).at_least_once.returns(report)
+ Puppet::Transaction::Report.expects(:new).returns(report)
Puppet::Util::Log.expects(:close).with(report)
@@ -229,11 +219,100 @@ describe Puppet::Configurer do
it "should return the report as the result of the run" do
report = Puppet::Transaction::Report.new("apply")
- Puppet::Transaction::Report.expects(:new).at_least_once.returns(report)
+ Puppet::Transaction::Report.expects(:new).returns(report)
@agent.run.should equal(report)
end
+ it "should send the transaction report even if the pre-run command fails" do
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
+
+ Puppet.settings[:prerun_command] = "/my/command"
+ Puppet::Util.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed")
+ @agent.expects(:send_report)
+
+ @agent.run.should be_nil
+ end
+
+ it "should include the pre-run command failure in the report" do
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
+
+ Puppet.settings[:prerun_command] = "/my/command"
+ Puppet::Util.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed")
+
+ report.expects(:<<).with { |log| log.message.start_with?("Could not run command from prerun_command") }
+
+ @agent.run.should be_nil
+ end
+
+ it "should send the transaction report even if the post-run command fails" do
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
+
+ Puppet.settings[:postrun_command] = "/my/command"
+ Puppet::Util.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed")
+ @agent.expects(:send_report)
+
+ @agent.run.should be_nil
+ end
+
+ it "should include the post-run command failure in the report" do
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
+
+ Puppet.settings[:postrun_command] = "/my/command"
+ Puppet::Util.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed")
+
+ report.expects(:<<).with { |log| log.message.start_with?("Could not run command from postrun_command") }
+
+ @agent.run.should be_nil
+ end
+
+ it "should execute post-run command even if the pre-run command fails" do
+ Puppet.settings[:prerun_command] = "/my/precommand"
+ Puppet.settings[:postrun_command] = "/my/postcommand"
+ Puppet::Util.expects(:execute).with(["/my/precommand"]).raises(Puppet::ExecutionFailure, "Failed")
+ Puppet::Util.expects(:execute).with(["/my/postcommand"])
+
+ @agent.run.should be_nil
+ end
+
+ it "should finalize the report" do
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
+
+ report.expects(:finalize_report)
+ @agent.run
+ end
+
+ it "should not apply the catalog if the pre-run command fails" do
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
+
+ Puppet.settings[:prerun_command] = "/my/command"
+ Puppet::Util.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed")
+
+ @catalog.expects(:apply).never()
+ @agent.expects(:send_report)
+
+ @agent.run.should be_nil
+ end
+
+ it "should apply the catalog, send the report, and return nil if the post-run command fails" do
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
+
+ Puppet.settings[:postrun_command] = "/my/command"
+ Puppet::Util.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed")
+
+ @catalog.expects(:apply)
+ @agent.expects(:send_report)
+
+ @agent.run.should be_nil
+ end
+
describe "when not using a REST terminus for catalogs" do
it "should not pass any facts when retrieving the catalog" do
Puppet::Resource::Catalog.terminus_class = :compiler
@@ -268,12 +347,6 @@ describe Puppet::Configurer do
Puppet[:lastrunfile] = tmpfile('last_run_file')
@report = Puppet::Transaction::Report.new("apply")
- @trans = stub 'transaction'
- end
-
- it "should finalize the report" do
- @report.expects(:finalize_report)
- @configurer.send_report(@report, @trans)
end
it "should print a report summary if configured to do so" do
@@ -282,42 +355,42 @@ describe Puppet::Configurer do
@report.expects(:summary).returns "stuff"
@configurer.expects(:puts).with("stuff")
- @configurer.send_report(@report, nil)
+ @configurer.send_report(@report)
end
it "should not print a report summary if not configured to do so" do
Puppet.settings[:summarize] = false
@configurer.expects(:puts).never
- @configurer.send_report(@report, nil)
+ @configurer.send_report(@report)
end
it "should save the report if reporting is enabled" do
Puppet.settings[:report] = true
@report.expects(:save)
- @configurer.send_report(@report, nil)
+ @configurer.send_report(@report)
end
it "should not save the report if reporting is disabled" do
Puppet.settings[:report] = false
@report.expects(:save).never
- @configurer.send_report(@report, nil)
+ @configurer.send_report(@report)
end
it "should save the last run summary if reporting is enabled" do
Puppet.settings[:report] = true
@configurer.expects(:save_last_run_summary).with(@report)
- @configurer.send_report(@report, nil)
+ @configurer.send_report(@report)
end
it "should save the last run summary if reporting is disabled" do
Puppet.settings[:report] = false
@configurer.expects(:save_last_run_summary).with(@report)
- @configurer.send_report(@report, nil)
+ @configurer.send_report(@report)
end
it "should log but not fail if saving the report fails" do
@@ -326,7 +399,7 @@ describe Puppet::Configurer do
@report.expects(:save).raises "whatever"
Puppet.expects(:err)
- lambda { @configurer.send_report(@report, nil) }.should_not raise_error
+ lambda { @configurer.send_report(@report) }.should_not raise_error
end
end
@@ -506,7 +579,6 @@ describe Puppet::Configurer do
@agent.stubs(:dostorage)
@agent.stubs(:download_fact_plugins)
@agent.stubs(:download_plugins)
- @agent.stubs(:execute_prerun_command)
@facts = {"one" => "two", "three" => "four"}
end
@@ -527,10 +599,5 @@ describe Puppet::Configurer do
@agent.prepare({})
end
-
- it "should perform the pre-run commands" do
- @agent.expects(:execute_prerun_command)
- @agent.prepare({})
- end
end
end
diff --git a/spec/unit/resource/catalog_spec.rb b/spec/unit/resource/catalog_spec.rb
index 942721464..a15a912ef 100755
--- a/spec/unit/resource/catalog_spec.rb
+++ b/spec/unit/resource/catalog_spec.rb
@@ -595,7 +595,7 @@ describe Puppet::Resource::Catalog, "when compiling" do
before :each do
@catalog = Puppet::Resource::Catalog.new("host")
- @transaction = mock 'transaction'
+ @transaction = Puppet::Transaction.new(@catalog)
Puppet::Transaction.stubs(:new).returns(@transaction)
@transaction.stubs(:evaluate)
@transaction.stubs(:add_times)
diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb
index 862413a31..b5703e752 100755
--- a/spec/unit/transaction_spec.rb
+++ b/spec/unit/transaction_spec.rb
@@ -88,13 +88,19 @@ describe Puppet::Transaction do
@transaction.should_not be_any_failed
end
- it "should be possible to replace the report object" do
+ it "should use the provided report object" do
report = Puppet::Transaction::Report.new("apply")
- @transaction.report = report
+ @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, report)
@transaction.report.should == report
end
+ it "should create a report if none is provided" do
+ @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
+
+ @transaction.report.should be_kind_of Puppet::Transaction::Report
+ end
+
it "should consider a resource to have failed dependencies if any of its dependencies are failed"
describe "when initializing" do