summaryrefslogtreecommitdiffstats
path: root/lib/puppet/reports/tagmail.rb
blob: 6521040963e5c19b04bdbcfbf11530f15cbbc363 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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`:trac: 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]+$/
          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