Skip to content

Commit 02f02cf

Browse files
committed
CLOS-4056: gate CLN-assuming actors on is_cln_configured()
Systems migrated to the no-auth (SWNG) scheme no longer have CLN as a package source. Several CL-specific actors assumed CLN was always active and either crashed on missing files or produced spurious inhibitors. Add cln_detect.is_cln_configured() — True when the CLN plumbing is present and not explicitly disabled (registration file exists + spacewalk plugin installed + plugin enabled). Gate these actors on it: - switch_cln_channel: skip the cln-switch-channel call on no-auth systems. Also downgrade the failed-switch inhibitor to a MEDIUM report, since a failure on a transitional system where CLN plumbing lingers but is no longer usable should not block the upgrade — CL9 packages come from cl-channel / cloudlinux9-baseos instead. - pin_cln_mirror / unpin_cln_mirror: no-op on no-auth systems; also wrap the up2date update in try/except in pin_cln_mirror in case the file was not shipped on the target. - check_rhn_version_override / reset_rhn_version_override: skip on no-auth systems and fall back cleanly when /etc/sysconfig/rhn/up2date is missing. reset_rhn_version_override: also fix a pre-existing bug where rebinding inside the loop did not update config_data, so the reset was silently a no-op. enable_yum_spacewalk_plugin is not touched here — CLOS-3960 has concurrent work on it.
1 parent 51eebe1 commit 02f02cf

7 files changed

Lines changed: 229 additions & 34 deletions

File tree

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from leapp.actors import Actor
22
from leapp import reporting
3+
from leapp.libraries.stdlib import api
34
from leapp.reporting import Report
45
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
56
from leapp.libraries.common.cllaunch import run_on_cloudlinux
7+
from leapp.libraries.common.cln_detect import is_cln_configured
68

79

810
class CheckRhnVersionOverride(Actor):
@@ -17,23 +19,35 @@ class CheckRhnVersionOverride(Actor):
1719

1820
@run_on_cloudlinux
1921
def process(self):
22+
if not is_cln_configured():
23+
# CLOS-4056: no-auth systems have no CLN config to inspect.
24+
return
25+
2026
up2date_config = '/etc/sysconfig/rhn/up2date'
21-
with open(up2date_config, 'r') as f:
22-
config_data = f.readlines()
23-
for line in config_data:
24-
if line.startswith('versionOverride='):
25-
stripped_line = line.strip().split("=")
26-
versionOverrideValue = stripped_line[1]
27-
# If the version is being overriden to 8, we can continue as is.
28-
if versionOverrideValue not in ['', '8']:
29-
title = 'RHN up2date: versionOverride overwritten by the upgrade'
30-
summary = ("The RHN config file up2date has a set value of the versionOverride option: {}."
31-
" This value will get overwritten by the upgrade process, and reset to an empty"
32-
" value once it's complete.".format(versionOverrideValue))
33-
reporting.create_report([
34-
reporting.Title(title),
35-
reporting.Summary(summary),
36-
reporting.Severity(reporting.Severity.MEDIUM),
37-
reporting.Groups([reporting.Groups.OS_FACTS]),
38-
reporting.RelatedResource('file', '/etc/sysconfig/rhn/up2date')
39-
])
27+
try:
28+
with open(up2date_config, 'r') as f:
29+
config_data = f.readlines()
30+
except (OSError, IOError):
31+
api.current_logger().info(
32+
"RHN up2date config %s not present; skipping versionOverride check",
33+
up2date_config,
34+
)
35+
return
36+
37+
for line in config_data:
38+
if line.startswith('versionOverride='):
39+
stripped_line = line.strip().split("=")
40+
versionOverrideValue = stripped_line[1]
41+
# If the version is being overriden to 8, we can continue as is.
42+
if versionOverrideValue not in ['', '8']:
43+
title = 'RHN up2date: versionOverride overwritten by the upgrade'
44+
summary = ("The RHN config file up2date has a set value of the versionOverride option: {}."
45+
" This value will get overwritten by the upgrade process, and reset to an empty"
46+
" value once it's complete.".format(versionOverrideValue))
47+
reporting.create_report([
48+
reporting.Title(title),
49+
reporting.Summary(summary),
50+
reporting.Severity(reporting.Severity.MEDIUM),
51+
reporting.Groups([reporting.Groups.OS_FACTS]),
52+
reporting.RelatedResource('file', '/etc/sysconfig/rhn/up2date')
53+
])

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from leapp.actors import Actor
55
from leapp.libraries.stdlib import api
66
from leapp.libraries.common.cllaunch import run_on_cloudlinux
7+
from leapp.libraries.common.cln_detect import is_cln_configured
78
from leapp.libraries.common.cln_switch import get_target_userspace_path
89
from leapp.tags import DownloadPhaseTag, IPUWorkflowTag
910
from leapp.libraries.common.config.version import get_target_major_version
@@ -25,6 +26,13 @@ class PinClnMirror(Actor):
2526
@run_on_cloudlinux
2627
def process(self):
2728
"""Pin CLN mirror"""
29+
if not is_cln_configured():
30+
# CLOS-4056: no-auth systems don't use CLN mirrors; skip cleanly.
31+
api.current_logger().info(
32+
"CLN is not configured on this system; skipping mirror pinning"
33+
)
34+
return
35+
2836
target_userspace = get_target_userspace_path()
2937
api.current_logger().info("Pin CLN mirror: target userspace=%s", target_userspace)
3038

