Merge ~litios/ubuntu-cve-tracker:feature/data-lib-rel-storage into ubuntu-cve-tracker:master

Proposed by David Fernandez Gonzalez
Status: Merged
Merge reported by: David Fernandez Gonzalez
Merged at revision: c50841adf9f42538dced48ea01465abe484f4ddc
Proposed branch: ~litios/ubuntu-cve-tracker:feature/data-lib-rel-storage
Merge into: ubuntu-cve-tracker:master
Diff against target: 561 lines (+229/-74)
4 files modified
dev/null (+0/-20)
scripts/datalib/storage.py (+46/-0)
scripts/datalib/uct_models.py (+12/-16)
scripts/datalib/uct_storage.py (+171/-38)
Reviewer Review Type Date Requested Status
Eduardo Barretto Approve
Nick Galanis Pending
Review via email: mp+467464@code.launchpad.net

Description of the change

Add ReleaseStorage class to datalib.

This class will handle the Release objects, avoiding duplication and handling the links among themselves.
Furthermore, it will provide convenient features for retrieving specific sets of releases.

As an extra, package cache loading is not sequential by default, due to the high memory consumption when performed in parallel.

To post a comment you must log in.
Revision history for this message
David Fernandez Gonzalez (litios) wrote :
Download full text (5.9 KiB)

Testing script:

from datalib import *
release_storage = ReleaseStorage()

trusty = release_storage.get_release('trusty')
print('Trusty release loaded as "trusty"', trusty)
trusty = release_storage.get_release('ubuntu/trusty')
print('Trusty release loaded as "ubuntu/trusty"',trusty)
print(' * ESM Apps', trusty.esm_apps_release)
print(' * ESM Infra', trusty.esm_infra_release)
print(' * ESM Infra parent', trusty.esm_infra_release.parent)

bionic = release_storage.get_release('bionic')
print('Bionic release loaded as "bionic"',bionic)
bionic = release_storage.get_release('ubuntu/bionic')
print('Bionic release loaded as "ubuntu/bionic"',bionic)
print(' * ESM Apps', bionic.esm_apps_release)
print(' * ESM Infra', bionic.esm_infra_release)
print(' * ESM Infra parent', bionic.esm_infra_release.parent)
print(' * ESM Apps parent', bionic.esm_apps_release.parent)

jammy = release_storage.get_release('jammy')
print('Jammy release loaded as "jammy"',jammy)
jammy = release_storage.get_release('ubuntu/jammy')
print('Jammy release loaded as "ubuntu/jammy"',jammy)
print(' * ESM Apps', jammy.esm_apps_release)
print(' * ESM Infra', jammy.esm_infra_release)
print(' * ESM Apps parent', jammy.esm_apps_release.parent)

print('All supported releases', release_storage.supported_releases)
print('ESM Apps releases', release_storage.esm_apps_releases)
print('ESM Infra releases', release_storage.esm_infra_releases)

print('Ubuntu product releases', release_storage.get_product_releases('ubuntu'))
print('Ubuntu product releases -- supported only', release_storage.get_product_releases('ubuntu', supported_only=True))

print('Devel release:', release_storage.get_devel_release())

import time

now = time.time()

cve_storage = UCTCVEStorage()
cve_storage.link_release_storage(release_storage)
cve_storage.load()

package_storage = UCTPackageStorage()
package_storage.link_release_storage(release_storage)
package_storage.load(filter_releases=release_storage.supported_releases)

usn_storage = USNStorage()
usn_storage.link_release_storage(release_storage)
usn_storage.load()

cve_storage.link_pkg_storage(package_storage)
usn_storage.link_pkg_storage(package_storage)
usn_storage.link_cve_storage(cve_storage)

later = time.time()
print('Total loading time: ', later - now)

# gpac = package_storage.get_package('python-idna')
# for version in gpac.source_versions:
# print(gpac, gpac.source_info[version]['release'], version)
# for binary in gpac.binaries[version]:
# print(f' - {binary.name} {binary.version} {binary.arches}')

# print(list(usn_storage.sns.keys()))

print()
print('------------------')
print('Showing CVE-2023-1625')
print('------------------')
cve = cve_storage.get_cve('CVE-2023-1625')
print(cve.id, '\n')
print('Description:', cve.description, '\n')
print('References:', cve.references, '\n')
print('Notes:', cve.notes,'\n')
for entry in cve.pkg_entries:
    print(' -', entry.cve, entry.pkg, entry.release, entry.status, entry.note)

