Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion leapp/dialogs/dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Dialog(object):
address values.
"""

def __init__(self, scope, reason, title=None, components=None):
def __init__(self, scope, reason, title=None, components=None, key=None):
"""

:param scope: Unique scope identifier for the data to be stored in the answer files. Scope + component key
Expand All @@ -30,11 +30,14 @@ def __init__(self, scope, reason, title=None, components=None):
:type title: str
:param components: Components to display in the given order in the dialog
:type components: tuple(leapp.dialogs.components.Component)
:param key: Key to appear in the dialog-related report entry
:type key: str
"""
self.components = components or self.components
self.title = title
self.scope = scope
self.reason = reason
self.key = key
self._store = None
self._min_label_width = None

Expand Down
6 changes: 5 additions & 1 deletion leapp/messaging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@ def register_dialog(self, dialog, actor):
userchoices = dialog.get_answers(self._answers)
if not userchoices:
# produce DialogModel messages for all the dialogs that don't have answers in answerfile
self.produce(DialogModel(actor=actor.name, answerfile_sections=dialog.answerfile_sections), actor)
stable_key = dialog.key if dialog.key else hashlib.sha1(
','.join(sorted(dialog.answerfile_sections.keys())).encode('utf-8')).hexdigest()
self.produce(DialogModel(actor=actor.name,
answerfile_sections=dialog.answerfile_sections,
key=stable_key), actor)
else:
# update dialogs with answers from answerfile. That is necessary for proper answerfile generation
for component, value in userchoices.items():
Expand Down
1 change: 1 addition & 0 deletions leapp/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class DialogModel(Model):
answerfile_sections = fields.JSON()
actor = fields.String()
details = fields.Nullable(fields.String())
key = fields.Nullable(fields.String())


def get_models():
Expand Down
51 changes: 51 additions & 0 deletions leapp/reporting/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import datetime
import json
import hashlib
import os
import six

from leapp.compat import string_types
from leapp.logger import configure_logger
from leapp.models import fields, Model, ErrorModel
from leapp.topics import ReportTopic
from leapp.libraries.stdlib.api import produce
Expand Down Expand Up @@ -125,6 +129,18 @@ def __init__(self, value=None):
self._value = value


class Key(BasePrimitive):
"""Stable identifier for report entries."""
name = 'key'

def __init__(self, uuid):
# uuid is allowed to be string or unicode only, checking in py2/py3 friendly mode
if not isinstance(uuid, six.string_types):
raise ValueError('Key value should be a string.')

self._value = uuid


class Tags(BasePrimitive):
"""Report tags"""
name = 'tags'
Expand Down Expand Up @@ -272,6 +288,41 @@ def _sanitize_entries(entries):
entries.append(Severity('info'))
if not any(isinstance(e, Audience) for e in entries):
entries.append(Audience('sysadmin'))
_check_stable_key(entries, raise_if_undefined=os.getenv('LEAPP_DEVEL_FIXED_REPORT_KEY', '0') != '0')


def _check_stable_key(entries, raise_if_undefined=False):
"""
Inserts a Key into the entries of the report if it doesn't exist.

