105 lines
3.2 KiB
Python
Executable file
105 lines
3.2 KiB
Python
Executable file
#!/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())
|