From c475b7f955f0b3abe18331ea8da5d7afac35ffc8 Mon Sep 17 00:00:00 2001 From: Matej Matuska Date: Thu, 3 Aug 2023 16:26:39 +0200 Subject: [PATCH] Add an ERROR report group Currently there is no straightforward way to identify whether a report is an ordinary report or an error report (report generated from an error). An `ERROR` group is introduced in `leapp.reporting.Groups.ERROR` which is used to group error reports. This is utilized in the leapp output to show the number of error reports in the summary. Also error reports in `leapp-report.txt` are now followed by a "(error)" string similar to inhibitors. Jira: OAMG-9532 --- leapp/reporting/__init__.py | 22 ++++++++++++++-- leapp/utils/output.py | 51 ++++++++++++++++++++++++------------- leapp/utils/report.py | 28 +++++++++++++------- 3 files changed, 73 insertions(+), 28 deletions(-) diff --git a/leapp/reporting/__init__.py b/leapp/reporting/__init__.py index 7b87a3867..d911d1aa6 100644 --- a/leapp/reporting/__init__.py +++ b/leapp/reporting/__init__.py @@ -125,6 +125,8 @@ class Groups(BaseListPrimitive): name = 'groups' INHIBITOR = 'inhibitor' + # TODO doc + ERROR = 'error' FAILURE = 'failure' ACCESSIBILITY = 'accessibility' AUTHENTICATION = 'authentication' @@ -186,7 +188,7 @@ def __init__(self, *args, **kwargs): # To allow backwards-compatibility with previous report-schema # Groups that match _DEPRECATION_FLAGS will be shown as flags, the rest as tags -_DEPRECATION_FLAGS = [Groups.INHIBITOR, Groups.FAILURE] +_DEPRECATION_FLAGS = [Groups.INHIBITOR, Groups.FAILURE, Groups.ERROR] class Key(BasePrimitive): @@ -378,12 +380,22 @@ def _create_report_object(entries): _sanitize_entries(entries) for entry in entries: entry.apply(report) + if Groups.INHIBITOR in report.get('groups', []): + if Groups.ERROR in report.get('groups', []): + # *error != inhibitor* + msg = ('Only one of Groups.ERROR and Groups.INHIBITOR can be set at once.' + ' Maybe you were looking for Groups.FAILURE?') + raise ValueError(msg) + # Before removing Flags, the original inhibitor detection worked # by checking the `flags` field; keep the `flags` field until we drop Flags completely # Currently we know that 'flags' does not exist otherwise so it's # safe to just set it report['flags'] = [Groups.INHIBITOR] + if Groups.ERROR in report.get('groups', []): + # see above for explanation + report['flags'] = [Groups.ERROR] return Report(report=report) @@ -431,6 +443,12 @@ def create_report_from_error(error_dict): Convert error json to report json """ error = ErrorModel.create(error_dict) - entries = [Title(error.message), Summary(error.details or ""), Severity('high'), Audience('sysadmin')] + entries = [ + Title(error.message), + Summary(error.details or ""), + Severity('high'), + Audience('sysadmin'), + Groups([Groups.ERROR]) + ] report = _create_report_object(entries) return json.loads(report.dump()['report']) diff --git a/leapp/utils/output.py b/leapp/utils/output.py index ae24a1d99..bcd9c7faa 100644 --- a/leapp/utils/output.py +++ b/leapp/utils/output.py @@ -69,9 +69,9 @@ def _print_report_titles(reports): def report_inhibitors(context_id): # The following imports are required to be here to avoid import loop problems - from leapp.utils.report import fetch_upgrade_report_messages, is_inhibitor # noqa; pylint: disable=import-outside-toplevel + from leapp.utils.report import fetch_upgrade_report_messages, Groups, has_flag_group # noqa; pylint: disable=import-outside-toplevel reports = fetch_upgrade_report_messages(context_id) - inhibitors = [report for report in reports if is_inhibitor(report)] + inhibitors = [report for report in reports if has_flag_group(report, Groups.INHIBITOR)] if inhibitors: text = 'UPGRADE INHIBITED' with pretty_block(text=text, end_text=text, color=Color.red, target=sys.stdout): @@ -110,25 +110,41 @@ def report_errors(errors): print_error(error) -def _filter_reports(reports, severity=None, is_inhibitor=False): +def _filter_reports(reports, severity=None, has_flag=None): # The following imports are required to be here to avoid import loop problems - from leapp.utils.report import is_inhibitor as isinhibitor # noqa; pylint: disable=import-outside-toplevel - if not severity: - return [report for report in reports if isinhibitor(report) == is_inhibitor] - return [report for report in reports if report['severity'] == severity and isinhibitor(report) == is_inhibitor] + from leapp.reporting import Groups # noqa; pylint: disable=import-outside-toplevel + from leapp.utils.report import has_flag_group # noqa; pylint: disable=import-outside-toplevel + + def is_ordinary_report(report): + return not has_flag_group(report, Groups.ERROR) and not has_flag_group(report, Groups.INHIBITOR) + + result = reports + if has_flag is False: + result = [rep for rep in result if is_ordinary_report(rep)] + elif has_flag is not None: + result = [rep for rep in result if has_flag_group(rep, has_flag)] + if severity: + result = [rep for rep in result if rep['severity'] == severity] + + return result def _print_reports_summary(reports): # The following imports are required to be here to avoid import loop problems - from leapp.reporting import Severity # noqa; pylint: disable=import-outside-toplevel + from leapp.reporting import Groups, Severity # noqa; pylint: disable=import-outside-toplevel + + errors = _filter_reports(reports, has_flag=Groups.ERROR) + inhibitors = _filter_reports(reports, has_flag=Groups.INHIBITOR) + + ordinary = _filter_reports(reports, has_flag=False) - inhibitors = _filter_reports(reports, is_inhibitor=True) - high = _filter_reports(reports, Severity.HIGH) - medium = _filter_reports(reports, Severity.MEDIUM) - low = _filter_reports(reports, Severity.LOW) - info = _filter_reports(reports, Severity.INFO) + high = _filter_reports(ordinary, Severity.HIGH) + medium = _filter_reports(ordinary, Severity.MEDIUM) + low = _filter_reports(ordinary, Severity.LOW) + info = _filter_reports(ordinary, Severity.INFO) print('Reports summary:') + print(' Errors: {:5}'.format(len(errors))) print(' Inhibitors: {:5}'.format(len(inhibitors))) print(' HIGH severity reports: {:5}'.format(len(high))) print(' MEDIUM severity reports: {:5}'.format(len(medium))) @@ -151,21 +167,22 @@ def report_info(context_id, report_paths, log_paths, answerfile=None, fail=False from leapp.utils.report import fetch_upgrade_report_messages # noqa; pylint: disable=import-outside-toplevel reports = fetch_upgrade_report_messages(context_id) - high = _filter_reports(reports, Severity.HIGH) - medium = _filter_reports(reports, Severity.MEDIUM) + ordinary = _filter_reports(reports, has_flag=False) + high = _filter_reports(ordinary, Severity.HIGH) + medium = _filter_reports(ordinary, Severity.MEDIUM) color = Color.green if medium or high: color = Color.yellow if fail: - color = Color.bold + color = Color.red with pretty_block("REPORT", target=sys.stdout, color=color): if high or medium: print('HIGH and MEDIUM severity reports:') _print_report_titles(high + medium) + sys.stdout.write('\n') - sys.stdout.write('\n') _print_reports_summary(reports) print( diff --git a/leapp/utils/report.py b/leapp/utils/report.py index d24a17740..a530b5321 100644 --- a/leapp/utils/report.py +++ b/leapp/utils/report.py @@ -51,7 +51,7 @@ def fetch_upgrade_report_messages(context_id): """ :param context_id: ID to identify the needed messages :type context_id: str - :return: All upgrade messages of type "Report" withing the given context + :return: All upgrade messages of type "Report" within the given context """ report_msgs = get_messages(names=['Report', 'ErrorModel'], context=context_id) or [] @@ -94,17 +94,22 @@ def fetch_upgrade_report_messages(context_id): def is_inhibitor(message): + return has_flag_group(message, Groups.INHIBITOR) + + +def has_flag_group(message, group): """ - A helper function to check if a message is inhibiting upgrade. - It takes care of possible differences in report schema, like tags/flags -> groups merge in 1.2.0 + A helper to check if a message has Group which would have been a Flag before the Tags/Flags merge in 1.2.0 """ - # NOTE(ivasilev) As groups and flags are mutual exclusive, let's not bother with report_schema version + # NOTE(borrowed from ivasilev) As groups and flags are mutual exclusive, let's not bother with report_schema version # and just check both fields for inhibitor presence - return Groups.INHIBITOR in message.get('groups', message.get('flags', [])) + return group in message.get('groups', message.get('flags', [])) def importance(message): - if is_inhibitor(message): + if has_flag_group(message, Groups.ERROR): + return -1 + if has_flag_group(message, Groups.INHIBITOR): return 0 return SEVERITY_LEVELS.get(message['severity'], 99) @@ -115,8 +120,13 @@ def generate_report_file(messages_to_report, context, path, report_schema='1.1.0 if path.endswith(".txt"): with io.open(path, 'w', encoding='utf-8') as f: for message in sorted(messages_to_report, key=importance): - f.write(u'Risk Factor: {}{}\n'.format(message['severity'], - ' (inhibitor)' if is_inhibitor(message) else '')) + flag = '' + if has_flag_group(message, Groups.ERROR): + flag = '(error)' + elif has_flag_group(message, Groups.INHIBITOR): + flag = '(inhibitor)' + + f.write(u'Risk Factor: {} {}\n'.format(message['severity'], flag)) f.write(u'Title: {}\n'.format(message['title'])) f.write(u'Summary: {}\n'.format(message['summary'])) remediation = Remediation.from_dict(message.get('detail', {})) @@ -136,7 +146,7 @@ def generate_report_file(messages_to_report, context, path, report_schema='1.1.0 f.write(u'-' * 40 + '\n') elif path.endswith(".json"): with io.open(path, 'w', encoding='utf-8') as f: - # Here all possible convertions will take place + # Here all possible conversions will take place if report_schema_tuple < (1, 1, 0): # report-schema 1.0.0 doesn't have a stable report key # copy list of messages here not to mess the initial structure for possible further invocations