print()
print('------------------')
print('Loading jammy into the storage')
package_storage.load_release('jammy')
cve = cve_storage.get_cve('CVE-2024-3651')
print('------------------')
print('Showing CVE entries now')
for entry in ...

Read more...

Revision history for this message
David Fernandez Gonzalez (litios) wrote :
Download full text (10.9 KiB)

Output without subprojects:

Trusty release loaded as "trusty" ubuntu/trusty
Trusty release loaded as "ubuntu/trusty" ubuntu/trusty
 * ESM Apps None
 * ESM Infra esm/trusty
 * ESM Infra parent ubuntu/trusty
Bionic release loaded as "bionic" ubuntu/bionic
Bionic release loaded as "ubuntu/bionic" ubuntu/bionic
 * ESM Apps esm-apps/bionic
 * ESM Infra esm-infra/bionic
 * ESM Infra parent ubuntu/bionic
 * ESM Apps parent esm-infra/bionic
Jammy release loaded as "jammy" ubuntu/jammy
Jammy release loaded as "ubuntu/jammy" ubuntu/jammy
 * ESM Apps esm-apps/jammy
 * ESM Infra None
 * ESM Apps parent ubuntu/jammy
All supported releases [esm-apps/jammy, fips-updates/focal, esm/trusty, esm-apps/focal, ros-esm/melodic, fips/xenial, esm-apps/bionic, fips/bionic, ubuntu/jammy, esm-apps/xenial, esm-infra/xenial, ubuntu/mantic, esm-infra/bionic, fips-updates/bionic, fips-updates/xenial, ubuntu/noble, ros-esm/kinetic, fips/focal, esm-apps/noble, snap, ubuntu/focal]
ESM Apps releases [esm-apps/jammy, esm-apps/noble, esm-apps/focal, esm-apps/bionic, esm-apps/xenial]
ESM Infra releases [esm-infra/bionic, esm/trusty, esm-infra/xenial]
Ubuntu product releases [ubuntu/warty, ubuntu/hoary, ubuntu/breezy, ubuntu/dapper, ubuntu/edgy, ubuntu/feisty, ubuntu/gutsy, ubuntu/hardy, ubuntu/intrepid, ubuntu/jaunty, ubuntu/karmic, ubuntu/lucid, ubuntu/maverick, ubuntu/natty, ubuntu/oneiric, ubuntu/precise, ubuntu/quantal, ubuntu/raring, ubuntu/saucy, ubuntu/trusty, ubuntu/utopic, ubuntu/vivid, ubuntu/wily, ubuntu/xenial, ubuntu/yakkety, ubuntu/zesty, ubuntu/artful, ubuntu/bionic, ubuntu/cosmic, ubuntu/disco, ubuntu/eoan, ubuntu/focal, ubuntu/groovy, ubuntu/hirsute, ubuntu/impish, ubuntu/jammy, ubuntu/kinetic, ubuntu/lunar, ubuntu/mantic, ubuntu/noble, ubuntu/oracular]
Ubuntu product releases -- supported only [ubuntu/jammy, ubuntu/mantic, ubuntu/noble, ubuntu/focal]
Devel release: ubuntu/oracular
Loading 50236 CVEs
Loading 21 releases
Couldn't find package cache for release ros-esm/melodic with filename ros-esm_melodic
Couldn't find package cache for release ros-esm/kinetic with filename ros-esm_kinetic
Couldn't find package cache for release snap with filename snap
Loading 5171 USNs
Total loading time: 27.690572500228882

------------------
Showing CVE-2023-1625
------------------
CVE-2023-1625

Description: An information leak was discovered in OpenStack heat. This issue could allow a remote, authenticated attacker to use the 'stack show' command to reveal parameters which are supposed to remain hidden. This has a low impact to the confidentiality, integrity, and availability of the system.

References: ['https://ubuntu.com/security/notices/USN-6066-1', 'https://ubuntu.com/security/notices/USN-6293-1', 'https://www.cve.org/CVERecord?id=CVE-2023-1625']

