Skip to content
Open
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,37 @@ 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,
# since the upgrade rewrites it to drive channel selection.
# On no-auth systems this does not apply.
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])
])
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
ParserClass = configparser.ConfigParser


# DNF plugin config path on the target system (CL8). FirstBoot runs after the
# target OS is already in place; on CL8 the plugin package is
# dnf-plugin-spacewalk (PES replacement for CL7's yum-rhn-plugin,
# pes-events id=1586) and its config ships with enabled=0.
# DNF plugin config path on the target system (CL8).
# FirstBoot runs after the target OS is already in place;
# on CL8 the plugin package is dnf-plugin-spacewalk
# (PES replacement for CL7's yum-rhn-plugin, pes-events id=1586)
# and its config ships with enabled=0.
DEFAULT_CONFIG_PATH = '/etc/dnf/plugins/spacewalk.conf'


Expand All @@ -24,8 +25,8 @@ def _enable_plugin(config_path, parser_cls=ParserClass, log=None):
when the plugin is not installed, and otherwise a human-readable
problem description suitable for a Leapp report.

Absence of `config_path` is treated as a silent skip: on no-auth /
SWNG systems (CLOS-4056) `rhn-client-tools >= 3.0.1` Obsoletes the
Absence of `config_path` is treated as a silent skip: on no-auth
systems (CLOS-4056) `rhn-client-tools >= 3.0.1` Obsoletes the
`dnf-plugin-spacewalk` package, so the config file is either gone by
then, or doesn't do anything.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ def _write(tmp_path, body):
def test_missing_config_is_silent_skip(tmp_path):
"""Config file absent -> silent skip: no change, no title, no report.

On no-auth / SWNG systems (CLOS-4056) the dnf-plugin-spacewalk
package is Obsoleted by rhn-client-tools >= 3.0.1 and the config
file is absent by design. Emitting a 'not found' report there
would be noise.
On no-auth systems (CLOS-4056) the dnf-plugin-spacewalk
package is Obsoleted by rhn-client-tools >= 3.0.1.
Emitting a 'not found' report there would be noise.
"""
changed, title = _enable_plugin(str(tmp_path / "absent.conf"), ParserClass)
assert changed is False
Expand Down
20 changes: 17 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,14 @@ 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.
# With the no-auth repo scheme active, there's no point in doing so.
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 +63,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,28 @@ class ResetRhnVersionOverride(Actor):

@run_on_cloudlinux
def process(self):
if not is_cln_package_channel_active():
# CLOS-4056: versionOverride only matters when CLN is delivering packages,
# since the upgrade rewrites it to drive channel selection.
# On no-auth systems this does not apply.
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)
28 changes: 22 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,21 @@ class SwitchClnChannel(Actor):

@run_on_cloudlinux
def process(self):
if not is_cln_package_channel_active():
# CLOS-4056: CLN package channel is inactive, so skipping the channel switch
# is correct - packages come from standard repositories instead.
# Leapp manages those without custom actions through repomaps.
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:
# Do not inhibit. Even on systems that ARE using CLN as the package channel,
# a transient CLN-server reachability problem at FirstBoot
# shouldn't block the upgrade.
reporting.create_report(
[
reporting.Title(
Expand All @@ -33,17 +46,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
Loading
Loading