182 lines
5.3 KiB
Python
Executable File
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:]))
|