summaryrefslogtreecommitdiffstats
path: root/lib/puppet/reports/tagmail.rb
blob: 4fdaa5137c06f876ac4e198df78f143d284ee3c9 (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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
require 'puppet'
require 'pp'

require 'net/smtp'

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)
        reports = []
        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|
                if neg.detect do |tag| log.tagged?(tag) end
                    true
                end
            end

            if messages.empty?
                Puppet.info "No messages to report to %s" % emails.join(",")
                next
            else
                reports << [emails, messages.collect { |m| m.to_report }.join("\n")]
            end
        end

        return reports
    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 %s" % tag.inspect
                end
                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.sub(/\s+$/,'').split(/\s*,\s*/)
            taglists << [emails, pos, neg]
        end
        return 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 %s" %
                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|
                            Puppet.info "Sending report to %s" % emails.join(", ")
                            smtp.open_message_stream(Puppet[:reportfrom], *emails) do |p|
                              p.puts "From: #{Puppet[:reportfrom]}"
                              p.puts "Subject: Puppet Report for %s" % self.host
                              p.puts "To: " + emails.join(", ")
                              p.puts "Date: " + Time.now.rfc2822
                              p.puts
                              p.puts messages
                            end
                        end
                    end
                rescue => detail
                    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
                        sync.synchronize do
                            IO.popen(Puppet[:sendmail] + " " + emails.join(" "), "w") do |p|
                                p.puts "From: #{Puppet[:reportfrom]}"
                                p.puts "Subject: Puppet Report for %s" % self.host
                                p.puts "To: " + emails.join(", ")

                                p.puts messages
                            end
                        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

        # Don't bother waiting for the pid to return.
        Process.detach(pid)
    end

    def sync
        unless defined?(@sync)
            @sync = Sync.new
        end
        @sync
    end
end