Skip to content

Commit b50e324

Browse files
committed
fix osrelease from rocky/alma/epel errata
1 parent 03d7c3d commit b50e324

3 files changed

Lines changed: 207 additions & 4 deletions

File tree

errata/sources/repos/yum.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,22 +184,49 @@ def get_osrelease_names(e, update):
184184
return osreleases
185185

186186

187+
def get_existing_el_osreleases(major_version):
188+
""" Returns existing OSReleases for EL-based distros matching the major version
189+
"""
190+
from operatingsystems.models import OSRelease
191+
el_patterns = [
192+
f'RHEL {major_version}',
193+
f'Red Hat Enterprise Linux {major_version}',
194+
f'CentOS Stream {major_version}',
195+
f'CentOS {major_version}',
196+
f'Rocky Linux {major_version}',
197+
f'Alma Linux {major_version}',
198+
f'AlmaLinux {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'):
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+
elif osrelease_name.startswith('almalinux'):
198223
version = osrelease_name.split('-')[1]
199-
osrelease_name = 'Alma Linux ' + version
224+
major_version = version.split('.')[0]
225+
osrelease_name = 'Alma Linux ' + major_version
200226
elif osrelease_name.startswith('rocky-linux'):
201227
version = osrelease_name.split('-')[2]
202-
osrelease_name = 'Rocky Linux ' + version
228+
major_version = version.split('.')[0]
229+
osrelease_name = 'Rocky Linux ' + major_version
203230
elif osrelease_name in ['Amazon Linux', 'Amazon Linux AMI']:
204231
osrelease_name = 'Amazon Linux 1'
205232
osrelease = get_or_create_osrelease(name=osrelease_name)