@@ -54,6 +62,11 @@ def process(self):
5462
api.current_logger().info("Pin CLN mirror %s in %s", mirror_url, mirrorlist_path)
5563

5664
up2date_path = os.path.join(target_userspace, 'etc/sysconfig/rhn/up2date')
57-
with open(up2date_path, 'a+') as file:
58-
file.write('\nmirrorURL[comment]=Set mirror URL to /etc/mirrorlist\nmirrorURL=file:///etc/mirrorlist\n')
59-
api.current_logger().info("Updated up2date_path %s", up2date_path)
65+
try:
66+
with open(up2date_path, 'a+') as file:
67+
file.write('\nmirrorURL[comment]=Set mirror URL to /etc/mirrorlist\nmirrorURL=file:///etc/mirrorlist\n')
68+
api.current_logger().info("Updated up2date_path %s", up2date_path)
69+
except (OSError, IOError) as e:
70+
api.current_logger().info(
71+
"Could not update %s: %s", up2date_path, e,
72+
)

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

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from leapp.actors import Actor
2+
from leapp.libraries.stdlib import api
23
from leapp.tags import FinalizationPhaseTag, IPUWorkflowTag
34
from leapp.libraries.common.cllaunch import run_on_cloudlinux
5+
from leapp.libraries.common.cln_detect import is_cln_configured
46

57

68
class ResetRhnVersionOverride(Actor):
@@ -15,11 +17,26 @@ class ResetRhnVersionOverride(Actor):
1517

1618
@run_on_cloudlinux
1719
def process(self):
20+
if not is_cln_configured():
21+
# CLOS-4056: no-auth systems have no CLN config to reset.
22+
return
23+
1824
up2date_config = '/etc/sysconfig/rhn/up2date'
19-
with open(up2date_config, 'r') as f:
20-
config_data = f.readlines()
21-
for line in config_data:
22-
if line.startswith('versionOverride='):
23-
line = 'versionOverride='
25+
try:
26+
with open(up2date_config, 'r') as f:
27+
config_data = f.readlines()
28+
except (OSError, IOError):
29+
api.current_logger().info(
30+
"RHN up2date config %s not present; skipping versionOverride reset",
31+
up2date_config,
32+
)
33+
return
34+
35+
new_data = []
36+
for line in config_data:
37+
if line.startswith('versionOverride='):
38+
new_data.append('versionOverride=\n')
39+
else:
40+
new_data.append(line)
2441
with open(up2date_config, 'w') as f:
25-
f.writelines(config_data)
42+
f.writelines(new_data)

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

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from leapp.tags import FirstBootPhaseTag, IPUWorkflowTag
44
from leapp.libraries.stdlib import CalledProcessError
55
from leapp.libraries.common.cllaunch import run_on_cloudlinux
6-
from leapp.libraries.common.cln_switch import cln_switch, get_target_userspace_path
6+
from leapp.libraries.common.cln_detect import is_cln_configured
7+
from leapp.libraries.common.cln_switch import cln_switch
78
from leapp import reporting
89
from leapp.reporting import Report
910
from leapp.libraries.common.config.version import get_target_major_version
@@ -22,9 +23,22 @@ class SwitchClnChannel(Actor):
2223

