redminer/task_to_redmine.py
2023-10-04 15:58:52 +04:00

182 lines
5.3 KiB
Python
Executable File

#!/usr/bin/env python3
from io import BytesIO
import requests
import json
import redminelib
LOG_LINES_FOR_TASK = 16
def format_dict(data, indent=False):
if indent:
return json.dumps(data, sort_keys=True,
indent=2, separators=(',', ': '),
default=repr)
else:
return json.dumps(data, sort_keys=True, default=repr)
_FORMAT_SUBTASK = {
'srpm': lambda st: 'srpm ' + st.get('srpm'),
'delete': lambda st: 'delete ' + st.get('package'),
'repo': lambda st: '%s=%s' % (st.get('dir'),
st.get('tag') or st.get('tag_name'))
}
_FORMAT_SUBTASK_SHORT = {
'srpm': lambda st: st.get('srpm'),
'delete': lambda st: 'del' + st.get('package'),
'repo': lambda st: '%s=%s' % (st.get('dir').rsplit('/')[-1],
st.get('tag') or st.get('tag_name'))
}
def format_subtask(subtask, short=False):
stype = subtask.get('type')
formatters = _FORMAT_SUBTASK_SHORT if short else _FORMAT_SUBTASK
return formatters.get(stype, format_dict)(subtask)
_TASK_FORMAT = '%(taskid)s %(state)s try=%(try)s.%(iter)s owner=%(owner)s'
def format_task(info):
result = [_TASK_FORMAT % info]
subtasks = sorted((int(k), format_subtask(s))
for k, s in info['subtasks'].items())
result.extend('%6d %s' % item for item in subtasks)
return '\n'.join(result)
class TaskSession:
def __init__(self, base_url, task_id, session=None):
self._session = session or requests.Session()
self.task_url = f"{base_url.rstrip('/')}/tasks/{task_id}"
def get_info(self):
info = self.get('/info.json').json()
info.setdefault('try', 0)
info.setdefault('iter', 0)
# XXX: get this from config?
info['architectures'] = [info['repo'].rsplit('_', 1)[-1]]
return info
def get(self, path):
assert path.startswith('/')
r = self._session.get(self.task_url + path)
r.raise_for_status()
return r
def failed_subtask(info, ts):
for stid in sorted(int(x) for x in info['subtasks']):
for arch in info['architectures']:
try:
status = ts.get(f"/build/{stid}/{arch}/status").content.strip()
if not status:
return arch, str(stid)
except requests.HTTPError as err:
if err.response.status_code == 404:
continue
raise
return None, None
def grab_task(base_url, task_id, subtasks):
issue = {
"project_id": "mips",
"uploads": []
}
ts = TaskSession(base_url, task_id)
info = ts.get_info()
if info.get('try'):
# grab the last log, if present
log_filename = f"events.{info['try']}.{info['iter']}.log"
log_data = ts.get('/logs/' + log_filename).content
# find the failed subtask, if any
issue['uploads'].append({'filename': log_filename, 'path': BytesIO(log_data)})
arch, failed_subtask_id = failed_subtask(info, ts)
else:
log_data = None
arch = None
failed_subtask_id = None
if not arch:
arch = info['architectures'][0]
if failed_subtask_id:
assert not subtasks
build_log = ts.get(f"/build/{failed_subtask_id}/{arch}/log").content
if len(build_log) > 4:
issue['uploads'].append({'filename': f"{arch}.{failed_subtask_id}.log",
'path': BytesIO(build_log)})
# this is a task about build failure
what_failed = format_subtask(info['subtasks'][failed_subtask_id], True)
issue['subject'] = f"[{arch}] FAILED {task_id}/{failed_subtask_id} {what_failed}"
intro_line = f"Failed to build subtask {failed_subtask_id} of task {task_id}:"
else:
if not subtasks:
if len(info['subtasks']) == 1:
subtasks = list(info['subtasks'])
else:
raise RuntimeError('Please specify subtasks')
to_update = (format_subtask(info['subtasks'][stid], True)
for stid in subtasks)
issue['subject'] = f"[{arch}] Update: {' '.join(to_update)}"
intro_line = f"The following update was proposed for {info['repo']}:"
issue['description'] = f'''
{ts.task_url}
{intro_line}
<pre>
{format_task(info)}
</pre>
'''.strip()
if log_data:
log_lines = b'\n'.join(log_data.splitlines()[-LOG_LINES_FOR_TASK:]).decode(errors='replace')
issue['description'] = f'''
{issue['description']}
{{{{collapse(The task events log ends with:)
<pre>
{log_lines}
</pre>
}}}}
'''.strip()
return issue
def main(arch, task_id, *subtasks):
with open('./config.json', 'r') as fp:
config = json.load(fp)
if arch == 'la64':
arch = 'loongarch64'
base_url = config['repos'][arch]
data = grab_task(base_url, task_id, subtasks)
print(format_dict(data, True))
print(data['description'])
answer = input('Should I go ahead and create the task?[y/N] ')
if answer.lower().startswith('y'):
print('Creating...')
R = redminelib.Redmine(**config['redmine'])
issue = R.issue.create(**data)
print(config['redmine']['url'] + '/issues/' + str(issue.id))
else:
print('aborted')
return 1
if __name__ == '__main__':
import sys
sys.exit(main(*sys.argv[1:]))