106 lines
		
	
	
	
		
			3.2 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
		
		
			
		
	
	
			106 lines
		
	
	
	
		
			3.2 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| 
								 | 
							
								#!/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='warning',
							 | 
						||
| 
								 | 
							
								        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
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    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("Acting on %s", 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())
							 |