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