Skip to content

Commit 8a353b5

Browse files
committed
Merge branch 'CLOS-4333' into cloudlinux
2 parents 3fbc8cb + ed85029 commit 8a353b5

10 files changed

Lines changed: 270 additions & 46 deletions

File tree

commands/upgrade/__init__.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,44 @@
88
from leapp.exceptions import CommandError, LeappError
99
from leapp.logger import configure_logger
1010
from leapp.utils.audit import Execution
11-
from leapp.utils.clicmd import command, command_opt
11+
from leapp.utils.clicmd import command, command_opt, _ensure_command
1212
from leapp.utils.output import beautify_actor_exception, report_errors, report_info
1313

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

1818

19+
def command_opt_with_aliases(name, *aliases, **kwargs):
20+
"""Like command_opt, but registers --<name> AND extra long-form aliases.
21+
22+
leapp framework's add_option (as of 0.18.0) accepts only one long name,
23+
so a plain `aliases=` kwarg trips on `add_option() got an unexpected
24+
keyword argument 'aliases'`. argparse, however, supports multiple long
25+
forms natively when add_argument is called with several name strings.
26+
We bypass add_option and call the lower-level _add_opt directly.
27+
28+
`dest` is derived by argparse from the first long form (here `name`),
29+
so existing consumers reading `args.<name>` keep working unchanged.
30+
"""
31+
is_flag = kwargs.pop('is_flag', False)
32+
help_text = kwargs.pop('help', '')
33+
action = kwargs.pop('action', 'store_true' if is_flag else 'store')
34+
inherit = kwargs.pop('inherit', False)
35+
36+
@_ensure_command
37+
def wrapper(f):
38+
names = ['--' + n.lstrip('-') for n in (name,) + aliases]
39+
f.command._add_opt(*names, action=action, help=help_text,
40+
internal={'wrapped': f, 'inherit': inherit},
41+
**kwargs)
42+
return f
43+
return wrapper
44+
45+
1946
@command('upgrade', help='Upgrade the current system to the next available major version.')
2047
@command_opt('resume', is_flag=True, help='Continue the last execution after it was stopped (e.g. after reboot)')
21-
@command_opt('nowarn', is_flag=True, help='Do not display interactive warnings',
22-
aliases=['non-interactive'])
48+
@command_opt_with_aliases('nowarn', 'non-interactive', is_flag=True, help='Do not display interactive warnings')
2349
@command_opt('reboot', is_flag=True, help='Automatically performs reboot when requested.')
2450
@command_opt('whitelist-experimental', action='append', metavar='ActorName', help='Enable experimental actors')
2551
@command_opt('debug', is_flag=True, help='Enable debug mode', inherit=False)

packaging/leapp-repository.spec

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,17 @@ Conflicts: leapp-upgrade-el7toel8
9494

9595
%endif
9696

97-
# Requires tools which allow switching between channels
98-
Requires: cln-switch-channel = 2
97+
# cln-switch-channel was provided by rhn-client-tools 2.x to support the
98+
# CLN-side channel switch. rhn-client-tools 3.0+ removes both the binary and
99+
# the Provide as part of the no-auth migration. The actor that invokes it
100+
# (switchclnchannel) is gated on is_cln_package_channel_active() (CLOS-4056),
101+
# so on no-auth systems it never runs and the missing binary is harmless. On
102+
# CLN-active systems rhn-client-tools 2.x is installed, supplying the binary
103+
# at runtime, so the install-time pin was redundant. Drop it to allow
104+
# rhn-client-tools 3.0+ on the same system as leapp-upgrade-el8toel9
105+
# (otherwise leapp_qa Run #54 dnf_transaction_check fails: rhn-client-tools
106+
# 3.x cannot be installed alongside an RPM that requires
107+
# cln-switch-channel = 2).
99108

100109
# IMPORTANT: every time the requirements are changed, increment number by one
101110
# - same for Provides in deps subpackage

repos/system_upgrade/cloudlinux/actors/checkcllicense/actor.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
55
from leapp.libraries.stdlib import CalledProcessError, run, api
66
from leapp.libraries.common.cllaunch import run_on_cloudlinux
7+
from leapp.libraries.common.cln_detect import is_cln_package_channel_active
78

