Project

General

Profile

Download (5.96 KB) Statistics
| Branch: | Tag: | Revision:
# copy this file to your report dir - e.g. /usr/lib/ruby/1.8/puppet/reports/
# add this report in your puppetmaster reports - e.g, in your puppet.conf add:
# reports=log, foreman # (or any other reports you want)

# URL of your Foreman installation
$foreman_url='<%= @foreman_url %>'
# if CA is specified, remote Foreman host will be verified
$foreman_ssl_ca = "<%= @ssl_ca -%>"
# ssl_cert and key are required if require_ssl_puppetmasters is enabled in Foreman
$foreman_ssl_cert = "<%= @ssl_cert -%>"
$foreman_ssl_key = "<%= @ssl_key -%>"

require 'puppet'
require 'net/http'
require 'net/https'
require 'uri'
begin
require 'json'
rescue LoadError
# Debian packaging guidelines state to avoid needing rubygems, so
# we only try to load it if the first require fails (for RPMs)
begin
require 'rubygems' rescue nil
require 'json'
rescue LoadError => e
puts "You need the `json` gem to use the Foreman ENC script"
# code 1 is already used below
exit 2
end
end

Puppet::Reports.register_report(:foreman) do
Puppet.settings.use(:reporting)
desc "Sends reports directly to Foreman"

def process
begin
# check for report metrics
raise(Puppet::ParseError, "Invalid report: can't find metrics information for #{self.host}") if self.metrics.nil?

uri = URI.parse($foreman_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
if http.use_ssl?
if $foreman_ssl_ca && !$foreman_ssl_ca.empty?
http.ca_file = $foreman_ssl_ca
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
else
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
if $foreman_ssl_cert && !$foreman_ssl_cert.empty? && $foreman_ssl_key && !$foreman_ssl_key.empty?
http.cert = OpenSSL::X509::Certificate.new(File.read($foreman_ssl_cert))
http.key = OpenSSL::PKey::RSA.new(File.read($foreman_ssl_key), nil)
end
end
req = Net::HTTP::Post.new("#{uri.path}/api/reports")
req.add_field('Accept', 'application/json,version=2' )
req.content_type = 'application/json'
req.body = {'report' => generate_report}.to_json
response = http.request(req)
rescue Exception => e
raise Puppet::Error, "Could not send report to Foreman at #{$foreman_url}/api/reports: #{e}\n#{e.backtrace}"
end
end

def generate_report
report = {}
set_report_format
report['host'] = self.host
# Time.to_s behaves differently in 1.8 / 1.9 so we explicity set the 1.9 format
report['reported_at'] = self.time.utc.strftime("%Y-%m-%d %H:%M:%S UTC")
report['status'] = metrics_to_hash(self)
report['metrics'] = m2h(self.metrics)
report['logs'] = logs_to_array(self.logs)

report
end

private

METRIC = %w[applied restarted failed failed_restarts skipped pending]

def metrics_to_hash(report)
report_status = {}
metrics = self.metrics

# find our metric values
METRIC.each do |m|
if @format == 0
report_status[m] = metrics["resources"][m.to_sym] unless metrics["resources"].nil?
else
h=translate_metrics_to26(m)
mv = metrics[h[:type]]
report_status[m] = mv[h[:name].to_sym] + mv[h[:name].to_s] rescue nil
end
report_status[m] ||= 0
end

# special fix for false warning about skips
# sometimes there are skip values, but there are no error messages, we ignore them.
if report_status["skipped"] > 0 and ((report_status.values.inject(:+)) - report_status["skipped"] == report.logs.size)
report_status["skipped"] = 0
end
# fix for reports that contain no metrics (i.e. failed catalog)
if @format > 1 and report.respond_to?(:status) and report.status == "failed"
report_status["failed"] += 1
end
# fix for Puppet non-resource errors (i.e. failed catalog fetches before falling back to cache)
report_status["failed"] += report.logs.find_all {|l| l.source == 'Puppet' && l.level.to_s == 'err' }.count

return report_status
end

def m2h metrics
h = {}
metrics.each do |title, mtype|
h[mtype.name] ||= {}
mtype.values.each{|m| h[mtype.name].merge!({m[0].to_s => m[2]})}
end
return h
end

def logs_to_array logs
h = []
logs.each do |log|
# skipping debug messages, we dont want them in Foreman's db
next if log.level == :debug

# skipping catalog summary run messages, we dont want them in Foreman's db
next if log.message =~ /^Finished catalog run in \d+.\d+ seconds$/

# Match Foreman's slightly odd API format...
l = { 'log' => { 'sources' => {}, 'messages' => {} } }
l['log']['level'] = log.level.to_s
l['log']['messages']['message'] = log.message
l['log']['sources']['source'] = log.source
h << l
end
return h
end

# The metrics layout has changed in Puppet 2.6.x release,
# this method attempts to align the bit value metrics and the new name scheme in 2.6.x
# returns a hash of { :type => "metric type", :name => "metric_name"}
def translate_metrics_to26 metric
case metric
when "applied"
case @format
when 0..1
{ :type => "total", :name => :changes}
else
{ :type => "changes", :name => "total"}
end
when "failed_restarts"
case @format
when 0..1
{ :type => "resources", :name => metric}
else
{ :type => "resources", :name => "failed_to_restart"}
end
when "pending"
{ :type => "events", :name => "noop" }
else
{ :type => "resources", :name => metric}
end
end

def set_report_format
@format ||= case
when self.instance_variables.detect {|v| v.to_s == "@environment"}
@format = 3
when self.instance_variables.detect {|v| v.to_s == "@report_format"}
@format = 2
when self.instance_variables.detect {|v| v.to_s == "@resource_statuses"}
@format = 1
else
@format = 0
end
end

end
(11-11/14)