From e53df64c453e255779a8f37e515e3fc17d74c0b6 Mon Sep 17 00:00:00 2001 From: Hunter Haugen Date: Fri, 30 Oct 2015 16:22:16 +0900 Subject: [PATCH] Add puppetdb_file report processor tl;dr This report processor consumes a puppet ruby report object and writes a json wire-format report to disk in the puppet reportdir. The report format produced by puppet does not match the wire format for reports that are accepted by puppetdb so this report processor turns the ruby report object produced by puppet into a json object in the format that puppetdb expects to receive. The code for doing this already exists in the puppetdb-terminus report processor, except that code then expects to directly communicate with the puppetdb service to submit the report and is not resuable. (I submitted https://tickets.puppetlabs.com/browse/PDB-2150 to document this feature request). The masterless nodes in openstack-infra are not allowed to directly communicate with the puppetdb service and so must have a custom report processor that writes the json report to disk. To enable this report processor, puppet must be configured with both `report = true` (the default) and `reports = puppetdb_file` Change-Id: I3b80a8012f2d6dc231c4d7701633d428a28b0006 --- .../lib/puppet/reports/puppetdb_file.rb | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 modules/openstack_project/lib/puppet/reports/puppetdb_file.rb diff --git a/modules/openstack_project/lib/puppet/reports/puppetdb_file.rb b/modules/openstack_project/lib/puppet/reports/puppetdb_file.rb new file mode 100644 index 0000000000..4c8b0bf847 --- /dev/null +++ b/modules/openstack_project/lib/puppet/reports/puppetdb_file.rb @@ -0,0 +1,130 @@ +# File-writing code is from the store report processor in puppet's master branch. +# The rest of the code is from the puppetdb report processor in puppetdb's 2.3.x branch. +require 'puppet' + +Puppet::Reports.register_report(:puppetdb_file) do + desc <<-DESC + Save report information to a file for sending to PuppetDB via the REST API +later. Reports are serialized to JSON format and may then submitted to puppetdb. + DESC + + # Process the report by formatting it into a PuppetDB 'store report' + # written to disk to be submitted to PuppetDB later. + # + # @return [void] + def process + dir = File.join(Puppet[:reportdir], host) + if ! Puppet::FileSystem.exist?(dir) + FileUtils.mkdir_p(dir) + FileUtils.chmod_R(0750, dir) + end + now = Time.now.gmtime + name = %w{year month day hour min}.collect do |method| + "%02d" % now.send(method).to_s + end.join("") + "_puppetdb.json" + file = File.join(dir, name) + begin + Puppet::Util.replace_file(file, 0640) do |fh| + fh.print({ "command" => "store report", "version" => 3, "payload" => report_to_hash }.to_json) + end + rescue => detail + Puppet.log_exception(detail, "Could not write report for #{host} at #{file}: #{detail}") + end + + nil + end + + # Convert `self` (an instance of `Puppet::Transaction::Report`) to a hash + # suitable for sending over the wire to PuppetDB + # + # @return Hash[] + # @api private + def report_to_hash + if environment.nil? + raise Puppet::Error, "Environment is nil, unable to submit report. This may be due a bug with Puppet. Ensure you are running the latest revision, see PUP-2508 for more details." + end + + { + "certname" => host, + "puppet-version" => puppet_version, + "report-format" => report_format, + "configuration-version" => configuration_version.to_s, + "start-time" => time.iso8601(9), + "end-time" => (time + run_duration).iso8601(9), + "resource-events" => build_events_list, + "environment" => environment, + "transaction-uuid" => transaction_uuid, + "status" => status, + } + end + + # Build a resource-events array from both evaluated and skipped resources. + # + # @return Array[Hash] + # @api private + def build_events_list + resource_statuses.inject([]) do |events, status_entry| + _, status = *status_entry + if ! (status.events.empty?) + events.concat(status.events.map { |event| event_to_hash(status, event) }) + elsif status.skipped + events.concat([fabricate_event(status, "skipped")]) + end + events + end + end + + # Calculate run duration. + # + # @return Number + # @api private + def run_duration + if metrics["time"] and metrics["time"]["total"] + metrics["time"]["total"] + else + 0 + end + end + + # Convert an instance of `Puppet::Transaction::Event` to a hash + # suitable for sending over the wire to PuppetDB + # + # @return Hash[] + # @api private + def event_to_hash(resource_status, event) + { + "status" => event.status, + "timestamp" => event.time.iso8601(9), + "resource-type" => resource_status.resource_type, + "resource-title" => resource_status.title.to_s, + "property" => event.property, + "new-value" => event.desired_value, + "old-value" => event.previous_value, + "message" => event.message, + "file" => resource_status.file, + "line" => resource_status.line, + "containment-path" => resource_status.containment_path, + } + end + + # Given an instance of `Puppet::Resource::Status` and a status + # string, this method fabricates a PuppetDB event object with the + # provided `"status"`. + # + # @api private + def fabricate_event(resource_status, event_status) + { + "status" => event_status, + "timestamp" => resource_status.time.iso8601(9), + "resource-type" => resource_status.resource_type, + "resource-title" => resource_status.title.to_s, + "property" => nil, + "new-value" => nil, + "old-value" => nil, + "message" => nil, + "file" => resource_status.file, + "line" => resource_status.line, + "containment-path" => resource_status.containment_path, + } + end +end