Blame milters/comment_email_milter.py

Pierre-Yves Chibon 6e7f9d
#!/usr/bin/env python2
Pierre-Yves Chibon 6e7f9d
# -*- coding: utf-8 -*-
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
# Milter calls methods of your class at milter events.
Pierre-Yves Chibon 6e7f9d
# Return REJECT,TEMPFAIL,ACCEPT to short circuit processing for a message.
Pierre-Yves Chibon 6e7f9d
# You can also add/del recipients, replacebody, add/del headers, etc.
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
import base64
Pierre-Yves Chibon 6e7f9d
import email
Pierre-Yves Chibon 6e7f9d
import os
Pierre-Yves Chibon 6e7f9d
import StringIO
Pierre-Yves Chibon 6e7f9d
import sys
Pierre-Yves Chibon 6e7f9d
import time
Pierre-Yves Chibon 6e7f9d
from socket import AF_INET, AF_INET6
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
import Milter
Pierre-Yves Chibon 6e7f9d
if True:
Pierre-Yves Chibon 6e7f9d
    from multiprocessing import Process as Thread, Queue
Pierre-Yves Chibon 6e7f9d
else:
Pierre-Yves Chibon 6e7f9d
    from threading import Thread
Pierre-Yves Chibon 6e7f9d
    from Queue import Queue
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
from Milter.utils import parse_addr
Pierre-Yves Chibon 6e7f9d
from sqlalchemy.exc import SQLAlchemyError
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
logq = Queue(maxsize=4)
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
if 'PAGURE_CONFIG' not in os.environ \
Pierre-Yves Chibon 6e7f9d
        and os.path.exists('/etc/pagure/pagure.cfg'):
Pierre-Yves Chibon 6e7f9d
    print 'Using configuration file `/etc/pagure/pagure.cfg`'
Pierre-Yves Chibon 6e7f9d
    os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg'
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
sys.path.insert(0, os.path.expanduser('/srv/progit'))
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
import pagure
Pierre-Yves Chibon 6e7f9d
import pagure.exceptions
Pierre-Yves Chibon 6e7f9d
import pagure.lib
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
def get_email_body(emailobj):
Pierre-Yves Chibon 6e7f9d
    ''' Return the body of the email, preferably in text.
Pierre-Yves Chibon 6e7f9d
    '''
Pierre-Yves Chibon 6e7f9d
    body = None
Pierre-Yves Chibon 6e7f9d
    if emailobj.is_multipart():
Pierre-Yves Chibon 6e7f9d
        for payload in emailobj.get_payload():
Pierre-Yves Chibon 6e7f9d
            body = payload.get_payload()
Pierre-Yves Chibon 6e7f9d
            if payload.get_content_type() == 'text/plain':
Pierre-Yves Chibon 6e7f9d
                break
Pierre-Yves Chibon 6e7f9d
    else:
Pierre-Yves Chibon 6e7f9d
        body = emailobj.get_payload()
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    enc = emailobj['Content-Transfer-Encoding']
Pierre-Yves Chibon 6e7f9d
    if enc == 'base64':
Pierre-Yves Chibon 6e7f9d
        body = base64.decodestring(body)
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    return body
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 2ba989
class PagureMilter(Milter.Base):
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    def __init__(self):  # A new instance with each new connection.
Pierre-Yves Chibon 6e7f9d
        self.id = Milter.uniqueID()  # Integer incremented with each call.
Pierre-Yves Chibon 6e7f9d
        self.fp = None
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    def envfrom(self, mailfrom, *str):
Pierre-Yves Chibon 6e7f9d
        self.log("mail from:", mailfrom, *str)
Pierre-Yves Chibon 6e7f9d
        self.fromparms = Milter.dictfromlist(str)
Pierre-Yves Chibon 6e7f9d
        # NOTE: self.fp is only an *internal* copy of message data.  You
Pierre-Yves Chibon 6e7f9d
        # must use addheader, chgheader, replacebody to change the message
Pierre-Yves Chibon 6e7f9d
        # on the MTA.
Pierre-Yves Chibon 6e7f9d
        self.fp = StringIO.StringIO()
Pierre-Yves Chibon 6e7f9d
        self.canon_from = '@'.join(parse_addr(mailfrom))
Pierre-Yves Chibon 6e7f9d
        self.fp.write('From %s %s\n' % (self.canon_from, time.ctime()))
Pierre-Yves Chibon 6e7f9d
        return Milter.CONTINUE
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    @Milter.noreply
Pierre-Yves Chibon 6e7f9d
    def connect(self, IPname, family, hostaddr):
Pierre-Yves Chibon 6e7f9d
        self.log("connected")
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        return Milter.CONTINUE
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    @Milter.noreply
Pierre-Yves Chibon 6e7f9d
    def header(self, name, hval):
