Skip to content

Commit 8c02be9

Browse files
committed
fix osrelease from rocky/alma/epel errata
1 parent 8102396 commit 8c02be9

File tree

7 files changed

+380
-18
lines changed

7 files changed

+380
-18
lines changed

errata/sources/distros/alma.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ def process_alma_erratum(release, advisory):
110110
def add_alma_erratum_osreleases(e, release):
111111
""" Update OS Release for Alma Linux errata
112112
"""
113-
osrelease = get_or_create_osrelease(name=f'Alma Linux {release}')
113+
from operatingsystems.utils import normalize_el_osrelease
114+
osrelease_name = normalize_el_osrelease(f'Alma Linux {release}')
115+
osrelease = get_or_create_osrelease(name=osrelease_name)
114116
e.osreleases.add(osrelease)
115117

116118

errata/sources/distros/centos.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,14 @@ def add_centos_erratum_references(e, references):
117117
def parse_centos_errata_children(e, children):
118118
""" Parse errata children to obtain architecture, release and packages
119119
"""
120+
from operatingsystems.utils import normalize_el_osrelease
120121
fixed_packages = set()
121122
for c in children:
122123
if c.tag == 'os_arch':
123124
pass
124125
elif c.tag == 'os_release':
125126
if accepted_centos_release([c.text]):
126-
osrelease_name = f'CentOS {c.text}'
127+
osrelease_name = normalize_el_osrelease(f'CentOS {c.text}')
127128
osrelease = get_or_create_osrelease(name=osrelease_name)
128129
e.osreleases.add(osrelease)
129130
elif c.tag == 'packages':

errata/sources/repos/yum.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
from defusedxml import ElementTree
2121
from django.db import connections
2222

23-
from operatingsystems.utils import get_or_create_osrelease
23+
from operatingsystems.utils import (
24+
get_or_create_osrelease, normalize_el_osrelease,
25+
)
2426
from packages.models import Package
2527
from packages.utils import get_or_create_package
2628
from patchman.signals import pbar_start, pbar_update
@@ -184,24 +186,40 @@ def get_osrelease_names(e, update):
184186
return osreleases
185187

186188

189+
def get_existing_el_osreleases(major_version):
190+
""" Returns existing OSReleases for EL-based distros matching the major version
191+
"""
192+
from operatingsystems.models import OSRelease
193+
el_patterns = [
194+
f'Red Hat Enterprise Linux {major_version}',
195+
f'CentOS Stream {major_version}',
196+
f'CentOS {major_version}',
197+
f'Rocky Linux {major_version}',
198+
f'Alma Linux {major_version}',
199+
f'Oracle Linux {major_version}',
200+
]
201+
return list(OSRelease.objects.filter(name__in=el_patterns))
202+
203+
187204
def add_updateinfo_osreleases(e, collection, osrelease_names):
188205
""" Adds OSRelease objects to an Erratum
189206
rocky and alma need some renaming
207+
EPEL maps to existing EL-based OSReleases only
190208
"""
191209
if not osrelease_names:
192210
collection_name = collection.find('name')
193211
if collection_name is not None:
194212
osrelease_name = collection_name.text
195213
osrelease_names.append(osrelease_name)
196214
for osrelease_name in osrelease_names:
197-
if osrelease_name.startswith('almalinux'):
198-
version = osrelease_name.split('-')[1]
199-
osrelease_name = 'Alma Linux ' + version
200-
elif osrelease_name.startswith('rocky-linux'):
201-
version = osrelease_name.split('-')[2]
202-
osrelease_name = 'Rocky Linux ' + version
203-
elif osrelease_name in ['Amazon Linux', 'Amazon Linux AMI']:
204-
osrelease_name = 'Amazon Linux 1'
215+
if osrelease_name.startswith('Fedora EPEL'):
216+
# "Fedora EPEL 10.0" → map to existing EL 10 OSReleases
217+
version_str = osrelease_name.split()[-1] # "10.0"
218+
major_version = version_str.split('.')[0] # "10"
219+
for osrelease in get_existing_el_osreleases(major_version):
220+
e.osreleases.add(osrelease)
221+
continue
222+
osrelease_name = normalize_el_osrelease(osrelease_name)
205223
osrelease = get_or_create_osrelease(name=osrelease_name)
206224
e.osreleases.add(osrelease)
207225

operatingsystems/tests/test_models.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,150 @@
1717
from django.test import TestCase, override_settings
1818

