+ add additional support for RT tickets, closing, changing Subject, and CCs.
[monitor.git] / mailer.py
1 #!/usr/bin/python2
2 #
3 # Copyright (c) 2004  The Trustees of Princeton University (Trustees).
4 #
5 # Faiyaz Ahmed <faiyaza@cs.princeton.edu>
6 #
7 # $Id: mailer.py,v 1.9 2007/07/03 19:57:16 soltesz Exp $
8 from emailTxt import *
9 import smtplib
10 from config import config
11 import logging
12 import os
13 import time
14
15 config = config()
16 logger = logging.getLogger("monitor")
17
18 MTA="localhost"
19 FROM="monitor@planet-lab.org"
20
21 def reformat_for_rt(text):
22         lines = text.split("\n")
23         spaced_text = ""
24         for line in lines:
25                 spaced_text += " %s\n" %line
26         return spaced_text
27                 
28
29 def _setupRTenvironment():
30         os.environ['PATH'] = os.environ['PATH'] + ":/home/soltesz/rpm/opt/rt3/bin/"
31         os.environ['RTSERVER'] = "https://rt.planet-lab.org/"
32         os.environ['RTUSER']   = "monitor"
33         os.environ['RTPASSWD'] = "ssorcmor"
34         os.environ['RTDEBUG'] = "0"
35         return
36
37 def setAdminCCViaRT(ticket_id, to):
38         # Set ENV Variables/PATH
39         _setupRTenvironment()
40         if ticket_id == None or ticket_id == "":
41                 raise Exception("ERROR: ticket_id must be set to some integer value")
42
43         # This will raise an exception if it is not a valid id.
44         i_ticket_id = int(ticket_id)
45
46         # create a comma-separated list
47         s_to = ",".join(to)
48         cmd = "rt edit ticket/%s set admincc='%s'" % (ticket_id, s_to)
49         (f_in, f_out, f_err) = os.popen3(cmd)
50         value = f_out.read()
51         l_values = value.split()
52         f_in.close() ; f_out.close() ; f_err.close()
53         if len(l_values) > 3 and "updated" in l_values[3]:
54                 # Success
55                 pass
56         else:
57                 print "ERROR: RT failed to update AdminCC for ticket %s" % ticket_id
58
59         return
60
61 def setSubjectViaRT(ticket_id, subject):
62         # Set ENV Variables/PATH
63         _setupRTenvironment()
64         if ticket_id == None or ticket_id == "":
65                 raise Exception("ERROR: ticket_id must be set to some integer value")
66
67         # This will raise an exception if it is not a valid id.
68         i_ticket_id = int(ticket_id)
69
70         cmd = "rt edit ticket/%s set subject='%s'" % (ticket_id, subject)
71         (f_in, f_out, f_err) = os.popen3(cmd)
72         value = f_out.read()
73         l_values = value.split()
74         f_in.close() ; f_out.close() ; f_err.close()
75         if len(l_values) > 3 and "updated" in l_values[3]:
76                 # Success
77                 pass
78         else:
79                 print "ERROR: RT failed to update subject for ticket %s" % ticket_id
80
81         return
82                 
83
84 def addCommentViaRT(ticket_id, comment):
85         # Set ENV Variables/PATH
86         _setupRTenvironment()
87         if ticket_id == None or ticket_id == "":
88                 raise Exception("ERROR: ticket_id must be set to some integer value")
89
90         # This will raise an exception if it is not a valid id.
91         i_ticket_id = int(ticket_id)
92
93         cmd = "rt comment -m '%s' ticket/%s" % (comment, i_ticket_id)
94         (f_in, f_out, f_err) = os.popen3(cmd)
95         value = f_out.read()
96         l_values = value.split()
97         f_in.close() ; f_out.close() ; f_err.close()
98         if len(l_values) > 1 and "recorded" in l_values[1]:
99                 # Success
100                 pass
101         else:
102                 # Error
103                 f_in.close() ; f_out.close() ; f_err.close()
104                 print "ERROR: RT failed to add comment to id %s" % ticket_id
105
106         return
107
108 def closeTicketViaRT(ticket_id, comment):
109         # Set ENV Variables/PATH
110         _setupRTenvironment()
111         if ticket_id == None or ticket_id == "":
112                 raise Exception("ERROR: ticket_id must be set to some integer value")
113
114         # This will raise an exception if it is not a valid id.
115         i_ticket_id = int(ticket_id)
116
117         # Append comment to RT ticket
118         addCommentViaRT(ticket_id, comment)
119
120         if not config.debug:
121                 cmd = "rt edit ticket/%s set status=resolved" % i_ticket_id
122                 (f_in, f_out, f_err) = os.popen3(cmd)
123                 f_in.close()
124                 value = f_out.read()
125                 f_out.close()
126                 f_err.close()
127                 l_values = value.split()
128                 if len(l_values) >= 4 and "updated" in l_values[3]:
129                         # Success!!
130                         pass
131                 else:
132                         # Failed!!
133                         print "FAILED to resolve Ticket %d" % ticket_id
134                         print "FAILED to resolve Ticket %d" % i_ticket_id
135
136         return
137
138 def emailViaRT(subject, text, to, ticket_id=None):
139         if ticket_id == None or ticket_id == "":
140                 return emailViaRT_NoTicket(subject, text, to)
141                 
142
143         # Set ENV Variables/PATH
144         _setupRTenvironment()
145
146         if config.mail and not config.debug:
147                 setSubjectViaRT(ticket_id, subject)
148                 setAdminCCViaRT(ticket_id, to)
149
150                 cmd = "rt correspond -m - %s" % ticket_id
151                 (f_in, f_out, f_err) = os.popen3(cmd)
152                 f_in.write(text)
153                 f_in.flush()
154                 f_in.close()
155                 value = f_out.read()
156
157                 # TODO: rt doesn't write to stderr on error!!!
158                 if value == "":
159                         raise Exception, f_err.read()
160
161                 del f_in
162                 f_out.close(); del f_out
163                 f_err.close(); del f_err
164                 os.wait()
165
166         return ticket_id
167         
168
169 def emailViaRT_NoTicket(subject, text, to):
170         """Use RT command line tools to send email.
171                 return the generated RT ticket ID number.
172         """
173         i_ticket = 0
174
175         if config.mail and config.debug:
176                 to = [config.email]
177
178         # Set ENV Variables/PATH
179         _setupRTenvironment()
180
181         # NOTE: AdminCc: (in PLC's RT configuration) gets an email sent.
182         # This is not the case (surprisingly) for Cc:
183         input_text  = "Subject: %s\n"
184         input_text += "Requestor: monitor@planet-lab.org\n"
185         input_text += "id: ticket/new\n"
186         input_text += "Queue: Monitor\n"
187         for recipient in to:
188                 input_text += "AdminCc: %s\n" % recipient
189         input_text += "Text: %s"
190
191         # Add a space for each new line to get RT to accept the file.
192         spaced_text = reformat_for_rt(text)
193
194         if config.mail and not config.debug:
195                 cmd = "rt create -i -t ticket"
196                 (f_in, f_out, f_err) = os.popen3(cmd)
197                 f_in.write(input_text % (subject, spaced_text))
198                 f_in.flush()
199                 f_in.close()
200                 value = f_out.read()
201
202                 # TODO: rt doesn't write to stderr on error!!!
203                 if value == "":
204                         raise Exception, f_err.read()
205
206                 print "MAILER: ticket value == %s" % value.split()[2]
207                 i_ticket = int(value.split()[2])
208                 # clean up the child process.
209                 f_in.close();  del f_in
210                 f_out.close(); del f_out
211                 f_err.close(); del f_err
212                 os.wait()
213         elif config.mail and config.debug:
214                 email(subject, spaced_text, to)
215                 i_ticket = 0
216         else:
217                 i_ticket = 0
218
219         return i_ticket
220
221 def email(subject, text, to):
222         """Create a mime-message that will render HTML in popular
223         MUAs, text in better ones"""
224         import MimeWriter
225         import mimetools
226         import cStringIO
227
228         if (config.mail and config.debug) or (not config.mail and not config.debug and config.bcc):
229                 to = [config.email]
230
231         out = cStringIO.StringIO() # output buffer for our message 
232         txtin = cStringIO.StringIO(text)
233
234         writer = MimeWriter.MimeWriter(out)
235         #
236         # set up some basic headers... we put subject here
237         # because smtplib.sendmail expects it to be in the
238         # message body
239         #
240         writer.addheader("Subject", subject)
241         if to.__class__ == [].__class__ :       
242                 writer.addheader("To", to[0])
243                 cc = ""
244                 for dest in to[1:len(to)]:
245                         cc +="%s, " % dest
246                 cc = cc.rstrip(", ") 
247                 writer.addheader("Cc", cc)
248         else:
249                 writer.addheader("To", to)
250
251         if config.bcc and not config.debug:
252                 writer.addheader("Bcc", config.email)
253
254         writer.addheader("Reply-To", 'monitor@planet-lab.org')
255                 
256         writer.addheader("MIME-Version", "1.0")
257         #
258         # start the multipart section of the message
259         # multipart/alternative seems to work better
260         # on some MUAs than multipart/mixed
261         #
262         writer.startmultipartbody("alternative")
263         writer.flushheaders()
264         #
265         # the plain text section
266         #
267         subpart = writer.nextpart()
268         subpart.addheader("Content-Transfer-Encoding", "quoted-printable")
269         pout = subpart.startbody("text/plain", [("charset", 'us-ascii')])
270         mimetools.encode(txtin, pout, 'quoted-printable')
271         txtin.close()
272         #
273         # Now that we're done, close our writer and
274         # return the message body
275         #
276         writer.lastpart()
277         msg = out.getvalue()
278         out.close()
279
280         # three cases:
281         #       mail but no-debug
282         #       mail and debug, 'to' changed at the beginning'
283         #   nomail, but report who I'd send to.
284         if config.mail:
285                 try:
286                         # This is normal operation
287                         server = smtplib.SMTP(MTA)
288                         server.sendmail(FROM, to,  msg)
289                         if config.bcc and not config.debug:
290                                 server.sendmail(FROM, config.email,  msg)
291                         server.quit()
292                 except Exception, err:
293                         print "Mailer error: %s" % err
294         elif not config.debug and not config.mail and config.bcc:
295                 try:
296                         server = smtplib.SMTP(MTA)
297                         server.sendmail(FROM, to,  msg)
298                         server.quit()
299                 except Exception, err:
300                         print "Mailer error: %s" % err
301         else:
302                 #print "Would mail %s" %to
303                 logger.debug("Would send mail to %s" % to)
304
305 if __name__=="__main__":
306         import smtplib
307         import emailTxt
308         import plc 
309         #email("[spam] bcc test from golf.cs.princeton.edu", 
310         #         "It gets to both recipients", 
311         #         "soltesz@cs.utk.edu")
312         #emailViaRT("rt via golf", 
313         #         "It gets to both recipients", 
314         #         "soltesz@cs.utk.edu")
315         email("Re: [PL #21323] TEST 7", 
316                            mailtxt.newbootcd_one[1] % {'hostname_list':"hostname list..."},
317                            ['monitor@planet-lab.org'])
318         #print "ticketid: %d" % id
319         #id = plc.siteId(["alice.cs.princeton.edu"])
320         #print id
321         #if id:
322                 #email('TEST', emailTxt.mailtxt.ssh % {'hostname': "ALICE.cs.princeton.edu"}, "tech-" + id + "@sites.planet-lab.org")
323         #else:
324         #       print "No dice."
325         #email("TEST111", "I'd like to see if this works anywhere", ["soltesz@cs.princeton.edu", "soltesz@cs.utk.edu"])
326         #print "mailer does nothing in main()"