The resulting id is calculated from the mandatory report
entries (severity, audience, title).
Please note that the original entries list is modified.
"""
def _find_entry(cls):
entry = next((e for e in entries if isinstance(e, cls)), None)
if entry:
return entry._value

logger = configure_logger()
key = _find_entry(Key)
if key is not None:
# Key is already there, check that it's not an empty string
if not key.strip():
logger.error('An empty string is specified as Key')
raise ValueError('Key can not be an empty string.')
return

if raise_if_undefined:
raise ValueError('Stable Key report entry with specified unique value not provided')

# No Key found - let's generate one!
key_str = u'{severity}:{audience}:{title}'.format(severity=_find_entry(Severity),
audience=_find_entry(Audience),
title=_find_entry(Title))
key_value = hashlib.sha1(key_str.encode('utf-8')).hexdigest()
entries.append(Key(key_value))
logger.warning('Stable Key report entry not provided, dynamically generating one - {}'.format(key_value))


def _create_report_object(entries):
Expand Down
1 change: 1 addition & 0 deletions leapp/utils/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def generate_report_file(messages_to_report, context, path):
remediation = Remediation.from_dict(message.get('detail', {}))
if remediation:
f.write('Remediation: {}\n'.format(remediation))
f.write('Key: {}\n'.format(message['key']))
f.write('-' * 40 + '\n')
elif path.endswith(".json"):
with open(path, 'w') as f:
Expand Down
70 changes: 70 additions & 0 deletions tests/scripts/test_reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
_DEPRECATION_SEVERITY_THRESHOLD,
create_report_from_deprecation,
create_report_from_error,
_create_report_object,
Audience, Key, Title, Summary, Severity, RelatedResource
)


Expand Down Expand Up @@ -103,6 +105,15 @@ def test_convert_from_error_to_report():
assert report['title'] == 'The system is not registered or subscribed.'
assert report['audience'] == 'sysadmin'
assert report['summary'] == 'Some other info that should go to report summary'
# make sure key argument is there
error_dict_key_not_specified = {
'message': 'The system is not registered or subscribed.',
'time': '2019-11-19T05:13:04.447562Z',
'details': 'Some other info that should go to report summary',
'actor': 'verify_check_results',
'severity': 'error'}
report = create_report_from_error(error_dict_key_not_specified)
assert "key" in report


def test_create_report_from_deprecation():
Expand All @@ -128,3 +139,62 @@ def test_create_report_from_deprecation():
assert data['reason'] in report['summary']
assert data['since'] in report['summary']
assert data['line'] in report['summary']
# make sure key argument is there
assert 'key' in report


def test_create_report_stable_key(monkeypatch):
report_entries1 = [Title('Some report title'), Summary('Some summary not used for dynamical key generation'),
Audience('sysadmin')]
report_entries2 = [Title('Some report title'),
Summary('Another summary not used for dynamical key generation'), Audience('sysadmin')]
report_entries_with_severity = [Title('Some report title'),
Summary('Another summary not used for dynamical key generation'),
Severity('high')]
report_entries_with_audience = [Title('Some report title'),
Summary('Another summary not used for dynamical key generation'),
Audience('developer')]
report_entries_fixed_key = [Title('Some report title'), Summary('Different summary'), Key('42')]
report_entries_dialog1 = [Title('Some report title'), RelatedResource('dialog', 'unanswered_section'),
Audience('sysadmin'), Summary('Summary')]
report_entries_dialog_key_set = [Title('Some report title'), RelatedResource('dialog', 'unanswered_section'),
RelatedResource('dialog', 'another_unanswered_section'), Audience('sysadmin'),
Summary('Summary'), Key('dialogkey42')]
report1 = _create_report_object(report_entries1)
report2 = _create_report_object(report_entries2)
report_with_severity = _create_report_object(report_entries_with_severity)
report_with_audience = _create_report_object(report_entries_with_audience)
report_fixed_key = _create_report_object(report_entries_fixed_key)
report_dialog1 = _create_report_object(report_entries_dialog1)
report_dialog_key_set = _create_report_object(report_entries_dialog_key_set)
# check that all reports have key field
for report in [report1, report2, report_with_severity, report_with_audience, report_fixed_key]:
assert "key" in report.report
# check that reports with same title but different summary have same dynamically generated key
assert report1.report["key"] == report2.report["key"]
# check that entries different in severity only will have different generated keys
assert report2.report["severity"] != report_with_severity.report["severity"]
assert report2.report["key"] != report_with_severity.report["key"]
# check that entries different in audience only will have different generated keys
assert report2.report["audience"] != report_with_audience.report["audience"]
assert report2.report["key"] != report_with_audience.report["key"]
# check that key from provided Key entry is taken
assert report_fixed_key.report["key"] == "42"
# check that specific dialog related resources are not considered in key generation
assert report_dialog1.report["key"] == report1.report["key"]
# check that providing a key for the dialog report is a solution
assert report_dialog1.report["key"] != report_dialog_key_set.report["key"]
assert report_dialog_key_set.report["key"] == "dialogkey42"
# check that empty string can't serve as report Key
with pytest.raises(ValueError) as err:
_create_report_object([Title('A title'), Summary('A summary'), Key('')])
assert str(err.value) == 'Key can not be an empty string.'
# check that if LEAPP_DEVEL_FIXED_REPORT_KEY is set and there is no key in the report - a ValueError is raised
monkeypatch.setenv('LEAPP_DEVEL_FIXED_REPORT_KEY', '1')
with pytest.raises(ValueError):
_create_report_object([Title('A title'), Summary('A summary')])
# check that Key accepts string parameters only
for bad_uuid in [42, object(), 42.42]:
with pytest.raises(ValueError) as err:
Key(bad_uuid)
assert str(err.value) == 'Key value should be a string.'