Skip to content
32 changes: 29 additions & 3 deletions commands/upgrade/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,44 @@
from leapp.exceptions import CommandError, LeappError
from leapp.logger import configure_logger
from leapp.utils.audit import Execution
from leapp.utils.clicmd import command, command_opt
from leapp.utils.clicmd import command, command_opt, _ensure_command
from leapp.utils.output import beautify_actor_exception, report_errors, report_info

# NOTE:
# If you are adding new parameters please ensure that they are set in the upgrade function invocation in `rerun`
# otherwise there might be errors.


def command_opt_with_aliases(name, *aliases, **kwargs):
"""Like command_opt, but registers --<name> AND extra long-form aliases.

leapp framework's add_option (as of 0.18.0) accepts only one long name,
so a plain `aliases=` kwarg trips on `add_option() got an unexpected
keyword argument 'aliases'`. argparse, however, supports multiple long
forms natively when add_argument is called with several name strings.
We bypass add_option and call the lower-level _add_opt directly.

`dest` is derived by argparse from the first long form (here `name`),
so existing consumers reading `args.<name>` keep working unchanged.
"""
is_flag = kwargs.pop('is_flag', False)
help_text = kwargs.pop('help', '')
action = kwargs.pop('action', 'store_true' if is_flag else 'store')
inherit = kwargs.pop('inherit', False)

@_ensure_command
def wrapper(f):
names = ['--' + n.lstrip('-') for n in (name,) + aliases]
f.command._add_opt(*names, action=action, help=help_text,
internal={'wrapped': f, 'inherit': inherit},
**kwargs)
return f
return wrapper


@command('upgrade', help='Upgrade the current system to the next available major version.')
@command_opt('resume', is_flag=True, help='Continue the last execution after it was stopped (e.g. after reboot)')
@command_opt('nowarn', is_flag=True, help='Do not display interactive warnings',
aliases=['non-interactive'])
@command_opt_with_aliases('nowarn', 'non-interactive', is_flag=True, help='Do not display interactive warnings')
@command_opt('reboot', is_flag=True, help='Automatically performs reboot when requested.')
@command_opt('whitelist-experimental', action='append', metavar='ActorName', help='Enable experimental actors')
@command_opt('debug', is_flag=True, help='Enable debug mode', inherit=False)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from leapp.actors import Actor
from leapp import reporting
from leapp.libraries.stdlib import api
from leapp.reporting import Report
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
from leapp.libraries.common.cllaunch import run_on_cloudlinux
from leapp.libraries.common.cln_detect import is_cln_package_channel_active


class CheckRhnVersionOverride(Actor):
Expand All @@ -17,23 +19,39 @@ class CheckRhnVersionOverride(Actor):

@run_on_cloudlinux
def process(self):
if not is_cln_package_channel_active():
# CLOS-4056: versionOverride only matters when CLN is delivering
# packages - the upgrade rewrites it to drive channel selection.
# On no-auth (SWNG) systems the package channel is cl-channel,
# not CLN, so there is nothing to inspect or warn about even if
# /etc/sysconfig/rhn/up2date is still present from registration.
return

up2date_config = '/etc/sysconfig/rhn/up2date'
with open(up2date_config, 'r') as f:
config_data = f.readlines()
for line in config_data:
if line.startswith('versionOverride='):
stripped_line = line.strip().split("=")
versionOverrideValue = stripped_line[1]
# If the version is being overriden to 8, we can continue as is.
if versionOverrideValue not in ['', '8']:
title = 'RHN up2date: versionOverride overwritten by the upgrade'
summary = ("The RHN config file up2date has a set value of the versionOverride option: {}."
" This value will get overwritten by the upgrade process, and reset to an empty"
" value once it's complete.".format(versionOverrideValue))
reporting.create_report([
reporting.Title(title),
reporting.Summary(summary),
reporting.Severity(reporting.Severity.MEDIUM),
reporting.Groups([reporting.Groups.OS_FACTS]),
reporting.RelatedResource('file', '/etc/sysconfig/rhn/up2date')
])
try:
with open(up2date_config, 'r') as f:
config_data = f.readlines()
except (OSError, IOError):
api.current_logger().info(
"RHN up2date config %s not present; skipping versionOverride check",
up2date_config,
)
return

