From 09046ed6042ad58bf9543fbb18a57a897ce69481 Mon Sep 17 00:00:00 2001 From: Andrew Lukoshko Date: Mon, 6 Mar 2023 16:40:31 +0100 Subject: [PATCH] Update ELevate patch --- SOURCES/leapp-repository-0.16.0-elevate.patch | 1309 +++++++++++++++-- SPECS/leapp-repository.spec | 2 +- 2 files changed, 1205 insertions(+), 106 deletions(-) diff --git a/SOURCES/leapp-repository-0.16.0-elevate.patch b/SOURCES/leapp-repository-0.16.0-elevate.patch index 7a80324..84c240d 100644 --- a/SOURCES/leapp-repository-0.16.0-elevate.patch +++ b/SOURCES/leapp-repository-0.16.0-elevate.patch @@ -726,7 +726,7 @@ index 4de458b..c82651d 100644 -You can reach us at IRC: `#leapp` on freenode. +For more information about Leapp and additional tutorials, visit the [official Leapp documentation](https://leapp.readthedocs.io/en/latest/tutorials.html). diff --git a/commands/command_utils.py b/commands/command_utils.py -index da62c50..a8e7d76 100644 +index da62c50..f062f78 100644 --- a/commands/command_utils.py +++ b/commands/command_utils.py @@ -12,7 +12,7 @@ LEAPP_UPGRADE_FLAVOUR_DEFAULT = 'default' @@ -738,20 +738,74 @@ index da62c50..a8e7d76 100644 def check_version(version): +@@ -68,9 +68,36 @@ def get_os_release_version_id(filepath): + + :return: `str` version_id + """ +- with open(filepath) as f: +- data = dict(l.strip().split('=', 1) for l in f.readlines() if '=' in l) +- return data.get('VERSION_ID', '').strip('"') ++ try: ++ with open(filepath) as f: ++ data = dict(l.strip().split('=', 1) for l in f.readlines() if '=' in l) ++ return data.get('VERSION_ID', '').strip('"') ++ except OSError as e: ++ raise CommandError( ++ "Unable to read system OS release from file {}, " ++ "error: {}".format( ++ filepath, ++ e.strerror ++ )) ++ ++ ++def get_os_release_id(filepath): ++ """ ++ Retrieve data about System OS ID from provided file. ++ ++ :return: `str` version_id ++ """ ++ try: ++ with open(filepath) as f: ++ data = dict(l.strip().split('=', 1) for l in f.readlines() if '=' in l) ++ return data.get('ID', '').strip('"') ++ except OSError as e: ++ raise CommandError( ++ "Unable to read system OS ID from file {}, " ++ "error: {}".format( ++ filepath, ++ e.strerror ++ )) + + + def get_upgrade_paths_config(): diff --git a/commands/upgrade/__init__.py b/commands/upgrade/__init__.py -index c9c2741..f4f27dd 100644 +index c9c2741..911d11d 100644 --- a/commands/upgrade/__init__.py +++ b/commands/upgrade/__init__.py -@@ -86,7 +86,7 @@ def upgrade(args, breadcrumbs): +@@ -18,6 +18,7 @@ from leapp.utils.output import beautify_actor_exception, report_errors, report_i + + @command('upgrade', help='Upgrade the current system to the next available major version.') + @command_opt('resume', is_flag=True, help='Continue the last execution after it was stopped (e.g. after reboot)') ++@command_opt('nowarn', is_flag=True, help='Do not display interactive warnings') + @command_opt('reboot', is_flag=True, help='Automatically performs reboot when requested.') + @command_opt('whitelist-experimental', action='append', metavar='ActorName', help='Enable experimental actors') + @command_opt('debug', is_flag=True, help='Enable debug mode', inherit=False) +@@ -86,7 +87,13 @@ def upgrade(args, breadcrumbs): workflow = repositories.lookup_workflow('IPUWorkflow')(auto_reboot=args.reboot) util.process_whitelist_experimental(repositories, workflow, configuration, logger) util.warn_if_unsupported(configuration) - with beautify_actor_exception(): ++ ++ if not args.resume and not args.nowarn: ++ if not util.ask_to_continue(): ++ logger.info("Upgrade cancelled by user") ++ sys.exit(1) ++ + with util.format_actor_exceptions(logger): logger.info("Using answerfile at %s", answerfile_path) workflow.load_answers(answerfile_path, userchoices_path) -@@ -98,6 +98,8 @@ def upgrade(args, breadcrumbs): +@@ -98,6 +105,8 @@ def upgrade(args, breadcrumbs): logger.info("Answerfile will be created at %s", answerfile_path) workflow.save_answers(answerfile_path, userchoices_path) @@ -760,7 +814,7 @@ index c9c2741..f4f27dd 100644 report_errors(workflow.errors) report_inhibitors(context) util.generate_report_files(context, report_schema) -@@ -106,6 +108,7 @@ def upgrade(args, breadcrumbs): +@@ -106,6 +115,7 @@ def upgrade(args, breadcrumbs): report_info(report_files, log_files, answerfile_path, fail=workflow.failure) if workflow.failure: @@ -769,16 +823,17 @@ index c9c2741..f4f27dd 100644 diff --git a/commands/upgrade/util.py b/commands/upgrade/util.py -index 75ffa6a..379d480 100644 +index 75ffa6a..27970b4 100644 --- a/commands/upgrade/util.py +++ b/commands/upgrade/util.py -@@ -2,18 +2,23 @@ import functools +@@ -2,18 +2,24 @@ import functools import itertools import json import os +import sys import shutil import tarfile ++import six.moves from datetime import datetime +from contextlib import contextmanager @@ -790,7 +845,7 @@ index 75ffa6a..379d480 100644 from leapp.utils import audit from leapp.utils.audit import get_checkpoints, get_connection, get_messages -from leapp.utils.output import report_unsupported -+from leapp.utils.output import report_unsupported, pretty_block_text ++from leapp.utils.output import report_unsupported, pretty_block_text, pretty_block, Color from leapp.utils.report import fetch_upgrade_report_messages, generate_report_file +from leapp.models import ErrorModel + @@ -798,7 +853,52 @@ index 75ffa6a..379d480 100644 def disable_database_sync(): -@@ -236,3 +241,61 @@ def process_report_schema(args, configuration): +@@ -167,6 +173,44 @@ def warn_if_unsupported(configuration): + report_unsupported(devel_vars, configuration["whitelist_experimental"]) + + ++def ask_to_continue(): ++ """ ++ Pause before starting the upgrade, warn the user about potential conseqences ++ and ask for confirmation. ++ Only done on whitelisted OS. ++ ++ :return: True if it's OK to continue, False if the upgrade should be interrupted. ++ """ ++ ++ ask_on_os = ['cloudlinux'] ++ os_id = command_utils.get_os_release_id('/etc/os-release') ++ ++ if os_id not in ask_on_os: ++ return True ++ ++ with pretty_block( ++ text="Upgrade workflow initiated", ++ end_text="Continue?", ++ target=sys.stdout, ++ color=Color.bold, ++ ): ++ warn_msg = ( ++ "Past this point, Leapp will begin making changes to your system.\n" ++ "An improperly or incompletely configured upgrade may break the system, " ++ "up to and including making it *completely inaccessible*.\n" ++ "Even if you've followed all the preparation steps correctly, " ++ "the chance of the upgrade going wrong remains non-zero.\n" ++ "Make sure you've run the pre-check, checked the logs and reports, and have a backup prepared." ++ ) ++ print(warn_msg) ++ ++ response = "" ++ while response not in ["y", "n"]: ++ response = six.moves.input("Y/N> ").lower() ++ ++ return response == "y" ++ ++ + def handle_output_level(args): + """ + Set environment variables following command line arguments. +@@ -236,3 +280,61 @@ def process_report_schema(args, configuration): raise CommandError('--report-schema version can not be greater that the ' 'actual {} one.'.format(default_report_schema)) return args.report_schema or default_report_schema @@ -1008,6 +1108,141 @@ index 0000000..2c935d7 + reporting.Flags([reporting.Flags.INHIBITOR]), + reporting.Remediation(hint=remediation), + ]) +diff --git a/repos/system_upgrade/cloudlinux/actors/checkpanelmemory/actor.py b/repos/system_upgrade/cloudlinux/actors/checkpanelmemory/actor.py +new file mode 100644 +index 0000000..1b1ffbc +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/actors/checkpanelmemory/actor.py +@@ -0,0 +1,20 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import checkpanelmemory ++from leapp.libraries.common.cllaunch import run_on_cloudlinux ++from leapp.models import MemoryInfo, InstalledControlPanel, Report ++from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++ ++ ++class CheckPanelMemory(Actor): ++ """ ++ Check if the system has enough memory for the corresponding panel. ++ """ ++ ++ name = 'check_panel_memory' ++ consumes = (MemoryInfo, InstalledControlPanel,) ++ produces = (Report,) ++ tags = (ChecksPhaseTag, IPUWorkflowTag) ++ ++ @run_on_cloudlinux ++ def process(self): ++ checkpanelmemory.process() +diff --git a/repos/system_upgrade/cloudlinux/actors/checkpanelmemory/libraries/checkpanelmemory.py b/repos/system_upgrade/cloudlinux/actors/checkpanelmemory/libraries/checkpanelmemory.py +new file mode 100644 +index 0000000..3d77ec1 +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/actors/checkpanelmemory/libraries/checkpanelmemory.py +@@ -0,0 +1,55 @@ ++from leapp import reporting ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.stdlib import api ++from leapp.models import MemoryInfo, InstalledControlPanel ++ ++from leapp.libraries.common.detectcontrolpanel import ( ++ UNKNOWN_NAME, ++ INTEGRATED_NAME, ++ CPANEL_NAME, ++) ++ ++required_memory = { ++ UNKNOWN_NAME: 1536 * 1024, # 1.5 Gb ++ INTEGRATED_NAME: 1536 * 1024, # 1.5 Gb ++ CPANEL_NAME: 2048 * 1024, # 2 Gb ++} ++ ++ ++def _check_memory(panel, mem_info): ++ msg = {} ++ ++ min_req = required_memory[panel] ++ is_ok = mem_info.mem_total >= min_req ++ msg = {} if is_ok else {"detected": mem_info.mem_total, "minimal_req": min_req} ++ ++ return msg ++ ++ ++def process(): ++ panel = next(api.consume(InstalledControlPanel), None) ++ memoryinfo = next(api.consume(MemoryInfo), None) ++ if panel is None: ++ raise StopActorExecutionError(message=("Missing information about the installed web panel.")) ++ if memoryinfo is None: ++ raise StopActorExecutionError(message=("Missing information about system memory.")) ++ ++ minimum_req_error = _check_memory(panel.name, memoryinfo) ++ ++ if minimum_req_error: ++ title = "Minimum memory requirements for panel {} are not met".format(panel.name) ++ summary = ( ++ "Insufficient memory may result in an instability of the upgrade process." ++ " This can cause an interruption of the process," ++ " which can leave the system in an unusable state. Memory detected:" ++ " {} KiB, required: {} KiB".format(minimum_req_error["detected"], minimum_req_error["minimal_req"]) ++ ) ++ reporting.create_report( ++ [ ++ reporting.Title(title), ++ reporting.Summary(summary), ++ reporting.Severity(reporting.Severity.HIGH), ++ reporting.Tags([reporting.Tags.SANITY]), ++ reporting.Flags([reporting.Flags.INHIBITOR]), ++ ] ++ ) +diff --git a/repos/system_upgrade/cloudlinux/actors/checkpanelmemory/tests/test_checkpanelmemory.py b/repos/system_upgrade/cloudlinux/actors/checkpanelmemory/tests/test_checkpanelmemory.py +new file mode 100644 +index 0000000..7a3c0be +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/actors/checkpanelmemory/tests/test_checkpanelmemory.py +@@ -0,0 +1,42 @@ ++from leapp import reporting ++from leapp.libraries.actor import checkmemory ++from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked ++from leapp.libraries.stdlib import api ++from leapp.models import MemoryInfo, InstalledControlPanel ++ ++from leapp.libraries.common.detectcontrolpanel import ( ++ UNKNOWN_NAME, ++ INTEGRATED_NAME, ++ CPANEL_NAME, ++) ++ ++ ++def test_check_memory_low(monkeypatch): ++ minimum_req_error = [] ++ monkeypatch.setattr(api, "current_actor", CurrentActorMocked()) ++ minimum_req_error = checkmemory._check_memory( ++ InstalledControlPanel(name=INTEGRATED_NAME), MemoryInfo(mem_total=1024) ++ ) ++ assert minimum_req_error ++ ++ ++def test_check_memory_high(monkeypatch): ++ minimum_req_error = [] ++ monkeypatch.setattr(api, "current_actor", CurrentActorMocked()) ++ minimum_req_error = checkmemory._check_memory( ++ InstalledControlPanel(name=CPANEL_NAME), MemoryInfo(mem_total=16273492) ++ ) ++ assert not minimum_req_error ++ ++ ++def test_report(monkeypatch): ++ title_msg = "Minimum memory requirements for panel {} are not met".format( ++ UNKNOWN_NAME ++ ) ++ monkeypatch.setattr(api, "current_actor", CurrentActorMocked()) ++ monkeypatch.setattr(api, "consume", lambda x: iter([MemoryInfo(mem_total=129)])) ++ monkeypatch.setattr(reporting, "create_report", create_report_mocked()) ++ checkmemory.process() ++ assert reporting.create_report.called ++ assert title_msg == reporting.create_report.report_fields["title"] ++ assert reporting.Flags.INHIBITOR in reporting.create_report.report_fields["flags"] diff --git a/repos/system_upgrade/cloudlinux/actors/checkrhnclienttools/actor.py b/repos/system_upgrade/cloudlinux/actors/checkrhnclienttools/actor.py new file mode 100644 index 0000000..a1c1cee @@ -1264,49 +1499,314 @@ index 0000000..cd6801b + except OSError as e: + if e.errno != errno.ENOENT: + raise +diff --git a/repos/system_upgrade/cloudlinux/actors/clmysqlrepositorysetup/actor.py b/repos/system_upgrade/cloudlinux/actors/clmysqlrepositorysetup/actor.py +new file mode 100644 +index 0000000..4651580 +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/actors/clmysqlrepositorysetup/actor.py +@@ -0,0 +1,35 @@ ++from leapp.actors import Actor ++from leapp.reporting import Report ++from leapp.libraries.actor import clmysqlrepositorysetup ++from leapp.models import ( ++ CustomTargetRepository, ++ CustomTargetRepositoryFile, ++ InstalledMySqlTypes, ++ RpmTransactionTasks, ++ InstalledRPM, ++) ++from leapp.tags import FactsPhaseTag, IPUWorkflowTag ++from leapp.libraries.common.cllaunch import run_on_cloudlinux ++ ++ ++class ClMysqlRepositorySetup(Actor): ++ """ ++ Gather data on what MySQL/MariaDB variant is installed on the system, if any. ++ Then prepare the custom repository data and the corresponding file ++ to be sent to the target environment creator. ++ """ ++ ++ name = "cl_mysql_repository_setup" ++ consumes = (InstalledRPM,) ++ produces = ( ++ CustomTargetRepository, ++ CustomTargetRepositoryFile, ++ InstalledMySqlTypes, ++ RpmTransactionTasks, ++ Report, ++ ) ++ tags = (FactsPhaseTag, IPUWorkflowTag) ++ ++ @run_on_cloudlinux ++ def process(self): ++ clmysqlrepositorysetup.process() +diff --git a/repos/system_upgrade/cloudlinux/actors/clmysqlrepositorysetup/libraries/clmysqlrepositorysetup.py b/repos/system_upgrade/cloudlinux/actors/clmysqlrepositorysetup/libraries/clmysqlrepositorysetup.py +new file mode 100644 +index 0000000..2d73dbb +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/actors/clmysqlrepositorysetup/libraries/clmysqlrepositorysetup.py +@@ -0,0 +1,218 @@ ++import os ++ ++from leapp.models import ( ++ InstalledMySqlTypes, ++ CustomTargetRepositoryFile, ++ CustomTargetRepository, ++ RpmTransactionTasks, ++ InstalledRPM, ++ Module ++) ++from leapp.libraries.stdlib import api ++from leapp.libraries.common import repofileutils ++from leapp import reporting ++from leapp.libraries.common.clmysql import get_clmysql_type, get_pkg_prefix, MODULE_STREAMS ++ ++REPO_DIR = '/etc/yum.repos.d' ++TEMP_DIR = '/var/lib/leapp/yum_custom_repofiles' ++REPOFILE_SUFFIX = ".repo" ++LEAPP_COPY_SUFFIX = "_leapp_custom.repo" ++CL_MARKERS = ['cl-mysql', 'cl-mariadb', 'cl-percona'] ++MARIA_MARKERS = ['MariaDB'] ++MYSQL_MARKERS = ['mysql-community'] ++OLD_MYSQL_VERSIONS = ['5.7', '5.6', '5.5'] ++ ++ ++def produce_leapp_repofile_copy(repofile_data, repo_name): ++ """ ++ Create a copy of an existing Yum repository config file, modified ++ to be used during the Leapp transaction. ++ It will be placed inside the isolated overlay environment Leapp runs the upgrade from. ++ """ ++ if not os.path.isdir(TEMP_DIR): ++ os.makedirs(TEMP_DIR) ++ leapp_repofile = repo_name + LEAPP_COPY_SUFFIX ++ leapp_repo_path = os.path.join(TEMP_DIR, leapp_repofile) ++ if os.path.exists(leapp_repo_path): ++ os.unlink(leapp_repo_path) ++ ++ api.current_logger().debug('Producing a Leapp repofile copy: {}'.format(leapp_repo_path)) ++ repofileutils.save_repofile(repofile_data, leapp_repo_path) ++ api.produce(CustomTargetRepositoryFile(file=leapp_repo_path)) ++ ++ ++def build_install_list(prefix): ++ """ ++ Find the installed cl-mysql packages that match the active ++ cl-mysql type as per Governor config. ++ ++ :param prefix: Package name prefix to search for. ++ :return: List of matching packages. ++ """ ++ to_upgrade = [] ++ if prefix: ++ for rpm_pkgs in api.consume(InstalledRPM): ++ for pkg in rpm_pkgs.items: ++ if (pkg.name.startswith(prefix)): ++ to_upgrade.append(pkg.name) ++ api.current_logger().debug('cl-mysql packages to upgrade: {}'.format(to_upgrade)) ++ return to_upgrade ++ ++ ++def process(): ++ mysql_types = [] ++ clmysql_type = None ++ ++ for repofile_full in os.listdir(REPO_DIR): ++ # Don't touch non-repository files or copied repofiles created by Leapp. ++ if repofile_full.endswith(LEAPP_COPY_SUFFIX) or not repofile_full.endswith(REPOFILE_SUFFIX): ++ continue ++ # Cut the .repo part to get only the name. ++ repofile_name = repofile_full[:-5] ++ full_repo_path = os.path.join(REPO_DIR, repofile_full) ++ ++ # Parse any repository files that may have something to do with MySQL or MariaDB. ++ api.current_logger().debug('Processing repofile {}, full path: {}'.format(repofile_full, full_repo_path)) ++ ++ # Process CL-provided options. ++ if any(mark in repofile_name for mark in CL_MARKERS): ++ repofile_data = repofileutils.parse_repofile(full_repo_path) ++ api.current_logger().debug('Data from repofile: {}'.format(repofile_data.data)) ++ ++ # Were any repositories enabled? ++ for repo in repofile_data.data: ++ # cl-mysql URLs look like this: ++ # baseurl=http://repo.cloudlinux.com/other/cl$releasever/mysqlmeta/cl-mariadb-10.3/$basearch/ ++ # We don't want any duplicate repoid entries. ++ repo.repoid = repo.repoid + '-8' ++ # releasever may be something like 8.6, while only 8 is acceptable. ++ repo.baseurl = repo.baseurl.replace('/cl$releasever/', '/cl8/') ++ # mysqlclient is usually disabled when installed from CL MySQL Governor. ++ # However, it should be enabled for the Leapp upgrade, seeing as some packages ++ # from it won't update otherwise. ++ ++ if repo.enabled or repo.repoid == 'mysqclient-8': ++ mysql_types.append('cloudlinux') ++ clmysql_type = get_clmysql_type() ++ api.current_logger().debug('Generating custom cl-mysql repo: {}'.format(repo.repoid)) ++ api.produce(CustomTargetRepository( ++ repoid=repo.repoid, ++ name=repo.name, ++ baseurl=repo.baseurl, ++ enabled=True, ++ )) ++ ++ if any(repo.enabled for repo in repofile_data.data): ++ produce_leapp_repofile_copy(repofile_data, repofile_name) ++ ++ # Process MariaDB options. ++ elif any(mark in repofile_name for mark in MARIA_MARKERS): ++ repofile_data = repofileutils.parse_repofile(full_repo_path) ++ ++ for repo in repofile_data.data: ++ # Maria URLs look like this: ++ # baseurl = https://archive.mariadb.org/mariadb-10.3/yum/centos/7/x86_64 ++ # baseurl = https://archive.mariadb.org/mariadb-10.7/yum/centos7-ppc64/ ++ # We want to replace the 7 in OS name after /yum/ ++ repo.repoid = repo.repoid + '-8' ++ if repo.enabled: ++ mysql_types.append('mariadb') ++ url_parts = repo.baseurl.split('yum') ++ url_parts[1] = 'yum' + url_parts[1].replace('7', '8') ++ repo.baseurl = ''.join(url_parts) ++ ++ api.current_logger().debug('Generating custom MariaDB repo: {}'.format(repo.repoid)) ++ api.produce(CustomTargetRepository( ++ repoid=repo.repoid, ++ name=repo.name, ++ baseurl=repo.baseurl, ++ enabled=repo.enabled, ++ )) ++ ++ if any(repo.enabled for repo in repofile_data.data): ++ # Since MariaDB URLs have major versions written in, we need a new repo file ++ # to feed to the target userspace. ++ produce_leapp_repofile_copy(repofile_data, repofile_name) ++ ++ # Process MySQL options. ++ elif any(mark in repofile_name for mark in MYSQL_MARKERS): ++ repofile_data = repofileutils.parse_repofile(full_repo_path) ++ ++ for repo in repofile_data.data: ++ if repo.enabled: ++ mysql_types.append('mysql') ++ # MySQL package repos don't have these versions available for EL8 anymore. ++ # There'll be nothing to upgrade to. ++ # CL repositories do provide them, though. ++ if any(ver in repo.name for ver in OLD_MYSQL_VERSIONS): ++ reporting.create_report([ ++ reporting.Title('An old MySQL version will no longer be available in EL8'), ++ reporting.Summary( ++ 'A yum repository for an old MySQL version is enabled on this system. ' ++ 'It will no longer be available on the target system. ' ++ 'This situation cannot be automatically resolved by Leapp. ' ++ 'Problematic repository: {0}'.format(repo.repoid) ++ ), ++ reporting.Severity(reporting.Severity.MEDIUM), ++ reporting.Tags([reporting.Tags.REPOSITORY]), ++ reporting.Flags([reporting.Flags.INHIBITOR]), ++ reporting.Remediation(hint=( ++ 'Upgrade to a more recent MySQL version, ' ++ 'uninstall the deprecated MySQL packages and disable the repository, ' ++ 'or switch to CloudLinux MySQL Governor-provided version of MySQL to continue using ' ++ 'the old MySQL version.' ++ ) ++ ) ++ ]) ++ else: ++ # URLs look like this: ++ # baseurl = https://repo.mysql.com/yum/mysql-8.0-community/el/7/x86_64/ ++ repo.repoid = repo.repoid + '-8' ++ repo.baseurl = repo.baseurl.replace('/el/7/', '/el/8/') ++ api.current_logger().debug('Generating custom MySQL repo: {}'.format(repo.repoid)) ++ api.produce(CustomTargetRepository( ++ repoid=repo.repoid, ++ name=repo.name, ++ baseurl=repo.baseurl, ++ enabled=repo.enabled, ++ )) ++ ++ if any(repo.enabled for repo in repofile_data.data): ++ produce_leapp_repofile_copy(repofile_data, repofile_name) ++ ++ if len(mysql_types) == 0: ++ api.current_logger().debug('No installed MySQL/MariaDB detected') ++ elif len(mysql_types) == 1: ++ api.current_logger().debug('Detected MySQL/MariaDB type: {}, version: {}'.format(mysql_types[0], clmysql_type)) ++ else: ++ api.current_logger().warning('Detected multiple MySQL types: {}'.format(", ".join(mysql_types))) ++ reporting.create_report([ ++ reporting.Title('Multpile MySQL/MariaDB versions detected'), ++ reporting.Summary( ++ 'Package repositories for multiple distributions of MySQL/MariaDB ' ++ 'were detected on the system. ' ++ 'Leapp will attempt to update all distributions detected. ' ++ 'To update only the distribution you use, disable YUM package repositories for all ' ++ 'other distributions. ' ++ 'Detected: {0}'.format(", ".join(mysql_types)) ++ ), ++ reporting.Severity(reporting.Severity.MEDIUM), ++ reporting.Tags([reporting.Tags.REPOSITORY, reporting.Tags.OS_FACTS]), ++ ]) ++ ++ if 'cloudlinux' in mysql_types and clmysql_type in MODULE_STREAMS.keys(): ++ mod_name, mod_stream = MODULE_STREAMS[clmysql_type].split(':') ++ modules_to_enable = [Module(name=mod_name, stream=mod_stream)] ++ pkg_prefix = get_pkg_prefix(clmysql_type) ++ ++ api.current_logger().debug('Enabling DNF module: {}:{}'.format(mod_name, mod_stream)) ++ api.produce(RpmTransactionTasks( ++ to_upgrade=build_install_list(pkg_prefix), ++ modules_to_enable=modules_to_enable ++ ) ++ ) ++ ++ api.produce(InstalledMySqlTypes( ++ types=mysql_types, ++ version=clmysql_type, ++ )) diff --git a/repos/system_upgrade/cloudlinux/actors/detectcontrolpanel/actor.py b/repos/system_upgrade/cloudlinux/actors/detectcontrolpanel/actor.py new file mode 100644 -index 0000000..e8ddc9a +index 0000000..4eed3ec --- /dev/null +++ b/repos/system_upgrade/cloudlinux/actors/detectcontrolpanel/actor.py @@ -0,0 +1,53 @@ -+from leapp import reporting +from leapp.actors import Actor ++from leapp import reporting +from leapp.reporting import Report ++from leapp.models import InstalledControlPanel +from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++from leapp.exceptions import StopActorExecutionError + +from leapp.libraries.common.cllaunch import run_on_cloudlinux -+from leapp.libraries.actor.detectcontrolpanel import ( -+ detect_panel, ++from leapp.libraries.common.detectcontrolpanel import ( + UNKNOWN_NAME, ++ INTEGRATED_NAME, + CPANEL_NAME, +) + + +class DetectControlPanel(Actor): + """ -+ Check for a presence of a control panel, and inhibit the upgrade if an unsupported one is found. ++ Inhibit the upgrade if an unsupported control panel is found. + """ + + name = "detect_control_panel" -+ consumes = () ++ consumes = (InstalledControlPanel,) + produces = (Report,) + tags = (ChecksPhaseTag, IPUWorkflowTag) + + @run_on_cloudlinux + def process(self): -+ panel = detect_panel() ++ panel = next(self.consume(InstalledControlPanel), None) ++ if panel is None: ++ raise StopActorExecutionError(message=("Missing information about the installed web panel.")) + -+ if panel == CPANEL_NAME: ++ if panel.name == CPANEL_NAME: + self.log.debug('cPanel detected, upgrade proceeding') ++ elif panel.name == INTEGRATED_NAME or panel.name == UNKNOWN_NAME: ++ self.log.debug('Integrated/no panel detected, upgrade proceeding') + elif panel: -+ summary_info = "Detected panel: {}".format(panel) -+ if panel == UNKNOWN_NAME: -+ summary_info = ( -+ "Legacy custom panel script detected in CloudLinux configuration" -+ ) -+ -+ # Block the upgrade on any systems with a panel detected. ++ # Block the upgrade on any systems with a non-supported panel detected. + reporting.create_report( + [ + reporting.Title( @@ -1316,87 +1816,13 @@ index 0000000..e8ddc9a + "Systems with a control panel present are not supported at the moment." + " No control panels are currently included in the Leapp database, which" + " makes loss of functionality after the upgrade extremely likely." -+ " {}.".format(summary_info) ++ " Detected panel: {}.".format(panel.name) + ), + reporting.Severity(reporting.Severity.HIGH), + reporting.Tags([reporting.Tags.OS_FACTS]), + reporting.Flags([reporting.Flags.INHIBITOR]), + ] + ) -diff --git a/repos/system_upgrade/cloudlinux/actors/detectcontrolpanel/libraries/detectcontrolpanel.py b/repos/system_upgrade/cloudlinux/actors/detectcontrolpanel/libraries/detectcontrolpanel.py -new file mode 100644 -index 0000000..15b797a ---- /dev/null -+++ b/repos/system_upgrade/cloudlinux/actors/detectcontrolpanel/libraries/detectcontrolpanel.py -@@ -0,0 +1,68 @@ -+import os -+import os.path -+ -+from leapp.libraries.stdlib import api -+ -+ -+CPANEL_NAME = 'cPanel' -+DIRECTADMIN_NAME = 'DirectAdmin' -+PLESK_NAME = 'Plesk' -+ISPMANAGER_NAME = 'ISPManager' -+INTERWORX_NAME = 'InterWorx' -+UNKNOWN_NAME = 'Unknown' -+INTEGRATED_NAME = 'Integrated' -+ -+CLSYSCONFIG = '/etc/sysconfig/cloudlinux' -+ -+ -+def lvectl_custompanel_script(): -+ """ -+ Retrives custom panel script for lvectl from CL config file -+ :return: Script path or None if script filename wasn't found in config -+ """ -+ config_param_name = 'CUSTOM_GETPACKAGE_SCRIPT' -+ try: -+ # Try to determine the custom script name -+ if os.path.exists(CLSYSCONFIG): -+ with open(CLSYSCONFIG, 'r') as f: -+ file_lines = f.readlines() -+ for line in file_lines: -+ line = line.strip() -+ if line.startswith(config_param_name): -+ line_parts = line.split('=') -+ if len(line_parts) == 2 and line_parts[0].strip() == config_param_name: -+ script_name = line_parts[1].strip() -+ if os.path.exists(script_name): -+ return script_name -+ except (OSError, IOError, IndexError): -+ # Ignore errors - what's important is that the script wasn't found -+ pass -+ return None -+ -+ -+def detect_panel(): -+ """ -+ This function will try to detect control panels supported by CloudLinux -+ :return: Detected control panel name or None -+ """ -+ panel_name = None -+ if os.path.isfile('/opt/cpvendor/etc/integration.ini'): -+ panel_name = INTEGRATED_NAME -+ elif os.path.isfile('/usr/local/cpanel/cpanel'): -+ panel_name = CPANEL_NAME -+ elif os.path.isfile('/usr/local/directadmin/directadmin') or\ -+ os.path.isfile('/usr/local/directadmin/custombuild/build'): -+ panel_name = DIRECTADMIN_NAME -+ elif os.path.isfile('/usr/local/psa/version'): -+ panel_name = PLESK_NAME -+ # ispmanager must have: -+ # v5: /usr/local/mgr5/ directory, -+ # v4: /usr/local/ispmgr/bin/ispmgr file -+ elif os.path.isfile('/usr/local/ispmgr/bin/ispmgr') or os.path.isdir('/usr/local/mgr5'): -+ panel_name = ISPMANAGER_NAME -+ elif os.path.isdir('/usr/local/interworx'): -+ panel_name = INTERWORX_NAME -+ # Check if the CL config has a legacy custom script for a control panel -+ elif lvectl_custompanel_script(): -+ panel_name = UNKNOWN_NAME -+ return panel_name diff --git a/repos/system_upgrade/cloudlinux/actors/enableyumspacewalkplugin/actor.py b/repos/system_upgrade/cloudlinux/actors/enableyumspacewalkplugin/actor.py new file mode 100644 index 0000000..1e353b1 @@ -1458,6 +1884,147 @@ index 0000000..1e353b1 + reporting.Severity(reporting.Severity.MEDIUM), + reporting.Tags([reporting.Tags.SANITY]) + ]) +diff --git a/repos/system_upgrade/cloudlinux/actors/registerpackageworkarounds/actor.py b/repos/system_upgrade/cloudlinux/actors/registerpackageworkarounds/actor.py +new file mode 100644 +index 0000000..e946c2b +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/actors/registerpackageworkarounds/actor.py +@@ -0,0 +1,21 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import registerpackageworkarounds ++from leapp.models import InstalledRPM, DNFWorkaround, PreRemovedRpmPackages ++from leapp.tags import FactsPhaseTag, IPUWorkflowTag ++from leapp.libraries.common.cllaunch import run_on_cloudlinux ++ ++ ++class RegisterPackageWorkarounds(Actor): ++ """ ++ Registers a yum workaround that adjusts the problematic packages that would ++ break the main upgrade transaction otherwise. ++ """ ++ ++ name = 'register_package_workarounds' ++ consumes = (InstalledRPM,) ++ produces = (DNFWorkaround, PreRemovedRpmPackages) ++ tags = (IPUWorkflowTag, FactsPhaseTag) ++ ++ @run_on_cloudlinux ++ def process(self): ++ registerpackageworkarounds.process() +diff --git a/repos/system_upgrade/cloudlinux/actors/registerpackageworkarounds/libraries/registerpackageworkarounds.py b/repos/system_upgrade/cloudlinux/actors/registerpackageworkarounds/libraries/registerpackageworkarounds.py +new file mode 100644 +index 0000000..358403b +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/actors/registerpackageworkarounds/libraries/registerpackageworkarounds.py +@@ -0,0 +1,38 @@ ++from leapp.actors import Actor ++from leapp.models import InstalledRPM, DNFWorkaround, PreRemovedRpmPackages ++from leapp.libraries.stdlib import api ++ ++# NOTE: The related packages are listed both here *and* in the workaround script! ++# If the list changes, it has to change in both places. ++# This is a limitation of the current DNFWorkaround implementation. ++# TODO: unify the list in one place. A separate common file, perhaps? ++TO_REINSTALL = ['gettext-devel'] # These packages will be marked for installation ++TO_DELETE = [] # These won't be ++ ++ ++def produce_workaround_msg(pkg_list, reinstall): ++ if not pkg_list: ++ return ++ preremoved_pkgs = PreRemovedRpmPackages(install=reinstall) ++ # Only produce a message if a package is actually about to be uninstalled ++ for rpm_pkgs in api.consume(InstalledRPM): ++ for pkg in rpm_pkgs.items: ++ if (pkg.name in pkg_list): ++ preremoved_pkgs.items.append(pkg) ++ api.current_logger().debug("Listing package {} to be pre-removed".format(pkg.name)) ++ if preremoved_pkgs.items: ++ api.produce(preremoved_pkgs) ++ ++ ++def process(): ++ produce_workaround_msg(TO_REINSTALL, True) ++ produce_workaround_msg(TO_DELETE, False) ++ ++ api.produce( ++ # yum doesn't consider attempting to remove a non-existent package to be an error ++ # we can safely give it the entire package list without checking if all are installed ++ DNFWorkaround( ++ display_name='problem package modification', ++ script_path=api.get_tool_path('remove-problem-packages'), ++ ) ++ ) +diff --git a/repos/system_upgrade/cloudlinux/actors/replacerpmnewconfigs/actor.py b/repos/system_upgrade/cloudlinux/actors/replacerpmnewconfigs/actor.py +new file mode 100644 +index 0000000..209d933 +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/actors/replacerpmnewconfigs/actor.py +@@ -0,0 +1,64 @@ ++from __future__ import print_function ++import os ++import fileinput ++ ++from leapp.actors import Actor ++from leapp.tags import FirstBootPhaseTag, IPUWorkflowTag ++from leapp import reporting ++from leapp.libraries.common.cllaunch import run_on_cloudlinux ++ ++REPO_DIR = '/etc/yum.repos.d' ++CL_MARKERS = ['cloudlinux', 'imunify'] ++RPMNEW = '.rpmnew' ++LEAPP_BACKUP_SUFFIX = '.leapp-backup' ++ ++ ++class ReplaceRpmnewConfigs(Actor): ++ """ ++ Replace CloudLinux-related repository config .rpmnew files. ++ """ ++ ++ name = 'replace_rpmnew_configs' ++ consumes = () ++ produces = () ++ tags = (FirstBootPhaseTag, IPUWorkflowTag) ++ ++ @run_on_cloudlinux ++ def process(self): ++ renamed_repofiles = [] ++ ++ for reponame in os.listdir(REPO_DIR): ++ if any(mark in reponame for mark in CL_MARKERS) and RPMNEW in reponame: ++ base_reponame = reponame[:-7] ++ base_path = os.path.join(REPO_DIR, base_reponame) ++ new_file_path = os.path.join(REPO_DIR, reponame) ++ backup_path = os.path.join(REPO_DIR, base_reponame + LEAPP_BACKUP_SUFFIX) ++ ++ os.rename(base_path, backup_path) ++ os.rename(new_file_path, base_path) ++ renamed_repofiles.append(base_reponame) ++ ++ for reponame in os.listdir(REPO_DIR): ++ if LEAPP_BACKUP_SUFFIX in reponame: ++ repofile_path = os.path.join(REPO_DIR, reponame) ++ for line in fileinput.input(repofile_path, inplace=True): ++ if line.startswith('enabled'): ++ print("enabled = 0") ++ else: ++ print(line, end='') ++ ++ if renamed_repofiles: ++ replaced_string = '\n'.join(['- {}'.format(repofile_name) for repofile_name in renamed_repofiles]) ++ reporting.create_report([ ++ reporting.Title('CloudLinux repository config files replaced by updated versions'), ++ reporting.Summary( ++ 'One or more RPM repository configuration files related to CloudLinux ' ++ 'have been replaced with new versions provided by the upgraded packages. ' ++ 'Any manual modifications to these files have been overriden by this process. ' ++ 'Old versions of files were backed up to files with a naming pattern ' ++ '.leapp-backup. ' ++ 'Replaced repository files: \n{}'.format(replaced_string) ++ ), ++ reporting.Severity(reporting.Severity.MEDIUM), ++ reporting.Tags([reporting.Tags.UPGRADE_PROCESS]) ++ ]) diff --git a/repos/system_upgrade/cloudlinux/actors/resetrhnversionoverride/actor.py b/repos/system_upgrade/cloudlinux/actors/resetrhnversionoverride/actor.py new file mode 100644 index 0000000..21b2164 @@ -1489,6 +2056,39 @@ index 0000000..21b2164 + line = 'versionOverride=' + with open(up2date_config, 'w') as f: + f.writelines(config_data) +diff --git a/repos/system_upgrade/cloudlinux/actors/scancontrolpanel/actor.py b/repos/system_upgrade/cloudlinux/actors/scancontrolpanel/actor.py +new file mode 100644 +index 0000000..96524ed +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/actors/scancontrolpanel/actor.py +@@ -0,0 +1,27 @@ ++from leapp.actors import Actor ++from leapp.models import InstalledControlPanel ++from leapp.tags import FactsPhaseTag, IPUWorkflowTag ++ ++from leapp.libraries.common.cllaunch import run_on_cloudlinux ++from leapp.libraries.common.detectcontrolpanel import detect_panel ++ ++ ++class ScanControlPanel(Actor): ++ """ ++ Scan for a presence of a control panel, and produce a corresponding message. ++ """ ++ ++ name = 'scan_control_panel' ++ consumes = () ++ produces = (InstalledControlPanel,) ++ tags = (FactsPhaseTag, IPUWorkflowTag) ++ ++ @run_on_cloudlinux ++ def process(self): ++ detected_panel = detect_panel() ++ ++ self.produce( ++ InstalledControlPanel( ++ name=detected_panel ++ ) ++ ) diff --git a/repos/system_upgrade/cloudlinux/actors/scanrolloutrepositories/actor.py b/repos/system_upgrade/cloudlinux/actors/scanrolloutrepositories/actor.py new file mode 100644 index 0000000..202e5f7 @@ -1703,6 +2303,175 @@ index 0000000..6cbab5d + return + return func(*args, **kwargs) + return wrapper +diff --git a/repos/system_upgrade/cloudlinux/libraries/clmysql.py b/repos/system_upgrade/cloudlinux/libraries/clmysql.py +new file mode 100644 +index 0000000..f04f6c5 +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/libraries/clmysql.py +@@ -0,0 +1,43 @@ ++import os ++ ++# This file contains the data on the currently active MySQL installation type and version. ++CL7_MYSQL_TYPE_FILE = "/usr/share/lve/dbgovernor/mysql.type.installed" ++ ++# This dict matches the MySQL type strings with DNF module and stream IDs. ++MODULE_STREAMS = { ++ "mysql55": "mysql:cl-MySQL55", ++ "mysql56": "mysql:cl-MySQL56", ++ "mysql57": "mysql:cl-MySQL57", ++ "mysql80": "mysql:cl-MySQL80", ++ "mariadb55": "mariadb:cl-MariaDB55", ++ "mariadb100": "mariadb:cl-MariaDB100", ++ "mariadb101": "mariadb:cl-MariaDB101", ++ "mariadb102": "mariadb:cl-MariaDB102", ++ "mariadb103": "mariadb:cl-MariaDB103", ++ "mariadb104": "mariadb:cl-MariaDB104", ++ "mariadb105": "mariadb:cl-MariaDB105", ++ "mariadb106": "mariadb:cl-MariaDB106", ++ "percona56": "percona:cl-Percona56", ++ "auto": "mysql:8.0" ++} ++ ++ ++def get_pkg_prefix(clmysql_type): ++ """ ++ Get a Yum package prefix string from cl-mysql type. ++ """ ++ if "mysql" in clmysql_type: ++ return "cl-MySQL" ++ elif "mariadb" in clmysql_type: ++ return "cl-MariaDB" ++ elif "percona" in clmysql_type: ++ return "cl-Percona" ++ else: ++ return None ++ ++ ++def get_clmysql_type(): ++ if os.path.isfile(CL7_MYSQL_TYPE_FILE): ++ with open(CL7_MYSQL_TYPE_FILE, "r") as mysql_f: ++ return mysql_f.read() ++ return None +diff --git a/repos/system_upgrade/cloudlinux/libraries/detectcontrolpanel.py b/repos/system_upgrade/cloudlinux/libraries/detectcontrolpanel.py +new file mode 100644 +index 0000000..15b797a +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/libraries/detectcontrolpanel.py +@@ -0,0 +1,68 @@ ++import os ++import os.path ++ ++from leapp.libraries.stdlib import api ++ ++ ++CPANEL_NAME = 'cPanel' ++DIRECTADMIN_NAME = 'DirectAdmin' ++PLESK_NAME = 'Plesk' ++ISPMANAGER_NAME = 'ISPManager' ++INTERWORX_NAME = 'InterWorx' ++UNKNOWN_NAME = 'Unknown' ++INTEGRATED_NAME = 'Integrated' ++ ++CLSYSCONFIG = '/etc/sysconfig/cloudlinux' ++ ++ ++def lvectl_custompanel_script(): ++ """ ++ Retrives custom panel script for lvectl from CL config file ++ :return: Script path or None if script filename wasn't found in config ++ """ ++ config_param_name = 'CUSTOM_GETPACKAGE_SCRIPT' ++ try: ++ # Try to determine the custom script name ++ if os.path.exists(CLSYSCONFIG): ++ with open(CLSYSCONFIG, 'r') as f: ++ file_lines = f.readlines() ++ for line in file_lines: ++ line = line.strip() ++ if line.startswith(config_param_name): ++ line_parts = line.split('=') ++ if len(line_parts) == 2 and line_parts[0].strip() == config_param_name: ++ script_name = line_parts[1].strip() ++ if os.path.exists(script_name): ++ return script_name ++ except (OSError, IOError, IndexError): ++ # Ignore errors - what's important is that the script wasn't found ++ pass ++ return None ++ ++ ++def detect_panel(): ++ """ ++ This function will try to detect control panels supported by CloudLinux ++ :return: Detected control panel name or None ++ """ ++ panel_name = None ++ if os.path.isfile('/opt/cpvendor/etc/integration.ini'): ++ panel_name = INTEGRATED_NAME ++ elif os.path.isfile('/usr/local/cpanel/cpanel'): ++ panel_name = CPANEL_NAME ++ elif os.path.isfile('/usr/local/directadmin/directadmin') or\ ++ os.path.isfile('/usr/local/directadmin/custombuild/build'): ++ panel_name = DIRECTADMIN_NAME ++ elif os.path.isfile('/usr/local/psa/version'): ++ panel_name = PLESK_NAME ++ # ispmanager must have: ++ # v5: /usr/local/mgr5/ directory, ++ # v4: /usr/local/ispmgr/bin/ispmgr file ++ elif os.path.isfile('/usr/local/ispmgr/bin/ispmgr') or os.path.isdir('/usr/local/mgr5'): ++ panel_name = ISPMANAGER_NAME ++ elif os.path.isdir('/usr/local/interworx'): ++ panel_name = INTERWORX_NAME ++ # Check if the CL config has a legacy custom script for a control panel ++ elif lvectl_custompanel_script(): ++ panel_name = UNKNOWN_NAME ++ return panel_name +diff --git a/repos/system_upgrade/cloudlinux/models/installedcontrolpanel.py b/repos/system_upgrade/cloudlinux/models/installedcontrolpanel.py +new file mode 100644 +index 0000000..ace1e15 +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/models/installedcontrolpanel.py +@@ -0,0 +1,12 @@ ++from leapp.models import Model, fields ++from leapp.topics import SystemInfoTopic ++ ++ ++class InstalledControlPanel(Model): ++ """ ++ Name of the web control panel present on the system. ++ 'Unknown' if detection failed. ++ """ ++ ++ topic = SystemInfoTopic ++ name = fields.String() +diff --git a/repos/system_upgrade/cloudlinux/models/installedmysqltype.py b/repos/system_upgrade/cloudlinux/models/installedmysqltype.py +new file mode 100644 +index 0000000..5cc475d +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/models/installedmysqltype.py +@@ -0,0 +1,12 @@ ++from leapp.models import Model, fields ++from leapp.topics import SystemInfoTopic ++ ++ ++class InstalledMySqlTypes(Model): ++ """ ++ Contains data about the MySQL/MariaDB/Percona installation on the source system. ++ """ ++ ++ topic = SystemInfoTopic ++ types = fields.List(fields.String()) ++ version = fields.Nullable(fields.String(default=None)) # used for cl-mysql +diff --git a/repos/system_upgrade/cloudlinux/tools/remove-problem-packages b/repos/system_upgrade/cloudlinux/tools/remove-problem-packages +new file mode 100755 +index 0000000..38eba60 +--- /dev/null ++++ b/repos/system_upgrade/cloudlinux/tools/remove-problem-packages +@@ -0,0 +1,4 @@ ++#!/usr/bin/bash -e ++ ++# can't be removed in the main transaction due to errors in %preun ++yum -y --setopt=tsflags=noscripts remove gettext-devel diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py index a2cede0..5ff1c76 100644 --- a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py @@ -1819,6 +2588,108 @@ index 14bd6e3..f6adacf 100755 [ -z "$os_version" ] && { # This should not happen as /etc/initrd-release is supposed to have API # stability, but check is better than broken system. +diff --git a/repos/system_upgrade/common/actors/detectwebservers/actor.py b/repos/system_upgrade/common/actors/detectwebservers/actor.py +new file mode 100644 +index 0000000..d600eb2 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/detectwebservers/actor.py +@@ -0,0 +1,48 @@ ++from leapp.actors import Actor ++from leapp import reporting ++from leapp.reporting import Report ++from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++ ++from leapp.libraries.actor.detectwebservers import ( ++ detect_litespeed, ++ detect_nginx ++) ++ ++ ++class DetectWebServers(Actor): ++ """ ++ Check for a presence of a web server, and produce a warning if one is installed. ++ """ ++ ++ name = 'detect_web_servers' ++ consumes = () ++ produces = (Report) ++ tags = (ChecksPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ litespeed_installed = detect_litespeed() ++ nginx_installed = detect_nginx() ++ ++ if litespeed_installed or nginx_installed: ++ server_name = "NGINX" if nginx_installed else "LiteSpeed" ++ reporting.create_report( ++ [ ++ reporting.Title( ++ "An installed web server might not be upgraded properly." ++ ), ++ reporting.Summary( ++ "A web server is present on the system." ++ " Depending on the source of installation, " ++ " it may not upgrade to the new version correctly," ++ " since not all varoants are currently supported by Leapp." ++ " Please check the list of packages that won't be upgraded in the report." ++ " Alternatively, upgrade the webserver manually after the process finishes." ++ " Detected webserver: {}.".format(server_name) ++ ), ++ reporting.Severity(reporting.Severity.HIGH), ++ reporting.Tags([ ++ reporting.Tags.OS_FACTS, ++ reporting.Tags.SERVICES ++ ]), ++ ] ++ ) +diff --git a/repos/system_upgrade/common/actors/detectwebservers/libraries/detectwebservers.py b/repos/system_upgrade/common/actors/detectwebservers/libraries/detectwebservers.py +new file mode 100644 +index 0000000..e0058e6 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/detectwebservers/libraries/detectwebservers.py +@@ -0,0 +1,42 @@ ++import os ++ ++LITESPEED_CONFIG_FILE = '/usr/local/lsws/conf/httpd_config.xml' ++LITESPEED_OPEN_CONFIG_FILE = '/usr/local/lsws/conf/httpd_config.conf' ++NGINX_BINARY = '/usr/sbin/nginx' ++ ++ ++def detect_webservers(): ++ """ ++ Wrapper function for detection. ++ """ ++ return (detect_litespeed() or detect_nginx()) ++ ++ ++# Detect LiteSpeed ++def detect_litespeed(): ++ """ ++ LiteSpeed can be enterprise or open source, and each of them ++ stores config in different formats ++ """ ++ return detect_enterprise_litespeed() or detect_open_litespeed() ++ ++ ++def detect_enterprise_litespeed(): ++ """ ++ Detects LSWS Enterprise presence ++ """ ++ return os.path.isfile(LITESPEED_CONFIG_FILE) ++ ++ ++def detect_open_litespeed(): ++ """ ++ Detects OpenLiteSpeed presence ++ """ ++ return os.path.isfile(LITESPEED_OPEN_CONFIG_FILE) ++ ++ ++def detect_nginx(): ++ """ ++ Detects NGINX presence ++ """ ++ return os.path.isfile(NGINX_BINARY) diff --git a/repos/system_upgrade/common/actors/efibootorderfix/finalization/actor.py b/repos/system_upgrade/common/actors/efibootorderfix/finalization/actor.py index f42909f..2728cb4 100644 --- a/repos/system_upgrade/common/actors/efibootorderfix/finalization/actor.py @@ -1931,18 +2802,58 @@ index f42909f..2728cb4 100644 + if not has_grub_cfg: + run(['/sbin/grub2-mkconfig', '-o', grub_cfg_path]) diff --git a/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py b/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py -index e0d89d9..52f93ef 100644 +index e0d89d9..8fc5954 100644 --- a/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py +++ b/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py -@@ -32,6 +32,7 @@ class FilterRpmTransactionTasks(Actor): +@@ -3,7 +3,8 @@ from leapp.models import ( + FilteredRpmTransactionTasks, + InstalledRedHatSignedRPM, + PESRpmTransactionTasks, +- RpmTransactionTasks ++ RpmTransactionTasks, ++ PreRemovedRpmPackages + ) + from leapp.tags import ChecksPhaseTag, IPUWorkflowTag + +@@ -18,34 +19,53 @@ class FilterRpmTransactionTasks(Actor): + """ + + name = 'check_rpm_transaction_events' +- consumes = (PESRpmTransactionTasks, RpmTransactionTasks, InstalledRedHatSignedRPM,) ++ consumes = ( ++ PESRpmTransactionTasks, ++ RpmTransactionTasks, ++ InstalledRedHatSignedRPM, ++ PreRemovedRpmPackages, ++ ) + produces = (FilteredRpmTransactionTasks,) + tags = (IPUWorkflowTag, ChecksPhaseTag) + + def process(self): + installed_pkgs = set() ++ preremoved_pkgs = set() ++ preremoved_pkgs_to_install = set() ++ + for rpm_pkgs in self.consume(InstalledRedHatSignedRPM): + installed_pkgs.update([pkg.name for pkg in rpm_pkgs.items]) ++ for rpm_pkgs in self.consume(PreRemovedRpmPackages): ++ preremoved_pkgs.update([pkg.name for pkg in rpm_pkgs.items]) ++ preremoved_pkgs_to_install.update([pkg.name for pkg in rpm_pkgs.items if rpm_pkgs.install]) ++ ++ installed_pkgs.difference_update(preremoved_pkgs) + + local_rpms = set() + to_install = set() to_remove = set() to_keep = set() to_upgrade = set() + to_reinstall = set() modules_to_enable = {} modules_to_reset = {} ++ ++ to_install.update(preremoved_pkgs_to_install) for event in self.consume(RpmTransactionTasks, PESRpmTransactionTasks): -@@ -39,13 +40,14 @@ class FilterRpmTransactionTasks(Actor): + local_rpms.update(event.local_rpms) to_install.update(event.to_install) to_remove.update(installed_pkgs.intersection(event.to_remove)) to_keep.update(installed_pkgs.intersection(event.to_keep)) @@ -1955,10 +2866,12 @@ index e0d89d9..52f93ef 100644 # run upgrade for the rest of RH signed pkgs which we do not have rule for - to_upgrade = installed_pkgs - (to_install | to_remove) + to_upgrade = installed_pkgs - (to_install | to_remove | to_reinstall) ++ ++ self.log.debug('DNF modules to enable: {}'.format(modules_to_enable.keys())) self.produce(FilteredRpmTransactionTasks( local_rpms=list(local_rpms), -@@ -53,5 +55,6 @@ class FilterRpmTransactionTasks(Actor): +@@ -53,5 +73,6 @@ class FilterRpmTransactionTasks(Actor): to_remove=list(to_remove), to_keep=list(to_keep), to_upgrade=list(to_upgrade), @@ -3160,7 +4073,7 @@ index 59b12c8..85d4a09 100644 def process(self): self.produce(systemfacts.get_sysctls_status()) diff --git a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -index 7a8bd99..a1f8db8 100644 +index 7a8bd99..d05aaf9 100644 --- a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py +++ b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py @@ -225,6 +225,12 @@ def _prep_repository_access(context, target_userspace): @@ -3176,7 +4089,19 @@ index 7a8bd99..a1f8db8 100644 if not rhsm.skip_rhsm(): run(['rm', '-rf', os.path.join(target_etc, 'pki')]) run(['rm', '-rf', os.path.join(target_etc, 'rhsm')]) -@@ -592,6 +598,7 @@ def _install_custom_repofiles(context, custom_repofiles): +@@ -392,6 +398,11 @@ def _inhibit_on_duplicate_repos(repofiles): + + def _get_all_available_repoids(context): + repofiles = repofileutils.get_parsed_repofiles(context) ++ ++ api.current_logger().debug("All available repositories inside the overlay FS:") ++ for repof in repofiles: ++ api.current_logger().debug("File: {}, repos: {}".format(repof.file, [repod.repoid for repod in repof.data])) ++ + # TODO: this is not good solution, but keep it as it is now + # Issue: #486 + if rhsm.skip_rhsm(): +@@ -592,6 +603,7 @@ def _install_custom_repofiles(context, custom_repofiles): """ for rfile in custom_repofiles: _dst_path = os.path.join('/etc/yum.repos.d', os.path.basename(rfile.file)) @@ -3492,6 +4417,51 @@ index 6a2f5aa..51030fd 100644 mount_names = [mount_point.fs_file for mount_point in mount_points] +diff --git a/repos/system_upgrade/common/libraries/repofileutils.py b/repos/system_upgrade/common/libraries/repofileutils.py +index a3f111b..4f7ea91 100644 +--- a/repos/system_upgrade/common/libraries/repofileutils.py ++++ b/repos/system_upgrade/common/libraries/repofileutils.py +@@ -26,6 +26,18 @@ def _parse_repository(repoid, repo_data): + return RepositoryData(**prepared) + + ++def _prepare_config(repodata, config_parser): ++ for repo in repodata.data: ++ config_parser.add_section(repo.repoid) ++ ++ repo_enabled = 1 if repo.enabled else 0 ++ config_parser.set(repo.repoid, 'name', repo.name) ++ config_parser.set(repo.repoid, 'baseurl', repo.baseurl) ++ config_parser.set(repo.repoid, 'metalink', repo.metalink) ++ config_parser.set(repo.repoid, 'mirrorlist', repo.mirrorlist) ++ config_parser.set(repo.repoid, 'enabled', repo_enabled) ++ ++ + def parse_repofile(repofile): + """ + Parse the given repo file. +@@ -42,6 +54,21 @@ def parse_repofile(repofile): + return RepositoryFile(file=repofile, data=data) + + ++def save_repofile(repodata, repofile_path): ++ """ ++ Save the given repository data to file. ++ ++ :param repodata: Repository data to save ++ :type repodata: RepositoryFile ++ :param repofile_path: Path to the repo file ++ :type repofile_path: str ++ """ ++ with open(repofile_path, mode='w') as fp: ++ cp = utils.create_config(repodata) ++ _prepare_config(repodata, cp) ++ cp.write(fp) ++ ++ + def get_repodirs(): + """ + Return all directories yum scans for repository files, if they exist. diff --git a/repos/system_upgrade/common/libraries/repomaputils.py b/repos/system_upgrade/common/libraries/repomaputils.py new file mode 100644 index 0000000..5c41620 @@ -3658,6 +4628,25 @@ index b7e4b21..dc038bf 100644 def with_rhsm(f): +diff --git a/repos/system_upgrade/common/libraries/utils.py b/repos/system_upgrade/common/libraries/utils.py +index 6793de6..8a38723 100644 +--- a/repos/system_upgrade/common/libraries/utils.py ++++ b/repos/system_upgrade/common/libraries/utils.py +@@ -43,6 +43,14 @@ def parse_config(cfg=None, strict=True): + return parser + + ++def create_config(repodata): ++ if six.PY3: ++ parser = six.moves.configparser.ConfigParser(strict=strict) # pylint: disable=unexpected-keyword-arg ++ else: ++ parser = six.moves.configparser.ConfigParser() ++ return parser ++ ++ + def makedirs(path, mode=0o777, exists_ok=True): + mounting._makedirs(path=path, mode=mode, exists_ok=exists_ok) + diff --git a/repos/system_upgrade/common/models/activevendorlist.py b/repos/system_upgrade/common/models/activevendorlist.py new file mode 100644 index 0000000..de4056f @@ -3671,6 +4660,19 @@ index 0000000..de4056f +class ActiveVendorList(Model): + topic = VendorTopic + data = fields.List(fields.String()) +diff --git a/repos/system_upgrade/common/models/installedrpm.py b/repos/system_upgrade/common/models/installedrpm.py +index 28b0aba..e53ab93 100644 +--- a/repos/system_upgrade/common/models/installedrpm.py ++++ b/repos/system_upgrade/common/models/installedrpm.py +@@ -27,3 +27,8 @@ class InstalledRedHatSignedRPM(InstalledRPM): + + class InstalledUnsignedRPM(InstalledRPM): + pass ++ ++ ++class PreRemovedRpmPackages(InstalledRPM): ++ # Do we want to install the package again when upgrading? ++ install = fields.Boolean(default=True) diff --git a/repos/system_upgrade/common/models/repositoriesmap.py b/repos/system_upgrade/common/models/repositoriesmap.py index c187333..ea6a75d 100644 --- a/repos/system_upgrade/common/models/repositoriesmap.py @@ -3752,6 +4754,103 @@ index 0000000..014b7af + +class VendorTopic(Topic): + name = 'vendor_topic' +diff --git a/repos/system_upgrade/el7toel8/actors/checkleftoverpackages/actor.py b/repos/system_upgrade/el7toel8/actors/checkleftoverpackages/actor.py +index 0c53950..7840219 100644 +--- a/repos/system_upgrade/el7toel8/actors/checkleftoverpackages/actor.py ++++ b/repos/system_upgrade/el7toel8/actors/checkleftoverpackages/actor.py +@@ -1,8 +1,24 @@ + from leapp.actors import Actor + from leapp.libraries.common.rpms import get_installed_rpms +-from leapp.models import LeftoverPackages, TransactionCompleted, InstalledUnsignedRPM, RPM ++from leapp.models import ( ++ LeftoverPackages, ++ TransactionCompleted, ++ InstalledUnsignedRPM, ++ RPM, ++) + from leapp.tags import RPMUpgradePhaseTag, IPUWorkflowTag + ++LEAPP_PACKAGES = [ ++ "leapp", ++ "leapp-repository", ++ "snactor", ++ "leapp-repository-deps-el8", ++ "leapp-deps-el8", ++ "python2-leapp", ++] ++ ++CPANEL_SUFFIX = "cpanel-" ++ + + class CheckLeftoverPackages(Actor): + """ +@@ -11,36 +27,50 @@ class CheckLeftoverPackages(Actor): + Actor produces message containing these packages. Message is empty if there are no el7 package left. + """ + +- name = 'check_leftover_packages' ++ name = "check_leftover_packages" + consumes = (TransactionCompleted, InstalledUnsignedRPM) + produces = (LeftoverPackages,) + tags = (RPMUpgradePhaseTag, IPUWorkflowTag) + ++ def skip_leftover_pkg(self, name, unsigned_set): ++ # Packages like these are expected to be not updated. ++ is_unsigned = name not in unsigned_set ++ # Packages like these are updated outside of Leapp. ++ is_external = name.startswith(CPANEL_SUFFIX) ++ ++ return is_unsigned or is_external ++ + def process(self): +- LEAPP_PACKAGES = ['leapp', 'leapp-repository', 'snactor', 'leapp-repository-deps-el8', 'leapp-deps-el8', +- 'python2-leapp'] + installed_rpms = get_installed_rpms() + if not installed_rpms: + return + + to_remove = LeftoverPackages() +- unsigned = [pkg.name for pkg in next(self.consume(InstalledUnsignedRPM), InstalledUnsignedRPM()).items] ++ unsigned = [ ++ pkg.name ++ for pkg in next( ++ self.consume(InstalledUnsignedRPM), InstalledUnsignedRPM() ++ ).items ++ ] ++ unsigned_set = set(unsigned + LEAPP_PACKAGES) + + for rpm in installed_rpms: + rpm = rpm.strip() + if not rpm: + continue +- name, version, release, epoch, packager, arch, pgpsig = rpm.split('|') +- +- if 'el7' in release and name not in set(unsigned + LEAPP_PACKAGES): +- to_remove.items.append(RPM( +- name=name, +- version=version, +- epoch=epoch, +- packager=packager, +- arch=arch, +- release=release, +- pgpsig=pgpsig +- )) ++ name, version, release, epoch, packager, arch, pgpsig = rpm.split("|") ++ ++ if "el7" in release and not self.skip_leftover_pkg(name, unsigned_set): ++ to_remove.items.append( ++ RPM( ++ name=name, ++ version=version, ++ epoch=epoch, ++ packager=packager, ++ arch=arch, ++ release=release, ++ pgpsig=pgpsig, ++ ) ++ ) + + self.produce(to_remove) diff --git a/repos/system_upgrade/el7toel8/actors/networkmanagerupdateconnections/actor.py b/repos/system_upgrade/el7toel8/actors/networkmanagerupdateconnections/actor.py index 69ca0f0..a7d7db1 100644 --- a/repos/system_upgrade/el7toel8/actors/networkmanagerupdateconnections/actor.py diff --git a/SPECS/leapp-repository.spec b/SPECS/leapp-repository.spec index d3a1e7e..a9575f9 100644 --- a/SPECS/leapp-repository.spec +++ b/SPECS/leapp-repository.spec @@ -43,7 +43,7 @@ py2_byte_compile "%1" "%2"} Epoch: 1 Name: leapp-repository Version: 0.16.0 -Release: 6%{?dist}.elevate.7 +Release: 6%{?dist}.elevate.8 Summary: Repositories for leapp License: ASL 2.0