Blame webhook-server/pagure-webhook-server.py

Pierre-Yves Chibon 12f5e6
#!/usr/bin/env python
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
"""
Pierre-Yves Chibon 12f5e6
 (c) 2015 - Copyright Red Hat Inc
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
 Authors:
Pierre-Yves Chibon 12f5e6
   Pierre-Yves Chibon <pingou@pingoured.fr></pingou@pingoured.fr>
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
This server listens to message sent via redis and send the corresponding
Pierre-Yves Chibon 12f5e6
web-hook request.
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
Using this mechanism, we no longer block the main application if the
Pierre-Yves Chibon 12f5e6
receiving end is offline or so.
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
"""
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
import datetime
Pierre-Yves Chibon 12f5e6
import hashlib
Pierre-Yves Chibon 12f5e6
import hmac
Pierre-Yves Chibon 12f5e6
import json
Pierre-Yves Chibon 12f5e6
import logging
Pierre-Yves Chibon 12f5e6
import os
Pierre-Yves Chibon 12f5e6
import requests
Pierre-Yves Chibon 12f5e6
import time
Pierre-Yves Chibon 12f5e6
import uuid
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
import six
Pierre-Yves Chibon 12f5e6
import trollius
Pierre-Yves Chibon 12f5e6
import trollius_redis
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
from kitchen.text.converters import to_bytes
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
log = logging.getLogger(__name__)
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
if 'PAGURE_CONFIG' not in os.environ \
Pierre-Yves Chibon 12f5e6
        and os.path.exists('/etc/pagure/pagure.cfg'):
Pierre-Yves Chibon 12f5e6
    print 'Using configuration file `/etc/pagure/pagure.cfg`'
Pierre-Yves Chibon 12f5e6
    os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg'
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
import pagure
Pierre-Yves Chibon 12f5e6
import pagure.lib
Pierre-Yves Chibon 12f5e6
from pagure.exceptions import PagureEvException
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
_i = 0
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon d38a61
def call_web_hooks(project, topic, msg, urls):
Pierre-Yves Chibon 12f5e6
    ''' Sends the web-hook notification. '''
Pierre-Yves Chibon fc2b26
    log.info(
Pierre-Yves Chibon fc2b26
        "Processing project: %s - topic: %s", project.fullname, topic)
Pierre-Yves Chibon fc2b26
    log.debug('msg: %s', msg)
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
    # Send web-hooks notification
Pierre-Yves Chibon 12f5e6
    global _i
Pierre-Yves Chibon 12f5e6
    _i += 1
Pierre-Yves Chibon 12f5e6
    year = datetime.datetime.now().year
Pierre-Yves Chibon 12f5e6
    if isinstance(topic, six.text_type):
Pierre-Yves Chibon 12f5e6
        topic = to_bytes(topic, encoding='utf8', nonstring="passthru")
Pierre-Yves Chibon d28610
    msg['pagure_instance'] = pagure.APP.config['APP_URL']
Pierre-Yves Chibon d28610
    msg['project_fullname'] = project.fullname
Pierre-Yves Chibon 12f5e6
    msg = dict(
Pierre-Yves Chibon 12f5e6
        topic=topic.decode('utf-8'),
Pierre-Yves Chibon 12f5e6
        msg=msg,
Pierre-Yves Chibon 12f5e6
        timestamp=int(time.time()),
Pierre-Yves Chibon 12f5e6
        msg_id=str(year) + '-' + str(uuid.uuid4()),
Pierre-Yves Chibon 12f5e6
        i=_i,
Pierre-Yves Chibon 12f5e6
    )
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
    content = json.dumps(msg)
Pierre-Yves Chibon 12f5e6
    hashhex = hmac.new(
Pierre-Yves Chibon 12f5e6
        str(project.hook_token), content, hashlib.sha1).hexdigest()
Patrick Uiterwijk e99b9a
    hashhex256 = hmac.new(
Patrick Uiterwijk e99b9a
        str(project.hook_token), content, hashlib.sha256).hexdigest()
Pierre-Yves Chibon 12f5e6
    headers = {
Pierre-Yves Chibon d28610
        'X-Pagure': pagure.APP.config['APP_URL'],
Pierre-Yves Chibon d28610
        'X-Pagure-project': project.fullname,
Patrick Uiterwijk e99b9a
        'X-Pagure-Signature': hashhex,
Pierre-Yves Chibon d28610
        'X-Pagure-Signature-256': hashhex256,
Pierre-Yves Chibon d28610
        'X-Pagure-Topic': topic,
Pierre-Yves Chibon 12f5e6
    }
Pierre-Yves Chibon 12f5e6
    msg = json.dumps(msg)
Pierre-Yves Chibon d38a61
    for url in urls:
Pierre-Yves Chibon 12f5e6
        url = url.strip()
Pierre-Yves Chibon 12f5e6
        log.info('Calling url %s' % url)
Pierre-Yves Chibon 12f5e6
        try:
Pierre-Yves Chibon 12f5e6
            req = requests.post(
Pierre-Yves Chibon 12f5e6
                url,
Pierre-Yves Chibon 12f5e6
                headers=headers,
Pierre-Yves Chibon d38a61
                data={'payload': msg},
Pierre-Yves Chibon d38a61
                timeout=60,
Pierre-Yves Chibon 12f5e6
            )
Pierre-Yves Chibon 12f5e6
            if not req:
Pierre-Yves Chibon 9bd1d8
                log.info(
Pierre-Yves Chibon 12f5e6
                    'An error occured while querying: %s - '
Pierre-Yves Chibon 12f5e6
                    'Error code: %s' % (url, req.status_code))
