diff --git a/.buckconfig b/.buckconfig index b0ed754..e15fc54 100644 --- a/.buckconfig +++ b/.buckconfig @@ -1,6 +1,11 @@ # Documentation: https://buckbuild.com/concept/buckconfig.html +[parser] + python_interpreter = /usr/bin/python2 [python] interpreter = python2.7 [python#py3] - interpreter = /usr/bin/python3.6 \ No newline at end of file + interpreter = /usr/bin/python3.6 +[repositories] + centos2alma = . + dist-upgrader = ./dist-upgrader diff --git a/.gitmodules b/.gitmodules index 53b9f64..5681f23 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "common"] - path = common - url = https://github.com/plesk/distro-conversion-base +[submodule "dist-upgrader"] + path = dist-upgrader + url = git@github.com:plesk/dist-upgrader.git diff --git a/BUCK b/BUCK index ee32395..78e3cde 100644 --- a/BUCK +++ b/BUCK @@ -1,7 +1,7 @@ # Copyright 1999-2024. WebPros International GmbH. All rights reserved. # vim:ft=python: -PRODUCT_VERSION = '1.2.3' +PRODUCT_VERSION = '1.3.0' genrule( name = 'version', @@ -9,35 +9,21 @@ genrule( bash = r"""echo "{\"version\": \"%s\", \"revision\": \"`git rev-parse HEAD`\"}" > $OUT""" % (PRODUCT_VERSION), ) -python_library( - name = 'actions.lib', - srcs = glob(['./actions/*.py']), -) - -python_library( - name = 'centos2alma.lib', - srcs = glob(['main.py', 'messages.py']), - deps = [ - ':actions.lib', - '//common:common.lib', - ], - resources = [ - ':version', - ], -) python_binary( - name = 'centos2alma-script', + name = 'centos2alma.pex', platform = 'py3', - main_module = 'main', + build_args = ['--python-shebang', '/usr/bin/env python3'], + main_module = 'centos2almaconverter.main', deps = [ - ':centos2alma.lib', - ] + 'dist-upgrader//pleskdistup:lib', + '//centos2almaconverter:lib', + ], ) genrule( name = 'centos2alma', - srcs = [':centos2alma-script'], + srcs = [':centos2alma.pex'], out = 'centos2alma', - cmd = 'cp $(location :centos2alma-script) $OUT && chmod +x $OUT', -) + cmd = 'cp $(location :centos2alma.pex) $OUT && chmod +x $OUT', +) \ No newline at end of file diff --git a/actions/common.py b/actions/common.py deleted file mode 100644 index 48f5729..0000000 --- a/actions/common.py +++ /dev/null @@ -1,297 +0,0 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. -import os -import shutil -import subprocess -import sys -import time -import typing - -from common import action, files, log, motd, plesk, rpm, util - - -class FixNamedConfig(action.ActiveAction): - def __init__(self): - self.name = "fix named configuration" - self.user_options_path = "/etc/named-user-options.conf" - self.chrooted_file_path = "/var/named/chroot/etc/named-user-options.conf" - - def _is_required(self) -> bool: - return os.path.exists(self.chrooted_file_path) - - def _prepare_action(self) -> None: - if not os.path.exists(self.user_options_path): - os.symlink(self.chrooted_file_path, self.user_options_path) - - if os.path.getsize(self.chrooted_file_path) == 0: - with open(self.chrooted_file_path, "w") as f: - f.write("# centos2alma workaround commentary") - - def _post_action(self) -> None: - if os.path.exists(self.user_options_path): - os.unlink(self.user_options_path) - - with open(self.chrooted_file_path, "r") as f: - if f.read() == "# centos2alma workaround commentary": - os.unlink(self.chrooted_file_path) - with open(self.chrooted_file_path, "w") as _: - pass - - def _revert_action(self) -> None: - if os.path.exists(self.user_options_path): - os.unlink(self.user_options_path) - - -class DisableSuspiciousKernelModules(action.ActiveAction): - def __init__(self): - self.name = "rule suspicious kernel modules" - self.suspicious_modules = ["pata_acpi", "btrfs", "floppy"] - self.modules_konfig_path = "/etc/modprobe.d/pataacpibl.conf" - - def _get_enabled_modules(self, lookup_modules: typing.List[str]) -> typing.List[str]: - modules = [] - modules_list = subprocess.check_output(["/usr/sbin/lsmod"], universal_newlines=True).splitlines() - for line in modules_list: - module_name = line[:line.find(' ')] - if module_name in lookup_modules: - modules.append(module_name) - return modules - - def _prepare_action(self) -> None: - with open(self.modules_konfig_path, "a") as kern_mods_config: - for suspicious_module in self.suspicious_modules: - kern_mods_config.write("blacklist {module}\n".format(module=suspicious_module)) - - for enabled_modules in self._get_enabled_modules(self.suspicious_modules): - util.logged_check_call(["/usr/sbin/rmmod", enabled_modules]) - - def _post_action(self) -> None: - for module in self.suspicious_modules: - files.replace_string(self.modules_konfig_path, "blacklist " + module, "") - - def _revert_action(self) -> None: - if not os.path.exists(self.modules_konfig_path): - return - - for module in self.suspicious_modules: - files.replace_string(self.modules_konfig_path, "blacklist " + module, "") - - -class RuleSelinux(action.ActiveAction): - def __init__(self): - self.name = "rule selinux status" - self.selinux_config = "/etc/selinux/config" - self.getenforce_cmd = "/usr/sbin/getenforce" - - def _is_required(self) -> bool: - if not os.path.exists(self.selinux_config) or not os.path.exists(self.getenforce_cmd): - return False - - return subprocess.check_output([self.getenforce_cmd], universal_newlines=True).strip() == "Enforcing" - - def _prepare_action(self) -> None: - files.replace_string(self.selinux_config, "SELINUX=enforcing", "SELINUX=permissive") - - def _post_action(self) -> None: - files.replace_string(self.selinux_config, "SELINUX=permissive", "SELINUX=enforcing") - - def _revert_action(self) -> None: - files.replace_string(self.selinux_config, "SELINUX=permissive", "SELINUX=enforcing") - - -class AddFinishSshLoginMessage(action.ActiveAction): - def __init__(self): - self.name = "add finish ssh login message" - self.finish_message = """ -The server has been converted to AlmaLinux 8. -""" - - def _prepare_action(self) -> None: - pass - - def _post_action(self) -> None: - motd.add_finish_ssh_login_message(self.finish_message) - motd.publish_finish_ssh_login_message() - - def _revert_action(self) -> None: - pass - - -class AddInProgressSshLoginMessage(action.ActiveAction): - def __init__(self): - self.name = "add in progress ssh login message" - path_to_script = os.path.abspath(sys.argv[0]) - self.in_progress_message = f""" -=============================================================================== -Message from the Plesk centos2alma tool: -The server is being converted to AlmaLinux 8. Please wait. -To see the current conversion status, run the '{path_to_script} --status' command. -To monitor the conversion progress in real time, run the '{path_to_script} --monitor' command. -=============================================================================== -""" - - def _prepare_action(self) -> None: - motd.restore_ssh_login_message() - motd.add_inprogress_ssh_login_message(self.in_progress_message) - - def _post_action(self) -> None: - pass - - def _revert_action(self) -> None: - motd.restore_ssh_login_message() - - -class DisablePleskSshBanner(action.ActiveAction): - def __init__(self): - self.name = "disable plesk ssh banner" - self.banner_command_path = "/root/.plesk_banner" - - def _prepare_action(self) -> None: - if os.path.exists(self.banner_command_path): - files.backup_file(self.banner_command_path) - os.unlink(self.banner_command_path) - - def _post_action(self) -> None: - files.restore_file_from_backup(self.banner_command_path) - - def _revert_action(self) -> None: - files.restore_file_from_backup(self.banner_command_path) - - -class PreRebootPause(action.ActiveAction): - def __init__(self, reboot_message: str, pause_time: int = 45): - self.name = "pause before reboot" - self.pause_time = pause_time - self.message = reboot_message - - def _prepare_action(self) -> None: - print(self.message) - time.sleep(self.pause_time) - - def _post_action(self) -> None: - pass - - def _revert_action(self) -> None: - pass - - -class HandleConversionStatus(action.ActiveAction): - def __init__(self): - self.name = "prepare and send conversion status" - - def _prepare_action(self) -> None: - plesk.prepare_conversion_flag() - - def _post_action(self) -> None: - plesk.send_conversion_status(True) - - def _revert_action(self) -> None: - plesk.remove_conversion_flag() - - -class FixSyslogLogrotateConfig(action.ActiveAction): - def __init__(self): - self.name = "fix logrotate config for rsyslog" - self.config_path = "/etc/logrotate.d/syslog" - self.right_logrotate_config = """ -/var/log/cron -/var/log/messages -/var/log/secure -/var/log/spooler -{ - missingok - sharedscripts - postrotate - /usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true - endscript -} -""" - - def _prepare_action(self): - pass - - def _post_action(self): - path_to_backup = plesk.CONVERTER_TEMP_DIRECTORY + "/syslog.logrotate.bak" - shutil.move(self.config_path, path_to_backup) - - with open(self.config_path, "w") as f: - f.write(self.right_logrotate_config) - - # File installed from the package not relay on our goals because - # it will rotate /var/log/maillog, which should be processed from plesk side - rpmnew_file = self.config_path + ".rpmnew" - if os.path.exists(rpmnew_file): - os.remove(rpmnew_file) - - motd.add_finish_ssh_login_message(f"The logrotate configuration for rsyslog has been updated. The old configuration has been saved as {path_to_backup}") - - def _revert_action(self): - pass - - -class RecreateAwstatConfigurationFiles(action.ActiveAction): - def __init__(self): - self.name = "recreate awstat configuration files for domains" - - def get_awstat_domains(self) -> typing.Set[str]: - domains_awstats_directory = "/usr/local/psa/etc/awstats/" - domains = set() - for awstat_config_file in os.listdir(domains_awstats_directory): - if awstat_config_file.startswith("awstats.") and awstat_config_file.endswith("-http.conf"): - domains.add(awstat_config_file.split("awstats.")[-1].rsplit("-http.conf")[0]) - return domains - - def _prepare_action(self): - pass - - def _post_action(self): - rpm.handle_all_rpmnew_files("/etc/awstats") - - for domain in self.get_awstat_domains(): - log.info(f"Recreating awstat configuration for domain: {domain}") - util.logged_check_call( - [ - "/usr/sbin/plesk", "sbin", "webstatmng", "--set-configs", - "--stat-prog", "awstats", "--domain-name", domain - ], stdin=subprocess.DEVNULL - ) - - def _revert_action(self): - pass - - def estimate_post_time(self) -> int: - return len(self.get_awstat_domains()) * 0.1 + 5 - - -class RevertChangesInGrub(action.ActiveAction): - grub_configs_paths: typing.List[str] - - def __init__(self): - self.name = "revert changes in grub made by elivate" - self.grub_configs_paths = [ - "/boot/grub2/grub.cfg", - "/boot/grub2/grubenv", - ] - - def _prepare_action(self): - for config in self.grub_configs_paths: - if os.path.exists(config): - files.backup_file(config) - - def _post_action(self): - for config in self.grub_configs_paths: - if os.path.exists(config): - files.remove_backup(config) - - def _revert_action(self): - for config in self.grub_configs_paths: - if os.path.exists(config): - files.restore_file_from_backup(config) - - def estimate_prepare_time(self): - return 1 - - def estimate_post_time(self): - return 1 - - def estimate_revert_time(self): - return 1 diff --git a/actions/convert.py b/actions/convert.py deleted file mode 100644 index 77fe8be..0000000 --- a/actions/convert.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. -from common import action, util - - -class DoConvert(action.ActiveAction): - def __init__(self): - self.name = "doing the conversion" - - def _prepare_action(self) -> None: - util.logged_check_call(["/usr/bin/leapp", "preupgrade"]) - util.logged_check_call(["/usr/bin/leapp", "upgrade"]) - - def _post_action(self) -> None: - pass - - def _revert_action(self) -> None: - pass - - def estimate_prepare_time(self) -> int: - return 17 * 60 diff --git a/actions/emails.py b/actions/emails.py deleted file mode 100644 index d26dabe..0000000 --- a/actions/emails.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. -import json -import os -import shutil -import subprocess - -from common import action, files, log, motd, plesk, util - - -class IncreaseDovecotDHParameters(action.ActiveAction): - def __init__(self): - self.name = "increase Dovecot DH parameters size to 2048 bits" - self.dhparam_size = 2048 - - def _is_required(self) -> bool: - proc = subprocess.run( - ["/usr/sbin/plesk", "sbin", "sslmng", "--show-config"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, - ) - if proc.returncode != 0: - log.warn(f"Failed to get ssl configuration by plesk sslmng: {proc.stdout}\n{proc.stderr}") - return False - - try: - sslmng_config = json.loads(proc.stdout) - if int(sslmng_config["effective"]["dovecot"]["dhparams_size"]) >= self.dhparam_size: - return False - except json.JSONDecodeError: - log.warn(f"Failed to parse plesk sslmng results: {proc.stdout}") - return False - - return True - - def _prepare_action(self) -> None: - pass - - def _post_action(self) -> None: - util.logged_check_call( - [ - "/usr/sbin/plesk", "sbin", "sslmng", - "--service", "dovecot", - "--strong-dh", - f"--dhparams-size={self.dhparam_size}", - ] - ) - - def _revert_action(self) -> None: - pass - - def estimate_post_time(self) -> int: - return 5 - - -class RestoreDovecotConfiguration(action.ActiveAction): - dovecot_config_path: str - - def __init__(self): - self.name = "restore Dovecot configuration" - self.dovecot_config_path = "/etc/dovecot/dovecot.conf" - - def _is_required(self) -> bool: - return os.path.exists(self.dovecot_config_path) - - def _prepare_action(self) -> None: - files.backup_file(self.dovecot_config_path) - - def _post_action(self): - path_to_backup = plesk.CONVERTER_TEMP_DIRECTORY + "/dovecot.conf.bak" - if os.path.exists(self.dovecot_config_path): - shutil.copy(self.dovecot_config_path, path_to_backup) - motd.add_finish_ssh_login_message(f"The dovecot configuration '{self.dovecot_config_path}' has been restored from CentOS 7. Modern configuration was placed in '{path_to_backup}'.") - - files.restore_file_from_backup(self.dovecot_config_path) - - def _revert_action(self): - files.remove_backup(self.dovecot_config_path) diff --git a/actions/extensions.py b/actions/extensions.py deleted file mode 100644 index b2b03f0..0000000 --- a/actions/extensions.py +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. -import os -import pwd -import shutil -import typing - -from common import action, util, log, leapp_configs, files - - -# We should do rebundling of ruby applications after the conversion -# because some of our libraries will be missing. -# The prime example of missing library - libmysqlclient.so.18 required by mysql2 gem -class RebundleRubyApplications(action.ActiveAction): - PLESK_APACHE_CONFIGS_PATH = "/etc/httpd/conf/plesk.conf.d/vhosts/" - - def __init__(self): - self.name = "rebundling ruby applications" - self.description = "rebundling ruby applications" - - def _is_ruby_domain(self, domain_path) -> bool: - return os.path.exists(os.path.join(domain_path, ".rbenv")) - - def _is_required(self) -> bool: - if not os.path.exists("/var/lib/rbenv/versions/"): - return False - - return any(self._is_ruby_domain(domain) for domain in os.scandir("/var/www/vhosts")) - - def _prepare_action(self) -> None: - pass - - def _get_ruby_application_path_by_config(self, domain_name: str) -> str: - apache_config = os.path.join(self.PLESK_APACHE_CONFIGS_PATH, domain_name + ".conf") - - if not os.path.isfile(apache_config) or not files.find_file_substrings(apache_config, "PassengerRuby"): - # Likely it means ruby application is not enabled, so we need to do direct search in the filesystem - return None - - application_root = files.find_file_substrings(apache_config, "PassengerAppRoot") - if not application_root: - return None - - # Record format is: PassengerAppRoot "[path]" - # We can expect it is always in this format since configuration is generated by plesk - return application_root[0].split()[1].strip("\"") - - def _get_ruby_application_path_by_bundle_subdir(self, domain_home: str) -> str: - bundle = files.find_subdirectory_by(domain_home, - lambda subdir: os.path.basename(subdir) == "bundle" and - os.path.exists(os.path.join(subdir, "ruby"))) - if bundle is None or not os.path.isdir(bundle): - return None - - return os.path.dirname(os.path.dirname(bundle)) - - def _get_ruby_application_paths(self, domain_path: str) -> typing.Tuple[str, str]: - app_directory = self._get_ruby_application_path_by_config(domain_path.name) - if app_directory is None: - log.debug("Ruby application is disabled. Search for application root in filesystem. Domain home directory: {}".format(domain_path)) - app_directory = self._get_ruby_application_path_by_bundle_subdir(domain_path) - - if app_directory is None: - return None, None - - return app_directory, os.path.join(app_directory, "vendor", "bundle") - - def _post_action(self) -> None: - ruby_domains = (domain_path for domain_path in os.scandir("/var/www/vhosts") if self._is_ruby_domain(domain_path)) - for domain_path in ruby_domains: - log.debug("Re-bundling ruby application in domain: {}".format(domain_path.name)) - - app_directory, bundle = self._get_ruby_application_paths(domain_path) - - if bundle is None or not os.path.isdir(bundle): - log.debug("Skip re-bundling for non bundling domain '{}'".format(domain_path.name)) - continue - - stat_info = os.stat(app_directory) - username = pwd.getpwuid(stat_info.st_uid).pw_name - - log.debug("Bundle: {}. App directory: {}. Username: {}".format(bundle, app_directory, username)) - - shutil.rmtree(bundle) - util.logged_check_call(["/usr/sbin/plesk", "sbin", "rubymng", "run-bundler", username, app_directory]) - - def _revert_action(self) -> None: - pass - - def estimate_post_time(self) -> int: - return 60 * len([domain_path for domain_path in os.scandir("/var/www/vhosts") if self._is_ruby_domain(domain_path)]) - - -class FixupImunify(action.ActiveAction): - def __init__(self): - self.name = "fixing up imunify360" - - def _is_required(self) -> bool: - return len(files.find_files_case_insensitive("/etc/yum.repos.d", ["imunify360.repo"])) > 0 - - def _prepare_action(self) -> None: - repofiles = files.find_files_case_insensitive("/etc/yum.repos.d", ["imunify*.repo"]) - - leapp_configs.add_repositories_mapping(repofiles) - - # For some reason leapp replace the libssh2 packageon installation. It's fine in most cases, - # but imunify packages require libssh2. So we should use PRESENT action to keep it. - leapp_configs.set_package_action("libssh2", leapp_configs.LeappActionType.PRESENT) - - def _post_action(self) -> None: - pass - - def _revert_action(self) -> None: - pass - - -class AdoptKolabRepositories(action.ActiveAction): - def __init__(self): - self.name = "adopting kolab repositories" - - def _is_required(self) -> bool: - return len(files.find_files_case_insensitive("/etc/yum.repos.d", ["kolab*.repo"])) > 0 - - def _prepare_action(self) -> None: - repofiles = files.find_files_case_insensitive("/etc/yum.repos.d", ["kolab*.repo"]) - - leapp_configs.add_repositories_mapping(repofiles, ignore=["kolab-16-source", - "kolab-16-testing-source", - "kolab-16-testing-candidate-source"]) - - def _post_action(self) -> None: - for file in files.find_files_case_insensitive("/etc/yum.repos.d", ["kolab*.repo"]): - leapp_configs.adopt_repositories(file) - - util.logged_check_call(["/usr/bin/dnf", "-y", "update"]) - - def _revert_action(self) -> None: - pass - - def estimate_prepare_time(self) -> int: - return 30 - - def estimate_post_time(self) -> int: - return 2 * 60 diff --git a/actions/php.py b/actions/php.py deleted file mode 100644 index 2a8df87..0000000 --- a/actions/php.py +++ /dev/null @@ -1,230 +0,0 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. - -import os -import shutil - -from common import action, log, packages, php, plesk, systemd, version - - -class AssertMinPhpVersionInstalled(action.CheckAction): - min_version: version.PHPVersion - - def __init__( - self, - min_version: str, - ): - self.name = "check for outdated PHP versions" - self.min_version = version.PHPVersion(min_version) - self.description = """Outdated PHP versions were detected: {versions}. -\tRemove outdated PHP packages via Plesk Installer to proceed with the conversion: -\tYou can do it by calling the following command: -\tplesk installer remove --components {remove_arg} -""" - - def _do_check(self) -> bool: - log.debug(f"Checking for minimum installed PHP version of {self.min_version}") - # TODO: get rid of the explicit version list - known_php_versions = php.get_known_php_versions() - - log.debug(f"Known PHP versions: {known_php_versions}") - outdated_php_versions = [php for php in known_php_versions if php < self.min_version] - outdated_php_packages = {f"plesk-php{php.major}{php.minor}": str(php) for php in outdated_php_versions} - log.debug(f"Outdated PHP versions: {outdated_php_versions}") - - installed_pkgs = packages.filter_installed_packages(outdated_php_packages.keys()) - log.debug(f"Outdated PHP packages installed: {installed_pkgs}") - if len(installed_pkgs) == 0: - log.debug("No outdated PHP versions installed") - return True - - self.description = self.description.format( - versions=", ".join([outdated_php_packages[installed] for installed in installed_pkgs]), - remove_arg=" ".join(outdated_php_packages[installed].replace(" ", "") for installed in installed_pkgs).lower() - ) - - log.debug("Outdated PHP versions found") - return False - - -class AssertMinPhpVersionUsedByWebsites(action.CheckAction): - min_version: version.PHPVersion - - def __init__( - self, - min_version: str, - optional: bool = True, - ): - self.name = "checking domains uses outdated PHP" - self.min_version = version.PHPVersion(min_version) - self.optional = optional - self.description = """We have identified that the domains are using older versions of PHP. -\tSwitch the following domains to {modern} or later in order to continue with the conversion process: -\t- {domains} - -\tYou can achieve this by executing the following command: -\t> plesk bin domain -u [domain] -php_handler_id plesk-php80-fastcgi -""" - - def _do_check(self) -> bool: - log.debug(f"Checking the minimum PHP version being used by the websites. The restriction is: {self.min_version}") - if not plesk.is_plesk_database_ready(): - if self.optional: - log.info("Plesk database is not ready. Skipping the minimum PHP for websites check.") - return True - raise RuntimeError("Plesk database is not ready. Skipping the minimum PHP for websites check.") - - outdated_php_handlers = [f"'{handler}'" for handler in php.get_outdated_php_handlers(self.min_version)] - log.debug(f"Outdated PHP handlers: {outdated_php_handlers}") - try: - looking_for_domains_sql_request = """ - SELECT d.name FROM domains d JOIN hosting h ON d.id = h.dom_id WHERE h.php_handler_id in ({}); - """.format(", ".join(outdated_php_handlers)) - - outdated_php_domains = plesk.get_from_plesk_database(looking_for_domains_sql_request) - if not outdated_php_domains: - return True - - log.debug(f"Outdated PHP domains: {outdated_php_domains}") - outdated_php_domains = "\n\t- ".join(outdated_php_domains) - self.description = self.description.format( - modern=self.min_version, - domains=outdated_php_domains - ) - except Exception as ex: - log.err("Unable to get domains list from plesk database!") - raise RuntimeError("Unable to get domains list from plesk database!") from ex - - return False - - -class AssertMinPhpVersionUsedByCron(action.CheckAction): - min_version: version.PHPVersion - - def __init__( - self, - min_version: str, - optional: bool = True, - ): - self.name = "checking cronjob uses outdated PHP" - self.min_version = version.PHPVersion(min_version) - self.optional = optional - self.description = """We have detected that some cronjobs are using outdated PHP versions. -\tSwitch the following cronjobs to {modern} or later in order to continue with the conversion process:" -\t- {cronjobs} - -\tYou can do this in the Plesk web interface by going “Tools & Settings” → “Scheduled Tasks”. -""" - - def _do_check(self) -> bool: - log.debug(f"Checking the minimum PHP version used in cronjobs. Restriction is: {self.min_version}") - if not plesk.is_plesk_database_ready(): - if self.optional: - log.info("Plesk database is not ready. Skipping the minimum PHP for cronjobs check.") - return True - raise RuntimeError("Plesk database is not ready. Skipping the minimum PHP for cronjobs check.") - - outdated_php_handlers = [f"'{handler}'" for handler in php.get_outdated_php_handlers(self.min_version)] - log.debug(f"Outdated PHP handlers: {outdated_php_handlers}") - - try: - looking_for_cronjobs_sql_request = """ - SELECT command from ScheduledTasks WHERE type = "php" and phpHandlerId in ({}); - """.format(", ".join(outdated_php_handlers)) - - outdated_php_cronjobs = plesk.get_from_plesk_database(looking_for_cronjobs_sql_request) - if not outdated_php_cronjobs: - return True - - log.debug(f"Outdated PHP cronjobs: {outdated_php_cronjobs}") - outdated_php_cronjobs = "\n\t- ".join(outdated_php_cronjobs) - - self.description = self.description.format( - modern=self.min_version, - cronjobs=outdated_php_cronjobs) - except Exception as ex: - log.err("Unable to get cronjobs list from plesk database!") - raise RuntimeError("Unable to get cronjobs list from plesk database!") from ex - - return False - - -class AssertOSVendorPHPUsedByWebsites(action.CheckAction): - min_version: version.PHPVersion - - def __init__( - self, - min_version: str, - ): - self.name = "checking OS vendor PHP used by websites" - self.min_version = version.PHPVersion(min_version) - self.description = """We have detected that some domains are using the OS vendor PHP version. -\tSwitch the following domains to {modern} or later in order to continue with the conversion process: -\t- {domains} - -\tYou can achieve this by executing the following command: -\t> plesk bin domain -u [domain] -php_handler_id plesk-php80-fastcgi -""" - - def _do_check(self) -> bool: - log.debug("Checking the OS vendor PHP version used by the websites") - if not plesk.is_plesk_database_ready(): - log.info("Plesk database is not ready. Skipping the OS vendor PHP check.") - return True - - os_vendor_php_handlers = ["'fpm'", "'fastcgi'"] - log.debug(f"OS vendor PHP handlers: {os_vendor_php_handlers}") - - try: - looking_for_domains_sql_request = """ - SELECT d.name FROM domains d JOIN hosting h ON d.id = h.dom_id WHERE h.php_handler_id in ({}); - """.format(", ".join(os_vendor_php_handlers)) - - os_vendor_php_domains = plesk.get_from_plesk_database(looking_for_domains_sql_request) - if not os_vendor_php_domains: - return True - - log.debug(f"OS vendor PHP domains: {os_vendor_php_domains}") - os_vendor_php_domains = "\n\t- ".join(os_vendor_php_domains) - self.description = self.description.format( - modern=self.min_version, - domains=os_vendor_php_domains - ) - except Exception as ex: - log.err("Unable to get domains list from plesk database!") - raise RuntimeError("Unable to get domains list from plesk database!") from ex - - return False - - -OS_VENDOR_PHP_FPM_CONFIG = "/etc/php-fpm.d/www.conf" - - -class FixOsVendorPhpFpmConfiguration(action.ActiveAction): - def __init__(self): - self.name = "fix OS vendor PHP configuration" - - def is_required(self) -> bool: - if os.path.exists(OS_VENDOR_PHP_FPM_CONFIG): - return True - - def _prepare_action(self): - pass - - def _post_action(self): - # Plesk expect www pool to be disabled by default. - # Every distro should has the same configuration generated by Plesk. - # However we store the original configuration in the www.conf.saved_by_psa file. - if os.path.exists(f"{OS_VENDOR_PHP_FPM_CONFIG}.rpmnew"): - shutil.move(f"{OS_VENDOR_PHP_FPM_CONFIG}.rpmnew", f"{OS_VENDOR_PHP_FPM_CONFIG}.saved_by_psa") - elif os.path.exists(f"{OS_VENDOR_PHP_FPM_CONFIG}.rpmsave"): - shutil.move(f"{OS_VENDOR_PHP_FPM_CONFIG}", f"{OS_VENDOR_PHP_FPM_CONFIG}.saved_by_psa") - shutil.move(f"{OS_VENDOR_PHP_FPM_CONFIG}.rpmsave", f"{OS_VENDOR_PHP_FPM_CONFIG}") - - if systemd.is_service_exists("php-fpm") and systemd.is_service_active("php-fpm"): - systemd.restart_services(["php-fpm"]) - - def _revert_action(self): - pass - - def estimate_post_time(self): - return 1 diff --git a/actions/spamassassin.py b/actions/spamassassin.py deleted file mode 100644 index 8a933f2..0000000 --- a/actions/spamassassin.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. -from common import action, motd, rpm, util - -import os - -SPAMASSASIN_CONFIG_PATH = "/etc/mail/spamassassin/init.pre" - - -class FixSpamassassinConfig(action.ActiveAction): - # Make sure the trick is preformed before any call of 'systemctl daemon-reload' - # because we change spamassassin.service configuration in scope of this action. - def __init__(self): - self.name = "fix spamassassin configuration" - - def _is_required(self) -> bool: - return rpm.is_package_installed("psa-spamassassin") - - def _prepare_action(self) -> None: - util.logged_check_call(["/usr/bin/systemctl", "stop", "spamassassin.service"]) - util.logged_check_call(["/usr/bin/systemctl", "disable", "spamassassin.service"]) - - def _post_action(self) -> None: - util.logged_check_call(["/usr/sbin/plesk", "sbin", "spammng", "--enable"]) - util.logged_check_call(["/usr/sbin/plesk", "sbin", "spammng", "--update", "--enable-server-configs", "--enable-user-configs"]) - - util.logged_check_call(["/usr/bin/systemctl", "daemon-reload"]) - util.logged_check_call(["/usr/bin/systemctl", "enable", "spamassassin.service"]) - - if rpm.handle_rpmnew(SPAMASSASIN_CONFIG_PATH): - motd.add_finish_ssh_login_message("Note that spamassasin configuration '{}' was changed during conversion. " - "Original configuration can be found in {}.rpmsave.".format(SPAMASSASIN_CONFIG_PATH, SPAMASSASIN_CONFIG_PATH)) - - def _revert_action(self) -> None: - util.logged_check_call(["/usr/bin/systemctl", "enable", "spamassassin.service"]) - util.logged_check_call(["/usr/bin/systemctl", "start", "spamassassin.service"]) - - -class CheckSpamassassinPlugins(action.CheckAction): - def __init__(self): - self.name = "check spamassassin additional plugins enabled" - self.description = """There are additional plugins enabled in spamassassin configuration: -\t- {} - -They will not be available after the conversion. Please disable them manually or use --disable-spamassasin-plugins option to force script to remove them automatically. -""" - self.supported_plugins = [ - "Mail::SpamAssassin::Plugin::URIDNSBL", - "Mail::SpamAssassin::Plugin::Hashcash", - "Mail::SpamAssassin::Plugin::SPF", - ] - - def _do_check(self) -> bool: - if not os.path.exists(SPAMASSASIN_CONFIG_PATH): - return True - - unsupported_plugins = [] - with open(SPAMASSASIN_CONFIG_PATH, "r") as fp: - for loadline in [line for line in fp.readlines() if line.startswith("loadplugin")]: - plugin = loadline.rstrip().split(' ')[1] - if plugin not in self.supported_plugins: - unsupported_plugins.append(plugin) - - if not unsupported_plugins: - return True - - self.description = self.description.format("\n\t- ".join(unsupported_plugins)) - return False diff --git a/actions/systemd.py b/actions/systemd.py deleted file mode 100644 index 98af06e..0000000 --- a/actions/systemd.py +++ /dev/null @@ -1,150 +0,0 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. -import os - -from common import action, systemd, util - - -class RulePleskRelatedServices(action.ActiveAction): - - def __init__(self): - self.name = "rule plesk services" - plesk_known_systemd_services = [ - "crond.service", - "dovecot.service", - "drwebd.service", - "fail2ban.service", - "httpd.service", - "mailman.service", - "mariadb.service", - "mysqld.service", - "named-chroot.service", - "plesk-ext-monitoring-hcd.service", - "plesk-ssh-terminal.service", - "plesk-task-manager.service", - "plesk-web-socket.service", - "psa.service", - "sw-collectd.service", - "sw-cp-server.service", - "sw-engine.service", - ] - self.plesk_systemd_services = [ - service for service in plesk_known_systemd_services if systemd.is_service_can_be_started(service) - ] - - # Oneshot services are special, so they shouldn't be started on revert or after conversion, just enabled - self.oneshot_services = [ - "plesk-ip-remapping.service", - ] - - # We don't remove postfix service when remove it during qmail installation - # so we should choose the right smtp service, otherwise they will conflict - if systemd.is_service_can_be_started("qmail.service"): - self.plesk_systemd_services.append("qmail.service") - elif systemd.is_service_can_be_started("postfix.service"): - self.plesk_systemd_services.append("postfix.service") - - def _prepare_action(self): - util.logged_check_call(["/usr/bin/systemctl", "stop"] + self.plesk_systemd_services) - util.logged_check_call(["/usr/bin/systemctl", "disable"] + self.plesk_systemd_services + self.oneshot_services) - - def _post_action(self): - util.logged_check_call(["/usr/bin/systemctl", "enable"] + self.plesk_systemd_services + self.oneshot_services) - # Don't do startup because the services will be started up after reboot at the end of the script anyway. - - def _revert_action(self): - util.logged_check_call(["/usr/bin/systemctl", "enable"] + self.plesk_systemd_services + self.oneshot_services) - util.logged_check_call(["/usr/bin/systemctl", "start"] + self.plesk_systemd_services) - - def estimate_prepare_time(self): - return 10 - - def estimate_post_time(self): - return 5 - - def estimate_revert_time(self): - return 10 - - -class AddUpgradeSystemdService(action.ActiveAction): - - def __init__(self, script_path, options): - self.name = "adding centos2alma resume service" - - self.script_path = script_path - # ToDo. It's pretty simple to forget to add argument here, so maybe we should find another way - self.options = [ - (" --upgrade-postgres", options.upgrade_postgres_allowed), - (" --verbose", options.verbose), - (" --no-reboot", options.no_reboot), - ] - - self.service_name = 'plesk-centos2alma.service' - self.service_file_path = os.path.join('/etc/systemd/system', self.service_name) - self.service_content = ''' -[Unit] -Description=First boot service for upgrade process from CentOS 7 to AlmaLinux8. -After=network.target network-online.target - -[Service] -Type=simple -# want to run it once per boot time -RemainAfterExit=yes -ExecStart={script_path} -s finish {arguments} - -[Install] -WantedBy=multi-user.target -''' - - def _prepare_action(self): - arguments = "" - for argument, enabled in self.options: - if enabled: - arguments += argument - - with open(self.service_file_path, "w") as dst: - dst.write(self.service_content.format(script_path=self.script_path, arguments=arguments)) - - util.logged_check_call(["/usr/bin/systemctl", "enable", self.service_name]) - - def _post_action(self): - if os.path.exists(self.service_file_path): - util.logged_check_call(["/usr/bin/systemctl", "disable", self.service_name]) - - os.remove(self.service_file_path) - - def _revert_action(self): - if os.path.exists(self.service_file_path): - util.logged_check_call(["/usr/bin/systemctl", "disable", self.service_name]) - - os.remove(self.service_file_path) - - -class StartPleskBasicServices(action.ActiveAction): - - def __init__(self): - self.name = "starting plesk services" - self.plesk_basic_services = [ - "mariadb.service", - "mysqld.service", - "plesk-task-manager.service", - "plesk-web-socket.service", - "sw-cp-server.service", - "sw-engine.service", - ] - self.plesk_basic_services = [service for service in self.plesk_basic_services if systemd.is_service_exists(service)] - - def _enable_services(self): - # MariaDB could be started before, so we should stop it first - util.logged_check_call(["/usr/bin/systemctl", "stop", "mariadb.service"]) - - util.logged_check_call(["/usr/bin/systemctl", "enable"] + self.plesk_basic_services) - util.logged_check_call(["/usr/bin/systemctl", "start"] + self.plesk_basic_services) - - def _prepare_action(self): - pass - - def _post_action(self): - self._enable_services() - - def _revert_action(self): - self._enable_services() \ No newline at end of file diff --git a/centos2almaconverter/BUCK b/centos2almaconverter/BUCK new file mode 100644 index 0000000..ec0ce6f --- /dev/null +++ b/centos2almaconverter/BUCK @@ -0,0 +1,21 @@ +# Copyright 1999-2023. Plesk International GmbH. All rights reserved. +# vim:ft=python: + +PRODUCT_VERSION = '1.3.0' + +genrule( + name = 'version', + out = 'version.json', + bash = r"""echo "{\"version\": \"%s\", \"revision\": \"`git rev-parse HEAD`\"}" > $OUT""" % (PRODUCT_VERSION), +) + +python_library( + name = 'lib', + srcs = glob( + ['**/*.py'], + ), + resources = [ + ':version', + ], + visibility = ['PUBLIC'], +) diff --git a/centos2almaconverter/__init__.py b/centos2almaconverter/__init__.py new file mode 100644 index 0000000..99c26a6 --- /dev/null +++ b/centos2almaconverter/__init__.py @@ -0,0 +1 @@ +# Copyright 1999-2023. Plesk International GmbH. All rights reserved. diff --git a/actions/__init__.py b/centos2almaconverter/actions/__init__.py similarity index 64% rename from actions/__init__.py rename to centos2almaconverter/actions/__init__.py index d36ff68..9ac0855 100644 --- a/actions/__init__.py +++ b/centos2almaconverter/actions/__init__.py @@ -1,9 +1,8 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. +# Copyright 1999 - 2023. Plesk International GmbH. All rights reserved. from .common_checks import * from .common import * from .configure import * from .convert import * -from .emails import * from .extensions import * from .installation import * from .mariadb import * @@ -11,5 +10,3 @@ from .perl import * from .php import * from .postgres import * -from .spamassassin import * -from .systemd import * \ No newline at end of file diff --git a/centos2almaconverter/actions/common.py b/centos2almaconverter/actions/common.py new file mode 100644 index 0000000..20add14 --- /dev/null +++ b/centos2almaconverter/actions/common.py @@ -0,0 +1,166 @@ +# Copyright 1999 - 2023. Plesk International GmbH. All rights reserved. +import os +import shutil +import subprocess +import typing + +from pleskdistup.common import action, files, log, motd, rpm, util + + +class FixNamedConfig(action.ActiveAction): + def __init__(self): + self.name = "fix named configuration" + self.user_options_path = "/etc/named-user-options.conf" + self.chrooted_file_path = "/var/named/chroot/etc/named-user-options.conf" + + def _is_required(self) -> bool: + return os.path.exists(self.chrooted_file_path) + + def _prepare_action(self) -> action.ActionResult: + if not os.path.exists(self.user_options_path): + os.symlink(self.chrooted_file_path, self.user_options_path) + + if os.path.getsize(self.chrooted_file_path) == 0: + with open(self.chrooted_file_path, "w") as f: + f.write("# centos2alma workaround commentary") + + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: + if os.path.exists(self.user_options_path): + os.unlink(self.user_options_path) + + with open(self.chrooted_file_path, "r") as f: + if f.read() == "# centos2alma workaround commentary": + os.unlink(self.chrooted_file_path) + with open(self.chrooted_file_path, "w") as _: + pass + + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: + if os.path.exists(self.user_options_path): + os.unlink(self.user_options_path) + + return action.ActionResult() + + +class DisableSuspiciousKernelModules(action.ActiveAction): + def __init__(self): + self.name = "rule suspicious kernel modules" + self.suspicious_modules = ["pata_acpi", "btrfs", "floppy"] + self.modules_konfig_path = "/etc/modprobe.d/pataacpibl.conf" + + def _get_enabled_modules(self, lookup_modules: typing.List[str]) -> typing.List[str]: + modules = [] + modules_list = subprocess.check_output(["/usr/sbin/lsmod"], universal_newlines=True).splitlines() + for line in modules_list: + module_name = line[:line.find(' ')] + if module_name in lookup_modules: + modules.append(module_name) + return modules + + def _prepare_action(self) -> action.ActionResult: + with open(self.modules_konfig_path, "a") as kern_mods_config: + for suspicious_module in self.suspicious_modules: + kern_mods_config.write("blacklist {module}\n".format(module=suspicious_module)) + + for enabled_modules in self._get_enabled_modules(self.suspicious_modules): + util.logged_check_call(["/usr/sbin/rmmod", enabled_modules]) + + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: + for module in self.suspicious_modules: + files.replace_string(self.modules_konfig_path, "blacklist " + module, "") + + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: + if not os.path.exists(self.modules_konfig_path): + return action.ActionResult() + + for module in self.suspicious_modules: + files.replace_string(self.modules_konfig_path, "blacklist " + module, "") + + return action.ActionResult() + + +class FixSyslogLogrotateConfig(action.ActiveAction): + config_path: str + path_to_backup: str + right_logrotate_config: str + + def __init__(self, store_dir: str): + self.name = "fix logrotate config for rsyslog" + self.config_path = "/etc/logrotate.d/syslog" + self.path_to_backup = store_dir + "/syslog.logrotate.bak" + self.right_logrotate_config = """ +/var/log/cron +/var/log/messages +/var/log/secure +/var/log/spooler +{ + missingok + sharedscripts + postrotate + /usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true + endscript +} +""" + + def _prepare_action(self) -> action.ActionResult: + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: + shutil.move(self.config_path, self.path_to_backup) + + with open(self.config_path, "w") as f: + f.write(self.right_logrotate_config) + + # File installed from the package not relay on our goals because + # it will rotate /var/log/maillog, which should be processed from plesk side + rpmnew_file = self.config_path + ".rpmnew" + if os.path.exists(rpmnew_file): + os.remove(rpmnew_file) + + motd.add_finish_ssh_login_message(f"The logrotate configuration for rsyslog has been updated. The old configuration has been saved as {self.path_to_backup}\n") + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() + + +class RecreateAwstatConfigurationFiles(action.ActiveAction): + def __init__(self): + self.name = "recreate awstat configuration files for domains" + + def get_awstat_domains(self) -> typing.Set[str]: + domains_awstats_directory = "/usr/local/psa/etc/awstats/" + domains = set() + for awstat_config_file in os.listdir(domains_awstats_directory): + if awstat_config_file.startswith("awstats.") and awstat_config_file.endswith("-http.conf"): + domains.add(awstat_config_file.split("awstats.")[-1].rsplit("-http.conf")[0]) + return domains + + def _prepare_action(self) -> action.ActionResult: + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: + rpm.handle_all_rpmnew_files("/etc/awstats") + + for domain in self.get_awstat_domains(): + log.info(f"Recreating awstat configuration for domain: {domain}") + util.logged_check_call( + [ + "/usr/sbin/plesk", "sbin", "webstatmng", "--set-configs", + "--stat-prog", "awstats", "--domain-name", domain + ], stdin=subprocess.DEVNULL + ) + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() + + def estimate_post_time(self) -> int: + return len(self.get_awstat_domains()) * 0.1 + 5 diff --git a/actions/common_checks.py b/centos2almaconverter/actions/common_checks.py similarity index 67% rename from actions/common_checks.py rename to centos2almaconverter/actions/common_checks.py index d58b877..fd7d400 100644 --- a/actions/common_checks.py +++ b/centos2almaconverter/actions/common_checks.py @@ -1,4 +1,4 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. +# Copyright 1999 - 2023. Plesk International GmbH. All rights reserved. import collections import os @@ -6,22 +6,7 @@ import shutil import subprocess -from common import action, files, log, plesk, version - - -class PleskInstallerNotInProgress(action.CheckAction): - def __init__(self): - self.name = "checking if Plesk installer is in progress" - self.description = """The conversion process cannot continue because Plesk Installer is working. -\tPlease wait until it finishes or call 'plesk installer stop' to abort it. -""" - - def _do_check(self) -> bool: - installer_status = subprocess.check_output(["/usr/sbin/plesk", "installer", "--query-status", "--enable-xml-output"], - universal_newlines=True) - if "query_ok" in installer_status: - return True - return False +from pleskdistup.common import action, dist, files, log, plesk, rpm, version class DistroIsCentos79(action.CheckAction): @@ -39,74 +24,16 @@ def _do_check(self) -> bool: return False -class DistroIsAlmalinux8(action.CheckAction): +class AssertDistroIsAlmalinux8(action.CheckAction): def __init__(self): self.name = "checking if distro is AlmaLinux8" self.description = "You are running a distributive other than AlmaLinux 8. The finalization stage can only be started on AlmaLinux 8." def _do_check(self) -> bool: - distro = platform.linux_distribution() - major_version = distro[1].split(".")[0] - if distro[0] == "AlmaLinux" and int(major_version) == 8: - return True - return False - - -class PleskVersionIsActual(action.CheckAction): - def __init__(self): - self.name = "checking if Plesk version is actual" - self.description = "Only Plesk Obsidian 18.0.43 or later is supported. Update Plesk to version 18.0.43 or later and try again." - - def _do_check(self) -> bool: - try: - major, _, iter, _ = plesk.get_plesk_version() - return int(major) >= 18 and int(iter) >= 43 - except Exception as ex: - log.warn("Checking plesk version is failed with error: {}".format(ex)) - - return False - - -class CheckAvailableSpace(action.CheckAction): - def __init__(self): - self.name = "checking available space" - self.required_space = 5 * 1024 * 1024 * 1024 # 5GB - self.description = """There is insufficient disk space available. Leapp requires a minimum of {} of free space -\ton the disk where the '/var/lib' directory is located. Available space: {}. -\tFree up enough disk space and try again. -""" - - def _huminize_size(self, size): - original = size - for unit in ("B", "KB", "MB", "GB", "TB"): - if size < 1024: - return f"{size:.2f} {unit}" - size /= 1024 - return f"{original} B" - - def _do_check(self) -> bool: - # Leapp stores rhel 8 filesystem in /var/lib/leapp - # That's why it takes so much disk space - available_space = shutil.disk_usage("/var/lib")[2] - if available_space >= self.required_space: - return True - - self.description = self.description.format(self._huminize_size(self.required_space), self._huminize_size(available_space)) - return False - - -class CheckGrubInstalled(action.CheckAction): - def __init__(self): - self.name = "checking if grub is installed" - self.description = """The /etc/default/grub file is missing. GRUB may not be installed. -\tMake sure that GRUB is installed and try again. -""" - - def _do_check(self) -> bool: - return os.path.exists("/etc/default/grub") + return dist.get_distro() == dist.Distro.ALMALINUX8 -class CheckNoMoreThenOneKernelNamedNIC(action.CheckAction): +class AssertNoMoreThenOneKernelNamedNIC(action.CheckAction): def __init__(self): self.name = "checking if there is more than one NIC interface using ketnel-name" self.description = """The system has one or more network interface cards (NICs) using kernel-names (ethX). @@ -129,35 +56,8 @@ def _do_check(self) -> bool: return True -class CheckIsInContainer(action.CheckAction): - def __init__(self): - self.name = "checking if the system not in a container" - self.description = "The system is running in a container-like environment ({}). The conversion is not supported for such systems." - - def _is_docker(self) -> bool: - return os.path.exists("/.dockerenv") - - def _is_podman(self) -> bool: - return os.path.exists("/run/.containerenv") - - def _is_vz_like(self) -> bool: - return os.path.exists("/proc/vz") - - def _do_check(self) -> bool: - if self._is_docker(): - self.description = self.description.format("Docker container") - return False - elif self._is_podman(): - self.description = self.description.format("Podman container") - return False - elif self._is_vz_like(): - self.description = self.description.format("Virtuozzo container") - return False - - return True - - -class CheckLastInstalledKernelInUse(action.CheckAction): +# ToDo. Implement for deb-based and move to common part. Might be useful for distupgrade/other converters +class AssertLastInstalledKernelInUse(action.CheckAction): def __init__(self): self.name = "checking if the last installed kernel is in use" self.description = """The last installed kernel is not in use. @@ -171,17 +71,24 @@ def _get_kernel_version_in_use(self) -> version.KernelVersion: return version.KernelVersion(curr_kernel) def _get_last_installed_kernel_version(self) -> version.KernelVersion: - versions = subprocess.check_output( - [ - "/usr/bin/rpm", "-q", "-a", "kernel", "kernel-plus", "kernel-rt-core" - ], universal_newlines=True - ).splitlines() + versions = subprocess.check_output(["/usr/bin/rpm", "-q", "-a", "kernel"], universal_newlines=True).splitlines() + # There is 'kernel-' prefix, that doesn't matter for us now, so just skip it + versions = [ver.split("-", 1)[-1] for ver in versions] log.debug("Installed kernel versions: {}".format(', '.join(versions))) + versions = [version.KernelVersion(ver) for ver in versions] return max(versions) + def _is_realtime_installed(self) -> bool: + return len(subprocess.check_output(["/usr/bin/rpm", "-q", "-a", "kernel-rt"], universal_newlines=True).splitlines()) > 0 + def _do_check(self) -> bool: + # For now skip checking realtime kernels. leapp will check it on it's side + # I believe we have no much installation with realtime kernel + if self._is_realtime_installed(): + return True + last_installed_kernel_version = self._get_last_installed_kernel_version() used_kernel_version = self._get_kernel_version_in_use() @@ -192,7 +99,7 @@ def _do_check(self) -> bool: return True -class CheckIsLocalRepositoryNotPresent(action.CheckAction): +class AssertLocalRepositoryNotPresent(action.CheckAction): def __init__(self): self.name = "checking if the local repository is present" self.description = """There are rpm repository with local storage present. Leapp is not support such kind of repositories. @@ -220,7 +127,7 @@ def _do_check(self) -> bool: return False -class CheckRepositoryDuplicates(action.CheckAction): +class AssertThereIsNoRepositoryDuplicates(action.CheckAction): def __init__(self): self.name = "checking if there are duplicate repositories" self.description = """There are duplicate repositories present: @@ -232,7 +139,7 @@ def __init__(self): def _do_check(self) -> bool: repositories = [] repofiles = files.find_files_case_insensitive("/etc/yum.repos.d", ["*.repo"]) - for repofile in repofiles: + for repofile in repofiles: with open(repofile, "r") as f: for line in f.readlines(): line = line.strip() @@ -247,7 +154,7 @@ def _do_check(self) -> bool: return False -class CheckPackagesUpToDate(action.CheckAction): +class AssertPackagesUpToDate(action.CheckAction): def __init__(self): self.name = "checking if all packages are up to date" self.description = "There are packages which are not up to date. Call `yum update -y && reboot` to update the packages.\n" @@ -256,3 +163,31 @@ def _do_check(self) -> bool: subprocess.check_call(["/usr/bin/yum", "clean", "all"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) checker = subprocess.run(["/usr/bin/yum", "check-update"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return checker.returncode == 0 + + +class AssertAvailableSpace(action.CheckAction): + def __init__(self): + self.name = "checking available space" + self.required_space = 5 * 1024 * 1024 * 1024 # 5GB + self.description = """There is insufficient disk space available. Leapp requires a minimum of {} of free space +\ton the disk where the '/var/lib' directory is located. Available space: {}. +\tFree up enough disk space and try again. +""" + + def _huminize_size(self, size): + original = size + for unit in ("B", "KB", "MB", "GB", "TB"): + if size < 1024: + return f"{size:.2f} {unit}" + size /= 1024 + return f"{original} B" + + def _do_check(self) -> bool: + # Leapp stores rhel 8 filesystem in /var/lib/leapp + # That's why it takes so much disk space + available_space = shutil.disk_usage("/var/lib")[2] + if available_space >= self.required_space: + return True + + self.description = self.description.format(self._huminize_size(self.required_space), self._huminize_size(available_space)) + return False \ No newline at end of file diff --git a/actions/configure.py b/centos2almaconverter/actions/configure.py similarity index 68% rename from actions/configure.py rename to centos2almaconverter/actions/configure.py index 8c98c31..c40bc45 100644 --- a/actions/configure.py +++ b/centos2almaconverter/actions/configure.py @@ -1,7 +1,7 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. +# Copyright 1999 - 2023. Plesk International GmbH. All rights reserved. import os -from common import action, leapp_configs, files +from pleskdistup.common import action, leapp_configs, files class PrepareLeappConfigurationBackup(action.ActiveAction): @@ -11,41 +11,48 @@ def __init__(self): "/etc/leapp/files/repomap.csv", "/etc/leapp/files/pes-events.json"] - def _prepare_action(self) -> None: + def _prepare_action(self) -> action.ActionResult: for file in self.leapp_configs: if os.path.exists(file): files.backup_file(file) - def _post_action(self) -> None: + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: for file in self.leapp_configs: if os.path.exists(file): files.remove_backup(file) - def _revert_action(self) -> None: + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: for file in self.leapp_configs: if os.path.exists(file): files.restore_file_from_backup(file) + return action.ActionResult() + class LeapReposConfiguration(action.ActiveAction): def __init__(self): self.name = "map plesk repositories for leapp" - def _prepare_action(self) -> None: + def _prepare_action(self) -> action.ActionResult: repofiles = files.find_files_case_insensitive("/etc/yum.repos.d", ["plesk*.repo", "epel.repo"]) leapp_configs.add_repositories_mapping(repofiles, ignore=[ "PLESK_17_PHP52", "PLESK_17_PHP53", "PLESK_17_PHP54", "PLESK_17_PHP55", "PLESK_17_PHP56", "PLESK_17_PHP70", ]) + return action.ActionResult() - def _post_action(self) -> None: + def _post_action(self) -> action.ActionResult: # Since only leap related files should be changed, there is nothing to do after on finishing stage - pass + return action.ActionResult() - def _revert_action(self) -> None: - pass + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() class LeapChoicesConfiguration(action.ActiveAction): @@ -54,7 +61,7 @@ def __init__(self): self.name = "configure leapp user choices" self.answer_file_path = "/var/log/leapp/answerfile.userchoices" - def _prepare_action(self) -> None: + def _prepare_action(self) -> action.ActionResult: try: with open(self.answer_file_path, 'w') as usercoise: usercoise.write("[remove_pam_pkcs11_module_check]\nconfirm = True\n") @@ -63,12 +70,15 @@ def _prepare_action(self) -> None: "sufficient permissions to write in this directory. Please run the script as root " "and use `setenforce 0` to disable selinux".format(self.answer_file_path)) - def _post_action(self) -> None: + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: if os.path.exists(self.answer_file_path): os.unlink(self.answer_file_path) + return action.ActionResult() - def _revert_action(self) -> None: - pass + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() class PatchLeappErrorOutput(action.ActiveAction): @@ -77,15 +87,16 @@ def __init__(self): self.name = "patch leapp error log output" self.path_to_src = "/usr/share/leapp-repository/repositories/system_upgrade/common/libraries/dnfplugin.py" - def _prepare_action(self) -> None: + def _prepare_action(self) -> action.ActionResult: # Looks like there is no setter for stdout/stderr in the python for leapp files.replace_string(self.path_to_src, "if six.PY2:", "if False:") + return action.ActionResult() - def _post_action(self) -> None: - pass + def _post_action(self) -> action.ActionResult: + return action.ActionResult() - def _revert_action(self) -> None: - pass + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() class PatchLeappDebugNonAsciiPackager(action.ActiveAction): @@ -97,13 +108,14 @@ def __init__(self): def is_required(self) -> bool: return os.path.exists(self.path_to_src) - def _prepare_action(self) -> None: + def _prepare_action(self) -> action.ActionResult: # so sometimes we could have non-ascii packager name, in this case leapp will fail # on printing debug message. So we need to encode it to utf-8 before print (and only before print I think) files.replace_string(self.path_to_src, ", pkg.packager", ", pkg.packager.encode('utf-8')") + return action.ActionResult() - def _post_action(self) -> None: - pass + def _post_action(self) -> action.ActionResult: + return action.ActionResult() - def _revert_action(self) -> None: - pass + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() diff --git a/centos2almaconverter/actions/convert.py b/centos2almaconverter/actions/convert.py new file mode 100644 index 0000000..cc90c4b --- /dev/null +++ b/centos2almaconverter/actions/convert.py @@ -0,0 +1,21 @@ +# Copyright 1999 - 2023. Plesk International GmbH. All rights reserved. +from pleskdistup.common import action, util + + +class DoCentos2AlmaConvert(action.ActiveAction): + def __init__(self): + self.name = "doing the conversion" + + def _prepare_action(self) -> action.ActionResult: + util.logged_check_call(["/usr/bin/leapp", "preupgrade"]) + util.logged_check_call(["/usr/bin/leapp", "upgrade"]) + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() + + def estimate_prepare_time(self) -> int: + return 17 * 60 diff --git a/centos2almaconverter/actions/extensions.py b/centos2almaconverter/actions/extensions.py new file mode 100644 index 0000000..06b1183 --- /dev/null +++ b/centos2almaconverter/actions/extensions.py @@ -0,0 +1,58 @@ +# Copyright 1999 - 2023. Plesk International GmbH. All rights reserved. +from pleskdistup.common import action, util, leapp_configs, files + + +class FixupImunify(action.ActiveAction): + def __init__(self): + self.name = "fixing up imunify360" + + def _is_required(self) -> bool: + return len(files.find_files_case_insensitive("/etc/yum.repos.d", ["imunify360.repo"])) > 0 + + def _prepare_action(self) -> action.ActionResult: + repofiles = files.find_files_case_insensitive("/etc/yum.repos.d", ["imunify*.repo"]) + + leapp_configs.add_repositories_mapping(repofiles) + + # For some reason leapp replace the libssh2 packageon installation. It's fine in most cases, + # but imunify packages require libssh2. So we should use PRESENT action to keep it. + leapp_configs.set_package_action("libssh2", leapp_configs.LeappActionType.PRESENT) + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() + + +class AdoptKolabRepositories(action.ActiveAction): + def __init__(self): + self.name = "adopting kolab repositories" + + def _is_required(self) -> bool: + return len(files.find_files_case_insensitive("/etc/yum.repos.d", ["kolab*.repo"])) > 0 + + def _prepare_action(self) -> action.ActionResult: + repofiles = files.find_files_case_insensitive("/etc/yum.repos.d", ["kolab*.repo"]) + + leapp_configs.add_repositories_mapping(repofiles, ignore=["kolab-16-source", + "kolab-16-testing-source", + "kolab-16-testing-candidate-source"]) + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: + for file in files.find_files_case_insensitive("/etc/yum.repos.d", ["kolab*.repo"]): + leapp_configs.adopt_repositories(file) + + util.logged_check_call(["/usr/bin/dnf", "-y", "update"]) + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() + + def estimate_prepare_time(self) -> int: + return 30 + + def estimate_post_time(self) -> int: + return 2 * 60 diff --git a/actions/installation.py b/centos2almaconverter/actions/installation.py similarity index 75% rename from actions/installation.py rename to centos2almaconverter/actions/installation.py index 7463afe..6053173 100644 --- a/actions/installation.py +++ b/centos2almaconverter/actions/installation.py @@ -1,8 +1,8 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. +# Copyright 1999 - 2023. Plesk International GmbH. All rights reserved. import os import shutil -from common import action, rpm, util +from pleskdistup.common import action, rpm, util class LeapInstallation(action.ActiveAction): @@ -15,7 +15,7 @@ def __init__(self): "leapp-data-almalinux-0.1-6.el7", ] - def _prepare_action(self) -> None: + def _prepare_action(self) -> action.ActionResult: if not rpm.is_package_installed("elevate-release"): util.logged_check_call(["/usr/bin/yum", "install", "-y", "https://repo.almalinux.org/elevate/elevate-release-latest-el7.noarch.rpm"]) @@ -28,9 +28,14 @@ def _prepare_action(self) -> None: # the pre-checker from detecting leapp as outdated and prevent re-evaluation # on the next restart. util.logged_check_call(["/usr/bin/yum-config-manager", "--disable", "elevate"]) + return action.ActionResult() - def _post_action(self) -> None: - rpm.remove_packages(rpm.filter_installed_packages(self.pkgs_to_install + ["elevate-release"])) + def _post_action(self) -> action.ActionResult: + rpm.remove_packages( + rpm.filter_installed_packages( + self.pkgs_to_install + ["elevate-release", "leapp-upgrade-el7toel8"] + ) + ) leapp_related_files = [ "/root/tmp_leapp_py3/leapp", @@ -49,8 +54,10 @@ def _post_action(self) -> None: if os.path.exists(directory): shutil.rmtree(directory) - def _revert_action(self) -> None: - pass + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() def estimate_prepare_time(self) -> int: return 40 diff --git a/actions/mariadb.py b/centos2almaconverter/actions/mariadb.py similarity index 78% rename from actions/mariadb.py rename to centos2almaconverter/actions/mariadb.py index 84f7b50..82d967a 100644 --- a/actions/mariadb.py +++ b/centos2almaconverter/actions/mariadb.py @@ -1,14 +1,14 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. +# Copyright 1999 - 2023. Plesk International GmbH. All rights reserved. import subprocess import os -from common import action, leapp_configs, files, log, mariadb, rpm, util +from pleskdistup.common import action, leapp_configs, files, log, mariadb, rpm, util MARIADB_VERSION_ON_ALMA = mariadb.MariaDBVersion("10.3.39") -class CheckMariadbRepoAvailable(action.CheckAction): +class AssertMariadbRepoAvailable(action.CheckAction): def __init__(self): self.name = "check mariadb repo available" self.description = """ @@ -49,7 +49,7 @@ def __init__(self): def _is_required(self) -> bool: return mariadb.is_mariadb_installed() and mariadb.get_installed_mariadb_version() > MARIADB_VERSION_ON_ALMA - def _prepare_action(self) -> None: + def _prepare_action(self) -> action.ActionResult: repofiles = files.find_files_case_insensitive("/etc/yum.repos.d", ["mariadb.repo"]) if len(repofiles) == 0: raise Exception("Mariadb installed from unknown repository. Please check the '{}' file is present".format("/etc/yum.repos.d/mariadb.repo")) @@ -59,11 +59,12 @@ def _prepare_action(self) -> None: log.debug("Set repository mapping in the leapp configuration file") leapp_configs.set_package_repository("mariadb", "alma-mariadb") + return action.ActionResult() - def _post_action(self) -> None: + def _post_action(self) -> action.ActionResult: repofiles = files.find_files_case_insensitive("/etc/yum.repos.d", ["mariadb.repo"]) if len(repofiles) == 0: - return 0 + return action.ActionResult() for repofile in repofiles: leapp_configs.adopt_repositories(repofile) @@ -78,9 +79,10 @@ def _post_action(self) -> None: "MariaDB-server-compat", "MariaDB-shared"])) rpm.install_packages(["MariaDB-client", "MariaDB-server"], repository=mariadb_repo_id) + return action.ActionResult() - def _revert_action(self) -> None: - pass + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() def estimate_prepare_time(self) -> int: return 30 @@ -96,10 +98,17 @@ def __init__(self): def _is_required(self) -> bool: return mariadb.is_mariadb_installed() and not mariadb.get_installed_mariadb_version() > MARIADB_VERSION_ON_ALMA - def _prepare_action(self) -> None: - pass + def _prepare_action(self) -> action.ActionResult: + rpm.remove_packages(rpm.filter_installed_packages(["MariaDB-client", + "MariaDB-client-compat", + "MariaDB-compat", + "MariaDB-common", + "MariaDB-server", + "MariaDB-server-compat", + "MariaDB-shared"])) + return action.ActionResult() - def _post_action(self) -> None: + def _post_action(self) -> action.ActionResult: # Leapp is not remove non-standard MariaDB-client package. But since we have updated # mariadb to 10.3.35 old client is not relevant anymore. So we have to switch to new client. # On the other hand we want to be sure AlmaLinux mariadb-server installed as well @@ -125,9 +134,10 @@ def _post_action(self) -> None: # Also find a way to drop cookies, because it will ruin your day # We have to delete it once again, because leapp going to install it in scope of conversion process, # but without right configs + return action.ActionResult() - def _revert_action(self) -> None: - pass + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() def estimate_post_time(self): return 2 * 60 @@ -140,11 +150,12 @@ def __init__(self): def _is_required(self) -> bool: return mariadb.is_mysql_installed - def _prepare_action(self) -> None: - pass + def _prepare_action(self) -> action.ActionResult: + return action.ActionResult() - def _post_action(self) -> None: + def _post_action(self) -> action.ActionResult: subprocess.check_call(["/usr/bin/dnf", "install", "-y", "mariadb-connector-c"]) + return action.ActionResult() - def _revert_action(self) -> None: - pass + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() diff --git a/actions/packages.py b/centos2almaconverter/actions/packages.py similarity index 71% rename from actions/packages.py rename to centos2almaconverter/actions/packages.py index b27c40e..65c44eb 100644 --- a/actions/packages.py +++ b/centos2almaconverter/actions/packages.py @@ -1,9 +1,10 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. -from common import action, files, leapp_configs, log, motd, plesk, rpm, util - +# Copyright 1999 - 2023. Plesk International GmbH. All rights reserved. import os +import typing import shutil +from pleskdistup.common import action, files, leapp_configs, log, motd, packages, plesk, rpm, util + class RemovingPleskConflictPackages(action.ActiveAction): @@ -16,14 +17,16 @@ def __init__(self): "psa-mod_proxy", ] - def _prepare_action(self): - rpm.remove_packages(rpm.filter_installed_packages(self.conflict_pkgs)) + def _prepare_action(self) -> action.ActionResult: + packages.remove_packages(rpm.filter_installed_packages(self.conflict_pkgs)) + return action.ActionResult() - def _post_action(self): - pass + def _post_action(self) -> action.ActionResult: + return action.ActionResult() - def _revert_action(self): - rpm.install_packages(self.conflict_pkgs) + def _revert_action(self) -> action.ActionResult: + packages.install_packages(self.conflict_pkgs) + return action.ActionResult() def estimate_prepare_time(self): return 2 @@ -36,27 +39,30 @@ class ReinstallPleskComponents(action.ActiveAction): def __init__(self): self.name = "re-installing plesk components" - def _prepare_action(self): + def _prepare_action(self) -> action.ActionResult: components_pkgs = [ "plesk-roundcube", "psa-phpmyadmin", ] - rpm.remove_packages(rpm.filter_installed_packages(components_pkgs)) + packages.remove_packages(rpm.filter_installed_packages(components_pkgs)) + return action.ActionResult() - def _post_action(self): + def _post_action(self) -> action.ActionResult: # We should reinstall psa-phpmyadmin over plesk installer to make sure every trigger # will be called. It's because triggers that creates phpmyadmin configuration files # expect plesk on board. Hence when we install the package in scope of temporary OS # the file can't be created. - rpm.remove_packages(["psa-phpmyadmin"]) + packages.remove_packages(["psa-phpmyadmin"]) util.logged_check_call(["/usr/sbin/plesk", "installer", "update"]) util.logged_check_call(["/usr/sbin/plesk", "installer", "add", "--components", "roundcube"]) + return action.ActionResult() - def _revert_action(self): + def _revert_action(self) -> action.ActionResult: util.logged_check_call(["/usr/sbin/plesk", "installer", "update"]) util.logged_check_call(["/usr/sbin/plesk", "installer", "add", "--components", "roundcube"]) + return action.ActionResult() def estimate_prepare_time(self): return 10 @@ -69,9 +75,12 @@ def estimate_revert_time(self): class ReinstallConflictPackages(action.ActiveAction): - def __init__(self): + removed_packages_file: str + conflict_pkgs_map: typing.Dict[str, str] + + def __init__(self, temp_directory: str): self.name = "re-installing common conflict packages" - self.removed_packages_file = plesk.CONVERTER_TEMP_DIRECTORY + "/centos2alma_removed_packages.txt" + self.removed_packages_file = temp_directory + "/centos2alma_removed_packages.txt" self.conflict_pkgs_map = { "galera": "galera", "python36-argcomplete": "python3-argcomplete", @@ -109,35 +118,39 @@ def __init__(self): def _is_required(self): return len(rpm.filter_installed_packages(self.conflict_pkgs_map.keys())) > 0 - def _prepare_action(self): + def _prepare_action(self) -> action.ActionResult: packages_to_remove = rpm.filter_installed_packages(self.conflict_pkgs_map.keys()) rpm.remove_packages(packages_to_remove) - with open(self.removed_packages_file, "w") as f: - f.write("\n".join(packages_to_remove)) + with open(self.removed_packages_file, "a") as f: + f.write("\n".join(packages_to_remove) + "\n") - def _post_action(self): + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: if not os.path.exists(self.removed_packages_file): log.warn("File with removed packages list is not exists. While the action itself was not skipped. Skip reinstalling packages.") - return + return action.ActionResult() with open(self.removed_packages_file, "r") as f: - packages_to_install = [self.conflict_pkgs_map[pkg] for pkg in f.read().splitlines()] + packages_to_install = [self.conflict_pkgs_map[pkg] for pkg in set(f.read().splitlines())] rpm.install_packages(packages_to_install) os.unlink(self.removed_packages_file) + return action.ActionResult() - def _revert_action(self): + def _revert_action(self) -> action.ActionResult: if not os.path.exists(self.removed_packages_file): log.warn("File with removed packages list is not exists. While the action itself was not skipped. Skip reinstalling packages.") - return + return action.ActionResult() with open(self.removed_packages_file, "r") as f: - packages_to_install = f.read().splitlines() + packages_to_install = list(set(f.read().splitlines())) rpm.install_packages(packages_to_install) os.unlink(self.removed_packages_file) + return action.ActionResult() def estimate_prepare_time(self): return 10 @@ -157,30 +170,6 @@ def estimate_revert_time(self): return 60 + 10 * pkgs_number -class UpdatePlesk(action.ActiveAction): - def __init__(self): - self.name = "updating plesk" - - def _prepare_action(self): - # The conversion process removes the python36-lxml package since it conflicts with python3-lxml from AlmaLinux. - # If the conversion fails for any reason and there is no rollback, we need to reinstall the package. - # Otherwise, the Plesk installer will encounter issues. The problem with conversion only occurs when - # new Plesk packages have been published, such as hotfixes, so the impact of the problem is low. - # However, because we don't do a rollback for every conversion failure, this scenario is possible - # and could be confusing for users. Therefore, we've decided to handle it proactively. - if not rpm.is_package_installed("python36-lxml"): - rpm.install_packages(["python36-lxml"]) - - util.logged_check_call(["/usr/sbin/plesk", "installer", "update"]) - - def _post_action(self): - pass - - def _revert_action(self): - pass - - def estimate_prepare_time(self): - return 3 * 60 CHANGED_REPOS_MSG_FMT = """During the conversion, some of customized .repo files were updated. You can find the old @@ -193,14 +182,21 @@ class AdoptRepositories(action.ActiveAction): def __init__(self): self.name = "adopting repositories" - def _prepare_action(self): - pass + def _prepare_action(self) -> action.ActionResult: + return action.ActionResult() def _use_rpmnew_repositories(self): # The problem is about changed repofiles, that leapp tring to install form packages. - # For example, when epel.repo file was changed, dnf will save the new one as epel.repo.rpmnew. + # For example, when epel.repo file was changed, dnf will save the new one as epel.repo.rpmnew. # I beleive there could be other files with the same problem, so lets iterate every .rpmnew file in /etc/yum.repos.d - fixed_list = rpm.handle_all_rpmnew_files("/etc/yum.repos.d") + fixed_list = [] + for file in files.find_files_case_insensitive("/etc/yum.repos.d", ["*.rpmnew"]): + original_file = file[:-len(".rpmnew")] + if os.path.exists(original_file): + shutil.move(original_file, original_file + ".rpmsave") + fixed_list.append(original_file) + + shutil.move(file, original_file) if len(fixed_list) > 0: motd.add_finish_ssh_login_message(CHANGED_REPOS_MSG_FMT.format(changed_files="\n\t".join(fixed_list))) @@ -214,13 +210,14 @@ def _adopt_plesk_repositories(self): ]) leapp_configs.adopt_repositories(file) - def _post_action(self): + def _post_action(self) -> action.ActionResult: self._use_rpmnew_repositories() self._adopt_plesk_repositories() util.logged_check_call(["/usr/bin/dnf", "-y", "update"]) + return action.ActionResult() - def _revert_action(self): - pass + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() def estimate_post_time(self): return 2 * 60 @@ -230,7 +227,7 @@ class RemoveOldMigratorThirparty(action.ActiveAction): def __init__(self): self.name = "removing old migrator thirdparty packages" - def _is_required(self): + def _is_required(self) -> bool: for file in files.find_files_case_insensitive("/etc/yum.repos.d", ["plesk*migrator*.repo"]): for _1, _2, url, _3, _4, _5 in rpm.extract_repodata(file): if "PMM_0.1.10/thirdparty-rpm" in url: @@ -238,41 +235,45 @@ def _is_required(self): return False - def _prepare_action(self): + def _prepare_action(self) -> action.ActionResult: for file in files.find_files_case_insensitive("/etc/yum.repos.d", ["plesk*migrator*.repo"]): files.backup_file(file) rpm.remove_repositories(file, [ - lambda _1, _2, baseurl, _3, _4: "PMM_0.1.10/thirdparty-rpm" in baseurl, + lambda _1, _2, baseurl, _3: "PMM_0.1.10/thirdparty-rpm" in baseurl, ]) + return action.ActionResult() - def _post_action(self): + def _post_action(self) -> action.ActionResult: for file in files.find_files_case_insensitive("/etc/yum.repos.d", ["plesk*migrator*.repo"]): files.remove_backup(file) + return action.ActionResult() - def _revert_action(self): + def _revert_action(self) -> action.ActionResult: for file in files.find_files_case_insensitive("/etc/yum.repos.d", ["plesk*migrator*.repo"]): files.restore_file_from_backup(file) + return action.ActionResult() class RestoreMissingNginx(action.ActiveAction): def __init__(self): self.name = "restore nginx if it was removed during the conversion" - def _is_required(self): + def _is_required(self) -> bool: # nginx related to plesk could be removed by user. So we need to make sure # it is installed before we start the conversion - return rpm.is_package_installed("sw-nginx") + return packages.is_package_installed("sw-nginx") - def _prepare_action(self): - pass + def _prepare_action(self) -> action.ActionResult: + return action.ActionResult() - def _post_action(self): - if not rpm.is_package_installed("sw-nginx"): + def _post_action(self) -> action.ActionResult: + if not packages.is_package_installed("sw-nginx"): util.logged_check_call(["/usr/sbin/plesk", "installer", "add", "--components", "nginx"]) + return action.ActionResult() - def _revert_action(self): - pass + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() def estimate_post_time(self): return 3 * 60 @@ -290,7 +291,7 @@ def __init__(self): \t3. rm {repo_paths} """ - def _do_check(self): + def _do_check(self) -> bool: for path in self.OUTDATED_LETSENCRYPT_REPO_PATHS: if os.path.exists(path): self.description = self.description.format(repo_paths=path) @@ -304,15 +305,16 @@ class AdoptAtomicRepositories(action.ActiveAction): def __init__(self): self.name = "adopting atomic repositories" - def is_required(self): + def is_required(self) -> bool: return os.path.exists(self.atomic_repository_path) - def _prepare_action(self): + def _prepare_action(self) -> action.ActionResult: leapp_configs.add_repositories_mapping([self.atomic_repository_path]) + return action.ActionResult() - def _post_action(self): + def _post_action(self) -> action.ActionResult: # We don't need to adopt repositories here because repositories uses $releasever-$basearch - pass + return action.ActionResult() - def _revert_action(self): - pass + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() diff --git a/actions/perl.py b/centos2almaconverter/actions/perl.py similarity index 85% rename from actions/perl.py rename to centos2almaconverter/actions/perl.py index 072136e..21b9edd 100644 --- a/actions/perl.py +++ b/centos2almaconverter/actions/perl.py @@ -1,8 +1,8 @@ -# Copyright 1999-2024. WebPros International GmbH. All rights reserved. +# Copyright 1999-2023. Plesk International GmbH. All rights reserved. import os import shutil -from common import action, files, log, motd, plesk, rpm +from pleskdistup.common import action, files, log, motd, plesk, rpm CPAN_MODULES_DIRECTORY = "/usr/local/lib64/perl5" CPAN_MODULES_RPM_MAPPING = { @@ -34,7 +34,7 @@ } -class CheckUnknownPerlCpanModules(action.CheckAction): +class AssertThereIsNoUnknownPerlCpanModules(action.CheckAction): def __init__(self): self.name = "checking if there are no unknown perl cpan modules" self.description = """There are Perl modules installed by CPAN without known RPM package analogues are found. @@ -66,14 +66,14 @@ def _do_check(self): class ReinstallPerlCpanModules(action.ActiveAction): - def __init__(self): + def __init__(self, store_dir: str): self.name = "reinstalling perl cpan modules" - self.removed_modules_file = plesk.CONVERTER_TEMP_DIRECTORY + "/centos2alma_removed_perl_modules.txt" + self.removed_modules_file = os.path.join(store_dir, "centos2alma_removed_perl_modules.txt") def _is_required(self): return not files.is_directory_empty(CPAN_MODULES_DIRECTORY) - def _prepare_action(self): + def _prepare_action(self) -> action.ActionResult: with open(self.removed_modules_file, "w") as f: for module in files.find_files_case_insensitive(CPAN_MODULES_DIRECTORY, ["*.pm"], recursive=True): if module in CPAN_MODULES_RPM_MAPPING.keys(): @@ -84,13 +84,14 @@ def _prepare_action(self): # Since we can't be sure cpan-minimal is installed, we have to # remove all in barbaric way. shutil.move(CPAN_MODULES_DIRECTORY, CPAN_MODULES_DIRECTORY + ".backup") + return action.ActionResult() - def _post_action(self): + def _post_action(self) -> action.ActionResult: if not os.path.exists(self.removed_modules_file): - no_file_warning = "The file containing the list of removed Perl modules does not exist. However, the action itself was not skipped. You can find the previously installed modules at the following path: {}.".format(CPAN_MODULES_DIRECTORY + ".backup") + no_file_warning = "The file containing the list of removed Perl modules does not exist. However, the action itself was not skipped. You can find the previously installed modules at the following path: {}.\n".format(CPAN_MODULES_DIRECTORY + ".backup") log.warn(no_file_warning) motd.add_finish_ssh_login_message(no_file_warning) - return + return action.ActionResult() with open(self.removed_modules_file, "r") as f: packages_to_install = f.read().splitlines() @@ -98,10 +99,12 @@ def _post_action(self): os.unlink(self.removed_modules_file) shutil.rmtree(CPAN_MODULES_DIRECTORY + ".backup") + return action.ActionResult() - def _revert_action(self): + def _revert_action(self) -> action.ActionResult: shutil.move(CPAN_MODULES_DIRECTORY + ".backup", CPAN_MODULES_DIRECTORY) os.unlink(self.removed_modules_file) + return action.ActionResult() def estimate_post_time(self): return 60 diff --git a/centos2almaconverter/actions/php.py b/centos2almaconverter/actions/php.py new file mode 100644 index 0000000..14ed500 --- /dev/null +++ b/centos2almaconverter/actions/php.py @@ -0,0 +1,41 @@ +# Copyright 2023-2024. WebPros International GmbH. All rights reserved. + +import os +import shutil + +from pleskdistup.common import action, systemd + +OS_VENDOR_PHP_FPM_CONFIG = "/etc/php-fpm.d/www.conf" + + +class FixOsVendorPhpFpmConfiguration(action.ActiveAction): + def __init__(self): + self.name = "fix OS vendor PHP configuration" + + def is_required(self) -> bool: + if os.path.exists(OS_VENDOR_PHP_FPM_CONFIG): + return True + + def _prepare_action(self) -> action.ActionResult: + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: + # Plesk expect www pool to be disabled by default. + # Every distro should has the same configuration generated by Plesk. + # However we store the original configuration in the www.conf.saved_by_psa file. + if os.path.exists(f"{OS_VENDOR_PHP_FPM_CONFIG}.rpmnew"): + shutil.move(f"{OS_VENDOR_PHP_FPM_CONFIG}.rpmnew", f"{OS_VENDOR_PHP_FPM_CONFIG}.saved_by_psa") + elif os.path.exists(f"{OS_VENDOR_PHP_FPM_CONFIG}.rpmsave"): + shutil.move(f"{OS_VENDOR_PHP_FPM_CONFIG}", f"{OS_VENDOR_PHP_FPM_CONFIG}.saved_by_psa") + shutil.move(f"{OS_VENDOR_PHP_FPM_CONFIG}.rpmsave", f"{OS_VENDOR_PHP_FPM_CONFIG}") + + if systemd.is_service_exists("php-fpm") and systemd.is_service_active("php-fpm"): + systemd.restart_services(["php-fpm"]) + + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: + return action.ActionResult() + + def estimate_post_time(self): + return 1 diff --git a/actions/postgres.py b/centos2almaconverter/actions/postgres.py similarity index 89% rename from actions/postgres.py rename to centos2almaconverter/actions/postgres.py index 7db58a2..c219192 100644 --- a/actions/postgres.py +++ b/centos2almaconverter/actions/postgres.py @@ -1,8 +1,8 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. +# Copyright 1999 - 2023. Plesk International GmbH. All rights reserved. import os import subprocess -from common import action, files, leapp_configs, util +from pleskdistup.common import action, files, leapp_configs, util _PATH_TO_PGSQL = '/var/lib/pgsql' _PATH_TO_DATA = os.path.join(_PATH_TO_PGSQL, 'data') @@ -35,7 +35,7 @@ def _is_modern_database(): return True -class CheckOutdatedPostgresInstalled(action.CheckAction): +class AssertOutdatedPostgresNotInstalled(action.CheckAction): def __init__(self): self.name = "checking postgres version 10 or later is installed" self.description = '''Postgres version less then 10. This means the database should be upgraded. @@ -55,9 +55,10 @@ def __init__(self): def _is_required(self): return _is_postgres_installed() and _is_database_initialized() and not _is_modern_database() - def _prepare_action(self): + def _prepare_action(self) -> action.ActionResult: util.logged_check_call(['systemctl', 'stop', self.service_name]) util.logged_check_call(['systemctl', 'disable', self.service_name]) + return action.ActionResult() def _upgrade_database(self): util.logged_check_call(['dnf', 'install', '-y', 'postgresql-upgrade']) @@ -79,12 +80,14 @@ def _enable_postgresql(self): util.logged_check_call(['systemctl', 'enable', self.service_name]) util.logged_check_call(['systemctl', 'start', self.service_name]) - def _post_action(self): + def _post_action(self) -> action.ActionResult: self._upgrade_database() self._enable_postgresql() + return action.ActionResult() - def _revert_action(self): + def _revert_action(self) -> action.ActionResult: self._enable_postgresql() + return action.ActionResult() def estimate_post_time(self): return 3 * 60 @@ -107,7 +110,7 @@ def _is_service_active(self, service): res = subprocess.run(['/usr/bin/systemctl', 'is-active', service]) return res.returncode == 0 - def _prepare_action(self): + def _prepare_action(self) -> action.ActionResult: leapp_configs.add_repositories_mapping(["/etc/yum.repos.d/pgdg-redhat-all.repo"]) for major_version in self._get_versions(): @@ -118,7 +121,9 @@ def _prepare_action(self): util.logged_check_call(['/usr/bin/systemctl', 'stop', service_name]) util.logged_check_call(['/usr/bin/systemctl', 'disable', service_name]) - def _post_action(self): + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: for major_version in self._get_versions(): if major_version > _MODERN_POSTGRES: util.logged_check_call(['/usr/bin/dnf', '-q', '-y', 'module', 'disable', 'postgresql']) @@ -135,7 +140,9 @@ def _post_action(self): util.logged_check_call(['/usr/bin/systemctl', 'start', service_name]) os.remove(os.path.join(_PATH_TO_PGSQL, str(major_version) + '.enabled')) - def _revert_action(self): + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: for major_version in self._get_versions(): if os.path.exists(os.path.join(_PATH_TO_PGSQL, str(major_version) + '.enabled')): service_name = 'postgresql-' + str(major_version) @@ -143,5 +150,7 @@ def _revert_action(self): util.logged_check_call(['/usr/bin/systemctl', 'disable', service_name]) os.remove(os.path.join(_PATH_TO_PGSQL, str(major_version) + '.enabled')) + return action.ActionResult() + def estimate_post_time(self): return 3 * 60 diff --git a/centos2almaconverter/main.py b/centos2almaconverter/main.py new file mode 100644 index 0000000..18e7687 --- /dev/null +++ b/centos2almaconverter/main.py @@ -0,0 +1,13 @@ +#!/usr/bin/python3 +# Copyright 1999-2023. Plesk International GmbH. All rights reserved. + +import sys + +import pleskdistup.main +import pleskdistup.registry + +import centos2almaconverter.upgrader + +if __name__ == "__main__": + pleskdistup.registry.register_upgrader(centos2almaconverter.upgrader.Centos2AlmaConverterFactory()) + sys.exit(pleskdistup.main.main()) diff --git a/centos2almaconverter/upgrader.py b/centos2almaconverter/upgrader.py new file mode 100644 index 0000000..3b2d4fd --- /dev/null +++ b/centos2almaconverter/upgrader.py @@ -0,0 +1,284 @@ +# Copyright 1999-2023. Plesk International GmbH. All rights reserved. + +import argparse +import json +import os +import pkg_resources +import typing +import sys + +from centos2almaconverter import actions as centos2alma_actions +from pleskdistup import actions as common_actions +from pleskdistup.common import action, dist, feedback, files, util +from pleskdistup.phase import Phase +from pleskdistup.messages import REBOOT_WARN_MESSAGE +from pleskdistup.upgrader import DistUpgrader, DistUpgraderFactory, PathType + + +def get_version() -> str: + with pkg_resources.resource_stream(__name__, "version.json") as f: + return json.load(f)["version"] + + +def get_revision(short: bool = True) -> str: + with pkg_resources.resource_stream(__name__, "version.json") as f: + revision = json.load(f)["revision"] + if short: + revision = revision[:8] + return revision + + +class Centos2AlmaConverter(DistUpgrader): + _distro_from = dist.CentOs("7") + _distro_to = dist.AlmaLinux("8") + + _pre_reboot_delay = 45 + + def __init__(self): + super().__init__() + + self.upgrade_postgres_allowed = False + self.remove_unknown_perl_modules = False + self.disable_spamassasin_plugins = False + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(From {self._distro_from}, To {self._distro_to})" + + def __str__(self) -> str: + return f"{self.__class__.__name__}" + + @classmethod + def supports( + cls, + from_system: typing.Optional[dist.Distro] = None, + to_system: typing.Optional[dist.Distro] = None + ) -> bool: + return ( + (from_system is None or cls._distro_from == from_system) + and (to_system is None or cls._distro_to == to_system) + ) + + @property + def upgrader_name(self) -> str: + return "Plesk::Centos2AlmaConverter" + + @property + def upgrader_version(self) -> str: + return get_version() + "-" + get_revision() + + @property + def issues_url(self) -> str: + return "https://github.com/plesk/centos2alma/issues" + + def prepare_feedback( + self, + feed: feedback.Feedback, + ) -> feedback.Feedback: + + feed.collect_actions += [ + feedback.collect_installed_packages_yum, + feedback.collect_plesk_version, + ] + + feed.attached_files += [ + "/etc/leapp/files/repomap.csv", + "/etc/leapp/files/pes-events.json", + "/etc/leapp/files/leapp_upgrade_repositories.repo", + "/var/log/leapp/leapp-report.txt", + "/var/log/leapp/leapp-preupgrade.log", + "/var/log/leapp/leapp-upgrade.log", + ] + + for repofile in files.find_files_case_insensitive("/etc/yum.repos.d", ["*.repo*"]): + feed.attached_files.append(repofile) + + return feed + + def construct_actions( + self, + upgrader_bin_path: PathType, + options: typing.Any, + phase: Phase + ) -> typing.Dict[str, typing.List[action.ActiveAction]]: + new_os = str(self._distro_to) + + actions_map = { + "Status informing": [ + common_actions.HandleConversionStatus(options.status_flag_path, options.completion_flag_path), + common_actions.AddFinishSshLoginMessage(new_os), # Executed at the finish phase only + common_actions.AddInProgressSshLoginMessage(new_os), + ], + "Leapp instllation": [ + centos2alma_actions.LeapInstallation(), + ], + "Prepare configurations": [ + common_actions.RevertChangesInGrub(), + centos2alma_actions.PrepareLeappConfigurationBackup(), + centos2alma_actions.RemoveOldMigratorThirparty(), + centos2alma_actions.LeapReposConfiguration(), + centos2alma_actions.LeapChoicesConfiguration(), + centos2alma_actions.AdoptKolabRepositories(), + centos2alma_actions.AdoptAtomicRepositories(), + centos2alma_actions.FixupImunify(), + centos2alma_actions.PatchLeappErrorOutput(), + centos2alma_actions.PatchLeappDebugNonAsciiPackager(), + common_actions.AddUpgradeSystemdService(os.path.abspath(sys.argv[0]), options), + common_actions.UpdatePlesk(), + centos2alma_actions.PostgresReinstallModernPackage(), + centos2alma_actions.FixNamedConfig(), + common_actions.DisablePleskSshBanner(), + centos2alma_actions.FixSyslogLogrotateConfig(options.state_dir), + common_actions.SetMinDovecotDhParamSize(dhparam_size=2048), + common_actions.RestoreDovecotConfiguration(options.state_dir), + centos2alma_actions.RecreateAwstatConfigurationFiles(), + ], + "Handle plesk related services": [ + common_actions.DisablePleskRelatedServicesDuringUpgrade(), + ], + "Handle packages and services": [ + centos2alma_actions.FixOsVendorPhpFpmConfiguration(), + common_actions.RebundleRubyApplications(), + centos2alma_actions.RemovingPleskConflictPackages(), + centos2alma_actions.ReinstallPleskComponents(), + centos2alma_actions.ReinstallConflictPackages(options.state_dir), + centos2alma_actions.ReinstallPerlCpanModules(options.state_dir), + centos2alma_actions.DisableSuspiciousKernelModules(), + common_actions.HandleUpdatedSpamassassinConfig(), + common_actions.DisableSelinuxDuringUpgrade(), + centos2alma_actions.RestoreMissingNginx(), + ], + "First plesk start": [ + common_actions.StartPleskBasicServices(), + ], + "Update databases": [ + centos2alma_actions.UpdateMariadbDatabase(), + centos2alma_actions.UpdateModernMariadb(), + centos2alma_actions.AddMysqlConnector(), + ], + "Do convert": [ + centos2alma_actions.AdoptRepositories(), + centos2alma_actions.DoCentos2AlmaConvert(), + ], + "Pause before reboot": [ + ], + "Reboot": { + common_actions.Reboot( + prepare_next_phase=Phase.FINISH, + post_reboot=action.RebootType.AFTER_LAST_STAGE, + name="reboot and perform finishing actions", + ) + } + } + + if not options.no_reboot: + actions_map = util.merge_dicts_of_lists(actions_map, { + "Pause before reboot": [ + common_actions.PreRebootPause( + REBOOT_WARN_MESSAGE.format(delay=self._pre_reboot_delay, util_name="centos2alma"), + self._pre_reboot_delay + ), + ] + }) + + if self.upgrade_postgres_allowed: + actions_map = util.merge_dicts_of_lists(actions_map, { + "Prepare configurations": [ + centos2alma_actions.PostgresDatabasesUpdate(), + ] + }) + + return actions_map + + def get_check_actions(self, options: typing.Any, phase: Phase) -> typing.List[action.CheckAction]: + if phase is Phase.FINISH: + return [centos2alma_actions.AssertDistroIsAlmalinux8()] + + FIRST_SUPPORTED_BY_ALMA_8_PHP_VERSION = "7.1" + checks = [ + common_actions.AssertMinPleskVersion("18.0.43"), + common_actions.AssertPleskInstallerNotInProgress(), + centos2alma_actions.AssertAvailableSpace(), + common_actions.AssertMinPhpVersionInstalled(FIRST_SUPPORTED_BY_ALMA_8_PHP_VERSION), + common_actions.AssertMinPhpVersionUsedByWebsites(FIRST_SUPPORTED_BY_ALMA_8_PHP_VERSION), + common_actions.AssertMinPhpVersionUsedByCron(FIRST_SUPPORTED_BY_ALMA_8_PHP_VERSION), + common_actions.AssertOsVendorPhpUsedByWebsites(FIRST_SUPPORTED_BY_ALMA_8_PHP_VERSION), + common_actions.AssertGrubInstalled(), + centos2alma_actions.AssertNoMoreThenOneKernelNamedNIC(), + centos2alma_actions.AssertLastInstalledKernelInUse(), + centos2alma_actions.AssertLocalRepositoryNotPresent(), + centos2alma_actions.AssertThereIsNoRepositoryDuplicates(), + centos2alma_actions.AssertMariadbRepoAvailable(), + common_actions.AssertNotInContainer(), + centos2alma_actions.AssertPackagesUpToDate(), + centos2alma_actions.CheckOutdatedLetsencryptExtensionRepository(), + ] + + if not self.upgrade_postgres_allowed: + checks.append(centos2alma_actions.AssertOutdatedPostgresNotInstalled()) + if not self.remove_unknown_perl_modules: + checks.append(centos2alma_actions.AssertThereIsNoUnknownPerlCpanModules()) + if not self.disable_spamassasin_plugins: + checks.append(common_actions.AssertSpamassassinAdditionalPluginsDisabled()) + + return checks + + def parse_args(self, args: typing.Sequence[str]) -> None: + DESC_MESSAGE = f"""Use this script to convert {str(self._distro_from)} server with Plesk to {str(self._distro_to)}. The process consists of the following general stages: + +- Preparation (about 20 minutes) - The Leapp utility is installed and configured. The OS is prepared for the conversion. The Leapp utility is then called to create a temporary OS distribution. +- Conversion (about 20 minutes) - The conversion takes place. During this stage, you cannot connect to the server via SSH. +- Finalization (about 5 minutes) - The server is returned to normal operation. + +To see the detailed plan, run the utility with the --show-plan option. + +The script writes a log to the /var/log/plesk/centos2alma.log file. If there are any issues, you can find more information in the log file. +For assistance, submit an issue here {self.issues_url} and attach the feedback archive generated with --prepare-feedback or at least the log file.. +""" + parser = argparse.ArgumentParser( + usage=argparse.SUPPRESS, + description=DESC_MESSAGE, + formatter_class=argparse.RawDescriptionHelpFormatter, + add_help=False, + ) + parser.add_argument( + "-h", "--help", action="help", default=argparse.SUPPRESS, + help=argparse.SUPPRESS, + ) + parser.add_argument("--upgrade-postgres", action="store_true", dest="upgrade_postgres_allowed", default=False, + help="Upgrade all hosted PostgreSQL databases. To avoid data loss, create backups of all " + "hosted PostgreSQL databases before calling this option.") + parser.add_argument("--remove-unknown-perl-modules", action="store_true", dest="remove_unknown_perl_modules", default=False, + help="Allow to remove unknown perl modules installed from cpan. In this case all modules installed " + "by cpan will be removed. Note that it could lead to some issues with perl scripts") + parser.add_argument("--disable-spamassasin-plugins", action="store_true", dest="disable_spamassasin_plugins", default=False, + help="Disable additional plugins in spamassasin configuration during the conversion.") + options = parser.parse_args(args) + + self.upgrade_postgres_allowed = options.upgrade_postgres_allowed + self.remove_unknown_perl_modules = options.remove_unknown_perl_modules + self.disable_spamassasin_plugins = options.disable_spamassasin_plugins + + +class Centos2AlmaConverterFactory(DistUpgraderFactory): + def __init__(self): + super().__init__() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(upgrader_name={self.upgrader_name})" + + def __str__(self) -> str: + return f"{self.__class__.__name__} (creates {self.upgrader_name})" + + def supports( + self, + from_system: typing.Optional[dist.Distro] = None, + to_system: typing.Optional[dist.Distro] = None + ) -> bool: + return Centos2AlmaConverter.supports(from_system, to_system) + + @property + def upgrader_name(self) -> str: + return "Plesk::Centos2AlmaConverter" + + def create_upgrader(self, *args, **kwargs) -> DistUpgrader: + return Centos2AlmaConverter(*args, **kwargs) diff --git a/common b/common deleted file mode 160000 index a1a727f..0000000 --- a/common +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a1a727f6438f512df22ef65cb74b8573a30401c2 diff --git a/dist-upgrader b/dist-upgrader new file mode 160000 index 0000000..efc9aee --- /dev/null +++ b/dist-upgrader @@ -0,0 +1 @@ +Subproject commit efc9aee44929e19a97fdc0f48e62f46079aa36b3 diff --git a/tests/versiontests.py b/tests/versiontests.py deleted file mode 100644 index 20e3676..0000000 --- a/tests/versiontests.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright 1999 - 2024. WebPros International GmbH. All rights reserved. -import unittest - -from common import version - - -class KernelVersionTests(unittest.TestCase): - - def _check_parse(self, version_string, expected): - kernel = version.KernelVersion(version_string) - self.assertEqual(str(kernel), expected) - - def test_kernel_parse_simple(self): - self._check_parse("3.10.0-1160.95.1.el7.x86_64", "3.10.0-1160.95.1.el7.x86_64") - - def test_kernel_parse_small_build(self): - self._check_parse("3.10.0-1160.el7.x86_64", "3.10.0-1160.el7.x86_64") - - def test_kernel_parse_large_build(self): - self._check_parse("2.25.16-1.2.3.4.5.el7.x86_64", "2.25.16-1.2.3.4.5.el7.x86_64") - - def test_kernel_parse_no_build(self): - self._check_parse("3.10.0.el7.x86_64", "3.10.0.el7.x86_64") - - def test_compare_simple_equal(self): - kernel1 = version.KernelVersion("3.10.0-1160.95.1.el7.x86_64") - kernel2 = version.KernelVersion("3.10.0-1160.95.1.el7.x86_64") - self.assertEqual(kernel1, kernel2) - - def test_compare_simple_less_build(self): - kernel1 = version.KernelVersion("3.10.0-1160.95.1.el7.x86_64") - kernel2 = version.KernelVersion("3.10.0-1160.95.2.el7.x86_64") - self.assertLess(kernel1, kernel2) - - def test_compare_simple_less_patch(self): - kernel1 = version.KernelVersion("3.10.0-1160.95.1.el7.x86_64") - kernel2 = version.KernelVersion("3.10.2-1160.95.1.el7.x86_64") - self.assertLess(kernel1, kernel2) - - def test_compare_simple_less_patch_exponent(self): - kernel1 = version.KernelVersion("3.10.1-1160.95.1.el7.x86_64") - kernel2 = version.KernelVersion("3.10.10-1160.95.1.el7.x86_64") - self.assertLess(kernel1, kernel2) - - def test_compare_simple_less_minor(self): - kernel1 = version.KernelVersion("3.10.0-1160.95.1.el7.x86_64") - kernel2 = version.KernelVersion("3.11.0-1160.95.1.el7.x86_64") - self.assertLess(kernel1, kernel2) - - def test_compare_simple_less_minor_exponent(self): - kernel1 = version.KernelVersion("3.10.0-1160.95.1.el7.x86_64") - kernel2 = version.KernelVersion("3.101.0-1160.95.1.el7.x86_64") - self.assertLess(kernel1, kernel2) - - def test_compare_simple_less_major(self): - kernel1 = version.KernelVersion("3.10.0-1160.95.1.el7.x86_64") - kernel2 = version.KernelVersion("4.10.0-1160.95.1.el7.x86_64") - self.assertLess(kernel1, kernel2) - - def test_compare_simple_less_major_exponent(self): - kernel1 = version.KernelVersion("3.10.0-1160.95.1.el7.x86_64") - kernel2 = version.KernelVersion("30.10.0-1160.95.1.el7.x86_64") - self.assertLess(kernel1, kernel2) - - def test_compare_different_length_build(self): - kernel1 = version.KernelVersion("3.10.0-957.5.1.el7.x86_64") - kernel2 = version.KernelVersion("3.10.0-1160.95.1.el7.x86_64") - self.assertLess(kernel1, kernel2) - - def test_compare_different_build_subversion(self): - kernel1 = version.KernelVersion("3.10.0-957.el7.x86_64") - kernel2 = version.KernelVersion("3.10.0-1160.99.1.el7.x86_64") - self.assertLess(kernel1, kernel2) - - def test_compare_different_length_build_after_dot(self): - kernel1 = version.KernelVersion("3.10.0-1160.95.1.23.el7.x86_64") - kernel2 = version.KernelVersion("3.10.0-1160.95.11.23.el7.x86_64") - self.assertLess(kernel1, kernel2) - - def test_compare_simple_build_vs_short(self): - kernel1 = version.KernelVersion("3.10.0-1160.95.1.el7.x86_64") - kernel2 = version.KernelVersion("3.10.0-1160.el7.x86_64") - self.assertGreater(kernel1, kernel2) - - def test_find_last_kernel(self): - kernels_strings = [ - "3.10.0-1160.76.1.el7.x86_64", - "3.10.0-1160.95.1.el7.x86_64", - "3.10.0-1160.el7.x86_64", - "3.10.0-1160.45.1.el7.x86_64", - ] - kernels = [version.KernelVersion(s) for s in kernels_strings] - - self.assertEqual(str(max(kernels)), "3.10.0-1160.95.1.el7.x86_64") - - def test_sort_kernels(self): - kernels_strings = [ - "3.10.0-1160.76.1.el7.x86_64", - "3.10.0-1160.95.1.el7.x86_64", - "3.10.0-1160.el7.x86_64", - "3.10.0-1160.45.1.el7.x86_64", - ] - kernels = [version.KernelVersion(s) for s in kernels_strings] - kernels.sort(reverse=True) - - expected = [ - "3.10.0-1160.95.1.el7.x86_64", - "3.10.0-1160.76.1.el7.x86_64", - "3.10.0-1160.45.1.el7.x86_64", - "3.10.0-1160.el7.x86_64", - ] - - self.assertEqual([str(k) for k in kernels], expected)