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