Notes: [['mdeslaur', 'fixed in jammy-updates and kinetic-updates as a SRU, but not\nyet in the -security pocket.']]

  - CVE-2023-1625 heat ubuntu/focal released 1:14.2.0-0ubuntu1.1
  - CVE-2023-1625 heat ubuntu/jammy released 1:18.0.1-0ubuntu1.1
  - CVE-2023-1625 heat ubuntu/mantic not-affected 1:20.0.0-0ubuntu1
  - CVE-2023-1625 heat ubuntu/noble not-affected 1:20.0.0-0ubuntu1

------------------
Loading jammy into the ...

9bafe15... by David Fernandez Gonzalez

[DATALIB] Fix release loading to include parents

Signed-off-by: David Fernandez Gonzalez <email address hidden>

c6132b2... by David Fernandez Gonzalez

[DATALIB] Don't include embargoed dir

Signed-off-by: David Fernandez Gonzalez <email address hidden>

915557b... by David Fernandez Gonzalez

[DATALIB] Infer USN ESM release by pocket instead of version

Signed-off-by: David Fernandez Gonzalez <email address hidden>

c50841a... by David Fernandez Gonzalez

[DATALIB] Add DirtyCache class

This commits add a cache system to prevent relinking objects
if the storage classes haven't been updated since the last time
they were linked.

Signed-off-by: David Fernandez Gonzalez <email address hidden>

Revision history for this message
David Fernandez Gonzalez (litios) wrote :

More changes:

* Fix release loading so that when asking for a release, the parents are also loaded.
* Prevent the embargoed CVE dir from being loaded by mistake.
* Since USNs don't specify ESM releases, do best effort to actually figure out the ESM release based on binary pockets
* Implement cache system to avoid relinking objects if the storage classes have not changed since the object was linked before. When loading all USNs, total time went from +70 sec to 30sec.

Testing:

Loading 50228 CVEs
Loading 3 releases
Loading 5219 USNs
*** Done loading
*** Loading USN
Cache:: Marking everything dirty
USN 6812-1 :: Dirty - relink
Cache:: Marking everything dirty
CVE CVE-2024-21012 :: Dirty - relink
CVE CVE-2024-21011 :: Dirty - relink
CVE CVE-2024-21094 :: Dirty - relink
CVE CVE-2024-21068 :: Dirty - relink
*** Loading CVE
CVE CVE-2023-1625 :: Dirty - relink
*** Loading again; cache should reuse
*** Loading USN
USN 6812-1 :: Clean - reuse
*** Loading CVE
CVE CVE-2023-1625 :: Clean - reuse
*** Loading jammy
Loading 1 releases
*** Again with new release; it should relink
*** Loading USN
Cache:: Marking everything dirty
USN 6812-1 :: Dirty - relink
Cache:: Marking everything dirty
CVE CVE-2024-21012 :: Dirty - relink
CVE CVE-2024-21011 :: Dirty - relink
CVE CVE-2024-21094 :: Dirty - relink
CVE CVE-2024-21068 :: Dirty - relink
*** Loading CVE
CVE CVE-2023-1625 :: Dirty - relink
CVE CVE-2024-21011 :: Clean - reuse

Revision history for this message
Eduardo Barretto (ebarretto) wrote :

