Skip to content

Commit 0ef37eb

Browse files
authored
Merge pull request #33 from cloudlinux/epel
CLOS-3205, CLOS-3230: Rework package conflict actor and add EPEL repo reset
2 parents 9f840f9 + 4eb5d1c commit 0ef37eb

File tree

6 files changed

+259
-116
lines changed

6 files changed

+259
-116
lines changed
Lines changed: 11 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,28 @@
1-
import os
2-
import errno
3-
import shutil
4-
51
from leapp.actors import Actor
62
from leapp.models import InstalledRPM
73
from leapp.tags import DownloadPhaseTag, IPUWorkflowTag
84
from leapp.libraries.common.cllaunch import run_on_cloudlinux
5+
from leapp.libraries.actor import clearpackageconflicts
96

107

118
class ClearPackageConflicts(Actor):
129
"""
13-
Remove several python package files manually to resolve conflicts between versions of packages to be upgraded.
10+
Remove several Python package files manually to resolve conflicts
11+
between versions of packages to be upgraded.
12+
13+
When the corresponding packages are detected,
14+
the conflicting files are removed to allow for an upgrade to the new package versions.
15+
16+
While most packages are handled automatically by the package manager,
17+
some specific packages require direct intervention to resolve conflicts
18+
between their own versions on different OS releases.
1419
"""
1520

1621
name = "clear_package_conflicts"
1722
consumes = (InstalledRPM,)
1823
produces = ()
1924
tags = (DownloadPhaseTag.Before, IPUWorkflowTag)
20-
rpm_lookup = None
21-
22-
def has_package(self, name):
23-
"""
24-
Check whether the package is installed.
25-
Looks only for the package name, nothing else.
26-
"""
27-
if self.rpm_lookup:
28-
return name in self.rpm_lookup
29-
30-
def problem_packages_installed(self, problem_packages):
31-
"""
32-
Check whether any of the problem packages are present in the system.
33-
"""
34-
for pkg in problem_packages:
35-
if self.has_package(pkg):
36-
self.log.debug("Conflicting package {} detected".format(pkg))
37-
return True
38-
return False
39-
40-
def clear_problem_files(self, problem_files, problem_dirs):
41-
"""
42-
Go over the list of problem files and directories and remove them if they exist.
43-
They'll be replaced by the new packages.
44-
"""
45-
for p_dir in problem_dirs:
46-
try:
47-
if os.path.isdir(p_dir):
48-
shutil.rmtree(p_dir)
49-
self.log.debug("Conflicting directory {} removed".format(p_dir))
50-
except OSError as e:
51-
if e.errno != errno.ENOENT:
52-
raise
53-
54-
for p_file in problem_files:
55-
try:
56-
if os.path.isfile(p_file):
57-
os.remove(p_file)
58-
self.log.debug("Conflicting file {} removed".format(p_file))
59-
except OSError as e:
60-
if e.errno != errno.ENOENT:
61-
raise
62-
63-
def alt_python37_handle(self):
64-
"""
65-
These alt-python37 packages are conflicting with their own builds for EL8.
66-
"""
67-
problem_packages = [
68-
"alt-python37-six",
69-
"alt-python37-pytz",
70-
]
71-
problem_files = []
72-
problem_dirs = [
73-
"/opt/alt/python37/lib/python3.7/site-packages/six-1.15.0-py3.7.egg-info",
74-
"/opt/alt/python37/lib/python3.7/site-packages/pytz-2017.2-py3.7.egg-info",
75-
]
76-
77-
if self.problem_packages_installed(problem_packages):
78-
self.clear_problem_files(problem_files, problem_dirs)
79-
80-
def lua_cjson_handle(self):
81-
"""
82-
lua-cjson package is conflicting with the incoming lua-cjson package for EL8.
83-
"""
84-
problem_packages = [
85-
"lua-cjson"
86-
]
87-
problem_files = [
88-
"/usr/lib64/lua/5.1/cjson.so",
89-
"/usr/share/lua/5.1/cjson/tests/bench.lua",
90-
"/usr/share/lua/5.1/cjson/tests/genutf8.pl",
91-
"/usr/share/lua/5.1/cjson/tests/test.lua",
92-
]
93-
problem_dirs = []
94-
95-
if self.problem_packages_installed(problem_packages):
96-
self.clear_problem_files(problem_files, problem_dirs)
9725