Pierre-Yves Chibon 6e7f9d
        self.fp.write("%s: %s\n" % (name, hval))     # add header to buffer
Pierre-Yves Chibon 6e7f9d
        return Milter.CONTINUE
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    @Milter.noreply
Pierre-Yves Chibon 6e7f9d
    def eoh(self):
Pierre-Yves Chibon 6e7f9d
        self.fp.write("\n")                         # terminate headers
Pierre-Yves Chibon 6e7f9d
        return Milter.CONTINUE
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    @Milter.noreply
Pierre-Yves Chibon 6e7f9d
    def body(self, chunk):
Pierre-Yves Chibon 6e7f9d
        self.fp.write(chunk)
Pierre-Yves Chibon 6e7f9d
        return Milter.CONTINUE
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    ##  def envrcpt(self, to, *str):
Pierre-Yves Chibon 6e7f9d
    @Milter.noreply
Pierre-Yves Chibon 6e7f9d
    def envrcpt(self, to, *str):
Pierre-Yves Chibon 6e7f9d
        rcptinfo = to, Milter.dictfromlist(str)
Pierre-Yves Chibon 6e7f9d
        print rcptinfo
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        return Milter.CONTINUE
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    def eom(self):
Pierre-Yves Chibon 6e7f9d
        self.fp.seek(0)
Pierre-Yves Chibon 6e7f9d
        msg = email.message_from_file(self.fp)
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        msg_id = msg['In-Reply-To']
Pierre-Yves Chibon 6e7f9d
        self.log('msg-ig', msg_id)
Pierre-Yves Chibon 6e7f9d
        self.log('To', msg['to'])
Pierre-Yves Chibon 6e7f9d
        self.log('From', msg['From'])
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        for key in ['<', '>']:
Pierre-Yves Chibon 6e7f9d
            if key in msg_id:
Pierre-Yves Chibon 6e7f9d
                msg_id = msg_id.replace(key, '')
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        if msg_id and '-ticket-' in msg_id:
Pierre-Yves Chibon 6e7f9d
            return self.handle_ticket_email(msg, msg_id)
Pierre-Yves Chibon 6e7f9d
        elif msg_id and '-pull-request-' in msg_id:
Pierre-Yves Chibon 6e7f9d
            return self.handle_request_email(msg, msg_id)
Pierre-Yves Chibon 6e7f9d
        else:
Pierre-Yves Chibon 6e7f9d
            self.log('Not a pagure ticket or pull-request email, let it go')
Pierre-Yves Chibon 6e7f9d
            return Milter.CONTINUE
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    def log(self,*msg):
Pierre-Yves Chibon 6e7f9d
        logq.put((msg, self.id,time.time()))
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    def handle_ticket_email(self, emailobj, msg_id):
Pierre-Yves Chibon 6e7f9d
        ''' Add the email as a comment on a ticket. '''
Pierre-Yves Chibon 6e7f9d
        uid  = msg_id.split('-ticket-')[-1].split('@')[0]
Pierre-Yves Chibon 6e7f9d
        parent_id = None
Pierre-Yves Chibon 6e7f9d
        if '-' in uid:
Pierre-Yves Chibon 6e7f9d
            uid, parent_id = uid.rsplit('-', 1)
Pierre-Yves Chibon 6e7f9d
        if '/' in uid:
Pierre-Yves Chibon 6e7f9d
            uid = uid.split('/')[0]
Pierre-Yves Chibon 6e7f9d
        self.log('uid', uid)
Pierre-Yves Chibon 6e7f9d
        self.log('parent_id', parent_id)
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        issue = pagure.lib.get_issue_by_uid(
Pierre-Yves Chibon 6e7f9d
            pagure.SESSION,
Pierre-Yves Chibon 6e7f9d
            issue_uid = uid
Pierre-Yves Chibon 6e7f9d
        )
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        if not issue:
Pierre-Yves Chibon 6e7f9d
            self.log('No related ticket found, let it go')
Pierre-Yves Chibon 6e7f9d
            return Milter.CONTINUE
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        try:
Pierre-Yves Chibon 6e7f9d
            message = pagure.lib.add_issue_comment(
Pierre-Yves Chibon 6e7f9d
                pagure.SESSION,
Pierre-Yves Chibon 6e7f9d
                issue=issue,
Pierre-Yves Chibon 6e7f9d
                comment=get_email_body(emailobj),
Pierre-Yves Chibon 6e7f9d
                user=emailobj['From'],
Pierre-Yves Chibon 6e7f9d
                ticketfolder=pagure.APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon 6e7f9d
            )
Pierre-Yves Chibon 6e7f9d
            pagure.SESSION.commit()
Pierre-Yves Chibon 6e7f9d
        except pagure.exceptions.PagureException, err:
Pierre-Yves Chibon 6e7f9d
            self.log(str(err))