lgtm, thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/scripts/datalib/storage.py b/scripts/datalib/storage.py
2index fc5c271..308be59 100644
3--- a/scripts/datalib/storage.py
4+++ b/scripts/datalib/storage.py
5@@ -12,13 +12,50 @@
6 from datalib.uct_models import SourcePackage
7 from .models import *
8
9+class DirtyCache:
10+ """Cache class for handling modifications"""
11+ dirty_cache: dict[str: bool]
12+ last_updated: int
13+ others_last_updated: list[int]
14+
15+ def __init__(self) -> None:
16+ self.dirty_cache = {}
17+ self.others_last_updated = []
18+ self.last_updated = 0
19+
20+ def is_dirty(self, id: str, others_last_updated: list[int]) -> bool:
21+ if not self.others_last_updated:
22+ self.mark_dirty()
23+ else:
24+ for index in range(len(others_last_updated)):
25+ if self.others_last_updated[index] < others_last_updated[index]:
26+ self.mark_dirty()
27+ break
28+ self.others_last_updated = others_last_updated
29+ return self.dirty_cache[id]
30+
31+ def add(self, id: str) -> None:
32+ self.dirty_cache[id] = True
33+
34+ def make_clean(self, id: str) -> None:
35+ self.dirty_cache[id] = False
36+
37+ def mark_dirty(self) -> None:
38+ for item in self.dirty_cache:
39+ self.dirty_cache[item] = True
40+
41+ def increment_last_updated(self) -> None:
42+ self.last_updated += 1
43+
44 class PackageStorage:
45 """
46 Storage class for handling packages.
47 """
48 packages: dict[str: SourcePackage]
49+ dirty_cache: DirtyCache
50
51 def __init__(self) -> None:
52+ self.dirty_cache = DirtyCache()
53 self.packages = {}
54
55 def _order_packages(self) -> None:
56@@ -34,11 +71,13 @@ class CVEStorage:
57 """
58 cves: dict[str: CVE]
59 package_storage: PackageStorage
60+ dirty_cache: DirtyCache
61
62 # TODO: add a changed cache so we can cache CVEs and only update when something got loaded
63
64 def __init__(self) -> None:
65 self.cves = {}
66+ self.dirty_cache = DirtyCache()
67
68 def link_pkg_storage(self, package_storage: PackageStorage) -> None:
69 self.package_storage = package_storage
70@@ -50,6 +89,7 @@ class CVEStorage:
71 def _init_cve(self, cve: CVE, pkg_data: dict) -> None:
72 """Internal function to add CVE object to the class storage"""
73 self.cves[cve.id] = (cve, pkg_data)
74+ self.dirty_cache.add(cve.id)
75
76 def load_cve(self, cve_id: str) -> None:
77 """Load a CVE into the storage"""
78@@ -68,9 +108,15 @@ class SNStorage:
79 sns: dict[str: SN]
80 package_storage: PackageStorage
81 cve_storage: CVEStorage
82+ dirty_cache: DirtyCache
83
84 def __init__(self) -> None:
85 self.sns = {}
86+ self.dirty_cache = DirtyCache()
87+
88+ def _init_sn(self, sn_object: SN, cves: set, pkgs: dict):
89+ self.sns[sn_object.id] = (sn_object, cves, pkgs)
90+ self.dirty_cache.add(sn_object.id)
91
92 def _order_sns(self) -> None:
93 self.sns = dict(sorted(self.sns.items(),
94diff --git a/scripts/datalib/uct_models.py b/scripts/datalib/uct_models.py
95index 7b9fe01..31568f1 100644
96--- a/scripts/datalib/uct_models.py
97+++ b/scripts/datalib/uct_models.py
98@@ -20,10 +20,13 @@ class Release:
99 codename: str
100 product: str
101 series: str
102+ is_devel: bool
103 is_esm: bool
104 is_active: bool
105 requires_oval: bool
106 parent: "Release"
107+ esm_infra_release: "Release"
108+ esm_apps_release: "Release"
109
110 def __init__(self, name: str) -> None:
111 canon, product, series, details = get_subproject_details(name)
112@@ -39,19 +42,27 @@ class Release:
113 self.parent = None
114 self.version = None
115 self.codename = None
116+ self.esm_infra_release = None
117+ self.esm_apps_release = None
118+ self.is_devel = False
119 if not details: return
120 if 'oval' in details:
121 self.requires_oval = details['oval']
122 else:
123 self.requires_oval = True
124 if 'parent' in details:
125- self.parent = Release(details['parent'])
126+ self.parent = details['parent']
127 if 'version' in details:
128 self.version = details['version']
129 if 'codename' in details:
130 self.codename = details['codename']
131+ if 'codename' in details:
132+ self.codename = details['codename']
133+ if 'devel' in details:
134+ self.is_devel = details['devel']
135
136 def __eq__(self, value: object) -> bool:
137+ if not hasattr(value, 'name'): return False
138 return self.name == value.name
139
140 def __repr__(self) -> str:
141@@ -60,21 +71,6 @@ class Release:
142 def __hash__(self):
143 return hash(self.name)
144
145- def get_esm_release(self, component: str) -> "Release":
146- esm_releases = get_active_esm_releases()
147- potential_releases = list(filter(lambda release: self.series in release, esm_releases))
148- if len(potential_releases) == 1:
149- return Release(potential_releases[0])
150- elif len(potential_releases) == 2:
151- if component in ['main', 'restricted']:
152- target = 'esm-infra'
153- elif component in ['universe', 'multiverse']:
154- target = 'esm-apps'
155-
156- if target in potential_releases[0]: return Release(potential_releases[0])
157- else: return Release(potential_releases[1])
158-
159- return None
160 def is_parent_release(self, release: "Release") -> bool:
161 return release in self.get_all_parents()
162
163diff --git a/scripts/datalib/uct_storage.py b/scripts/datalib/uct_storage.py
164index 14f7b98..28cacea 100644
165--- a/scripts/datalib/uct_storage.py
166+++ b/scripts/datalib/uct_storage.py
167@@ -12,7 +12,6 @@
168 from .storage import *
169 from .uct_models import *
170 from .config import PKG_CACHE_DIR, USN_DATABASE
171-from .utils import get_active_ubuntu_releases
172 from .config import UCT
173
174 import json
175@@ -20,6 +19,77 @@ import os
176 import cve_lib
177 from multiprocessing import Pool
178
179+class ReleaseStorage():
180+ releases: list[str: Release]
181+ supported_releases: list[Release]
182+ esm_apps_releases: list[Release]
183+ esm_infra_releases: list[Release]
184+
185+ def __init__(self) -> None:
186+ self.releases = {}
187+ self.supported_releases = []
188+ self.esm_apps_releases = []
189+ self.esm_infra_releases = []
190+
191+ for release in cve_lib.all_releases:
192+ rel_obj = Release(release)
193+ self.releases[str(rel_obj)] = rel_obj
194+
195+ # Link parents
196+ for release in self.releases.values():
197+ if release.parent: release.parent = self.releases[release.parent]
198+
199+ esm_releases = get_active_esm_releases()
200+ supported_releases_names = list(set(cve_lib.all_releases).difference(set(cve_lib.eol_releases)).difference(set([cve_lib.devel_release])))
201+ esm_infra_releases_names = list(filter(lambda release: release == 'trusty/esm' or 'esm-infra/' in release, esm_releases))
202+ esm_apps_releases_names = list(filter(lambda release: 'esm-apps/' in release, esm_releases))
203+
204+ for release in supported_releases_names:
205+ self.supported_releases.append(self.get_release(release))
206+ for release in esm_infra_releases_names:
207+ rel_obj = self.get_release(release)
208+ self.esm_infra_releases.append(rel_obj)
209+ rel_obj.get_oldest_parent().esm_infra_release = rel_obj
210+ for release in esm_apps_releases_names:
211+ rel_obj = self.get_release(release)
212+ self.esm_apps_releases.append(rel_obj)
213+ rel_obj.get_oldest_parent().esm_apps_release = rel_obj
214+
215+ def get_all_releases(self) -> list[Release]:
216+ return list(self.releases.values())
217+
218+ def get_release(self, release_name: str) -> Release:
219+ if release_name == 'devel':
220+ return self.get_devel_release()
221+
222+ release = self.releases.get(release_name, None)
223+ if not release:
224+ release = self.releases.get(release_name.split('/')[0], None)
225+
226+ if not release:
227+ for cve_lib_rel in cve_lib.subprojects:
228+ if 'alias' in cve_lib.subprojects[cve_lib_rel] and \
229+ cve_lib.subprojects[cve_lib_rel]['alias'] == release_name:
230+ release = self.releases.get(cve_lib_rel, None)
231+ break
232+
233+ return release
234+
235+ def get_product_releases(self, product: str, supported_only: bool = False) -> list [Release]:
236+ """Get all releases that belong to a product"""
237+ if supported_only:
238+ releases = list(filter(lambda release: release.product == product, self.supported_releases))
239+ else:
240+ releases = list(filter(lambda release: release.product == product, self.releases.values()))
241+ return releases
242+
243+ def get_devel_release(self) -> Release:
244+ for release in self.releases.values():
245+ if release.is_devel:
246+ return release
247+
248+ return None
249+
250 class UCTPackageStorage(PackageStorage):
251 """
252 Storage class for handling UCT packages.
253@@ -27,41 +97,59 @@ class UCTPackageStorage(PackageStorage):
254 packages: dict[str: UbuntuSourcePackage]
255 latest_date_created: dict[Release:datetime.datetime]
256 loaded_releases: set[Release]
257+ release_storage: ReleaseStorage
258
259 def __init__(self) -> None:
260 self.loaded_releases = set()
261 self.latest_date_created = {}
262+ self.release_storage = None
263 super().__init__()
264
265- def load(self, filter_releases: list = []) -> None:
266- final_releases = []
267- active_ubuntu_releases = get_active_ubuntu_releases()
268- for release in active_ubuntu_releases:
269- if filter_releases and release not in filter_releases: continue
270- final_releases.append(release)
271-
272- with Pool(processes=os.cpu_count()) as pool:
273- print(f'Loading {len(final_releases)} releases')
274- package_data = pool.map(self._load_release_from_cache, final_releases)
275+ def load(self, filter_releases: list[Release] = [], multithreading: bool = False) -> None:
276+ self.dirty_cache.increment_last_updated()
277+ releases = self.release_storage.supported_releases if not filter_releases else filter_releases
278+ final_releases = set()
279+
280+ for release in releases:
281+ final_releases.add(release)
282+ while release.parent:
283+ release = release.parent
284+ if release not in final_releases:
285+ final_releases.add(release)
286+
287+ print(f'Loading {len(final_releases)} releases')
288+ if multithreading:
289+ with Pool(processes=os.cpu_count()) as pool:
290+ package_data = pool.map(self._load_release_from_cache, final_releases)
291+ else:
292+ package_data = []
293+ for release in final_releases:
294+ package_data.append(self._load_release_from_cache(release))
295
296 for release, release_data, last_date_created in package_data:
297 self._init_release(release, release_data, last_date_created)
298
299 self._order_packages()
300
301- def _load_release_from_cache(self, release: str) -> dict:
302+ def _load_release_from_cache(self, release: Release) -> dict:
303 data = {}
304+ pkg_cache_filename = []
305+ if release.name == 'esm/trusty':
306+ pkg_cache_filename = 'trusty_esm'
307+ elif release.product == 'ubuntu':
308+ pkg_cache_filename = release.series
309+ else:
310+ pkg_cache_filename = release.name.replace('/', '_')
311
312 try:
313- with open(os.path.join(os.path.expanduser(PKG_CACHE_DIR), release.replace('/', '_') + '-pkg-cache.json')) as f:
314+ with open(os.path.join(os.path.expanduser(PKG_CACHE_DIR), pkg_cache_filename + '-pkg-cache.json')) as f:
315 data = json.load(f)
316- except FileNotFoundError as ex:
317- print(ex)
318+ except FileNotFoundError:
319+ print(f'Couldn\'t find package cache for release {release.name} with filename {pkg_cache_filename}')
320 return (release, None, None)
321
322 release_data = {}
323 latest_date_created = None
324- release_obj = Release(release)
325 for src_pkg_name, src_pkg_versions in data.items():
326 if src_pkg_name == 'latest_date_created':
327 latest_date_created = datetime.datetime.fromtimestamp(src_pkg_versions)
328@@ -71,7 +159,7 @@ class UCTPackageStorage(PackageStorage):
329 release_data[src_pkg_name] = source_package
330 for src_pkg_version, src_pkg_version_data in src_pkg_versions.items():
331 src_pkg_version_obj = UbuntuVersion(src_pkg_version)
332- source_package.add_source_version(src_pkg_version_obj, release_obj,
333+ source_package.add_source_version(src_pkg_version_obj, release,
334 src_pkg_version_data['component'],
335 src_pkg_version_data['pocket'])
336
337@@ -82,7 +170,7 @@ class UCTPackageStorage(PackageStorage):
338 source_package,
339 binary_data['component']))
340
341- return (release_obj, release_data, latest_date_created)
342+ return (release, release_data, latest_date_created)
343
344 def _init_release(self, release: Release, release_data: dict, latest_date_created: str) -> None:
345 if not release_data: return
346@@ -98,13 +186,13 @@ class UCTPackageStorage(PackageStorage):
347 def get_package(self, pkg_name: str) -> UbuntuSourcePackage:
348 return super().get_package(pkg_name)
349
350- def load_release(self, release:str) -> None:
351- if Release(release) in self.loaded_releases: return
352+ def load_release(self, release: Release) -> None:
353+ if self.release_storage.get_release(release) in self.loaded_releases: return
354 release_obj, data, date = self._load_release_from_cache(release)
355 self._init_release(release_obj, data, date)
356
357- def unload_release(self, release: str) -> None:
358- release = Release(release)
359+ def unload_release(self, release: Release) -> None:
360+ release = self.release_storage.get_release(release)
361 if release not in self.loaded_releases: return
362 self.loaded_releases.remove(release)
363
364@@ -113,6 +201,9 @@ class UCTPackageStorage(PackageStorage):
365 for version in original_source_versions:
366 ver_rel = package.get_version_release(version)
367 if ver_rel == release: package.remove_version(version)
368+
369+ def link_release_storage(self, release_storage: ReleaseStorage) -> None:
370+ self.release_storage = release_storage
371
372 class UCTCVEStorage(CVEStorage):
373 """
374@@ -120,10 +211,18 @@ class UCTCVEStorage(CVEStorage):
375 """
376 cves: dict[str: UbuntuCVE]
377 package_storage: UCTPackageStorage
378+ release_storage: ReleaseStorage
379
380 # TODO: add a changed cache so we can cache CVEs and only update when something got loaded
381+ def __init__(self) -> None:
382+ self.release_storage = None
383+ super().__init__()
384+
385+ def link_release_storage(self, release_storage: ReleaseStorage) -> None:
386+ self.release_storage = release_storage
387
388 def load(self, cves_filter: list = []) -> None:
389+ self.dirty_cache.increment_last_updated()
390 final_cves = []
391 cves_ids = self.get_uct_cve_ids()
392 for cve_id in cves_ids:
393@@ -171,11 +270,15 @@ class UCTCVEStorage(CVEStorage):
394
395 return (cve, data['pkgs'])
396
397- def get_uct_cve_ids(self) -> list:
398+ def get_uct_cve_ids(self, embargoed: bool = False) -> list:
399 """Return all CVE IDs identified from UCT"""
400
401 cve_ids = []
402- for dir in cve_lib.cve_dirs:
403+ cve_dirs = [cve_lib.active_dir, cve_lib.retired_dir, cve_lib.ignored_dir]
404+ if embargoed:
405+ cve_dirs.append(cve_lib.embargoed_dir)
406+
407+ for dir in cve_dirs:
408 for (_, _, filenames) in os.walk(dir):
409 for filename in filenames:
410 if not re.search(CVE.ID_REGEX, filename): continue
411@@ -212,7 +315,8 @@ class UCTCVEStorage(CVEStorage):
412 package = self.package_storage.get_package(package_name)
413 if not package: continue
414 for release, status in package_entries.items():
415- release = Release(release)
416+ release = self.release_storage.get_release(release)
417+ if not release: continue
418 if not package.release_exists(release, include_parents=True): continue
419 cve.add_cve_entry(UbuntuCVEPkgEntry(package, cve, status[0], status[1], release))
420
421@@ -228,23 +332,36 @@ class UCTCVEStorage(CVEStorage):
422 if cve_id not in self.cves: return None
423
424 cve, _ = self.cves[cve_id]
425+ if not self.dirty_cache.is_dirty(cve_id, [self.package_storage.dirty_cache.last_updated]):
426+ return cve
427+
428 cve.pkg_entries = list()
429 if not with_pkg_links: return cve
430 self.populate_cve_entries(cve_id)
431
432+ self.dirty_cache.make_clean(cve_id)
433 return cve
434
435 class USNStorage(SNStorage):
436 sns: dict[str: tuple[USN, list, dict]]
437 package_storage: UCTPackageStorage
438 cve_storage: UCTCVEStorage
439+ release_storage: ReleaseStorage
440+
441+ def __init__(self) -> None:
442+ self.release_storage = None
443+ super().__init__()
444+
445+ def link_release_storage(self, release_storage: ReleaseStorage) -> None:
446+ self.release_storage = release_storage
447
448 def load(self) -> None:
449+ self.dirty_cache.increment_last_updated()
450 usns_data = self._get_usn_database_data()
451 print(f'Loading {len(usns_data)} USNs')
452 for usn_id, usn_data in usns_data.items():
453 _, _, cves, lp_bugs, pkgs = self.process_usn_data((usn_id, usn_data))
454- self.add_usn(usn_id, usn_data, cves, lp_bugs, pkgs)
455+ self._init_sn(USN(usn_id, usn_data, set(), dict(), lp_bugs), cves, pkgs)
456
457 self._order_sns()
458
459@@ -265,7 +382,21 @@ class USNStorage(SNStorage):
460 # Get package
461 for pkg, pkg_info in info['sources'].items():
462 pkgs.setdefault(pkg, [])
463- pkgs[pkg].append((pkg_info['version'], rel))
464+ pocket = None
465+ # Best effort, there are too many corner cases
466+ # since the format is not always the same for all
467+ # USNs
468+ if 'allbinaries' in info:
469+ for binary_data in info['allbinaries'].values():
470+ if 'source' in binary_data and binary_data['source'] != pkg: continue
471+ if 'pocket' not in binary_data: continue
472+ if pocket and binary_data['pocket'] != pocket:
473+ # Different pocket in binaries, default to parent
474+ pocket = None
475+ break
476+ elif not pocket:
477+ pocket = binary_data['pocket']
478+ pkgs[pkg].append((pkg_info['version'], pocket, rel))
479
480 # CVE loading
481 for cve_text in usn_data['cves']:
482@@ -276,9 +407,6 @@ class USNStorage(SNStorage):
483 cves.add(cve_text)
484
485 return usn_id, usn_data, cves, lp_bugs, pkgs
486-
487- def add_usn(self, usn_id: str, usn_data: dict, cves: set, lp_bugs: set, pkgs: dict) -> None:
488- self.sns[usn_id] = (USN(usn_id, usn_data, set(), dict(), lp_bugs), cves, pkgs)
489
490 def get_usns_by_cve(self, cve_id: str) -> list[USN]:
491 target_usns = []
492@@ -291,6 +419,11 @@ class USNStorage(SNStorage):
493 def get_usn(self, usn_id: str) -> USN:
494 if usn_id not in self.sns: return None
495 usn, cves, pkgs = self.sns[usn_id]
496+ if not self.dirty_cache.is_dirty(usn_id,
497+ [self.cve_storage.dirty_cache.last_updated,
498+ self.package_storage.dirty_cache.last_updated]):
499+ return usn
500+
501 cve_mapping = set()
502 pkg_mapping = dict()
503
504@@ -302,22 +435,22 @@ class USNStorage(SNStorage):
505 for pkg_name in pkgs:
506 pkg = self.package_storage.get_package(pkg_name)
507 if not pkg: continue
508- for version, release in pkgs[pkg_name]:
509- release = Release(release)
510+ for version, pocket, release in pkgs[pkg_name]:
511+ release = self.release_storage.get_release(release)
512+ if not release: continue
513 if not pkg.release_exists(release, include_parents=True): continue
514 pkg_mapping.setdefault(pkg, [])
515 version = UbuntuVersion(version)
516- if version.is_esm():
517- for pkg_version in pkg.source_versions:
518- if pkg.get_version_release(pkg_version) == release:
519- release = release.get_esm_release(pkg.get_version_component(pkg_version))
520- break
521+ if pocket == 'esm-apps':
522+ release = release.esm_apps_release
523+ elif pocket == 'esm-infra':
524+ release = release.esm_infra_release
525
526 pkg_mapping[pkg].append((version, release))
527
528 usn.cves = cve_mapping
529 usn.package_fixed_versions = pkg_mapping
530
531+ self.dirty_cache.make_clean(usn_id)
532 return usn
533
534-
535diff --git a/scripts/datalib/utils.py b/scripts/datalib/utils.py
536deleted file mode 100644
537index cc072e0..0000000
538--- a/scripts/datalib/utils.py
539+++ /dev/null
540@@ -1,20 +0,0 @@
541-#!/usr/bin/python3
542-# -*- coding: utf-8 -*-
543-# Module containing classes that represent CVE and Package data
544-#
545-# Author: David Fernandez Gonzalez <david.fernandezgonzalez@canonical.com>
546-# Copyright (C) 2024 Canonical Ltd.
547-#
548-# This script is distributed under the terms and conditions of the GNU General
549-# Public License, Version 2 or later. See http://www.gnu.org/copyleft/gpl.html
550-# for details.
551-#
552-import cve_lib
553-
554-def get_active_ubuntu_releases():
555- active_ubuntu_releases = []
556- for release in cve_lib.all_releases:
557- if release not in cve_lib.eol_releases or release in cve_lib.get_active_releases_with_esm():
558- active_ubuntu_releases.append(release)
559-
560- return active_ubuntu_releases
561\ No newline at end of file

Subscribers

People subscribed via source and target branches