9826
@run_on_cloudlinux
9927
def process(self):
100-
# todo: (CLOS-3205) investigate why set is needed here
101-
self.rpm_lookup = [rpm for rpm in self.consume(InstalledRPM)]
102-
self.alt_python37_handle()
28+
clearpackageconflicts.process()
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import os
2+
import errno
3+
import shutil
4+
5+
from leapp.libraries.stdlib import api
6+
from leapp.models import InstalledRPM
7+
8+
9+
def problem_packages_installed(problem_packages, lookup):
10+
"""
11+
Check whether any of the problem packages are present in the system.
12+
"""
13+
for pkg in problem_packages:
14+
if pkg in lookup:
15+
api.current_logger().debug("Conflicting package {} detected".format(pkg))
16+
return True
17+
return False
18+
19+
20+
def clear_problem_files(problem_files, problem_dirs):
21+
"""
22+
Go over the list of problem files and directories and remove them if they exist.
23+
They'll be replaced by the new packages.
24+
"""
25+
for p_dir in problem_dirs:
26+
try:
27+
if os.path.isdir(p_dir):
28+
shutil.rmtree(p_dir)
29+
api.current_logger().debug("Conflicting directory {} removed".format(p_dir))
30+
except OSError as e:
31+
if e.errno != errno.ENOENT:
32+
raise
33+
34+
for p_file in problem_files:
35+
try:
36+
if os.path.isfile(p_file):
37+
os.remove(p_file)
38+
api.current_logger().debug("Conflicting file {} removed".format(p_file))
39+
except OSError as e:
40+
if e.errno != errno.ENOENT:
41+
raise
42+
43+
44+
def alt_python37_handle(package_lookup):
45+
"""
46+
These alt-python37 packages are conflicting with their own builds for EL8.
47+
"""
48+
problem_packages = [
49+
"alt-python37-six",
50+
"alt-python37-pytz",
51+
]
52+
problem_files = []
53+
problem_dirs = [
54+
"/opt/alt/python37/lib/python3.7/site-packages/six-1.15.0-py3.7.egg-info",
55+
"/opt/alt/python37/lib/python3.7/site-packages/pytz-2017.2-py3.7.egg-info",
56+
]
57+
58+
if problem_packages_installed(problem_packages, package_lookup):
59+
clear_problem_files(problem_files, problem_dirs)
60+
61+
62+
def lua_cjson_handle(package_lookup):
63+
"""
64+
lua-cjson package is conflicting with the incoming lua-cjson package for EL8.
65+
"""
66+
problem_packages = [
67+
"lua-cjson"
68+
]
69+
problem_files = [
70+
"/usr/lib64/lua/5.1/cjson.so",
71+
"/usr/share/lua/5.1/cjson/tests/bench.lua",
72+
"/usr/share/lua/5.1/cjson/tests/genutf8.pl",
73+
"/usr/share/lua/5.1/cjson/tests/test.lua",
74+
]
75+
problem_dirs = []
76+
77+
if problem_packages_installed(problem_packages, package_lookup):
78+
clear_problem_files(problem_files, problem_dirs)
79+
80+
81+
def process():
82+
rpm_lookup = set()
83+
# Each InstalledRPM is a list of RPM objects.
84+
# There's a bunch of other fields, but all that we're interested in here is their names.
85+
installed_rpm_messages = api.consume(InstalledRPM)
86+
for rpm_list in installed_rpm_messages:
87+
rpm_names = [item.name for item in rpm_list.items]
88+
rpm_lookup.update(rpm_names)
89+
90+
alt_python37_handle(rpm_lookup)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import pytest
2+
3+
# from leapp import reporting
4+
from leapp.libraries.actor import clearpackageconflicts
5+
6+
7+
@pytest.mark.parametrize(
8+
"problem_pkgs,lookup,expected_res",
9+
(
10+
(["cagefs"], {"cagefs", "dnf"}, True),
11+
(["lve-utils"], {"lve-utils", "dnf"}, True),
12+
(["nonexistent-pkg"], {"cagefs", "dnf"}, False),
13+
(["cagefs"], {"lve-utils", "dnf"}, False),
14+
),
15+
)
16+
def test_problem_packages_installed(problem_pkgs, lookup, expected_res):
17+
assert expected_res == clearpackageconflicts.problem_packages_installed(problem_pkgs, lookup)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from __future__ import print_function
2+
from operator import is_
3+
import os
4+
5+
from leapp.actors import Actor
6+
from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag
7+
from leapp.libraries.common.cllaunch import run_on_cloudlinux
8+
from leapp.libraries.common.backup import backup_and_remove
9+
from leapp.libraries.common.config.version import get_target_major_version
10+
11+
REPO_DIR = '/etc/yum.repos.d'
12+
13+
14+
class RefreshEPEL(Actor):
15+
"""
16+
Check that the EPEL repositories are correctly configured after the upgrade.
17+
18+
Depending on how the upgrade went, the EPEL repositories might still be targeting the old OS version.
19+
This actor checks that the EPEL repositories are correctly configured and if not, it will install the
20+
correct EPEL release package and refresh the repositories.
21+
"""
22+
23+
name = 'refresh_epel'
24+
# We can't depend on InstalledRPM message because by this point
25+
# the system is upgraded and the RPMs are not the same as when the data was collected.
26+
consumes = ()
27+
produces = ()
28+
tags = (ApplicationsPhaseTag.After, IPUWorkflowTag)
29+
30+
def clear_epel_repo_files(self):
31+
for repofile in os.listdir(REPO_DIR):
32+
if repofile.startswith('epel'):
33+
epel_file = os.path.join(REPO_DIR, repofile)
34+
backup_and_remove(epel_file)
35+
36+
def install_epel_release_package(self, target_url):
37+
os.system('dnf install {}'.format(target_url))
38+
self.log.info('EPEL release package installed: {}'.format(target_url))
39+
40+
@run_on_cloudlinux
41+
def process(self):
42+
epel_install_url = 'https://dl.fedoraproject.org/pub/epel/epel-release-latest-{}.noarch.rpm'.format(get_target_major_version())
43+
44+
target_version = int(get_target_major_version())
45+
target_epel_release = epel_install_url.format(target_version)
46+
47+
# EPEL release package name is 'epel-release' and the version should match the target OS version
48+
epel_release_package = 'epel-release'
49+
50+
is_epel_installed = os.system('rpm -q {}'.format(epel_release_package)) == 0
51+
is_correct_version = os.system('rpm -q {}-{}'.format(epel_release_package, target_version)) == 0
52+
epel_files_verified = os.system('rpm -V {}'.format(epel_release_package)) == 0
53+
54+
# It's possible (although unusual) that the correct EPEL release package is installed during the upgrade,
55+
# but the EPEL repository files still point to the old OS version.
56+
# This was observed on client machines before.
57+
58+
if (is_epel_installed and not is_correct_version) or not epel_files_verified:
59+
# If the EPEL release package is installed but not the correct version, remove it
60+
# Same if the files from the package were modified
61+
os.system('rpm -e {}'.format(epel_release_package))
62+
if not is_epel_installed or not is_correct_version or not epel_files_verified:
63+
# Clear the EPEL repository files
64+
self.clear_epel_repo_files()
65+
# Install the correct EPEL release package
66+
self.install_epel_release_package(target_epel_release)
67+
# Logging for clarity
68+
self.log.info('EPEL release package installation invoked for: {}'.format(target_epel_release))

