Skip to content

Commit

Permalink
Add actor name discovery and some unit tests
Browse files Browse the repository at this point in the history
With this checking part should be complete.
  • Loading branch information
fernflower committed Nov 21, 2023
1 parent ceb0a78 commit 6802b63
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ast
import os

from leapp.exceptions import StopActorExecution
Expand All @@ -14,6 +15,40 @@ def _get_rpms_to_check():
return [rpm.format(source=get_source_major_version(), target=get_target_major_version()) for rpm in RPMS_TO_CHECK]


def deduce_actor_name(a_file):
"""A helper to map an actor/library to the actor name"""
if not os.path.exists(a_file):
return ''
with open(a_file) as f:
data = ast.parse(f.read())
# NOTE(ivasilev) Making proper syntax analysis is not the goal here, so let's get away with the bare minimum.
# An actor file will have an Actor ClassDef with a name attribute and at least one function defined
actor = next((obj for obj in data.body if isinstance(obj, ast.ClassDef) and obj.name and
any(isinstance(o, ast.FunctionDef) for o in obj.body)), None)
if not actor:
# Assuming here we are dealing with a library, so let's discover actor file and deduce actor name from it
# actor is expected to be found under ../../actor.py
assumed_actor_file = os.path.join(a_file.split('libraries')[0], 'actor.py')
if not os.path.exists(assumed_actor_file):
# Nothing more we can do - no actor name mapping, return ''
return ''
return deduce_actor_name(assumed_actor_file)
return actor.name


def _run_command(cmd, warning_to_log):
"""
A helper that executes a command and returns an (res, err) tuple.
Upon success results will contain a list with line-by-line output returned by the command.
"""
try:
res = run(get_rpm_files_command)
return (res['stdout'].strip().split('\n'), None)
except CalledProcessError as err:
api.current_logger().warning(warning_to_log)
return (None, err)


def check_for_modifications(dirs=DIRS_TO_SCAN):
"""
This will return a tuple (changes, custom) is case any untypical files or changes to shipped leapp files are
Expand All @@ -22,41 +57,33 @@ def check_for_modifications(dirs=DIRS_TO_SCAN):
"""
rpms = _get_rpms_to_check()
source_of_truth = []
leapp_files = []
# Let's collect data about what should have been installed from rpm
for rpm in rpms:
get_rpm_files_command = ['rpm', '-ql', rpm]
try:
rpm_files = run(get_rpm_files_command)
source_of_truth.extend(rpm_files['stdout'].strip().split())
except CalledProcessError:
api.current_logger().warning(
'Could not get list of installed files from rpm %s'.format(rpm)
)
res, _ = _run_command(['rpm', '-ql', rpm], 'Could not get a list of installed files from rpm %s'.format(rpm))
if res:
source_of_truth.extend(res)
else:
raise StopActorExecution()
leapp_files = []
# Let's collect data about what's really on the system
for directory in dirs:
get_files_command = ['find', directory, '-type', 'f']
try:
files = run(get_files_command)
leapp_files.extend(files['stdout'].strip().split())
except CalledProcessError:
api.current_logger().warning(
'Could not get list of leapp files in %s'.format(directory)
)
res, _ = _run_command(['find', directory, '-type', 'f'],
'Could not get a list of leapp files from %s'.format(directory))
if res:
leapp_files.extend(res)
else:
raise StopActorExecution()
# Let's check for unexpected additions
custom_files = set(leapp_files) - set(source_of_truth)
# Now let's check for modifications
modified_files = []
for rpm in rpms:
try:
modified = run(['rpm', '-V', rpm])
modified_files.extend(modified['stdout'].strip().split())
except CalledProcessError:
api.current_logger().warning(
'Could not check authenticity of the files from %s'.format(rpm)
)
res, _ = _run_command(['rpm', '-V', rpm], 'Could not check authenticity of the files from %s'.format(rpm))
if res:
modified_files.extend([tuple(x.split()) for x in res])
else:
raise StopActorExecution()
# NOTE(ivasilev) Now the fun part TBD - the mapping between actor file / library and actor's
# name
# XXX Leaving blank atm as it's tbd
return ([CustomModifications(actor_name='', filename=f, type='modified') for f in modified_files],
[CustomModifications(actor_name='', filename=f, type='custom') for f in custom_files])
return ([CustomModifications(actor_name=deduce_actor_name(f[1]), filename=f[1], type='modified',
rpm_checks_str=f[0])
for f in modified_files],
[CustomModifications(actor_name=deduce_actor_name(f), filename=f, type='custom') for f in custom_files])
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import pytest