Pierre-Yves Chibon 12f5e6
        except (requests.exceptions.RequestException, Exception) as err:
Pierre-Yves Chibon 9bd1d8
            log.info(
Pierre-Yves Chibon 12f5e6
                'An error occured while querying: %s - Error: %s' % (
Pierre-Yves Chibon 12f5e6
                    url, err))
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
@trollius.coroutine
Pierre-Yves Chibon af4c20
def handle_messages():
Pierre-Yves Chibon 12f5e6
        connection = yield trollius.From(trollius_redis.Connection.create(
Pierre-Yves Chibon 12f5e6
            host='0.0.0.0', port=6379, db=0))
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
        # Create subscriber.
Pierre-Yves Chibon 12f5e6
        subscriber = yield trollius.From(connection.start_subscribe())
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
        # Subscribe to channel.
Pierre-Yves Chibon 8e41fb
        yield trollius.From(subscriber.subscribe(['pagure.hook']))
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
        # Inside a while loop, wait for incoming events.
Pierre-Yves Chibon 12f5e6
        while True:
Pierre-Yves Chibon 12f5e6
            reply = yield trollius.From(subscriber.next_published())
Pierre-Yves Chibon fc2b26
            log.info(
Pierre-Yves Chibon fc2b26
                'Received: %s on channel: %s',
Pierre-Yves Chibon fc2b26
                repr(reply.value), reply.channel)
Pierre-Yves Chibon 12f5e6
            data = json.loads(reply.value)
Pierre-Yves Chibon 12f5e6
            username = None
Pierre-Yves Chibon 8e02d4
            if data['project'].startswith('forks'):
Pierre-Yves Chibon d38a61
                username, projectname = data['project'].split('/', 2)[1:]
Pierre-Yves Chibon 12f5e6
            else:
Pierre-Yves Chibon 12f5e6
                projectname = data['project']
Pierre-Yves Chibon 8e02d4
Pierre-Yves Chibon 05271f
            namespace = None
Pierre-Yves Chibon 05271f
            if '/' in projectname:
Pierre-Yves Chibon 05271f
                namespace, projectname = projectname.split('/', 1)
Pierre-Yves Chibon 05271f
Pierre-Yves Chibon d38a61
            log.info(
Pierre-Yves Chibon d38a61
                'Searching %s/%s/%s' % (username, namespace, projectname))
Pierre-Yves Chibon d1cbf9
            session = pagure.lib.create_session(pagure.APP.config['DB_URL'])
Pierre-Yves Chibon 12f5e6
            project = pagure.lib.get_project(
Pierre-Yves Chibon d38a61
                session=session, name=projectname, user=username,
Pierre-Yves Chibon d38a61
                namespace=namespace)
Pierre-Yves Chibon d38a61
            if not project:
Pierre-Yves Chibon d38a61
                log.info('No project found with these criteria')
Pierre-Yves Chibon d38a61
                session.close()
Pierre-Yves Chibon d38a61
                continue
Pierre-Yves Chibon d38a61
            urls = project.settings.get('Web-hooks')
Pierre-Yves Chibon d1cbf9
            session.close()
Pierre-Yves Chibon d38a61
            if not urls:
Pierre-Yves Chibon d38a61
                log.info('No URLs set: %s' % urls)
Pierre-Yves Chibon d38a61
                continue
Pierre-Yves Chibon d38a61
            urls = urls.split('\n')
Pierre-Yves Chibon fc2b26
            log.info('Got the project, going to the webhooks')
Pierre-Yves Chibon d38a61
            call_web_hooks(project, data['topic'], data['msg'], urls)
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
def main():
Pierre-Yves Chibon 12f5e6
    server = None
Pierre-Yves Chibon 12f5e6
    try:
Pierre-Yves Chibon 12f5e6
        loop = trollius.get_event_loop()
Pierre-Yves Chibon 12f5e6
        tasks = [
Pierre-Yves Chibon af4c20
            trollius.async(handle_messages()),
Pierre-Yves Chibon 12f5e6
        ]
Pierre-Yves Chibon 12f5e6
        loop.run_until_complete(trollius.wait(tasks))
Pierre-Yves Chibon 12f5e6
        loop.run_forever()
Pierre-Yves Chibon 12f5e6
    except KeyboardInterrupt:
Pierre-Yves Chibon 12f5e6
        pass
Pierre-Yves Chibon 12f5e6
    except trollius.ConnectionResetError:
Pierre-Yves Chibon 12f5e6
        pass
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
    log.info("End Connection")
Pierre-Yves Chibon 12f5e6
    loop.close()
Pierre-Yves Chibon 12f5e6
    log.info("End")
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
if __name__ == '__main__':
Pierre-Yves Chibon 12f5e6
    log = logging.getLogger("")
Pierre-Yves Chibon 12f5e6
    formatter = logging.Formatter(
Pierre-Yves Chibon 12f5e6
        "%(asctime)s %(levelname)s [%(module)s:%(lineno)d] %(message)s")
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
    # setup console logging
Pierre-Yves Chibon 12f5e6
    log.setLevel(logging.DEBUG)
Pierre-Yves Chibon 12f5e6
    ch = logging.StreamHandler()
Pierre-Yves Chibon 12f5e6
    ch.setLevel(logging.DEBUG)
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
    aslog = logging.getLogger("asyncio")
Pierre-Yves Chibon 12f5e6
    aslog.setLevel(logging.DEBUG)
Pierre-Yves Chibon 12f5e6
Pierre-Yves Chibon 12f5e6
    ch.setFormatter(formatter)
Pierre-Yves Chibon 12f5e6
    log.addHandler(ch)
Pierre-Yves Chibon 12f5e6
    main()