repos/system_upgrade/cloudlinux/actors/replacerpmnewconfigs/actor.py

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
from leapp import reporting
88
from leapp.reporting import Report
99
from leapp.libraries.common.cllaunch import run_on_cloudlinux
10+
from leapp.libraries.common.backup import backup_and_remove, LEAPP_BACKUP_SUFFIX
1011

1112
REPO_DIR = '/etc/yum.repos.d'
12-
REPO_DELETE_MARKERS = ['cloudlinux', 'imunify', 'epel']
13+
# These markers are used to identify which repository files should be directly replaced with new versions.
14+
REPO_DELETE_MARKERS = ['cloudlinux', 'imunify']
15+
# These markers are used to identify which repository files should be replaced with new versions and backed up.
1316
REPO_BACKUP_MARKERS = []
17+
# This suffix is used to identify .rpmnew files that appear after package upgrade.
1418
RPMNEW = '.rpmnew'
15-
LEAPP_BACKUP_SUFFIX = '.leapp-backup'
1619

1720

1821
class ReplaceRpmnewConfigs(Actor):
@@ -30,32 +33,31 @@ def process(self):
3033
deleted_repofiles = []
3134
renamed_repofiles = []
3235

33-
for reponame in os.listdir(REPO_DIR):
34-
if any(mark in reponame for mark in REPO_DELETE_MARKERS) and RPMNEW in reponame:
35-
base_reponame = reponame[:-len(RPMNEW)]
36-
base_path = os.path.join(REPO_DIR, base_reponame)
37-
new_file_path = os.path.join(REPO_DIR, reponame)
36+
for rpmnew_filename in os.listdir(REPO_DIR):
37+
if any(mark in rpmnew_filename for mark in REPO_DELETE_MARKERS) and rpmnew_filename.endswith(RPMNEW):
38+
main_reponame = rpmnew_filename[:-len(RPMNEW)]
39+
main_file_path = os.path.join(REPO_DIR, main_reponame)
40+
rpmnew_file_path = os.path.join(REPO_DIR, rpmnew_filename)
3841

