2019-05-16 06:59:25 +02:00
|
|
|
#!/usr/bin/python3
|
2018-11-17 09:43:04 +01:00
|
|
|
|
2014-09-16 13:41:15 +02:00
|
|
|
import argparse
|
|
|
|
from datetime import datetime, timedelta
|
2014-09-16 17:59:34 +02:00
|
|
|
from collections import defaultdict
|
2014-09-16 13:41:15 +02:00
|
|
|
|
|
|
|
from osclib.comments import CommentAPI
|
2015-02-19 10:57:55 +01:00
|
|
|
from osclib.conf import Config
|
2014-09-16 13:41:15 +02:00
|
|
|
from osclib.stagingapi import StagingAPI
|
|
|
|
import osc
|
|
|
|
|
2014-09-16 14:47:20 +02:00
|
|
|
MARGIN_HOURS = 4
|
2014-09-22 18:41:56 +02:00
|
|
|
MAX_LINES = 6
|
2018-11-09 16:03:41 -06:00
|
|
|
MARKER = 'StagingReport'
|
2014-09-16 13:41:15 +02:00
|
|
|
|
2014-09-16 17:59:34 +02:00
|
|
|
|
2018-11-09 16:03:41 -06:00
|
|
|
class StagingReport(object):
|
2014-09-16 13:41:15 +02:00
|
|
|
def __init__(self, api):
|
|
|
|
self.api = api
|
2015-02-26 10:56:04 +01:00
|
|
|
self.comment = CommentAPI(api.apiurl)
|
2014-09-16 13:41:15 +02:00
|
|
|
|
2014-09-16 17:59:34 +02:00
|
|
|
def _package_url(self, package):
|
2018-07-05 20:43:07 +02:00
|
|
|
link = '/package/live_build_log/%s/%s/%s/%s'
|
2019-11-19 08:00:04 +01:00
|
|
|
link = link % (package.get('project'),
|
|
|
|
package.get('package'),
|
|
|
|
package.get('repository'),
|
|
|
|
package.get('arch'))
|
2024-05-07 17:55:17 +02:00
|
|
|
text = f"[{package.get('arch')}]({link})"
|
2014-09-16 17:59:34 +02:00
|
|
|
return text
|
|
|
|
|
2014-09-16 13:41:15 +02:00
|
|
|
def old_enough(self, _date):
|
|
|
|
time_delta = datetime.utcnow() - _date
|
2014-09-16 14:47:20 +02:00
|
|
|
safe_margin = timedelta(hours=MARGIN_HOURS)
|
2014-09-16 13:41:15 +02:00
|
|
|
return safe_margin <= time_delta
|
|
|
|
|
2018-11-09 15:56:54 -06:00
|
|
|
def update_status_comment(self, project, report, force=False, only_replace=False):
|
|
|
|
report = self.comment.add_marker(report, MARKER)
|
2014-09-30 11:29:06 +02:00
|
|
|
comments = self.comment.get_comments(project_name=project)
|
2018-11-09 15:56:54 -06:00
|
|
|
comment, _ = self.comment.comment_find(comments, MARKER)
|
|
|
|
if comment:
|
|
|
|
write_comment = (report != comment['comment'] and self.old_enough(comment['when']))
|
|
|
|
else:
|
|
|
|
write_comment = not only_replace
|
2014-09-16 13:41:15 +02:00
|
|
|
|
2014-09-30 11:29:06 +02:00
|
|
|
if write_comment or force:
|
|
|
|
if osc.conf.config['debug']:
|
2018-11-17 09:43:04 +01:00
|
|
|
print('Updating comment')
|
2018-11-09 15:56:54 -06:00
|
|
|
if comment:
|
|
|
|
self.comment.delete(comment['id'])
|
2014-09-16 13:41:15 +02:00
|
|
|
self.comment.add_comment(project_name=project, comment=report)
|
|
|
|
|
2014-09-16 17:59:34 +02:00
|
|
|
def _report_broken_packages(self, info):
|
|
|
|
# Group packages by name
|
|
|
|
groups = defaultdict(list)
|
2019-11-19 08:00:04 +01:00
|
|
|
for package in info.findall('broken_packages/package'):
|
|
|
|
groups[package.get('package')].append(package)
|
2014-09-16 17:59:34 +02:00
|
|
|
|
|
|
|
failing_lines = [
|
2024-05-07 17:55:17 +02:00
|
|
|
f"* Build failed {key} ({', '.join(self._package_url(p) for p in value)})"
|
2019-05-18 17:14:40 +02:00
|
|
|
for key, value in groups.items()
|
2014-09-16 17:59:34 +02:00
|
|
|
]
|
|
|
|
|
2014-09-22 18:41:56 +02:00
|
|
|
report = '\n'.join(failing_lines[:MAX_LINES])
|
|
|
|
if len(failing_lines) > MAX_LINES:
|
2024-05-07 17:55:17 +02:00
|
|
|
report += f'* and more ({len(failing_lines) - MAX_LINES}) ...'
|
2014-09-22 18:41:56 +02:00
|
|
|
return report
|
2014-09-16 17:59:34 +02:00
|
|
|
|
2018-11-09 15:13:58 -06:00
|
|
|
def report_checks(self, info):
|
|
|
|
links_state = {}
|
2019-11-19 08:00:04 +01:00
|
|
|
for check in info.findall('checks/check'):
|
|
|
|
state = check.find('state').text
|
|
|
|
links_state.setdefault(state, [])
|
2024-05-07 17:55:17 +02:00
|
|
|
links_state[state].append(f"[{check.get('name')}]({check.find('url').text})")
|
2018-11-09 15:13:58 -06:00
|
|
|
|
|
|
|
lines = []
|
|
|
|
failure = False
|
|
|
|
for state, links in links_state.items():
|
|
|
|
if len(links) > MAX_LINES:
|
|
|
|
extra = len(links) - MAX_LINES
|
|
|
|
links = links[:MAX_LINES]
|
2024-05-07 17:55:17 +02:00
|
|
|
links.append(f'and {extra} more...')
|
2018-11-09 15:13:58 -06:00
|
|
|
|
2024-05-07 17:55:17 +02:00
|
|
|
lines.append(f'- {state}')
|
2018-11-09 15:13:58 -06:00
|
|
|
if state != 'success':
|
2024-05-07 17:55:17 +02:00
|
|
|
lines.extend([f' - {link}' for link in links])
|
2018-11-09 15:13:58 -06:00
|
|
|
failure = True
|
2014-09-16 13:41:15 +02:00
|
|
|
else:
|
2024-05-07 17:55:17 +02:00
|
|
|
lines[-1] += f": {', '.join(links)}"
|
2014-09-16 14:47:20 +02:00
|
|
|
|
2018-11-09 15:13:58 -06:00
|
|
|
return '\n'.join(lines).strip(), failure
|
2014-09-16 13:41:15 +02:00
|
|
|
|
2019-11-19 08:00:04 +01:00
|
|
|
def report(self, project, force=False):
|
|
|
|
info = self.api.project_status(project)
|
2014-09-19 14:50:57 +02:00
|
|
|
|
2018-11-16 13:36:38 -06:00
|
|
|
# Do not attempt to process projects without staging info, or projects
|
|
|
|
# in a pending state that will change before settling. This avoids
|
|
|
|
# intermediate notifications that may end up being spammy and for
|
|
|
|
# long-lived stagings where checks may be re-triggered multiple times
|
|
|
|
# and thus enter pending state (not seen on first run) which is not
|
|
|
|
# useful to report.
|
2019-11-19 08:00:04 +01:00
|
|
|
if info is None or not self.api.project_status_final(info):
|
2014-10-01 11:54:38 +02:00
|
|
|
return
|
|
|
|
|
2014-09-16 17:59:34 +02:00
|
|
|
report_broken_packages = self._report_broken_packages(info)
|
2018-11-09 15:13:58 -06:00
|
|
|
report_checks, check_failure = self.report_checks(info)
|
2014-09-16 14:47:20 +02:00
|
|
|
|
2018-11-09 15:13:58 -06:00
|
|
|
if report_broken_packages or check_failure:
|
2017-06-15 17:07:19 -05:00
|
|
|
if report_broken_packages:
|
|
|
|
report_broken_packages = 'Broken:\n\n' + report_broken_packages
|
2018-11-09 15:13:58 -06:00
|
|
|
if report_checks:
|
|
|
|
report_checks = 'Checks:\n\n' + report_checks
|
|
|
|
report = '\n\n'.join((report_broken_packages, report_checks))
|
2014-09-19 14:50:57 +02:00
|
|
|
report = report.strip()
|
2018-11-09 15:56:54 -06:00
|
|
|
only_replace = False
|
|
|
|
else:
|
|
|
|
report = 'Congratulations! All fine now.'
|
|
|
|
only_replace = True
|
|
|
|
|
2022-02-14 16:28:05 +01:00
|
|
|
report = self.cc_list(project, info) + report
|
2018-11-09 15:56:54 -06:00
|
|
|
self.update_status_comment(project, report, force=force, only_replace=only_replace)
|
|
|
|
|
|
|
|
if osc.conf.config['debug']:
|
2018-11-17 09:43:04 +01:00
|
|
|
print(project)
|
|
|
|
print('-' * len(project))
|
|
|
|
print(report)
|
2014-09-16 14:47:20 +02:00
|
|
|
|
2022-02-14 16:28:05 +01:00
|
|
|
def cc_list(self, project, info):
|
|
|
|
if not self.api.is_adi_project(project):
|
|
|
|
return ""
|
|
|
|
ccs = set()
|
|
|
|
for req in info.findall('staged_requests/request'):
|
|
|
|
ccs.add("@" + req.get('creator'))
|
|
|
|
str = "Submitters: " + " ".join(sorted(list(ccs))) + "\n\n"
|
|
|
|
return str
|
2014-09-16 13:41:15 +02:00
|
|
|
|
2022-02-18 17:11:46 +01:00
|
|
|
|
2014-09-16 13:41:15 +02:00
|
|
|
if __name__ == '__main__':
|
|
|
|
parser = argparse.ArgumentParser(
|
2018-11-09 16:03:41 -06:00
|
|
|
description='Publish report on staging status as comment on staging project')
|
2014-09-16 13:41:15 +02:00
|
|
|
parser.add_argument('-s', '--staging', type=str, default=None,
|
2018-11-09 15:11:26 -06:00
|
|
|
help='staging project')
|
2014-09-16 14:47:20 +02:00
|
|
|
parser.add_argument('-f', '--force', action='store_true', default=False,
|
2018-11-09 15:11:26 -06:00
|
|
|
help='force a comment to be written')
|
|
|
|
parser.add_argument('-p', '--project', type=str, default='openSUSE:Factory',
|
|
|
|
help='project to check (ex. openSUSE:Factory, openSUSE:Leap:15.1)')
|
2014-09-16 13:41:15 +02:00
|
|
|
parser.add_argument('-d', '--debug', action='store_true', default=False,
|
|
|
|
help='enable debug information')
|
2019-02-19 07:02:19 +01:00
|
|
|
parser.add_argument('-A', '--apiurl', metavar='URL', help='API URL')
|
2014-09-16 13:41:15 +02:00
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
2019-02-19 07:02:19 +01:00
|
|
|
osc.conf.get_config(override_apiurl=args.apiurl)
|
2014-09-16 13:41:15 +02:00
|
|
|
osc.conf.config['debug'] = args.debug
|
|
|
|
|
2018-08-16 21:46:05 -05:00
|
|
|
apiurl = osc.conf.config['apiurl']
|
|
|
|
Config(apiurl, args.project)
|
|
|
|
api = StagingAPI(apiurl, args.project)
|
2018-11-09 16:03:41 -06:00
|
|
|
staging_report = StagingReport(api)
|
2014-09-16 13:41:15 +02:00
|
|
|
|
|
|
|
if args.staging:
|
2019-11-19 08:00:04 +01:00
|
|
|
staging_report.report(api.prj_from_letter(args.staging), args.force)
|
2014-09-16 13:41:15 +02:00
|
|
|
else:
|
2018-07-05 20:43:07 +02:00
|
|
|
for staging in api.get_staging_projects():
|
2019-11-19 08:00:04 +01:00
|
|
|
staging_report.report(staging, args.force)
|