for line in config_data:
if line.startswith('versionOverride='):
stripped_line = line.strip().split("=")
versionOverrideValue = stripped_line[1]
# If the version is being overriden to 8, we can continue as is.
if versionOverrideValue not in ['', '8']:
title = 'RHN up2date: versionOverride overwritten by the upgrade'
summary = ("The RHN config file up2date has a set value of the versionOverride option: {}."
" This value will get overwritten by the upgrade process, and reset to an empty"
" value once it's complete.".format(versionOverrideValue))
reporting.create_report([
reporting.Title(title),
reporting.Summary(summary),
reporting.Severity(reporting.Severity.MEDIUM),
reporting.Groups([reporting.Groups.OS_FACTS]),
reporting.RelatedResource('file', '/etc/sysconfig/rhn/up2date')
])
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def clmysql_process(lib, repofile_name, repofile_data):
reporting.Summary(
"MySQL Governor records the installed database type as '{governor}', "
"but the mysqld binary on disk belongs to '{rpm}'. "
"This usually means 'mysqlgovernor.py --mysql-version' was run "
"This usually means '/usr/share/lve/dbgovernor/mysqlgovernor.py --mysql-version' was run "
"without a follow-up '--install', or packages were changed manually. "
"Proceeding could enable the wrong DNF module stream and break the upgrade.".format(
governor=detected.governor_type, rpm=detected.pkg_type
Expand All @@ -56,11 +56,11 @@ def clmysql_process(lib, repofile_name, repofile_data):
hint=(
"Examine the current state of the system's DB packages."
"Complete the pending Governor install:\n"
" mysqlgovernor.py --mysql-version={governor}\n"
" mysqlgovernor.py --install --yes\n"
" /usr/share/lve/dbgovernor/mysqlgovernor.py --mysql-version={governor}\n"
" /usr/share/lve/dbgovernor/mysqlgovernor.py --install --yes\n"
"Or reset Governor to match the actual packages:\n"
" mysqlgovernor.py --mysql-version={rpm}\n"
" mysqlgovernor.py --install --yes\n"
" /usr/share/lve/dbgovernor/mysqlgovernor.py --mysql-version={rpm}\n"
" /usr/share/lve/dbgovernor/mysqlgovernor.py --install --yes\n"
"Then restart the upgrade process.".format(
governor=detected.governor_type, rpm=detected.pkg_type
)
Expand Down Expand Up @@ -109,7 +109,7 @@ def clmysql_process(lib, repofile_name, repofile_data):
"The detected database type is '{}', but the cl-mysql-meta "
"repo URL points to '{}'. "
"This may happen when the database version was changed "
"without a follow-up 'mysqlgovernor.py --install', or the "
"without a follow-up '/usr/share/lve/dbgovernor/mysqlgovernor.py --install', or the "
"cl-mysql.repo file was manually edited. "
"Proceeding with the wrong repository would result in "
"an incorrect upgrade operation."
Expand All @@ -125,13 +125,16 @@ def clmysql_process(lib, repofile_name, repofile_data):
reporting.Groups([reporting.Groups.INHIBITOR]),
reporting.Remediation(
hint=(
"Re-run MySQL Governor to regenerate the repository file: "
"mysqlgovernor.py --install --yes, "
"then restart the upgrade process. "
"Alternatively, if the repository file was manually edited, "
"either correct the baseurl to match the installed DB type or "
"set the desired DB type in Governor and re-run --install "
"to have it write the correct URL."
"Download the correct repository file for the installed "
"database type: "
"curl -o /etc/yum.repos.d/cl-mysql.repo "
"http://repo.cloudlinux.com/other/"
"cl${{releasever}}/mysqlmeta/{expected}-common.repo\n"
"Or re-run MySQL Governor to regenerate it "
"(this reinstalls the full DB stack): "
"/usr/share/lve/dbgovernor/mysqlgovernor.py --install --yes\n"
"Then restart the upgrade process."
.format(expected=expected_fragment)
)
),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ class EnableYumSpacewalkPlugin(Actor):
consumes = ()
produces = (Report,)
tags = (FirstBootPhaseTag, IPUWorkflowTag)
config = enableyumspacewalkplugin.DEFAULT_CONFIG_PATH

CONFIG_PATH = enableyumspacewalkplugin.DEFAULT_CONFIG_PATH

@run_on_cloudlinux
def process(self):
_, title = enableyumspacewalkplugin._enable_plugin(
self.config, enableyumspacewalkplugin.ParserClass, self.log
self.CONFIG_PATH, enableyumspacewalkplugin.ParserClass, self.log
)
if title:
reporting.create_report([
reporting.Title(title),
reporting.Summary("DNF spacewalk plugin must be enabled for CLN channels. Config path: " + self.config),
reporting.Summary("DNF spacewalk plugin must be enabled for CLN channels. Config path: " + self.CONFIG_PATH),
reporting.Severity(reporting.Severity.MEDIUM),
reporting.Groups([reporting.Groups.SANITY])
])
22 changes: 19 additions & 3 deletions repos/system_upgrade/cloudlinux/actors/pinclnmirror/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
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_detect import is_cln_package_channel_active
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
Expand All @@ -25,6 +26,16 @@ class PinClnMirror(Actor):
@run_on_cloudlinux
def process(self):
"""Pin CLN mirror"""
if not is_cln_package_channel_active():
# CLOS-4056: pinning the CLN mirror is only meaningful when CLN
# is delivering packages. On no-auth (SWNG) systems packages come
# from cl-channel via mirrorlist, so there is nothing to pin -
# registration may still be in place but is irrelevant here.
api.current_logger().info(
"CLN is not the active package channel; skipping mirror pinning"
)
return

target_userspace = get_target_userspace_path()
api.current_logger().info("Pin CLN mirror: target userspace=%s", target_userspace)

Expand Down Expand Up @@ -54,6 +65,11 @@ def process(self):
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)
try:
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)
except (OSError, IOError) as e:
api.current_logger().info(
"Could not update %s: %s", up2date_path, e,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from leapp.actors import Actor
from leapp.libraries.stdlib import api
from leapp.tags import FinalizationPhaseTag, IPUWorkflowTag
from leapp.libraries.common.cllaunch import run_on_cloudlinux
from leapp.libraries.common.cln_detect import is_cln_package_channel_active


class ResetRhnVersionOverride(Actor):
Expand All @@ -15,11 +17,29 @@ class ResetRhnVersionOverride(Actor):

@run_on_cloudlinux
def process(self):
if not is_cln_package_channel_active():
# CLOS-4056: versionOverride is only set/used by the CLN package
# channel flow. If the system isn't on CLN for packages, leave
# /etc/sysconfig/rhn/up2date alone - registration metadata there
# is not ours to touch.
return

up2date_config = '/etc/sysconfig/rhn/up2date'
with open(up2date_config, 'r') as f:
config_data = f.readlines()
for line in config_data:
if line.startswith('versionOverride='):
line = 'versionOverride='
try:
with open(up2date_config, 'r') as f:
config_data = f.readlines()
except (OSError, IOError):
api.current_logger().info(
"RHN up2date config %s not present; skipping versionOverride reset",
up2date_config,
)
return

new_data = []
for line in config_data:
if line.startswith('versionOverride='):
new_data.append('versionOverride=\n')
else:
new_data.append(line)
with open(up2date_config, 'w') as f:
f.writelines(config_data)
f.writelines(new_data)
31 changes: 25 additions & 6 deletions repos/system_upgrade/cloudlinux/actors/switchclnchannel/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
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, get_target_userspace_path
from leapp.libraries.common.cln_detect import is_cln_package_channel_active
from leapp.libraries.common.cln_switch import cln_switch
from leapp import reporting
from leapp.reporting import Report
from leapp.libraries.common.config.version import get_target_major_version
Expand All @@ -22,9 +23,24 @@ class SwitchClnChannel(Actor):

@run_on_cloudlinux
def process(self):
if not is_cln_package_channel_active():
# CLOS-4056: CLN is no longer the package channel here (no-auth /
# SWNG mode). Skipping the channel switch is correct - packages
# come from cl-channel / cloudlinux9-baseos instead. The system
# may still be CLN-registered for licensing; that is a separate
# concern this actor does not need to manage.
api.current_logger().info(
"CLN is not the active package channel; skipping channel switch"
)
return

try:
cln_switch(target=int(get_target_major_version()))
except CalledProcessError as e:
# CLOS-4056: Do not inhibit. Even on systems that ARE using CLN
# as the package channel, a transient CLN-server reachability
# problem at FirstBoot (DNS/network not up yet) shouldn't block
# the upgrade - the no-auth fallback repos still serve packages.
reporting.create_report(
[
reporting.Title(
Expand All @@ -33,17 +49,20 @@ def process(self):
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)
" CloudLinux Network registration. If this system now uses the"
" no-auth (SWNG) repository scheme, this failure is harmless -"
" CL9 packages come from cl-channel / cloudlinux9-baseos instead"
" of CLN.".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\'."
hint="If you rely on CLN: check registration with 'rhn_check' and"
" re-register with 'rhnreg_ks --force'. If you have migrated to"
" no-auth repos, this message can be ignored."
),
reporting.Severity(reporting.Severity.HIGH),
reporting.Severity(reporting.Severity.MEDIUM),
reporting.Groups(
[reporting.Groups.OS_FACTS, reporting.Groups.AUTHENTICATION]
),
reporting.Groups([reporting.Groups.INHIBITOR]),
]
)
except OSError as e:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from leapp.actors import Actor
from leapp.libraries.common.cllaunch import run_on_cloudlinux
from leapp.libraries.common.cln_detect import is_cln_package_channel_active
from leapp.libraries.common.cln_switch import get_target_userspace_path
from leapp.tags import FirstBootPhaseTag, IPUWorkflowTag

Expand All @@ -19,6 +20,12 @@ class UnpinClnMirror(Actor):

@run_on_cloudlinux
def process(self):
if not is_cln_package_channel_active():
# CLOS-4056: pinclnmirror skipped its work for the same reason
# (CLN is not the package channel here), so there is nothing
# for us to unpin.
return

target_userspace = get_target_userspace_path()

mirrorlist_path = os.path.join(target_userspace, 'etc/mirrorlist')
Expand Down
Loading
Loading