#!/usr/bin/env python3 import argparse import logging import pathlib import subprocess import sys logger = logging.getLogger('rt-bulk-send') def parse_arguments(arglist): parser = argparse.ArgumentParser() action = parser.add_mutually_exclusive_group() action.add_argument( '--correspond', dest='action', action='store_const', const='correspond', default='correspond', help="Send correspondence on found tickets (default)", ) action.add_argument( '--comment', dest='action', action='store_const', const='comment', help="Comment on found tickets", ) parser.add_argument( '--loglevel', choices=['debug', 'info', 'warning', 'error', 'critical'], default='info', help="Show log messages at this level (default %(default)s)", ) parser.add_argument( 'search', help="TicketSQL search, like you would pass to `rt search`", ) parser.add_argument( 'body_file', type=pathlib.Path, help="Path to file that has the content of your correspondence/comment", ) parser.add_argument( 'rt_args', metavar='rt arguments', nargs=argparse.REMAINDER, help="Additional arguments to pass to `rt correspond/comment`", ) return parser.parse_args(arglist) def setup_logger(logger, loglevel, stream): formatter = logging.Formatter('%(name)s: %(levelname)s: %(message)s') handler = logging.StreamHandler(stream) handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(loglevel) def act_on_ticket(ticket_id, args, body_file): body_file.seek(0) return subprocess.run( ['rt', args.action, *args.rt_args, '-m', '-', ticket_id], stdin=body_file, check=True, ) def main(arglist=None, stdout=sys.stdout, stderr=sys.stderr): args = parse_arguments(arglist) setup_logger(logger, getattr(logging, args.loglevel.upper()), stderr) try: body_file = args.body_file.open() except OSError as error: logger.critical("error opening {}: {}".format(args.body_file, error)) return 3 if not body_file.seekable(): logger.critical("file {} must be seekable".format(args.body_file)) with body_file: return 3 action_verb = args.action.title() failures = 0 with body_file, subprocess.Popen( ['rt', 'search', '-i', args.search], stdout=subprocess.PIPE, universal_newlines=True, ) as search_pipe: for line in search_pipe.stdout: ticket_id = line.rstrip('\n') if not ticket_id: continue logger.info("%sing on %s", action_verb, ticket_id) try: act_on_ticket(ticket_id, args, body_file) except subprocess.CalledProcessError as error: logger.error("Failed to %s on %s: rt returned exit code %s", args.action, ticket_id, error.returncode) failures += 1 if search_pipe.returncode != 0: logger.critical("`rt search` returned exit code %s", search_pipe.returncode) return 4 elif failures == 0: return 0 else: return min(10 + failures, 99) if __name__ == '__main__': exit(main())