errata/tests/test_yum.py

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# Copyright 2025 Marcus Furlong <furlongm@gmail.com>
2+
#
3+
# This file is part of Patchman.
4+
#
5+
# Patchman is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, version 3 only.
8+
#
9+
# Patchman is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with Patchman. If not, see <http://www.gnu.org/licenses/>
16+
17+
from unittest.mock import MagicMock
18+
19+
from django.test import TestCase
20+
from django.utils import timezone
21+
22+
from errata.models import Erratum
23+
from errata.sources.repos.yum import (
24+
add_updateinfo_osreleases, get_existing_el_osreleases,
25+
)
26+
from operatingsystems.models import OSRelease
27+
28+
29+
class GetExistingELOSReleasesTest(TestCase):
30+
"""Tests for get_existing_el_osreleases()"""
31+
32+
def test_returns_empty_when_no_matching_osreleases(self):
33+
"""Should return empty list when no EL OSReleases exist"""
34+
result = get_existing_el_osreleases('10')
35+
self.assertEqual(result, [])
36+
37+
def test_returns_matching_rhel_osrelease(self):
38+
"""Should return RHEL OSRelease when it exists"""
39+
rhel = OSRelease.objects.create(name='RHEL 10')
40+
result = get_existing_el_osreleases('10')
41+
self.assertIn(rhel, result)
42+
43+
def test_returns_matching_rocky_osrelease(self):
44+
"""Should return Rocky Linux OSRelease when it exists"""
45+
rocky = OSRelease.objects.create(name='Rocky Linux 10')
46+
result = get_existing_el_osreleases('10')
47+
self.assertIn(rocky, result)
48+
49+
def test_returns_matching_alma_osrelease(self):
50+
"""Should return Alma Linux OSRelease when it exists"""
51+
alma = OSRelease.objects.create(name='Alma Linux 10')
52+
result = get_existing_el_osreleases('10')
53+
self.assertIn(alma, result)
54+
55+
def test_returns_multiple_matching_osreleases(self):
56+
"""Should return all matching EL OSReleases"""
57+
rhel = OSRelease.objects.create(name='RHEL 10')
58+
rocky = OSRelease.objects.create(name='Rocky Linux 10')
59+
alma = OSRelease.objects.create(name='Alma Linux 10')
60+
result = get_existing_el_osreleases('10')
61+
self.assertEqual(len(result), 3)
62+
self.assertIn(rhel, result)
63+
self.assertIn(rocky, result)
64+
self.assertIn(alma, result)
65+
66+
def test_does_not_return_different_version(self):
67+
"""Should not return OSReleases with different major version"""
68+
rocky9 = OSRelease.objects.create(name='Rocky Linux 9')
69+
rocky10 = OSRelease.objects.create(name='Rocky Linux 10')
70+
result = get_existing_el_osreleases('10')
71+
self.assertIn(rocky10, result)
72+
self.assertNotIn(rocky9, result)
73+
74+
def test_matches_centos_stream(self):
75+
"""Should match CentOS Stream"""
76+
centos = OSRelease.objects.create(name='CentOS Stream 10')
77+
result = get_existing_el_osreleases('10')
78+
self.assertIn(centos, result)
79+
80+
def test_matches_oracle_linux(self):
81+
"""Should match Oracle Linux"""
82+
oracle = OSRelease.objects.create(name='Oracle Linux 10')
83+
result = get_existing_el_osreleases('10')
84+
self.assertIn(oracle, result)
85+
86+
87+
class AddUpdateinfoOSReleasesTest(TestCase):
88+
"""Tests for add_updateinfo_osreleases()"""
89+
90+
def setUp(self):
91+
self.erratum = Erratum.objects.create(
92+
name='TEST-2025-001',
93+
e_type='security',
94+
issue_date=timezone.now(),
95+
)
96+
97+
def _mock_collection(self, name=None):
98+
"""Create a mock collection element"""
99+
collection = MagicMock()
100+
name_elem = MagicMock()
101+
if name:
102+
name_elem.text = name
103+
collection.find.return_value = name_elem
104+
else:
105+
collection.find.return_value = None
106+
return collection
107+
108+
def test_epel_maps_to_existing_el_osreleases(self):
109+
"""EPEL should map to existing EL OSReleases only"""
110+
rocky = OSRelease.objects.create(name='Rocky Linux 10')
111+
alma = OSRelease.objects.create(name='Alma Linux 10')
112+
collection = self._mock_collection()
113+
114+
add_updateinfo_osreleases(self.erratum, collection, ['Fedora EPEL 10.0'])
115+
116+
self.assertIn(rocky, self.erratum.osreleases.all())
117+
self.assertIn(alma, self.erratum.osreleases.all())
118+
119+
def test_epel_does_not_create_fake_osrelease(self):
120+
"""EPEL should not create 'Fedora EPEL 10' OSRelease"""
121+
collection = self._mock_collection()
122+
123+
add_updateinfo_osreleases(self.erratum, collection, ['Fedora EPEL 10.0'])
124+
125+
# No OSReleases should be created or associated
126+
self.assertEqual(self.erratum.osreleases.count(), 0)
127+
self.assertFalse(OSRelease.objects.filter(name__contains='EPEL').exists())
128+
129+
def test_epel_extracts_major_version_from_point_release(self):
130+
"""EPEL 10.1 should map to EL 10 OSReleases"""
131+
rocky = OSRelease.objects.create(name='Rocky Linux 10')
132+
collection = self._mock_collection()
133+
134+
add_updateinfo_osreleases(self.erratum, collection, ['Fedora EPEL 10.1'])
135+
136+
self.assertIn(rocky, self.erratum.osreleases.all())
137+
138+
def test_rocky_linux_extracts_major_version(self):
139+
"""rocky-linux-10.1 should create 'Rocky Linux 10' not '10.1'"""
140+
collection = self._mock_collection()
141+
142+
add_updateinfo_osreleases(self.erratum, collection, ['rocky-linux-10.1'])
143+
144+
osrelease = self.erratum.osreleases.first()
145+
self.assertEqual(osrelease.name, 'Rocky Linux 10')
146+
self.assertFalse(OSRelease.objects.filter(name='Rocky Linux 10.1').exists())
147+
148+
def test_almalinux_extracts_major_version(self):
149+
"""almalinux-10.1 should create 'Alma Linux 10' not '10.1'"""
150+
collection = self._mock_collection()
151+
152+
add_updateinfo_osreleases(self.erratum, collection, ['almalinux-10.1'])
153+
154+
osrelease = self.erratum.osreleases.first()
155+
self.assertEqual(osrelease.name, 'Alma Linux 10')
156+
self.assertFalse(OSRelease.objects.filter(name='Alma Linux 10.1').exists())
157+
158+
def test_amazon_linux_normalizes_name(self):
159+
"""Amazon Linux AMI should become Amazon Linux 1"""
160+
collection = self._mock_collection()
161+
162+
add_updateinfo_osreleases(self.erratum, collection, ['Amazon Linux AMI'])
163+
164+
osrelease = self.erratum.osreleases.first()
165+
self.assertEqual(osrelease.name, 'Amazon Linux 1')
166+
167+
def test_uses_collection_name_when_no_osrelease_names(self):
168+
"""Should use collection name when osrelease_names is empty"""
169+
collection = self._mock_collection(name='Fedora 40')
170+
171+
add_updateinfo_osreleases(self.erratum, collection, [])
172+
173+
osrelease = self.erratum.osreleases.first()
174+
self.assertEqual(osrelease.name, 'Fedora 40')

reports/utils.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,9 @@ def get_os(os, arch):
611611
lts = ''
612612
if 'LTS' in os:
613613
lts = ' LTS'
614-
major, minor, patch = os.split(' ')[1].split('.')
614+
version_parts = os.split(' ')[1].split('.')
615+
major = version_parts[0]
616+
minor = version_parts[1] if len(version_parts) > 1 else '04'
615617
ubuntu_version = f'{major}_{minor}'
616618
osrelease_name = f'Ubuntu {major}.{minor}{lts}'
617619
cpe_name = f"cpe:2.3:o:canonical:ubuntu_linux:{ubuntu_version}:*:*:*:{'lts' if lts else '*'}:*:*:*"

0 commit comments

Comments
 (0)