diff --git a/lib/puppet/reports/tagmail.rb b/lib/puppet/reports/tagmail.rb index 477246e0b..88ad89b5a 100644 --- a/lib/puppet/reports/tagmail.rb +++ b/lib/puppet/reports/tagmail.rb @@ -1,107 +1,131 @@ require 'puppet' +require 'pp' Puppet.config.setdefaults(:reporting, :tagmap => ["$confdir/tagmail.conf", "The mapping between reporting tags and email addresses."], :sendmail => [%x{which sendmail 2>/dev/null}.chomp, "Where to find the sendmail binary with which to send email."], :reportfrom => ["report@" + [Facter["hostname"].value, Facter["domain"].value].join("."), "The 'from' email address for the reports."], :smtpserver => ["none", "The server through which to send email reports."] ) require 'net/smtp' Puppet::Server::Report.newreport(:tagmail) do |report| unless FileTest.exists?(Puppet[:tagmap]) Puppet.notice "Cannot send tagmail report; no tagmap file %s" % Puppet[:tagmap] return end - p report - # Load the config file - tags = {} + taglists = {} File.readlines(Puppet[:tagmap]).each do |line| taglist = emails = nil case line.chomp when /^\s*#/: next when /^\s*$/: next when /^\s*(.+)\s*:\s*(.+)\s*$/: taglist = $1 emails = $2 else raise ArgumentError, "Invalid tagmail config file" end + pos = [] + neg = [] taglist.split(/\s*,\s*/).each do |tag| - tags[tag] = emails.split(/\s*,\s*/) + case tag + when /^\w+/: pos << tag + when /^!\w+/: neg << tag.sub("!", '') + else + raise Puppet::Error, "Invalid tag '%s'" % tag + end end + + # Now split the emails + emails = emails.split(/\s*,\s*/) + taglists[emails] = [pos, neg] end # Now find any appropriately tagged messages. reports = {} - tags.each do |tag, emails| + taglists.each do |emails, tags| + pos, neg = tags + + # First find all of the messages matched by our positive tags messages = nil - if tag == "all" + if pos.include?("all") messages = report.logs else + # Find all of the messages that are tagged with any of our + # tags. messages = report.logs.find_all do |log| - log.tagged?(tag) + pos.detect { |tag| log.tagged?(tag) } end end - if messages and ! messages.empty? - reports[emails] = messages.collect { |m| m.to_report }.join("\n") + # Now go through and remove any messages that match our negative tags + messages.reject! do |log| + if neg.detect do |tag| log.tagged?(tag) end + true + end + end + + if messages.empty? + Puppet.info "No messages to report for %s" % tag + next else - Puppet.info "No messages to report" + reports[emails] = messages.collect { |m| m.to_report }.join("\n") end end # Let's fork for the sending of the email, since you never know what might # happen. fork do if Puppet[:smtpserver] != "none" begin Net::SMTP.start(Puppet[:smtpserver]) do |smtp| reports.each do |emails, messages| Puppet.info "Sending report to %s" % emails.join(", ") smtp.send_message(messages, Puppet[:reportfrom], *emails) end end rescue => detail - if Puppet[:trace] + if Puppet[:debug] puts detail.backtrace end raise Puppet::Error, "Could not send report emails through smtp: %s" % detail end elsif Puppet[:sendmail] != "" begin reports.each do |emails, messages| Puppet.info "Sending report to %s" % emails.join(", ") # We need to open a separate process for every set of email addresses IO.popen(Puppet[:sendmail] + " " + emails.join(" "), "w") do |p| p.puts "From: #{Puppet[:reportfrom]}" p.puts "To: %s" % emails.join(', ') p.puts "Subject: Puppet Report for %s" % report.host + p.puts "To: " + emails.join(", ") p.puts messages end end rescue => detail if Puppet[:debug] puts detail.backtrace end raise Puppet::Error, "Could not send report emails via sendmail: %s" % detail end else raise Puppet::Error, "SMTP server is unset and could not find sendmail" end end end # $Id$ diff --git a/lib/puppet/server/report.rb b/lib/puppet/server/report.rb index 1c217561f..719ab3a16 100755 --- a/lib/puppet/server/report.rb +++ b/lib/puppet/server/report.rb @@ -1,178 +1,178 @@ module Puppet class Server # A simple server for triggering a new run on a Puppet client. class Report < Handler @interface = XMLRPC::Service::Interface.new("puppetreports") { |iface| iface.add_method("string report(array)") } Puppet.setdefaults(:reporting, :reportdirectory => {:default => "$vardir/reports", :mode => 0750, :owner => "$user", :group => "$group", :desc => "The directory in which to store reports received from the client. Each client gets a separate subdirectory."}, :reports => ["none", "The list of reports to generate. All reports are looked for in puppet/reports/.rb, and multiple report names should be comma-separated (whitespace is okay)." ] ) @reports = {} @reportloader = Puppet::Autoload.new(self, "puppet/reports") class << self attr_reader :hooks end def self.reportmethod(report) "report_" + report.to_s end # Add a hook for processing reports. def self.newreport(name, &block) name = name.intern if name.is_a? String method = reportmethod(name) # We want to define a method so that reports can use 'return'. define_method(method, &block) @reports[name] = method end # Load a report. def self.report(name) name = name.intern if name.is_a? String unless @reports.include? name @reportloader.load(name) unless @reports.include? name Puppet.warning( "Loaded report file for %s but report was not defined" % name ) return nil end end @reports[name] end def initialize(*args) super Puppet.config.use(:reporting) Puppet.config.use(:metrics) end # Dynamically create the report methods as necessary. def method_missing(name, *args) if name.to_s =~ /^report_(.+)$/ if self.class.report($1) send(name, *args) else super end else super end end # Accept a report from a client. def report(report, client = nil, clientip = nil) # We need the client name for storing files. client ||= Facter["hostname"].value # Unescape the report unless @local report = CGI.unescape(report) end process(report) # We don't want any tracking back in the fs. Unlikely, but there # you go. client.gsub("..",".") dir = File.join(Puppet[:reportdirectory], client) unless FileTest.exists?(dir) mkclientdir(client, dir) end # Now store the report. now = Time.now.gmtime name = %w{year month day hour min}.collect do |method| # Make sure we're at least two digits everywhere "%02d" % now.send(method).to_s end.join("") + ".yaml" file = File.join(dir, name) begin File.open(file, "w", 0640) do |f| f.puts report end rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.warning "Could not write report for %s at %s: %s" % [client, file, detail] end # Our report is in YAML return file end private def mkclientdir(client, dir) Puppet.config.setdefaults("reportclient-#{client}", "clientdir-#{client}" => { :default => dir, :mode => 0750, :owner => "$user", :group => "$group" } ) Puppet.config.use("reportclient-#{client}") end # Process the report using all of the existing hooks. def process(report) return if Puppet[:reports] == "none" # First convert the report to real objects begin report = YAML.load(report) rescue => detail Puppet.warning "Could not load report: %s" % detail return end Puppet[:reports].split(/\s*,\s*/).each do |name| method = self.class.report(name) if respond_to? method Puppet.info "Processing report %s" % name begin send(method, report) rescue => detail - if Puppet[:debug] + if Puppet[:trace] puts detail.backtrace end Puppet.err "Report %s failed: %s" % [name, detail] end else Puppet.warning "No report named '%s'" % name end end end end end end # $Id$