Pierre-Yves Chibon 6e7f9d
        except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon 6e7f9d
            pagure.SESSION.rollback()
Pierre-Yves Chibon 6e7f9d
            self.log(str(err))
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        return Milter.ACCEPT
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
    def handle_request_email(self, emailobj, msg_id):
Pierre-Yves Chibon 6e7f9d
        ''' Add the email as a comment on a request. '''
Pierre-Yves Chibon 6e7f9d
        msg_id = msg['In-Reply-To']
Pierre-Yves Chibon 6e7f9d
        uid  = msg_id.split('-pull-request-')[-1].split('@')[0]
Pierre-Yves Chibon 6e7f9d
        parent_id = None
Pierre-Yves Chibon 6e7f9d
        if '-' in uid:
Pierre-Yves Chibon 6e7f9d
            uid, parent_id = uid.rsplit('-', 1)
Pierre-Yves Chibon 6e7f9d
        if '/' in uid:
Pierre-Yves Chibon 6e7f9d
            uid = uid.split('/')[0]
Pierre-Yves Chibon 6e7f9d
        self.log('uid', uid)
Pierre-Yves Chibon 6e7f9d
        self.log('parent_id', parent_id)
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        request = pagure.lib.get_request_by_uid(
Pierre-Yves Chibon 6e7f9d
            pagure.SESSION,
Pierre-Yves Chibon 6e7f9d
            request_uid = uid
Pierre-Yves Chibon 6e7f9d
        )
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        if not request:
Pierre-Yves Chibon 6e7f9d
            self.log('No related pull-request found, let it go')
Pierre-Yves Chibon 6e7f9d
            return Milter.CONTINUE
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        try:
Pierre-Yves Chibon 6e7f9d
            message = pagure.lib.add_pull_request_comment(
Pierre-Yves Chibon 6e7f9d
                pagure.SESSION,
Pierre-Yves Chibon 6e7f9d
                request=request,
Pierre-Yves Chibon 6e7f9d
                comment=get_email_body(emailobj),
Pierre-Yves Chibon 6e7f9d
                user=emailobj['From'],
Pierre-Yves Chibon 6e7f9d
                requestfolder=pagure.APP.config['REQUESTS_FOLDER'],
Pierre-Yves Chibon 6e7f9d
            )
Pierre-Yves Chibon 6e7f9d
            pagure.SESSION.commit()
Pierre-Yves Chibon 6e7f9d
        except pagure.exceptions.PagureException, err:
Pierre-Yves Chibon 6e7f9d
            self.log(str(err))
Pierre-Yves Chibon 6e7f9d
        except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon 6e7f9d
            pagure.SESSION.rollback()
Pierre-Yves Chibon 6e7f9d
            self.log(str(err))
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
        return Milter.ACCEPT
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
def background():
Pierre-Yves Chibon 6e7f9d
    while True:
Pierre-Yves Chibon 6e7f9d
        t = logq.get()
Pierre-Yves Chibon 6e7f9d
        if not t: break
Pierre-Yves Chibon 6e7f9d
        msg,id,ts = t
Pierre-Yves Chibon 6e7f9d
        print "%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S',time.localtime(ts)),id),
Pierre-Yves Chibon 6e7f9d
        # 2005Oct13 02:34:11 [1] msg1 msg2 msg3 ...
Pierre-Yves Chibon 6e7f9d
        for i in msg: print i,
Pierre-Yves Chibon 6e7f9d
        print
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
## ===
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
def main():
Pierre-Yves Chibon 6e7f9d
    bt = Thread(target=background)
Pierre-Yves Chibon 6e7f9d
    bt.start()
Pierre-Yves Chibon 2ba989
    socketname = "/var/run/pagure/paguresock"
Pierre-Yves Chibon 6e7f9d
    timeout = 600
Pierre-Yves Chibon 6e7f9d
    # Register to have the Milter factory create instances of your class:
Pierre-Yves Chibon 2ba989
    Milter.factory = PagureMilter
Pierre-Yves Chibon 2ba989
    print "%s pagure milter startup" % time.strftime('%Y%b%d %H:%M:%S')
Pierre-Yves Chibon 6e7f9d
    sys.stdout.flush()
Pierre-Yves Chibon 2ba989
    Milter.runmilter("paguremilter", socketname, timeout)
Pierre-Yves Chibon 6e7f9d
    logq.put(None)
Pierre-Yves Chibon 6e7f9d
    bt.join()
Pierre-Yves Chibon 2ba989
    print "%s pagure milter shutdown" % time.strftime('%Y%b%d %H:%M:%S')
Pierre-Yves Chibon 6e7f9d
Pierre-Yves Chibon 6e7f9d
if __name__ == "__main__":
Pierre-Yves Chibon 6e7f9d
    main()