#!/usr/bin/python
#
# rpm-ostree-toolbox-build-monitor - listen on koji message bus, trigger compose
#
# This tool is part of the ostree build process. It is intended to
# run 24x7, probably via systemd on a fixed internal host. It monitors
# the QPID message bus, listening for koji newRepo completion events. When
# it detects a desired repo, it triggers a new compose.
#

from configobj import ConfigObj
import argparse
import qpid.messaging
import qpid.datatypes
import sys
import os

# Script name, used in a few places
progname = os.path.basename(sys.argv[0])

###############################################################################
# BEGIN Fault dictionary

# If you see this message:
#    qpid.messaging.exceptions.AuthenticationFailure: sasl negotiation failed: no mechanism agreed
# ...you need to:
#    # yum install python-saslwrapper

# If you see this message:
#    No worthy mechs found
# ...you need to:
#    # yum install cyrus-sasl-gssapi

# END   Fault dictionary
###############################################################################
# BEGIN code

def timestamp():
    import time
    return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())

def dprint(msg):
    if os.getenv('DEBUG_BUILD_MONITOR'):
        print timestamp(), " ", msg

def listener(config):
    "Main entry point. Connects to QPID broker, waits for messages."

    # At this point we must have $KRB5CCNAME defined and pointing to
    # a valid Kerberos credential cache file.
    if os.getenv("KRB5CCNAME") is None:
        print >> sys.stderr, "%s: WARNING: KRB5CCNAME envariable is not set, QPID connection is likely to fail!" % progname

    dprint("About to Connect()")
    conn = qpid.messaging.Connection.establish(
        host            = config['qpid']['host'],
        port            = config['qpid']['port'],
        sasl_mechanisms = config['qpid']['mechanism'],
        transport       = config['qpid']['transport'],
        heartbeat       = int(config['qpid']['heartbeat'])
    )

    dprint("About to conn.session()")
    session = conn.session()

    # In production, our config file will specify queue_name.
    # For testing: if no queue_name specified, create a unique 'tmp.' one.
    if 'queue_name' not in config['qpid'] or not config['qpid']['queue_name']:
        config['qpid']['queue_name'] = "tmp.%s-%s-%d-%d" % \
          (progname, os.uname()[1], os.getuid(), os.getpid())

    # Durable queues: you probably don't have one.
    durable = not('tmp.' in config['qpid']['queue_name'])
    config['qpid']['durable']    = str(durable)
    config['qpid']['autodelete'] = str(not(durable))

    # configobj %(...)s interpolation happens now, at fetch time:
    address = config['qpid']['address_template']
    dprint("About to session.receiver()")
    receiver = session.receiver(address)

    while True:
        dprint("About to receiver.fetch()")
        message = receiver.fetch()

        # Without this, if we restart this process, we'll keep getting
        # old messages over and over
        session.acknowledge(message)

        # message.content will be a deep structure; we're only interested
        # in a small subset of it, ->info->request, which is the name of
        # the repo. All newRepo messages will have this structure, but we
        # wrap this inside 'try' just in case.
        try:
            repo = message.content['info']['request'][0]
            # Just echo it to stdout. This goes to systemd journal, which
            # is monitored by another process.
            print "repo:%s" % repo
        except Exception, e:
            print "WARNING: possibly malformed message: %s" % str(e)

    # Will never trigger; left in place for reference only
    session.close()
    conn.close()


def main(config):
    listener(config)

#
# Begin Here
#
if __name__ == "__main__":
    # Read command-line options.
    parser = argparse.ArgumentParser(
        description='monitor message bus, trigger treecompose on <event>',
        epilog='%(prog)s is intended to be run via systemd')
    parser.add_argument('-c', '--config', type=file, required=True,
                        help='path to config file')
    parser.add_argument('-v', '--verbose', action='store_true',
                        help='run verbosefulatiously')
    args = parser.parse_args()

    # Now read config options
    conf = ConfigObj(args.config)

    # Autoflush stdout! otherwise our print()s never make it to journal
    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

    main(conf)
