Marvin/main.py
2021-11-04 17:14:14 +01:00

106 lines
4.2 KiB
Python

#!/usr/bin/env python3
import sys
import os
from datetime import date, datetime, timedelta
import pytz
import logging
from redminelib import Redmine
from envyaml import EnvYAML
from jinja2 import Template
from exceptions import IssueStatusNotFoundException
def treat_issues():
dir_path = os.path.dirname(os.path.realpath(__file__)) + '/'
# Set up logging
logging.basicConfig(
filename=dir_path + 'marvin.log',
level=logging.ERROR,
format='%(asctime)s - %(message)s',
)
# Load configuration
try:
config = EnvYAML(dir_path + 'config.yaml', dir_path + '.env')
url = config['redmine']['url']
version = config['redmine']['version']
api_key = config['redmine']['api_key']
time_zone = pytz.timezone(config['redmine']['time_zone'])
issue_closed_status = config['redmine']['issue_closed_status']
no_bot_tag = config['redmine']['no_bot_tag']
actions = config['actions']
logging.getLogger().setLevel(config['logging']['level'].upper())
except Exception as e:
logging.error('Could not load config.yaml', exc_info=True)
sys.exit(1)
# Create Redmine object instance
try:
redmine = Redmine(url, version=version, key=api_key)
except Exception as e:
logging.error('Could not instantiate Redmine object', exc_info=True)
sys.exit(1)
# Get status id for closed issues
try:
all_status = redmine.issue_status.all()
issue_closed_statuses = all_status.filter(name=issue_closed_status)
if len(issue_closed_statuses) < 1:
raise IssueStatusNotFoundException(issue_closed_status)
elif len(issue_closed_statuses) > 1:
logging.warning(f'Expected one issue status with the name {issue_closed_status} but found '
f'{len(issue_closed_statuses)}')
issue_closed_status_id = issue_closed_statuses[0].id
except IssueStatusNotFoundException as e:
logging.error(e, exc_info=True)
sys.exit(1)
# Loop through all actions defined in config.yaml
for action in actions.values():
# Calculate end_date
end_date = date.today() - timedelta(days=+int(action['time_range']))
# Loop through affected issues
try:
for issue in redmine.issue \
.filter(updated_on=f"><{action['start_date']}|{end_date.isoformat()}") \
.filter(project__name__in=action['projects'], status__name__in=action['status'], closed_on=None):
# Skip issue if a no_bot_tag is found in the issue description or any of its journals
def find_no_bot_tag_in_journals(journals):
for journal in journals:
if no_bot_tag in journal.notes:
return True
return False
if no_bot_tag in issue.description or find_no_bot_tag_in_journals(issue.journals):
continue
with open(f"{dir_path}templates/{action['template']}", newline='\r\n') as f:
content = f.read()
template = Template(content)
notes = template.render(
issue=issue,
time_range=action['time_range'],
days_since_last_update=(datetime.now(time_zone) - issue.updated_on.replace(tzinfo=time_zone)).days
)
# Update issue
if action.get('close_ticket') is True:
redmine.issue.update(issue.id, notes=notes, status_id=issue_closed_status_id)
logging.info(f"Ticket ID: {issue.id}, ticket closed")
elif action.get('change_status_to') is not None and isinstance(action.get('change_status_to'), int):
redmine.issue.update(issue.id, notes=notes, status_id=action['change_status_to'])
logging.info(f"Ticket ID: {issue.id}, changed ticket status")
else:
redmine.issue.update(issue.id, notes=notes)
logging.info(f"Ticket ID: {issue.id}")
except Exception as e:
logging.error('Could not process issues', exc_info=True)
sys.exit(1)
if __name__ == "__main__":
treat_issues()