1919
from operatingsystems.models import OSRelease, OSVariant
20+
from operatingsystems.utils import normalize_el_osrelease
21+
22+
23+
@override_settings(
24+
CELERY_TASK_ALWAYS_EAGER=True,
25+
CACHES={'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}}
26+
)
27+
class NormalizeELOSReleaseTests(TestCase):
28+
"""Tests for normalize_el_osrelease() function.
29+
30+
This function normalizes EL-based distro names to major version only,
31+
ensuring consistent OSRelease naming across errata and reports.
32+
33+
Regression notes - OLD behavior that caused duplicate OSReleases:
34+
- 'rocky-linux-10.1' -> 'Rocky Linux 10.1' (should be 'Rocky Linux 10')
35+
- 'almalinux-10.1' -> 'Alma Linux 10.1' (should be 'Alma Linux 10')
36+
- 'Rocky Linux 10.1' -> passed through unchanged (should be 'Rocky Linux 10')
37+
- 'CentOS 7.9' -> passed through unchanged (should be 'CentOS 7')
38+
"""
39+
40+
# ===========================================
41+
# REGRESSION TESTS - These were bugs before
42+
# ===========================================
43+
44+
def test_regression_rocky_dash_format_minor_stripped(self):
45+
"""REGRESSION: rocky-linux-10.1 was creating 'Rocky Linux 10.1'
46+
47+
Old behavior: version = osrelease_name.split('-')[2] -> '10.1'
48+
result = 'Rocky Linux 10.1'
49+
New behavior: major_version = '10.1'.split('.')[0] -> '10'
50+
result = 'Rocky Linux 10'
51+
"""
52+
# OLD (wrong): 'Rocky Linux 10.1'
53+
# NEW (correct): 'Rocky Linux 10'
54+
self.assertEqual(normalize_el_osrelease('rocky-linux-10.1'), 'Rocky Linux 10')
55+
self.assertNotEqual(normalize_el_osrelease('rocky-linux-10.1'), 'Rocky Linux 10.1')
56+
57+
def test_regression_alma_dash_format_minor_stripped(self):
58+
"""REGRESSION: almalinux-10.1 was creating 'Alma Linux 10.1'
59+
60+
Old behavior: version = osrelease_name.split('-')[1] -> '10.1'
61+
result = 'Alma Linux 10.1'
62+
New behavior: major_version = '10.1'.split('.')[0] -> '10'
63+
result = 'Alma Linux 10'
64+
"""
65+
# OLD (wrong): 'Alma Linux 10.1'
66+
# NEW (correct): 'Alma Linux 10'
67+
self.assertEqual(normalize_el_osrelease('almalinux-10.1'), 'Alma Linux 10')
68+
self.assertNotEqual(normalize_el_osrelease('almalinux-10.1'), 'Alma Linux 10.1')
69+
70+
def test_regression_rocky_human_format_minor_stripped(self):
71+
"""REGRESSION: 'Rocky Linux 10.1' was passed through unchanged
72+
73+
Old behavior: no handling, passed through as 'Rocky Linux 10.1'
74+
New behavior: normalized to 'Rocky Linux 10'
75+
"""
76+
# OLD (wrong): 'Rocky Linux 10.1'
77+
# NEW (correct): 'Rocky Linux 10'
78+
self.assertEqual(normalize_el_osrelease('Rocky Linux 10.1'), 'Rocky Linux 10')
79+
self.assertNotEqual(normalize_el_osrelease('Rocky Linux 10.1'), 'Rocky Linux 10.1')
80+
81+
def test_regression_centos_minor_stripped(self):
82+
"""REGRESSION: 'CentOS 7.9' was passed through unchanged
83+
84+
Old behavior: no handling for human-readable format
85+
New behavior: normalized to 'CentOS 7'
86+
"""
87+
# OLD (wrong): 'CentOS 7.9'
88+
# NEW (correct): 'CentOS 7'
89+
self.assertEqual(normalize_el_osrelease('CentOS 7.9'), 'CentOS 7')
90+
self.assertNotEqual(normalize_el_osrelease('CentOS 7.9'), 'CentOS 7.9')
91+
92+
# ===========================================
93+
# STANDARD TESTS - Expected behavior
94+
# ===========================================
95+
96+
def test_rocky_linux_with_minor_version(self):
97+
"""Test Rocky Linux X.Y -> Rocky Linux X"""
98+
self.assertEqual(normalize_el_osrelease('Rocky Linux 10.1'), 'Rocky Linux 10')
99+
self.assertEqual(normalize_el_osrelease('Rocky Linux 9.3'), 'Rocky Linux 9')
100+
101+
def test_rocky_linux_dash_format(self):
102+
"""Test rocky-linux-X.Y -> Rocky Linux X"""
103+
self.assertEqual(normalize_el_osrelease('rocky-linux-10.1'), 'Rocky Linux 10')
104+
self.assertEqual(normalize_el_osrelease('rocky-linux-9.3'), 'Rocky Linux 9')
105+
106+
def test_alma_linux_with_minor_version(self):
107+
"""Test Alma Linux X.Y -> Alma Linux X"""
108+
self.assertEqual(normalize_el_osrelease('Alma Linux 10.1'), 'Alma Linux 10')
109+
self.assertEqual(normalize_el_osrelease('Alma Linux 9.3'), 'Alma Linux 9')
110+
111+
def test_almalinux_dash_format(self):
112+
"""Test almalinux-X.Y -> Alma Linux X"""
113+
self.assertEqual(normalize_el_osrelease('almalinux-10.1'), 'Alma Linux 10')
114+
self.assertEqual(normalize_el_osrelease('almalinux-9.3'), 'Alma Linux 9')
115+
116+
def test_almalinux_no_space(self):
117+
"""Test AlmaLinux X.Y -> AlmaLinux X"""
118+
self.assertEqual(normalize_el_osrelease('AlmaLinux 10.1'), 'AlmaLinux 10')
119+
120+
def test_centos_with_minor_version(self):
121+
"""Test CentOS X.Y -> CentOS X"""
122+
self.assertEqual(normalize_el_osrelease('CentOS 7.9'), 'CentOS 7')
123+
self.assertEqual(normalize_el_osrelease('CentOS 8.5'), 'CentOS 8')
124+
125+
def test_rhel_with_minor_version(self):
126+
"""Test RHEL X.Y -> RHEL X"""
127+
self.assertEqual(normalize_el_osrelease('RHEL 8.2'), 'RHEL 8')
128+
self.assertEqual(normalize_el_osrelease('RHEL 9.1'), 'RHEL 9')
129+
130+
def test_red_hat_enterprise_linux_with_minor_version(self):
131+
"""Test Red Hat Enterprise Linux X.Y -> Red Hat Enterprise Linux X"""
132+
self.assertEqual(
133+
normalize_el_osrelease('Red Hat Enterprise Linux 8.2'),
134+
'Red Hat Enterprise Linux 8'
135+
)
136+
137+
def test_oracle_linux_with_minor_version(self):
138+
"""Test Oracle Linux X.Y -> Oracle Linux X"""
139+
self.assertEqual(normalize_el_osrelease('Oracle Linux 8.1'), 'Oracle Linux 8')
140+
141+
def test_amazon_linux_normalization(self):
142+
"""Test Amazon Linux -> Amazon Linux 1"""
143+
self.assertEqual(normalize_el_osrelease('Amazon Linux'), 'Amazon Linux 1')
144+
self.assertEqual(normalize_el_osrelease('Amazon Linux AMI'), 'Amazon Linux 1')
145+
146+
# ===========================================
147+
# NO-OP TESTS - Should remain unchanged
148+
# ===========================================
149+
150+
def test_major_version_only_unchanged(self):
151+
"""Test that major-version-only names are unchanged (no regression)"""
152+
self.assertEqual(normalize_el_osrelease('Rocky Linux 10'), 'Rocky Linux 10')
153+
self.assertEqual(normalize_el_osrelease('CentOS 7'), 'CentOS 7')
154+
self.assertEqual(normalize_el_osrelease('RHEL 9'), 'RHEL 9')
155+
self.assertEqual(normalize_el_osrelease('Alma Linux 9'), 'Alma Linux 9')
156+
157+
def test_non_el_distros_unchanged(self):
158+
"""Test that non-EL distros are unchanged (no false positives)"""
159+
self.assertEqual(normalize_el_osrelease('Ubuntu 22.04'), 'Ubuntu 22.04')
160+
self.assertEqual(normalize_el_osrelease('Debian 12'), 'Debian 12')
161+
self.assertEqual(normalize_el_osrelease('Fedora 39'), 'Fedora 39')
162+
self.assertEqual(normalize_el_osrelease('Arch Linux'), 'Arch Linux')
163+
self.assertEqual(normalize_el_osrelease('openSUSE Leap 15.5'), 'openSUSE Leap 15.5')
20164

21165

22166
@override_settings(

operatingsystems/utils.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,39 @@
1717
from django.db import IntegrityError
1818

1919

20+
def normalize_el_osrelease(osrelease_name):
21+
"""Normalize EL-based distros to major version only.
22+
e.g. 'Rocky Linux 10.1' -> 'Rocky Linux 10'
23+
'rocky-linux-10.1' -> 'Rocky Linux 10'
24+
'almalinux-10.1' -> 'Alma Linux 10'
25+
"""
26+
if osrelease_name.startswith('rocky-linux-'):
27+
major_version = osrelease_name.split('-')[2].split('.')[0]
28+
return f'Rocky Linux {major_version}'
29+
elif osrelease_name.startswith('almalinux-'):
30+
major_version = osrelease_name.split('-')[1].split('.')[0]
31+
return f'Alma Linux {major_version}'
32+
elif osrelease_name in ['Amazon Linux', 'Amazon Linux AMI']:
33+
return 'Amazon Linux 1'
34+
35+
el_distro_prefixes = [
36+
'Rocky Linux',
37+
'Alma Linux',
38+
'AlmaLinux',
39+
'CentOS',
40+
'RHEL',
41+
'Red Hat Enterprise Linux',
42+
'Oracle Linux',
43+
]
44+
for prefix in el_distro_prefixes:
45+
if osrelease_name.startswith(prefix):
46+
version_part = osrelease_name[len(prefix):].strip()
47+
if '.' in version_part:
48+
major_version = version_part.split('.')[0]
49+
return f'{prefix} {major_version}'
50+
return osrelease_name
51+
52+
2053
def get_or_create_osrelease(name, cpe_name=None, codename=None):
2154
""" Get or create OSRelease from OS details
2255
"""

0 commit comments

Comments
 (0)