diff --git a/lib/puppet/reports/tagmail.rb b/lib/puppet/reports/tagmail.rb index 71bafb2da..e17143e2f 100644 --- a/lib/puppet/reports/tagmail.rb +++ b/lib/puppet/reports/tagmail.rb @@ -1,168 +1,168 @@ require 'puppet' require 'pp' require 'net/smtp' require 'time' Puppet::Reports.register_report(:tagmail) do desc "This report sends specific log messages to specific email addresses based on the tags in the log messages. See the [UsingTags tag documentation](http://projects.puppetlabs.com/projects/puppet/wiki/Using_Tags) for more information on tags. To use this report, you must create a `tagmail.conf` (in the location specified by `tagmap`). This is a simple file that maps tags to email addresses: Any log messages in the report that match the specified tags will be sent to the specified email addresses. Tags must be comma-separated, and they can be negated so that messages only match when they do not have that tag. The tags are separated from the email addresses by a colon, and the email addresses should also be comma-separated. Lastly, there is an `all` tag that will always match all log messages. Here is an example `tagmail.conf`: all: me@domain.com webserver, !mailserver: httpadmins@domain.com This will send all messages to `me@domain.com`, and all messages from webservers that are not also from mailservers to `httpadmins@domain.com`. If you are using anti-spam controls, such as grey-listing, on your mail server you should whitelist the sending email (controlled by `reportform` configuration option) to ensure your email is not discarded as spam. " # Find all matching messages. def match(taglists) matching_logs = [] taglists.each do |emails, pos, neg| # First find all of the messages matched by our positive tags messages = nil if pos.include?("all") messages = self.logs else # Find all of the messages that are tagged with any of our # tags. messages = self.logs.find_all do |log| pos.detect { |tag| log.tagged?(tag) } end end # Now go through and remove any messages that match our negative tags messages = messages.reject do |log| true if neg.detect do |tag| log.tagged?(tag) end end if messages.empty? Puppet.info "No messages to report to #{emails.join(",")}" next else matching_logs << [emails, messages.collect { |m| m.to_report }.join("\n")] end end matching_logs end # Load the config file def parse(text) taglists = [] text.split("\n").each do |line| taglist = emails = nil case line.chomp when /^\s*#/; next when /^\s*$/; next when /^\s*(.+)\s*:\s*(.+)\s*$/ taglist = $1 emails = $2.sub(/#.*$/,'') else raise ArgumentError, "Invalid tagmail config file" end pos = [] neg = [] taglist.sub(/\s+$/,'').split(/\s*,\s*/).each do |tag| - unless tag =~ /^!?[-\w]+$/ + unless tag =~ /^!?[-\w\.]+$/ raise ArgumentError, "Invalid tag #{tag.inspect}" end case tag when /^\w+/; pos << tag when /^!\w+/; neg << tag.sub("!", '') else raise Puppet::Error, "Invalid tag '#{tag}'" end end # Now split the emails emails = emails.sub(/\s+$/,'').split(/\s*,\s*/) taglists << [emails, pos, neg] end taglists end # Process the report. This just calls the other associated messages. def process unless FileTest.exists?(Puppet[:tagmap]) Puppet.notice "Cannot send tagmail report; no tagmap file #{Puppet[:tagmap]}" return end taglists = parse(File.read(Puppet[:tagmap])) # Now find any appropriately tagged messages. reports = match(taglists) send(reports) end # Send the email reports. def send(reports) pid = fork do if Puppet[:smtpserver] != "none" begin Net::SMTP.start(Puppet[:smtpserver]) do |smtp| reports.each do |emails, messages| smtp.open_message_stream(Puppet[:reportfrom], *emails) do |p| p.puts "From: #{Puppet[:reportfrom]}" p.puts "Subject: Puppet Report for #{self.host}" p.puts "To: " + emails.join(", ") p.puts "Date: #{Time.now.rfc2822}" p.puts p.puts messages end end end rescue => detail puts detail.backtrace if Puppet[:debug] raise Puppet::Error, "Could not send report emails through smtp: #{detail}" end elsif Puppet[:sendmail] != "" begin reports.each do |emails, messages| # 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 "Subject: Puppet Report for #{self.host}" p.puts "To: " + emails.join(", ") p.puts messages end end rescue => detail puts detail.backtrace if Puppet[:debug] raise Puppet::Error, "Could not send report emails via sendmail: #{detail}" end else raise Puppet::Error, "SMTP server is unset and could not find sendmail" end end # Don't bother waiting for the pid to return. Process.detach(pid) end end diff --git a/spec/unit/reports/tagmail_spec.rb b/spec/unit/reports/tagmail_spec.rb index 59cb5b2b8..bdb16600e 100755 --- a/spec/unit/reports/tagmail_spec.rb +++ b/spec/unit/reports/tagmail_spec.rb @@ -1,94 +1,95 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/reports' require 'puppettest/support/utils' tagmail = Puppet::Reports.report(:tagmail) describe tagmail do extend PuppetTest::Support::Utils before do @processor = Puppet::Transaction::Report.new @processor.extend(Puppet::Reports.report(:tagmail)) end passers = File.join(datadir, "reports", "tagmail_passers.conf") File.readlines(passers).each do |line| it "should be able to parse '#{line.inspect}'" do @processor.parse(line) end end failers = File.join(datadir, "reports", "tagmail_failers.conf") File.readlines(failers).each do |line| it "should not be able to parse '#{line.inspect}'" do lambda { @processor.parse(line) }.should raise_error(ArgumentError) end end { "tag: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag}, []], + "tag.localhost: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag.localhost}, []], "tag, other: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag other}, []], "tag-other: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag-other}, []], "tag, !other: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag}, %w{other}], "tag, !other, one, !two: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag one}, %w{other two}], "tag: abuse@domain.com, other@domain.com" => [%w{abuse@domain.com other@domain.com}, %w{tag}, []] }.each do |line, results| it "should parse '#{line}' as #{results.inspect}" do @processor.parse(line).shift.should == results end end describe "when matching logs" do before do @processor << Puppet::Util::Log.new(:level => :notice, :message => "first", :tags => %w{one}) @processor << Puppet::Util::Log.new(:level => :notice, :message => "second", :tags => %w{one two}) @processor << Puppet::Util::Log.new(:level => :notice, :message => "third", :tags => %w{one two three}) end def match(pos = [], neg = []) pos = Array(pos) neg = Array(neg) result = @processor.match([[%w{abuse@domain.com}, pos, neg]]) actual_result = result.shift if actual_result actual_result[1] else nil end end it "should match all messages when provided the 'all' tag as a positive matcher" do results = match("all") %w{first second third}.each do |str| results.should be_include(str) end end it "should remove messages that match a negated tag" do match("all", "three").should_not be_include("third") end it "should find any messages tagged with a provided tag" do results = match("two") results.should be_include("second") results.should be_include("third") results.should_not be_include("first") end it "should allow negation of specific tags from a specific tag list" do results = match("two", "three") results.should be_include("second") results.should_not be_include("third") end it "should allow a tag to negate all matches" do results = match([], "one") results.should be_nil end end end