89
from leapp.models import (
910
TargetUserSpacePreupgradeTasks,
@@ -29,10 +30,34 @@ class CheckClLicense(Actor):
2930

3031
@run_on_cloudlinux
3132
def process(self):
33+
# CLOS-4056: the rhn_check XML-RPC call only verifies licenses on
34+
# systems that use CLN as the package channel. Under no-auth (SWNG)
35+
# the license is conveyed by other means (IP-based licensing,
36+
# cloudlinux-release content) and the rhn_check round-trip is not a
37+
# meaningful gate - on rhn-client-tools 3.0+ it fails outright with
38+
# "Invalid System Credentials" against systemid files written by
39+
# clnreg_ks. Skip the check under no-auth.
40+
if not is_cln_package_channel_active():
41+
api.current_logger().info(
42+
"CLN is not the active package channel; skipping rhn_check"
43+
" license verification (no-auth systems use IP licensing,"
44+
" not the CLN XML-RPC roundtrip)."
45+
)
46+
return
47+
3248
res = None
3349
if os.path.exists(self.system_id_path):
34-
res = run([self.rhn_check_bin])
35-
self.log.debug('rhn_check result: %s', res)
50+
try:
51+
res = run([self.rhn_check_bin])
52+
self.log.debug('rhn_check result: %s', res)
53+
except CalledProcessError as e:
54+
# The original implementation assigned `res = run(...)`
55+
# bare, but `run()` raises on non-zero exit codes - so
56+
# the "produce an inhibitor on non-zero / non-empty stderr"
57+
# branch below was dead code. Catch the failure and let
58+
# the existing reporting path take over.
59+
self.log.debug('rhn_check failed: %s', e)
60+
res = None
3661
if not res or res['exit_code'] != 0 or res['stderr']:
3762
title = 'Server does not have an active CloudLinux license'
3863
summary = 'Server does not have an active CloudLinux license. This renders key CloudLinux packages ' \

repos/system_upgrade/cloudlinux/actors/clmysqlrepositorysetup/libraries/clmysql_cloudlinux.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def clmysql_process(lib, repofile_name, repofile_data):
4141
reporting.Summary(
4242
"MySQL Governor records the installed database type as '{governor}', "
4343
"but the mysqld binary on disk belongs to '{rpm}'. "
44-
"This usually means 'mysqlgovernor.py --mysql-version' was run "
44+
"This usually means '/usr/share/lve/dbgovernor/mysqlgovernor.py --mysql-version' was run "
4545
"without a follow-up '--install', or packages were changed manually. "
4646
"Proceeding could enable the wrong DNF module stream and break the upgrade.".format(
4747
governor=detected.governor_type, rpm=detected.pkg_type
@@ -56,11 +56,11 @@ def clmysql_process(lib, repofile_name, repofile_data):
5656
hint=(
5757
"Examine the current state of the system's DB packages."
5858
"Complete the pending Governor install:\n"
59-
" mysqlgovernor.py --mysql-version={governor}\n"
60-
" mysqlgovernor.py --install --yes\n"
59+
" /usr/share/lve/dbgovernor/mysqlgovernor.py --mysql-version={governor}\n"
60+
" /usr/share/lve/dbgovernor/mysqlgovernor.py --install --yes\n"
6161
"Or reset Governor to match the actual packages:\n"
62-
" mysqlgovernor.py --mysql-version={rpm}\n"
63-
" mysqlgovernor.py --install --yes\n"
62+
" /usr/share/lve/dbgovernor/mysqlgovernor.py --mysql-version={rpm}\n"
63+
" /usr/share/lve/dbgovernor/mysqlgovernor.py --install --yes\n"
6464
"Then restart the upgrade process.".format(
6565
governor=detected.governor_type, rpm=detected.pkg_type
6666
)
@@ -109,7 +109,7 @@ def clmysql_process(lib, repofile_name, repofile_data):
109109
"The detected database type is '{}', but the cl-mysql-meta "
110110
"repo URL points to '{}'. "
111111
"This may happen when the database version was changed "
112-
"without a follow-up 'mysqlgovernor.py --install', or the "
112+
"without a follow-up '/usr/share/lve/dbgovernor/mysqlgovernor.py --install', or the "
113113
"cl-mysql.repo file was manually edited. "
114114
"Proceeding with the wrong repository would result in "
115115
"an incorrect upgrade operation."
@@ -125,13 +125,16 @@ def clmysql_process(lib, repofile_name, repofile_data):
125125
reporting.Groups([reporting.Groups.INHIBITOR]),
126126
reporting.Remediation(
127127
hint=(
128-
"Re-run MySQL Governor to regenerate the repository file: "
129-
"mysqlgovernor.py --install --yes, "
130-
"then restart the upgrade process. "
131-
"Alternatively, if the repository file was manually edited, "
132-
"either correct the baseurl to match the installed DB type or "
133-
"set the desired DB type in Governor and re-run --install "
134-
"to have it write the correct URL."
128+
"Download the correct repository file for the installed "
129+
"database type: "
130+
"curl -o /etc/yum.repos.d/cl-mysql.repo "
131+
"http://repo.cloudlinux.com/other/"
132+
"cl${{releasever}}/mysqlmeta/{expected}-common.repo\n"
133+
"Or re-run MySQL Governor to regenerate it "
134+
"(this reinstalls the full DB stack): "
135+
"/usr/share/lve/dbgovernor/mysqlgovernor.py --install --yes\n"
136+
"Then restart the upgrade process."
137+
.format(expected=expected_fragment)
135138
)
136139
),
137140
]

repos/system_upgrade/cloudlinux/actors/copycllicense/actor.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from leapp.reporting import Report
44
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
55
from leapp.libraries.common.cllaunch import run_on_cloudlinux
6+
from leapp.libraries.common.cln_detect import is_cln_package_channel_active
67
from leapp.libraries.stdlib import api
78
from leapp.models import (
89
TargetUserSpacePreupgradeTasks,
@@ -11,7 +12,18 @@
1112

1213

1314
RHN_CONFIG_DIR = '/etc/sysconfig/rhn'
14-
REQUIRED_PKGS = ['dnf-plugin-spacewalk', 'rhn-client-tools']
15+
16+
# rhn-client-tools is the CLN identity / licensing client. Keep it on the
17+
# target regardless of repo scheme - licensing does not go away under
18+
# no-auth, only repo management does.
19+
LICENSE_PKGS = ['rhn-client-tools']
20+
21+
# dnf-plugin-spacewalk is the DNF plugin that fetches packages from the
22+
# CLN-side spacewalk channel. Pure repo-management plumbing. Under
23+
# no-auth packages come from cl-channel via /etc/yum.repos.d/cl.repo and
24+
# this plugin is unused; rhn-client-tools >= 3.0.1 even Obsoletes it on
25+
# CL8/9.
26+
SPACEWALK_PLUGIN_PKG = 'dnf-plugin-spacewalk'
1527

1628

1729
class CopyClLicense(Actor):
@@ -38,7 +50,22 @@ def process(self):
3850
if os.path.isfile(src_path):
3951
files_to_copy.append(CopyFile(src=src_path))
4052

53+
# CLOS-4056: only the spacewalk plugin is repo-management and
54+
# therefore conditional on the CLN package channel being active.
55+
# Identity/licensing (rhn-client-tools, /etc/sysconfig/rhn) is
56+
# unconditional - it stays even when we move the system off CLN as
57+
# a package source.
58+
install_rpms = list(LICENSE_PKGS)
59+
if is_cln_package_channel_active():
60+
install_rpms.append(SPACEWALK_PLUGIN_PKG)
61+
else:
62+
api.current_logger().info(
63+
"CLN is not the active package channel; skipping %s in target"
64+
" userspace install set",
65+
SPACEWALK_PLUGIN_PKG,
66+
)
67+
4168
api.produce(TargetUserSpacePreupgradeTasks(
42-
install_rpms=REQUIRED_PKGS,
69+
install_rpms=install_rpms,
4370
copy_files=files_to_copy
4471
))

repos/system_upgrade/cloudlinux/actors/enableyumspacewalkplugin/actor.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,18 @@ class EnableYumSpacewalkPlugin(Actor):
1111
consumes = ()
1212
produces = (Report,)
1313
tags = (FirstBootPhaseTag, IPUWorkflowTag)
14-
config = enableyumspacewalkplugin.DEFAULT_CONFIG_PATH
14+
15+
CONFIG_PATH = enableyumspacewalkplugin.DEFAULT_CONFIG_PATH
1516

1617
@run_on_cloudlinux
1718
def process(self):
1819
_, title = enableyumspacewalkplugin._enable_plugin(
19-
self.config, enableyumspacewalkplugin.ParserClass, self.log
20+
self.CONFIG_PATH, enableyumspacewalkplugin.ParserClass, self.log
2021
)
2122
if title:
2223
reporting.create_report([
2324
reporting.Title(title),
24-
reporting.Summary("DNF spacewalk plugin must be enabled for CLN channels. Config path: " + self.config),
25+
reporting.Summary("DNF spacewalk plugin must be enabled for CLN channels. Config path: " + self.CONFIG_PATH),
2526
reporting.Severity(reporting.Severity.MEDIUM),
2627
reporting.Groups([reporting.Groups.SANITY])
2728
])

repos/system_upgrade/cloudlinux/libraries/cln_detect.py

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,24 @@
2626

2727
import os
2828

29+
from leapp.libraries.stdlib import CalledProcessError, run
30+
2931

3032
RHN_SYSTEMID = '/etc/sysconfig/rhn/systemid'
3133
SPACEWALK_DNF_CONF = '/etc/dnf/plugins/spacewalk.conf'
3234
SPACEWALK_YUM_CONF = '/etc/yum/pluginconf.d/spacewalk.conf'
3335

36+
# Packages that ship the spacewalk-protocol DNF/YUM plugin. Any one of
37+
# them being installed is sufficient evidence that CLN may serve
38+
# packages here; if none of them are present the plugin cannot run, no
39+
# matter what config files happen to be lying around (see
40+
# _spacewalk_plugin_installed below).
41+
_SPACEWALK_PLUGIN_PKGS = (
42+
'dnf-plugin-spacewalk',
43+
'python3-dnf-plugin-spacewalk',
44+
'yum-rhn-plugin',
45+
)
46+
3447

3548
def _plugin_explicitly_disabled(conf_path):
3649
try:
@@ -47,23 +60,58 @@ def _plugin_explicitly_disabled(conf_path):
4760
return False
4861

4962

63+
def _spacewalk_plugin_installed():
64+
"""True iff at least one spacewalk-protocol plugin package is installed.
65+
66+
Done via `rpm -q --quiet <pkg>` per package: rpm returns 0 only when
67+
*that* package is installed. We OR across the candidate names and
68+
return on the first hit. Errors invoking rpm itself (broken database,
69+
PATH issues) are treated as "not installed" - a false negative here
70+
only causes CLN-related actors to stand down, which is the safe side
71+
of the call.
72+
"""
73+
for pkg in _SPACEWALK_PLUGIN_PKGS:
74+
try:
75+
run(['rpm', '-q', '--quiet', pkg])
76+
return True
77+
except CalledProcessError:
78+
continue
79+
except (OSError, IOError):
80+
return False
81+
return False
82+
83+
5084
def is_cln_package_channel_active():
5185
"""Return True when CLN is the active package channel for this system.
5286
53-
A True result means the spacewalk DNF/YUM plugin is installed, not
54-
explicitly disabled, and the system has CLN registration state for
55-
the plugin to authenticate with. A False result means the system is
56-
either deregistered or has been moved to the no-auth (SWNG) scheme,
57-
so CLN-targeting actions (channel switch, mirror pinning, version
58-
overrides) are not meaningful and should be skipped.
87+
Requires all of:
88+
89+
* `/etc/sysconfig/rhn/systemid` present (CLN registration state),
90+
* at least one spacewalk-protocol plugin package installed,
91+
* a spacewalk plugin config file present, and
92+
* none of the present plugin config files explicitly setting `enabled = 0`.
93+
94+
A False result means the system is either deregistered, has no
95+
spacewalk plugin installed, or has been moved to the no-auth (SWNG)
96+
scheme, so CLN-targeting actions (channel switch, mirror pinning,
97+
version overrides) are not meaningful and should be skipped.
5998
6099
This is a deliberately heuristic check - it asks "is CLN going to
61100
serve packages here", not "is the system registered with CLN" (the
62101
two were the same thing pre-no-auth and have since diverged).
102+
103+
The plugin-package check guards against stale-config edge cases: when
104+
rhn-client-tools 3.0+ Obsoletes dnf-plugin-spacewalk, a leftover
105+
/etc/dnf/plugins/spacewalk.conf (saved without the .rpmsave suffix,
106+
or manually preserved) would otherwise make the helper claim CLN is
107+
active when no plugin can actually run.
63108
"""
64109
if not os.path.exists(RHN_SYSTEMID):
65110
return False
66111

112+
if not _spacewalk_plugin_installed():
113+
return False
114+
67115
configs = [p for p in (SPACEWALK_DNF_CONF, SPACEWALK_YUM_CONF) if os.path.exists(p)]
68116
if not configs:
69117
return False

0 commit comments

Comments
 (0)