diff --git a/packaging/leapp-repository.spec b/packaging/leapp-repository.spec index 413b25798b..d967174c0e 100644 --- a/packaging/leapp-repository.spec +++ b/packaging/leapp-repository.spec @@ -94,6 +94,9 @@ Conflicts: leapp-upgrade-el7toel8 %endif +# Requires tools which allow switching between channels +Requires: cln-switch-channel = 2 + # IMPORTANT: every time the requirements are changed, increment number by one # - same for Provides in deps subpackage Requires: leapp-repository-dependencies = %{leapp_repo_deps} diff --git a/repos/system_upgrade/cloudlinux/actors/checkcllicense/actor.py b/repos/system_upgrade/cloudlinux/actors/checkcllicense/actor.py index bf7b583a85..ad95dc1861 100644 --- a/repos/system_upgrade/cloudlinux/actors/checkcllicense/actor.py +++ b/repos/system_upgrade/cloudlinux/actors/checkcllicense/actor.py @@ -13,24 +13,6 @@ import os -RHN_CONFIG_DIR = '/etc/sysconfig/rhn' -REQUIRED_PKGS = ['dnf-plugin-spacewalk', 'rhn-client-tools'] - - -def rhn_to_target_userspace(): - """ - Produce messages to copy RHN configuration files and packages to the target userspace - """ - files_to_copy = [] - for dirpath, _, filenames in os.walk(RHN_CONFIG_DIR): - for filename in filenames: - src_path = os.path.join(dirpath, filename) - if os.path.isfile(src_path): - files_to_copy.append(CopyFile(src=src_path)) - - api.produce(TargetUserSpacePreupgradeTasks(install_rpms=REQUIRED_PKGS, copy_files=files_to_copy)) - api.produce(TargetUserSpaceUpgradeTasks(install_rpms=REQUIRED_PKGS, copy_files=files_to_copy)) - class CheckClLicense(Actor): """ @@ -39,17 +21,12 @@ class CheckClLicense(Actor): name = 'check_cl_license' consumes = () - produces = (Report, TargetUserSpacePreupgradeTasks, TargetUserSpaceUpgradeTasks) + produces = (Report,) tags = (ChecksPhaseTag, IPUWorkflowTag) system_id_path = '/etc/sysconfig/rhn/systemid' rhn_check_bin = '/usr/sbin/rhn_check' - # # Copy RHN data independent from RHSM config - # if os.path.isdir('/etc/sysconfig/rhn'): - # run(['rm', '-rf', os.path.join(target_etc, 'sysconfig/rhn')]) - # context.copytree_from('/etc/sysconfig/rhn', os.path.join(target_etc, 'sysconfig/rhn')) - @run_on_cloudlinux def process(self): res = None @@ -69,5 +46,3 @@ def process(self): reporting.Groups([reporting.Groups.INHIBITOR]), reporting.Remediation(hint=remediation), ]) - else: - rhn_to_target_userspace() diff --git a/repos/system_upgrade/cloudlinux/actors/checkrhnclienttools/actor.py b/repos/system_upgrade/cloudlinux/actors/checkrhnclienttools/actor.py deleted file mode 100644 index a36712ba45..0000000000 --- a/repos/system_upgrade/cloudlinux/actors/checkrhnclienttools/actor.py +++ /dev/null @@ -1,61 +0,0 @@ -from leapp.actors import Actor -from leapp import reporting -from leapp.reporting import Report -from leapp.tags import ChecksPhaseTag, IPUWorkflowTag -from leapp.libraries.common.cllaunch import run_on_cloudlinux - -from leapp.libraries.actor.version import ( - Version, VersionParsingError, -) - -import subprocess - - -class CheckRhnClientToolsVersion(Actor): - """ - Check the rhn-client-tools package version - """ - - name = 'check_rhn_client_tools_version' - consumes = () - produces = (Report,) - tags = (ChecksPhaseTag, IPUWorkflowTag) - - minimal_version = Version('2.0.2') - minimal_release_int = 43 - minimal_release = '%s.el7.cloudlinux' % minimal_release_int - - @run_on_cloudlinux - def process(self): - # todo: (CLOS-3202) update the actor - return - - title, summary, remediation = None, None, None - # ex: - # Version : 2.0.2 - # Release : 43.el7.cloudlinux - # res is: b'2.0.2\n43.el7.cloudlinux\n' - cmd = "yum info installed rhn-client-tools | grep '^Version' -A 1 | awk '{print $3}'" - res = subprocess.check_output(cmd, shell=True) - rhn_version, rhn_release = res.decode().split() - self.log.info('Current rhn-client-tools version: "%s"', rhn_version) - try: - current_version = Version(rhn_version) - except VersionParsingError: - title = 'rhn-client-tools: package is not installed' - summary = 'rhn-client-tools package is required to perform elevation.' - remediation = 'Install rhn-client-tools "%s" version before running Leapp again.' % self.minimal_version - else: - if current_version < self.minimal_version or int(rhn_release.split('.')[0]) < self.minimal_release_int: - title = 'rhn-client-tools: package version is too low' - summary = 'Current version of the rhn-client-tools package has no capability to perform elevation.' - remediation = 'Update rhn-client-tools to "%s %s" version before running Leapp again.' % (self.minimal_version, self.minimal_release) - if title: - reporting.create_report([ - reporting.Title(title), - reporting.Summary(summary), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.OS_FACTS]), - reporting.Groups([reporting.Groups.INHIBITOR]), - reporting.Remediation(hint=remediation), - ]) diff --git a/repos/system_upgrade/cloudlinux/actors/checkrhnclienttools/libraries/version.py b/repos/system_upgrade/cloudlinux/actors/checkrhnclienttools/libraries/version.py deleted file mode 100644 index 149bce23ed..0000000000 --- a/repos/system_upgrade/cloudlinux/actors/checkrhnclienttools/libraries/version.py +++ /dev/null @@ -1,46 +0,0 @@ -from six import reraise as raise_ -import sys - - -class VersionException(Exception): - pass - - -class VersionParsingError(VersionException): - pass - - -class Version(object): - def __init__(self, version): - self._raw = version - try: - self.value = tuple( - map(lambda x: int(x), version.split('.')) - ) - except Exception: - tb = sys.exc_info()[2] - raise_(VersionParsingError, 'failed to parse version: "%s"' % self._raw, tb) - - def __eq__(self, other): - return self.value == other.value - - def __gt__(self, other): - return any( - [v[0] > v[1] for v in zip(self.value, other.value)] - ) - - def __ge__(self, other): - return all( - [v[0] >= v[1] for v in zip(self.value, other.value)] - ) - - def __lt__(self, other): - return any( - [v[0] < v[1] for v in zip(self.value, other.value)] - ) - - def __le__(self, other): - return all( - [v[0] <= v[1] for v in zip(self.value, other.value)] - ) - diff --git a/repos/system_upgrade/cloudlinux/actors/copycllicense/actor.py b/repos/system_upgrade/cloudlinux/actors/copycllicense/actor.py new file mode 100644 index 0000000000..8d7ec3e841 --- /dev/null +++ b/repos/system_upgrade/cloudlinux/actors/copycllicense/actor.py @@ -0,0 +1,44 @@ +import os +from leapp.actors import Actor +from leapp.reporting import Report +from leapp.tags import ChecksPhaseTag, IPUWorkflowTag +from leapp.libraries.common.cllaunch import run_on_cloudlinux +from leapp.libraries.stdlib import api +from leapp.models import ( + TargetUserSpacePreupgradeTasks, + CopyFile +) + + +RHN_CONFIG_DIR = '/etc/sysconfig/rhn' +REQUIRED_PKGS = ['dnf-plugin-spacewalk', 'rhn-client-tools'] + + +class CopyClLicense(Actor): + """ + Produce task to copy CloudLinux license files to target system. + """ + + name = 'copy_rhn_client_tools_config' + consumes = () + produces = (Report, TargetUserSpacePreupgradeTasks) + tags = (ChecksPhaseTag, IPUWorkflowTag) + + @run_on_cloudlinux + def process(self): + """ + Produce artifacts to copy RHN configuration files + and install packages to the target userspace, + including up2date and systemid. + """ + files_to_copy = [] + for dirpath, _, filenames in os.walk(RHN_CONFIG_DIR): + for filename in filenames: + src_path = os.path.join(dirpath, filename) + if os.path.isfile(src_path): + files_to_copy.append(CopyFile(src=src_path)) + + api.produce(TargetUserSpacePreupgradeTasks( + install_rpms=REQUIRED_PKGS, + copy_files=files_to_copy + )) diff --git a/repos/system_upgrade/cloudlinux/actors/pinclnmirror/actor.py b/repos/system_upgrade/cloudlinux/actors/pinclnmirror/actor.py new file mode 100644 index 0000000000..bc1686233f --- /dev/null +++ b/repos/system_upgrade/cloudlinux/actors/pinclnmirror/actor.py @@ -0,0 +1,59 @@ +import json +import os + +from leapp.actors import Actor +from leapp.libraries.stdlib import api +from leapp.libraries.common.cllaunch import run_on_cloudlinux +from leapp.libraries.common.cln_switch import get_target_userspace_path +from leapp.tags import DownloadPhaseTag, IPUWorkflowTag +from leapp.libraries.common.config.version import get_target_major_version + + +class PinClnMirror(Actor): + """ + Save CLN mirror that was used last time. + """ + + name = 'pin_cln_mirror' + consumes = () + produces = () + tags = (IPUWorkflowTag, DownloadPhaseTag.Before) + + CLN_REPO_ID = "cloudlinux-x86_64-server-%s" + DEFAULT_CLN_MIRROR = "https://xmlrpc.cln.cloudlinux.com/XMLRPC/" + + @run_on_cloudlinux + def process(self): + """Pin CLN mirror""" + target_userspace = get_target_userspace_path() + api.current_logger().info("Pin CLN mirror: target userspace=%s", target_userspace) + + # load last mirror URL from dnf spacewalk plugin cache + spacewalk_settings = {} + + # find the mirror used in the last transaction + # (expecting to find the one used in dnf_package_download actor) + spacewalk_json_path = os.path.join(target_userspace, 'var/lib/dnf/_spacewalk.json') + try: + with open(spacewalk_json_path) as file: + spacewalk_settings = json.load(file) + except (OSError, IOError, ValueError): + api.current_logger().error( + "No spacewalk settings found in %s - can't identify the last used CLN mirror", + spacewalk_json_path, + ) + + mirror_url = spacewalk_settings.get( + self.CLN_REPO_ID % get_target_major_version(), {} + ).get("url", [self.DEFAULT_CLN_MIRROR])[0] + + # pin mirror + mirrorlist_path = os.path.join(target_userspace, 'etc/mirrorlist') + with open(mirrorlist_path, 'w') as file: + file.write(mirror_url + '\n') + api.current_logger().info("Pin CLN mirror %s in %s", mirror_url, mirrorlist_path) + + up2date_path = os.path.join(target_userspace, 'etc/sysconfig/rhn/up2date') + with open(up2date_path, 'a+') as file: + file.write('\nmirrorURL[comment]=Set mirror URL to /etc/mirrorlist\nmirrorURL=file:///etc/mirrorlist\n') + api.current_logger().info("Updated up2date_path %s", up2date_path) diff --git a/repos/system_upgrade/cloudlinux/actors/switchclnchannelreset/actor.py b/repos/system_upgrade/cloudlinux/actors/switchclnchannel/actor.py similarity index 76% rename from repos/system_upgrade/cloudlinux/actors/switchclnchannelreset/actor.py rename to repos/system_upgrade/cloudlinux/actors/switchclnchannel/actor.py index 637cefb571..86421856b2 100644 --- a/repos/system_upgrade/cloudlinux/actors/switchclnchannelreset/actor.py +++ b/repos/system_upgrade/cloudlinux/actors/switchclnchannel/actor.py @@ -1,33 +1,34 @@ from leapp.actors import Actor from leapp.libraries.stdlib import api -from leapp.tags import IPUWorkflowTag, TargetTransactionChecksPhaseTag +from leapp.tags import FirstBootPhaseTag, IPUWorkflowTag from leapp.libraries.stdlib import CalledProcessError from leapp.libraries.common.cllaunch import run_on_cloudlinux -from leapp.libraries.common.cln_switch import cln_switch -from leapp.libraries.common.config.version import get_source_major_version +from leapp.libraries.common.cln_switch import cln_switch, get_target_userspace_path from leapp import reporting from leapp.reporting import Report +from leapp.libraries.common.config.version import get_target_major_version -class SwitchClnChannelReset(Actor): +class SwitchClnChannel(Actor): """ - Reset the CLN channel to CL7 to keep the system state consistent before the main upgrade phase. + Permanently switch CLN channel to target os version + when upgrade is complete. """ - name = "switch_cln_channel_reset" + name = "switch_cln_channel" consumes = () produces = (Report,) - tags = (IPUWorkflowTag, TargetTransactionChecksPhaseTag.After) + tags = (FirstBootPhaseTag, IPUWorkflowTag) @run_on_cloudlinux def process(self): try: - cln_switch(target=get_source_major_version()) + cln_switch(target=int(get_target_major_version())) except CalledProcessError as e: reporting.create_report( [ reporting.Title( - "Failed to switch CloudLinux Network channel." + "Failed to switch CloudLinux Network channel" ), reporting.Summary( "Command {} failed with exit code {}." diff --git a/repos/system_upgrade/cloudlinux/actors/switchclnchanneldownload/actor.py b/repos/system_upgrade/cloudlinux/actors/switchclnchanneldownload/actor.py deleted file mode 100644 index bc1c9f5075..0000000000 --- a/repos/system_upgrade/cloudlinux/actors/switchclnchanneldownload/actor.py +++ /dev/null @@ -1,101 +0,0 @@ -import os -import json - -from leapp.actors import Actor -from leapp.libraries.stdlib import api -from leapp.tags import DownloadPhaseTag, IPUWorkflowTag -from leapp.libraries.stdlib import CalledProcessError -from leapp.libraries.common.cllaunch import run_on_cloudlinux -from leapp.libraries.common.cln_switch import cln_switch, get_target_userspace_path -from leapp import reporting -from leapp.reporting import Report -from leapp.libraries.common.config.version import get_target_major_version - - - -CLN_REPO_ID = "cloudlinux-x86_64-server-8" -DEFAULT_CLN_MIRROR = "https://xmlrpc.cln.cloudlinux.com/XMLRPC/" - - -class SwitchClnChannelDownload(Actor): - """ - Switch CLN channel from 7 to 8 to be able to download upgrade packages. - """ - - name = "switch_cln_channel_download" - consumes = () - produces = (Report,) - tags = (IPUWorkflowTag, DownloadPhaseTag.Before) - - @run_on_cloudlinux - def process(self): - try: - cln_switch(target=int(get_target_major_version())) - except CalledProcessError as e: - reporting.create_report( - [ - reporting.Title( - "Failed to switch CloudLinux Network channel from 7 to 8." - ), - reporting.Summary( - "Command {} failed with exit code {}." - " The most probable cause of that is a problem with this system's" - " CloudLinux Network registration.".format(e.command, e.exit_code) - ), - reporting.Remediation( - hint="Check the state of this system's registration with \'rhn_check\'." - " Attempt to re-register the system with \'rhnreg_ks --force\'." - ), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups( - [reporting.Groups.OS_FACTS, reporting.Groups.AUTHENTICATION] - ), - reporting.Groups([reporting.Groups.INHIBITOR]), - ] - ) - except OSError as e: - api.current_logger().error( - "Could not call RHN command: Message: %s", str(e), exc_info=True - ) - - self._pin_cln_mirror() - - def _pin_cln_mirror(self): - """Pin CLN mirror""" - target_userspace = get_target_userspace_path() - api.current_logger().info("Pin CLN mirror: target userspace=%s", target_userspace) - - # load last mirror URL from dnf spacewalk plugin cache - spacewalk_settings = {} - - # find the mirror used in the last transaction - # (expecting to find the one used in dnf_package_download actor) - spacewalk_json_path = os.path.join(target_userspace, 'var/lib/dnf/_spacewalk.json') - try: - with open(spacewalk_json_path) as file: - spacewalk_settings = json.load(file) - except (OSError, IOError, ValueError): - api.current_logger().error( - "No spacewalk settings found in %s - can't identify the last used CLN mirror", - spacewalk_json_path, - ) - - mirror_url = spacewalk_settings.get(CLN_REPO_ID, {}).get("url", [DEFAULT_CLN_MIRROR])[0] - - # pin mirror - for mirrorlist_path in [ - '/etc/mirrorlist', - os.path.join(target_userspace, 'etc/mirrorlist'), - ]: - with open(mirrorlist_path, 'w') as file: - file.write(mirror_url + '\n') - api.current_logger().info("Pin CLN mirror %s in %s", mirror_url, mirrorlist_path) - - for up2date_path in [ - '/etc/sysconfig/rhn/up2date', - os.path.join(target_userspace, 'etc/sysconfig/rhn/up2date'), - ]: - # At some point up2date in `target_userspace` might be overwritten by a default one - with open(up2date_path, 'a+') as file: - file.write('\nmirrorURL[comment]=Set mirror URL to /etc/mirrorlist\nmirrorURL=file:///etc/mirrorlist\n') - api.current_logger().info("Updated up2date_path %s", up2date_path) diff --git a/repos/system_upgrade/cloudlinux/actors/unpinclnmirror/actor.py b/repos/system_upgrade/cloudlinux/actors/unpinclnmirror/actor.py index ce123925d9..8e7ffc93a0 100644 --- a/repos/system_upgrade/cloudlinux/actors/unpinclnmirror/actor.py +++ b/repos/system_upgrade/cloudlinux/actors/unpinclnmirror/actor.py @@ -5,6 +5,7 @@ from leapp.libraries.common.cln_switch import get_target_userspace_path from leapp.tags import FirstBootPhaseTag, IPUWorkflowTag + class UnpinClnMirror(Actor): """ Remove the pinned CLN mirror. @@ -16,32 +17,23 @@ class UnpinClnMirror(Actor): produces = () tags = (IPUWorkflowTag, FirstBootPhaseTag) - CLN_REPO_ID = "cloudlinux-x86_64-server-8" - DEFAULT_CLN_MIRROR = "https://xmlrpc.cln.cloudlinux.com/XMLRPC/" - @run_on_cloudlinux def process(self): target_userspace = get_target_userspace_path() - for mirrorlist_path in [ - '/etc/mirrorlist', - os.path.join(target_userspace, 'etc/mirrorlist'), - ]: - try: - os.remove(mirrorlist_path) - except OSError: - self.log.info('Can\'t remove %s, file does not exist, doing nothing', mirrorlist_path) + mirrorlist_path = os.path.join(target_userspace, 'etc/mirrorlist') + try: + os.remove(mirrorlist_path) + except OSError: + self.log.info('Can\'t remove %s, file does not exist, doing nothing', mirrorlist_path) - for up2date_path in [ - '/etc/sysconfig/rhn/up2date', - os.path.join(target_userspace, 'etc/sysconfig/rhn/up2date'), - ]: - try: - with open(up2date_path, 'r') as file: - lines = [ - line for line in file.readlines() if 'etc/mirrorlist' not in line - ] - with open(up2date_path, 'w') as file: - file.writelines(lines) - except (OSError, IOError, ValueError): - self.log.info('Can update %s file, doing nothing', up2date_path) + up2date_path = os.path.join(target_userspace, 'etc/sysconfig/rhn/up2date') + try: + with open(up2date_path, 'r') as file: + lines = [ + line for line in file.readlines() if 'etc/mirrorlist' not in line + ] + with open(up2date_path, 'w') as file: + file.writelines(lines) + except (OSError, IOError, ValueError): + self.log.info('Can update %s file, doing nothing', up2date_path) diff --git a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py index dce6735389..8e4579a7ec 100644 --- a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py +++ b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py @@ -10,7 +10,7 @@ from leapp.libraries.common.config import get_env, get_product_type from leapp.libraries.common.config.version import get_target_major_version from leapp.libraries.common.gpg import get_path_to_gpg_certs, is_nogpgcheck_set -from leapp.libraries.common.cln_switch import cln_switch +from leapp.libraries.common.cln_switch import override_channel from leapp.libraries.stdlib import api, CalledProcessError, config, run from leapp.models import RequiredTargetUserspacePackages # deprecated from leapp.models import TMPTargetRepositoriesFacts # deprecated all the time @@ -330,19 +330,6 @@ def prepare_target_userspace(context, userspace_dir, enabled_repos, packages): raise StopActorExecutionError(message=message, details=details) - api.current_logger().debug('Checking the CLN registration status') - context.call(['rhn_check'], callback_raw=utils.logging_handler) - # To get packages from Spacewalk repos (aka CLN) we need to switch the CLN channel. - - # Note that this switches the channel for the entire host system, not just the target userspace - - # so if we don't reset it back to the original channel, the host system will be left in an inconsistent state. - - # The 'switch_cln_channel_reset' actor should reset the channel back to the original state after the - # transaction check phase is done - so the preupgrade checks won't affect the host system. - # The 'switch_cln_channel_download' actor should take care of switching the channel back to the CL8 channel - # when it's time to download the upgrade packages. - cln_switch(target=int(target_major_version)) - def _query_rpm_for_pkg_files(context, pkgs): files_owned_by_rpm = set() @@ -694,11 +681,7 @@ def _prep_repository_access(context, target_userspace): run(['rm', '-rf', os.path.join(target_etc, 'rhsm')]) context.copytree_from('/etc/rhsm', os.path.join(target_etc, 'rhsm')) - # Copy RHN data independent from RHSM config if os.path.isdir('/etc/sysconfig/rhn'): - context.call(['/usr/sbin/rhn_check'], callback_raw=utils.logging_handler) - run(['rm', '-rf', os.path.join(target_etc, 'sysconfig/rhn')]) - context.copytree_from('/etc/sysconfig/rhn', os.path.join(target_etc, 'sysconfig/rhn')) # Set up spacewalk plugin config with open(os.path.join(target_etc, 'dnf/plugins/spacewalk.conf'), 'r') as f: lines = f.readlines() @@ -1187,6 +1170,11 @@ def _create_target_userspace(context, packages, files, target_repoids): _copy_files(target_context, files) dnfplugin.install(_get_target_userspace()) + # do not switch channel before this stage because _copy_files above copies + # related configuration files from host to target userspace + target_major_version = get_target_major_version() + override_channel(os.path.join(_get_target_userspace(), 'etc/sysconfig/rhn/up2date'), target_major_version) + # and do not forget to set the rhsm into the container mode again with mounting.NspawnActions(_get_target_userspace()) as target_context: rhsm.set_container_mode(target_context) @@ -1288,4 +1276,4 @@ def perform(): api.produce(TargetUserSpaceInfo( path=_get_target_userspace(), scratch=constants.SCRATCH_DIR, - mounts=constants.MOUNTS_DIR)) + mounts=constants.MOUNTS_DIR)) \ No newline at end of file diff --git a/repos/system_upgrade/common/libraries/cln_switch.py b/repos/system_upgrade/common/libraries/cln_switch.py index ddd9331c43..de7970abbf 100644 --- a/repos/system_upgrade/common/libraries/cln_switch.py +++ b/repos/system_upgrade/common/libraries/cln_switch.py @@ -33,6 +33,7 @@ def get_cln_cacheonly_flag_path(): """ return os.path.join(get_target_userspace_path(), CLN_CACHEONLY_MARKER.lstrip('/')) + def cln_switch(target): """ Switch the CloudLinux Network channel to the specified target OS. @@ -47,3 +48,25 @@ def cln_switch(target): api.current_logger().debug('Channel switch result: %s', res) res = run(yum_clean_cmd) # required to update the repolist api.current_logger().debug('yum cleanup result: %s', res) + + +def override_channel(config_path, target): + """ + Override cln channel locally (not affecting information on CLN side) + aboit which channel we must use for upgrade. + """ + replaced = False + with open(config_path, 'r') as f: + lines = f.readlines() + new_lines = [] + for line in lines: + if line.startswith('channelOverride'): + line = 'channelOverride = cloudlinux-x86_64-server-%s\n' % target + replaced = True + new_lines.append(line) + + if not replaced: + new_lines.append('channelOverride = cloudlinux-x86_64-server-%s\n' % target) + + with open(config_path, 'w') as f: + f.writelines(new_lines)