#!/usr/bin/env python3 # This file is part of Cockpit. # # Copyright (C) 2018 Red Hat, Inc. # # Cockpit is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # # Cockpit is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with Cockpit; If not, see . MAX_PRIORITY = 9 import argparse import json import random import subprocess import sys import time import logging import pipes from task import redhat_network, distributed_queue logging.basicConfig(level=logging.INFO) # Returns a command to execute and the delivery tag needed to ack the message def consume_webhook_queue(channel, amqp): # interpret payload # call tests-scan or issue-scan appropriately method_frame, header_frame, body = channel.basic_get(queue='webhook') if not method_frame: return None, None body = json.loads(body) event = body['event'] request = body['request'] repo = None cmd = None if event == 'pull_request': pull_request = request['pull_request'] action = request['action'] # scan for body changes (edited) and the bots label if action == 'labeled' or (action == 'edited' and 'body' in request.get('changes', {})): cmd = ['bots/issue-scan', '--issues-data', json.dumps(request), '--amqp', amqp] if request['action'] in ['opened', 'synchronize']: repo = pull_request['base']['repo']['full_name'] cmd = ['bots/tests-scan', '--pull-data', json.dumps(request), '--amqp', amqp] elif event == 'status': repo = request['repository']['full_name'] sha = request['sha'] context = pipes.quote(request['context']) cmd = ['bots/tests-scan', '--sha', sha, '--amqp', amqp, '--context', context] elif event == 'issues': action = request['action'] # scan for opened, body changes (edited), and the bots label if action in ['opened', 'labeled'] or (action == 'edited' and 'body' in request.get('changes', {})): cmd = ['bots/issue-scan', '--issues-data', json.dumps(request), '--amqp', amqp] else: logging.error('Unkown event type in the webhook queue') return None, None # HACK: the cockpit repo itself is treated as a special case in tests-scan, in # order to avoid complexity avoid supplying the repo option for now if repo and repo != 'cockpit-project/cockpit': cmd += ['--repo', repo] return cmd, method_frame.delivery_tag # Returns a command to execute and the delivery tag needed to ack the message def consume_task_queue(channel, amqp, declare_public_result, declare_rhel_result): queue='public' if redhat_network(): # Try the rhel queue if the public queue is empty if declare_public_result.method.message_count == 0: queue = 'rhel' # If both are non-empty, shuffle elif declare_rhel_result.method.message_count > 0: queue = ['public', 'rhel'][random.randrange(2)] method_frame, header_frame, body = channel.basic_get(queue=queue) if not method_frame: return None, None body = json.loads(body) return body['command'], method_frame.delivery_tag # Consume from the webhook queue and republish to the task queue via *-scan # Consume from the task queue (endpoint) def main(): parser = argparse.ArgumentParser(description='Bot: read a single test command from the queue and execute it') parser.add_argument('--amqp', default='localhost:5671', help='The host:port of the AMQP server to consume from (default: %(default)s)') opts = parser.parse_args() with distributed_queue.DistributedQueue(opts.amqp, ['webhook', 'rhel', 'public']) as q: channel = q.channel cmd, delivery_tag = consume_webhook_queue(channel, opts.amqp) if not cmd and delivery_tag: logging.info("Webhook message interpretation resulted in nop") channel.basic_ack(delivery_tag) return 0 if not cmd: cmd, delivery_tag = consume_task_queue(channel, opts.amqp, q.declare_results['public'], q.declare_results['rhel']) if not cmd: logging.info("All queues are empty") return 1 logging.info("Consuming message with command: {0}\n".format(' '.join(cmd) if type(cmd) is list else cmd)) p = subprocess.Popen(cmd, shell=type(cmd) is str) while p.poll() is None: q.connection.process_data_events() time.sleep(3) channel.basic_ack(delivery_tag) return 0 if __name__ == '__main__': sys.exit(main())