from leapp.libraries.actor import trackcustommodifications
from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked
from leapp.libraries.stdlib import api


FILES_FROM_RPM = """
repos/system_upgrade/el8toel9/actors/xorgdrvfact/libraries/xorgdriverlib.py
repos/system_upgrade/el8toel9/actors/anotheractor/actor.py
repos/system_upgrade/el8toel9/files
"""

FILES_ON_SYSTEM = """
repos/system_upgrade/el8toel9/actors/xorgdrvfact/libraries/xorgdriverlib.py
repos/system_upgrade/el8toel9/actors/anotheractor/actor.py
repos/system_upgrade/el8toel9/files
/some/unrelated/to/leapp/file
repos/system_upgrade/el8toel9/files/file/that/should/not/be/there
repos/system_upgrade/el8toel9/actors/actor/that/should/not/be/there
"""

VERIFIED_FILES = """
.......T. repos/system_upgrade/el8toel9/actors/xorgdrvfact/libraries/xorgdriverlib.py
S.5....T. repos/system_upgrade/el8toel9/actors/anotheractor/actor.py
"""

@pytest.mark.parametrize(
'file,name',
[('repos/system_upgrade/el8toel9/actors/checkblacklistca/actor.py', 'CheckBlackListCA'),
('repos/system_upgrade/el7toel8/actors/checkmemcached/actor.py', 'CheckMemcached'),
('repos/system_upgrade/el7toel8/actors/checkmemcached/libraries/checkmemcached.py', 'CheckMemcached'),
# not a library and not an actor file
('repos/system_upgrade/el7toel8/models/authselect.py', ''),
]
)
def test_deduce_actor_name_from_file(file, name):
assert trackcustommodifications.deduce_actor_name(file) == name


def mocked__run_command(list_of_args, log_message):
if list_of_args == ['rpm', '-ql', 'leapp-upgrade-el7toel8']:
# get source of truth
return FILES_FROM_RPM.strip().split('\n'), None
if list_of_args and list_of_args[0] == 'find':
# listing files in directory
return FILES_ON_SYSTEM.strip().split('\n'), None
if list_of_args == ['rpm', '-V', 'leapp-upgrade-el7toel8']:
# checking authenticity
return VERIFIED_FILES.strip().split('\n'), None


def test_check_for_modifications(monkeypatch):
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', src_ver='7.9', dst_ver='8.4'))
monkeypatch.setattr(trackcustommodifications, '_run_command', mocked__run_command)
modified, custom = trackcustommodifications.check_for_modifications()
assert len(modified) == 2
assert modified[0].filename == 'repos/system_upgrade/el8toel9/actors/xorgdrvfact/libraries/xorgdriverlib.py'
assert modified[0].rpm_checks_str == '.......T.'
assert len(custom) == 3
assert custom[0].filename == '/some/unrelated/to/leapp/file'
assert custom[0].rpm_checks_str == ''
1 change: 1 addition & 0 deletions repos/system_upgrade/common/models/custommodifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ class CustomModifications(Model):
filename = fields.String()
actor_name = fields.String()
type = fields.StringEnum(choices=['custom', 'modified'])
rpm_checks_str = fields.String(default='')

0 comments on commit 6802b63

Please sign in to comment.