39-
os.unlink(base_path)
40-
os.rename(new_file_path, base_path)
41-
deleted_repofiles.append(base_reponame)
42-
self.log.debug('Yum repofile replaced: {}'.format(base_path))
42+
os.unlink(main_file_path)
43+
os.rename(rpmnew_file_path, main_file_path)
44+
deleted_repofiles.append(main_reponame)
45+
self.log.debug('Yum repofile replaced: {}'.format(main_file_path))
4346

44-
if any(mark in reponame for mark in REPO_BACKUP_MARKERS) and RPMNEW in reponame:
45-
base_reponame = reponame[:-len(RPMNEW)]
46-
base_path = os.path.join(REPO_DIR, base_reponame)
47-
new_file_path = os.path.join(REPO_DIR, reponame)
48-
backup_path = os.path.join(REPO_DIR, base_reponame + LEAPP_BACKUP_SUFFIX)
47+
if any(mark in rpmnew_filename for mark in REPO_BACKUP_MARKERS) and rpmnew_filename.endswith(RPMNEW):
48+
main_reponame = rpmnew_filename[:-len(RPMNEW)]
49+
main_file_path = os.path.join(REPO_DIR, main_reponame)
50+
rpmnew_file_path = os.path.join(REPO_DIR, rpmnew_filename)
4951

50-
os.rename(base_path, backup_path)
51-
os.rename(new_file_path, base_path)
52-
renamed_repofiles.append(base_reponame)
53-
self.log.debug('Yum repofile replaced with backup: {}'.format(base_path))
52+
backup_and_remove(main_file_path)
53+
os.rename(rpmnew_file_path, main_file_path)
54+
renamed_repofiles.append(main_reponame)
55+
self.log.debug('Yum repofile replaced with backup: {}'.format(main_file_path))
5456

5557
# Disable any old repositories.
56-
for reponame in os.listdir(REPO_DIR):
57-
if LEAPP_BACKUP_SUFFIX in reponame:
58-
repofile_path = os.path.join(REPO_DIR, reponame)
58+
for repofile_name in os.listdir(REPO_DIR):
59+
if LEAPP_BACKUP_SUFFIX in repofile_name:
60+
repofile_path = os.path.join(REPO_DIR, repofile_name)
5961
for line in fileinput.input(repofile_path, inplace=True):
6062
if line.startswith('enabled'):
6163
print("enabled = 0")
@@ -66,7 +68,7 @@ def process(self):
6668
deleted_string = '\n'.join(['{}'.format(repofile_name) for repofile_name in deleted_repofiles])
6769
replaced_string = '\n'.join(['{}'.format(repofile_name) for repofile_name in renamed_repofiles])
6870
reporting.create_report([
69-
reporting.Title('CloudLinux repository config files replaced by updated versions'),
71+
reporting.Title('Repository config files replaced by updated versions'),
7072
reporting.Summary(
7173
'One or more RPM repository configuration files '
7274
'have been replaced with new versions provided by the upgraded packages. '

0 commit comments

Comments
 (0)