2324
@run_on_cloudlinux
2425
def process(self):
26+
if not is_cln_configured():
27+
# CLOS-4056: No-auth (SWNG) systems have no CLN plumbing. Skipping
28+
# the channel switch here is correct — the system receives CL9
29+
# packages via cl-channel / cloudlinux9-baseos instead.
30+
api.current_logger().info(
31+
"CLN is not configured on this system; skipping channel switch"
32+
)
33+
return
34+
2535
try:
2636
cln_switch(target=int(get_target_major_version()))
2737
except CalledProcessError as e:
38+
# CLOS-4056: Do not inhibit. CLN may be partially configured (legacy
39+
# registration files present but no working registration) on systems
40+
# transitioning to the no-auth scheme, and a failed channel switch
41+
# there is expected — the no-auth repos still serve CL9 packages.
2842
reporting.create_report(
2943
[
3044
reporting.Title(
@@ -33,17 +47,20 @@ def process(self):
3347
reporting.Summary(
3448
"Command {} failed with exit code {}."
3549
" The most probable cause of that is a problem with this system's"
36-
" CloudLinux Network registration.".format(e.command, e.exit_code)
50+
" CloudLinux Network registration. If this system now uses the"
51+
" no-auth (SWNG) repository scheme, this failure is harmless —"
52+
" CL9 packages come from cl-channel / cloudlinux9-baseos instead"
53+
" of CLN.".format(e.command, e.exit_code)
3754
),
3855
reporting.Remediation(
39-
hint="Check the state of this system's registration with \'rhn_check\'."
40-
" Attempt to re-register the system with \'rhnreg_ks --force\'."
56+
hint="If you rely on CLN: check registration with 'rhn_check' and"
57+
" re-register with 'rhnreg_ks --force'. If you have migrated to"
58+
" no-auth repos, this message can be ignored."
4159
),
42-
reporting.Severity(reporting.Severity.HIGH),
60+
reporting.Severity(reporting.Severity.MEDIUM),
4361
reporting.Groups(
4462
[reporting.Groups.OS_FACTS, reporting.Groups.AUTHENTICATION]
4563
),
46-
reporting.Groups([reporting.Groups.INHIBITOR]),
4764
]
4865
)
4966
except OSError as e:

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from leapp.actors import Actor
44
from leapp.libraries.common.cllaunch import run_on_cloudlinux
5+
from leapp.libraries.common.cln_detect import is_cln_configured
56
from leapp.libraries.common.cln_switch import get_target_userspace_path
67
from leapp.tags import FirstBootPhaseTag, IPUWorkflowTag
78

@@ -19,6 +20,10 @@ class UnpinClnMirror(Actor):
1920

2021
@run_on_cloudlinux
2122
def process(self):
23+
if not is_cln_configured():
24+
# CLOS-4056: pinclnmirror was skipped on no-auth systems, nothing to unpin.
25+
return
26+
2227
target_userspace = get_target_userspace_path()
2328

2429
mirrorlist_path = os.path.join(target_userspace, 'etc/mirrorlist')
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Detection helpers for CLN (CloudLinux Network / Spacewalk) state.
2+
3+
A system is considered to have CLN *configured* when it has registration
4+
state plus the spacewalk DNF/YUM plugin installed and not explicitly
5+
disabled. Systems that have been migrated to the no-auth (SWNG mirrorlist)
6+
scheme have either:
7+
8+
- no `/etc/sysconfig/rhn/systemid` (never registered or deregistered),
9+
- no spacewalk plugin installed (rhn-client-tools >= 3.0.1 removes it), or
10+
- the plugin's `enabled = 0` in its config.
11+
12+
CLOS-4056: several CloudLinux-specific actors were written when CLN was the
13+
only scheme and assume it is always active. They need to gate their
14+
behavior on `is_cln_configured()` so systems on the no-auth scheme pass
15+
through without bogus inhibitors or crashes.
16+
"""
17+
18+
import os
19+
20+
21+
RHN_SYSTEMID = '/etc/sysconfig/rhn/systemid'
22+
SPACEWALK_DNF_CONF = '/etc/dnf/plugins/spacewalk.conf'
23+
SPACEWALK_YUM_CONF = '/etc/yum/pluginconf.d/spacewalk.conf'
24+
25+
26+
def _plugin_explicitly_disabled(conf_path):
27+
try:
28+
with open(conf_path) as f:
29+
for line in f:
30+
stripped = line.strip().lower()
31+
if not stripped or stripped.startswith('#') or stripped.startswith('['):
32+
continue
33+
if stripped.startswith('enabled') and '=' in stripped:
34+
value = stripped.split('=', 1)[1].strip()
35+
return value == '0'
36+
except (OSError, IOError):
37+
pass
38+
return False
39+
40+
41+
def is_cln_configured():
42+
"""Return True if CLN plumbing is present and not disabled on this system."""
43+
if not os.path.exists(RHN_SYSTEMID):
44+
return False
45+
46+
configs = [p for p in (SPACEWALK_DNF_CONF, SPACEWALK_YUM_CONF) if os.path.exists(p)]
47+
if not configs:
48+
return False
49+
50+
# If any plugin config explicitly disables the plugin, treat as no-auth.
51+
for conf in configs:
52+
if _plugin_explicitly_disabled(conf):
53+
return False
54+
55+
return True
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import os
2+
3+
import pytest
4+
5+
from leapp.libraries.common import cln_detect
6+
7+
8+
@pytest.fixture
9+
def clean_paths(monkeypatch, tmp_path):
10+
"""Point cln_detect at a clean tmp dir so each test starts from no state."""
11+
systemid = tmp_path / "systemid"
12+
dnf_conf = tmp_path / "dnf_spacewalk.conf"
13+
yum_conf = tmp_path / "yum_spacewalk.conf"
14+
monkeypatch.setattr(cln_detect, "RHN_SYSTEMID", str(systemid))
15+
monkeypatch.setattr(cln_detect, "SPACEWALK_DNF_CONF", str(dnf_conf))
16+
monkeypatch.setattr(cln_detect, "SPACEWALK_YUM_CONF", str(yum_conf))
17+
return {"systemid": systemid, "dnf_conf": dnf_conf, "yum_conf": yum_conf}
18+
19+
20+
def _touch(path, content=""):
21+
path.write_text(content)
22+
23+
24+
def test_no_systemid_means_no_cln(clean_paths):
25+
# Without /etc/sysconfig/rhn/systemid the system is not registered with CLN.
26+
assert cln_detect.is_cln_configured() is False
27+
28+
29+
def test_systemid_but_no_plugin_means_no_cln(clean_paths):
30+
_touch(clean_paths["systemid"])
31+
assert cln_detect.is_cln_configured() is False
32+
33+
34+
def test_systemid_and_enabled_dnf_plugin_means_cln(clean_paths):
35+
_touch(clean_paths["systemid"])
36+
_touch(clean_paths["dnf_conf"], "[main]\nenabled = 1\n")
37+
assert cln_detect.is_cln_configured() is True
38+
39+
40+
def test_explicit_disabled_dnf_plugin_means_no_cln(clean_paths):
41+
_touch(clean_paths["systemid"])
42+
_touch(clean_paths["dnf_conf"], "[main]\nenabled = 0\n")
43+
assert cln_detect.is_cln_configured() is False
44+
45+
46+
def test_explicit_disabled_yum_plugin_means_no_cln(clean_paths):
47+
_touch(clean_paths["systemid"])
48+
_touch(clean_paths["yum_conf"], "[main]\nenabled=0\n")
49+
assert cln_detect.is_cln_configured() is False
50+
51+
52+
def test_one_plugin_disabled_one_not_means_no_cln(clean_paths):
53+
# If either plugin config disables it, CLN is not usable.
54+
_touch(clean_paths["systemid"])
55+
_touch(clean_paths["dnf_conf"], "[main]\nenabled = 1\n")
56+
_touch(clean_paths["yum_conf"], "[main]\nenabled = 0\n")
57+
assert cln_detect.is_cln_configured() is False
58+
59+
60+
def test_plugin_conf_without_enabled_key_means_cln(clean_paths):
61+
# A plugin config that doesn't mention `enabled` defaults to enabled upstream,
62+
# so we must treat it as CLN active.
63+
_touch(clean_paths["systemid"])
64+
_touch(clean_paths["dnf_conf"], "[main]\ntimeout = 120\n")
65+
assert cln_detect.is_cln_configured() is True
66+
67+
68+
def test_comments_and_blank_lines_ignored(clean_paths):
69+
_touch(clean_paths["systemid"])
70+
_touch(
71+
clean_paths["dnf_conf"],
72+
"# some comment\n\n[main]\n# enabled = 0\nenabled = 1\n",
73+
)
74+
assert cln_detect.is_cln_configured() is True

0 commit comments

Comments
 (0)