Merge ~andrey-fedoseev/launchpad:uct-upstream into launchpad:master
- Git
- lp:~andrey-fedoseev/launchpad
- uct-upstream
- Merge into master
Proposed by
Andrey Fedoseev
Status: | Merged |
---|---|
Approved by: | Andrey Fedoseev |
Approved revision: | f256964a574a9d5183f0bad34c142472b2b0ad1f |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~andrey-fedoseev/launchpad:uct-upstream |
Merge into: | launchpad:master |
Prerequisite: | ~andrey-fedoseev/launchpad:uct-export |
Diff against target: |
879 lines (+329/-116) 5 files modified
lib/lp/bugs/scripts/tests/test_uct.py (+135/-50) lib/lp/bugs/scripts/uct/models.py (+123/-54) lib/lp/bugs/scripts/uct/uctexport.py (+22/-0) lib/lp/bugs/scripts/uct/uctimport.py (+46/-12) lib/lp/registry/model/distributionsourcepackage.py (+3/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+428893@code.launchpad.net |
Commit message
UCT import/export: handle upstream package status and ESM packages
Description of the change
This also includes the changes related to UCT export that were added in https:/
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/bugs/scripts/tests/test_uct.py b/lib/lp/bugs/scripts/tests/test_uct.py |
2 | index 47ff535..00f995d 100644 |
3 | --- a/lib/lp/bugs/scripts/tests/test_uct.py |
4 | +++ b/lib/lp/bugs/scripts/tests/test_uct.py |
5 | @@ -91,20 +91,20 @@ class TestUCTRecord(TestCase): |
6 | UCTRecord.Package( |
7 | name="linux", |
8 | statuses=[ |
9 | - UCTRecord.DistroSeriesPackageStatus( |
10 | - distroseries="upstream", |
11 | + UCTRecord.SeriesPackageStatus( |
12 | + series="upstream", |
13 | status=UCTRecord.PackageStatus.RELEASED, |
14 | reason="5.17~rc1", |
15 | priority=None, |
16 | ), |
17 | - UCTRecord.DistroSeriesPackageStatus( |
18 | - distroseries="impish", |
19 | + UCTRecord.SeriesPackageStatus( |
20 | + series="impish", |
21 | status=UCTRecord.PackageStatus.RELEASED, |
22 | reason="5.13.0-37.42", |
23 | priority=UCTRecord.Priority.MEDIUM, |
24 | ), |
25 | - UCTRecord.DistroSeriesPackageStatus( |
26 | - distroseries="devel", |
27 | + UCTRecord.SeriesPackageStatus( |
28 | + series="devel", |
29 | status=UCTRecord.PackageStatus.NOT_AFFECTED, |
30 | reason="5.15.0-25.25", |
31 | priority=UCTRecord.Priority.MEDIUM, |
32 | @@ -126,20 +126,20 @@ class TestUCTRecord(TestCase): |
33 | UCTRecord.Package( |
34 | name="linux-hwe", |
35 | statuses=[ |
36 | - UCTRecord.DistroSeriesPackageStatus( |
37 | - distroseries="upstream", |
38 | + UCTRecord.SeriesPackageStatus( |
39 | + series="upstream", |
40 | status=UCTRecord.PackageStatus.RELEASED, |
41 | reason="5.17~rc1", |
42 | priority=None, |
43 | ), |
44 | - UCTRecord.DistroSeriesPackageStatus( |
45 | - distroseries="impish", |
46 | + UCTRecord.SeriesPackageStatus( |
47 | + series="impish", |
48 | status=UCTRecord.PackageStatus.DOES_NOT_EXIST, |
49 | reason="", |
50 | priority=None, |
51 | ), |
52 | - UCTRecord.DistroSeriesPackageStatus( |
53 | - distroseries="devel", |
54 | + UCTRecord.SeriesPackageStatus( |
55 | + series="devel", |
56 | status=UCTRecord.PackageStatus.DOES_NOT_EXIST, |
57 | reason="", |
58 | priority=None, |
59 | @@ -186,8 +186,14 @@ class TextCVE(TestCaseWithFactory): |
60 | status=SeriesStatus.DEVELOPMENT, |
61 | name="kinetic", |
62 | ) |
63 | - dsp1 = self.factory.makeDistributionSourcePackage(distribution=ubuntu) |
64 | - dsp2 = self.factory.makeDistributionSourcePackage(distribution=ubuntu) |
65 | + product_1 = self.factory.makeProduct() |
66 | + product_2 = self.factory.makeProduct() |
67 | + dsp1 = self.factory.makeDistributionSourcePackage( |
68 | + sourcepackagename=product_1.name, distribution=ubuntu |
69 | + ) |
70 | + dsp2 = self.factory.makeDistributionSourcePackage( |
71 | + sourcepackagename=product_2.name, distribution=ubuntu |
72 | + ) |
73 | assignee = self.factory.makePerson() |
74 | |
75 | self.uct_record = UCTRecord( |
76 | @@ -224,24 +230,30 @@ class TextCVE(TestCaseWithFactory): |
77 | UCTRecord.Package( |
78 | name=dsp1.sourcepackagename.name, |
79 | statuses=[ |
80 | - UCTRecord.DistroSeriesPackageStatus( |
81 | - distroseries=supported_series.name, |
82 | + UCTRecord.SeriesPackageStatus( |
83 | + series=supported_series.name, |
84 | status=UCTRecord.PackageStatus.NOT_AFFECTED, |
85 | reason="reason 1", |
86 | priority=UCTRecord.Priority.MEDIUM, |
87 | ), |
88 | - UCTRecord.DistroSeriesPackageStatus( |
89 | - distroseries=current_series.name, |
90 | + UCTRecord.SeriesPackageStatus( |
91 | + series=current_series.name, |
92 | status=UCTRecord.PackageStatus.RELEASED, |
93 | reason="reason 2", |
94 | priority=UCTRecord.Priority.MEDIUM, |
95 | ), |
96 | - UCTRecord.DistroSeriesPackageStatus( |
97 | - distroseries="devel", |
98 | + UCTRecord.SeriesPackageStatus( |
99 | + series="devel", |
100 | status=UCTRecord.PackageStatus.RELEASED, |
101 | reason="reason 3", |
102 | priority=None, |
103 | ), |
104 | + UCTRecord.SeriesPackageStatus( |
105 | + series="upstream", |
106 | + status=UCTRecord.PackageStatus.RELEASED, |
107 | + reason="reason 4", |
108 | + priority=None, |
109 | + ), |
110 | ], |
111 | priority=None, |
112 | tags=set(), |
113 | @@ -250,20 +262,26 @@ class TextCVE(TestCaseWithFactory): |
114 | UCTRecord.Package( |
115 | name=dsp2.sourcepackagename.name, |
116 | statuses=[ |
117 | - UCTRecord.DistroSeriesPackageStatus( |
118 | - distroseries=supported_series.name, |
119 | + UCTRecord.SeriesPackageStatus( |
120 | + series=supported_series.name, |
121 | status=UCTRecord.PackageStatus.DOES_NOT_EXIST, |
122 | reason="", |
123 | priority=None, |
124 | ), |
125 | - UCTRecord.DistroSeriesPackageStatus( |
126 | - distroseries=current_series.name, |
127 | + UCTRecord.SeriesPackageStatus( |
128 | + series=current_series.name, |
129 | status=UCTRecord.PackageStatus.DOES_NOT_EXIST, |
130 | reason="", |
131 | priority=None, |
132 | ), |
133 | - UCTRecord.DistroSeriesPackageStatus( |
134 | - distroseries="devel", |
135 | + UCTRecord.SeriesPackageStatus( |
136 | + series="devel", |
137 | + status=UCTRecord.PackageStatus.RELEASED, |
138 | + reason="", |
139 | + priority=None, |
140 | + ), |
141 | + UCTRecord.SeriesPackageStatus( |
142 | + series="upstream", |
143 | status=UCTRecord.PackageStatus.RELEASED, |
144 | reason="", |
145 | priority=None, |
146 | @@ -353,6 +371,20 @@ class TextCVE(TestCaseWithFactory): |
147 | status_explanation="", |
148 | ), |
149 | ], |
150 | + upstream_packages=[ |
151 | + CVE.UpstreamPackage( |
152 | + package=product_1, |
153 | + importance=None, |
154 | + status=BugTaskStatus.FIXRELEASED, |
155 | + status_explanation="reason 4", |
156 | + ), |
157 | + CVE.SeriesPackage( |
158 | + package=product_2, |
159 | + importance=None, |
160 | + status=BugTaskStatus.FIXRELEASED, |
161 | + status_explanation="", |
162 | + ), |
163 | + ], |
164 | importance=BugTaskImportance.CRITICAL, |
165 | status=VulnerabilityStatus.ACTIVE, |
166 | assignee=assignee, |
167 | @@ -393,7 +425,7 @@ class TestUCTImporterExporter(TestCaseWithFactory): |
168 | super().setUp(*args, **kwargs) |
169 | celebrities = getUtility(ILaunchpadCelebrities) |
170 | self.ubuntu = celebrities.ubuntu |
171 | - self.esm = self.factory.makeDistribution("esm") |
172 | + self.esm = self.factory.makeDistribution("ubuntu-esm") |
173 | self.bug_importer = celebrities.bug_importer |
174 | self.ubuntu_supported_series = self.factory.makeDistroSeries( |
175 | distribution=self.ubuntu, |
176 | @@ -418,11 +450,13 @@ class TestUCTImporterExporter(TestCaseWithFactory): |
177 | status=SeriesStatus.CURRENT, |
178 | name="trusty", |
179 | ) |
180 | + self.product_1 = self.factory.makeProduct() |
181 | + self.product_2 = self.factory.makeProduct() |
182 | self.ubuntu_package = self.factory.makeDistributionSourcePackage( |
183 | - distribution=self.ubuntu |
184 | + sourcepackagename=self.product_1.name, distribution=self.ubuntu |
185 | ) |
186 | self.esm_package = self.factory.makeDistributionSourcePackage( |
187 | - distribution=self.esm |
188 | + sourcepackagename=self.product_2.name, distribution=self.esm |
189 | ) |
190 | for series in ( |
191 | self.ubuntu_supported_series, |
192 | @@ -446,7 +480,9 @@ class TestUCTImporterExporter(TestCaseWithFactory): |
193 | ) |
194 | |
195 | self.lp_cve = self.factory.makeCVE("2022-23222") |
196 | - self.now = datetime.datetime.now(datetime.timezone.utc) |
197 | + self.now = datetime.datetime.now(datetime.timezone.utc).replace( |
198 | + microsecond=0 |
199 | + ) |
200 | self.cve = CVE( |
201 | sequence="CVE-2022-23222", |
202 | crd=None, |
203 | @@ -509,6 +545,20 @@ class TestUCTImporterExporter(TestCaseWithFactory): |
204 | status_explanation="needs triage", |
205 | ), |
206 | ], |
207 | + upstream_packages=[ |
208 | + CVE.UpstreamPackage( |
209 | + package=self.product_1, |
210 | + importance=BugTaskImportance.HIGH, |
211 | + status=BugTaskStatus.FIXRELEASED, |
212 | + status_explanation="fix released", |
213 | + ), |
214 | + CVE.UpstreamPackage( |
215 | + package=self.product_2, |
216 | + importance=BugTaskImportance.LOW, |
217 | + status=BugTaskStatus.WONTFIX, |
218 | + status_explanation="ignored", |
219 | + ), |
220 | + ], |
221 | importance=BugTaskImportance.MEDIUM, |
222 | status=VulnerabilityStatus.ACTIVE, |
223 | assignee=self.factory.makePerson(), |
224 | @@ -552,7 +602,10 @@ class TestUCTImporterExporter(TestCaseWithFactory): |
225 | bug_tasks = bug.bugtasks # type: List[BugTask] |
226 | |
227 | self.assertEqual( |
228 | - len(cve.distro_packages) + len(cve.series_packages), len(bug_tasks) |
229 | + len(cve.distro_packages) |
230 | + + len(cve.series_packages) |
231 | + + len(cve.upstream_packages), |
232 | + len(bug_tasks), |
233 | ) |
234 | bug_tasks_by_target = {t.target: t for t in bug_tasks} |
235 | |
236 | @@ -563,7 +616,7 @@ class TestUCTImporterExporter(TestCaseWithFactory): |
237 | t = bug_tasks_by_target[distro_package.package] |
238 | package_importance = distro_package.importance or cve.importance |
239 | package_importances[ |
240 | - distro_package.package.sourcepackagename |
241 | + distro_package.package.sourcepackagename.name |
242 | ] = package_importance |
243 | conjoined_primary = t.conjoined_primary |
244 | if conjoined_primary: |
245 | @@ -580,7 +633,7 @@ class TestUCTImporterExporter(TestCaseWithFactory): |
246 | self.assertIn(series_package.package, bug_tasks_by_target) |
247 | t = bug_tasks_by_target[series_package.package] |
248 | package_importance = package_importances[ |
249 | - series_package.package.sourcepackagename |
250 | + series_package.package.sourcepackagename.name |
251 | ] |
252 | sp_importance = series_package.importance or package_importance |
253 | self.assertEqual(sp_importance, t.importance) |
254 | @@ -589,6 +642,19 @@ class TestUCTImporterExporter(TestCaseWithFactory): |
255 | series_package.status_explanation, t.status_explanation |
256 | ) |
257 | |
258 | + for upstream_package in cve.upstream_packages: |
259 | + self.assertIn(upstream_package.package, bug_tasks_by_target) |
260 | + t = bug_tasks_by_target[upstream_package.package] |
261 | + package_importance = package_importances[ |
262 | + upstream_package.package.name |
263 | + ] |
264 | + sp_importance = upstream_package.importance or package_importance |
265 | + self.assertEqual(sp_importance, t.importance) |
266 | + self.assertEqual(upstream_package.status, t.status) |
267 | + self.assertEqual( |
268 | + upstream_package.status_explanation, t.status_explanation |
269 | + ) |
270 | + |
271 | for t in bug_tasks: |
272 | self.assertEqual(cve.assignee, t.assignee) |
273 | |
274 | @@ -625,6 +691,32 @@ class TestUCTImporterExporter(TestCaseWithFactory): |
275 | lp_cve.cvss, |
276 | ) |
277 | |
278 | + def checkCVE(self, expected: CVE, actual: CVE): |
279 | + self.assertEqual(expected.sequence, actual.sequence) |
280 | + self.assertEqual(expected.crd, actual.crd) |
281 | + self.assertEqual(expected.public_date, actual.public_date) |
282 | + self.assertEqual( |
283 | + expected.public_date_at_USN, actual.public_date_at_USN |
284 | + ) |
285 | + self.assertListEqual(expected.distro_packages, actual.distro_packages) |
286 | + self.assertListEqual(expected.series_packages, actual.series_packages) |
287 | + self.assertListEqual( |
288 | + expected.upstream_packages, actual.upstream_packages |
289 | + ) |
290 | + self.assertEqual(expected.importance, actual.importance) |
291 | + self.assertEqual(expected.status, actual.status) |
292 | + self.assertEqual(expected.assignee, actual.assignee) |
293 | + self.assertEqual(expected.discovered_by, actual.discovered_by) |
294 | + self.assertEqual(expected.description, actual.description) |
295 | + self.assertEqual( |
296 | + expected.ubuntu_description, actual.ubuntu_description |
297 | + ) |
298 | + self.assertListEqual(expected.bug_urls, actual.bug_urls) |
299 | + self.assertListEqual(expected.references, actual.references) |
300 | + self.assertEqual(expected.notes, actual.notes) |
301 | + self.assertEqual(expected.mitigation, actual.mitigation) |
302 | + self.assertListEqual(expected.cvss, actual.cvss) |
303 | + |
304 | def test_create_bug(self): |
305 | bug = self.importer.create_bug(self.cve, self.lp_cve) |
306 | |
307 | @@ -891,20 +983,13 @@ class TestUCTImporterExporter(TestCaseWithFactory): |
308 | self.importer.import_cve(self.cve) |
309 | bug = self.importer._find_existing_bug(self.cve, self.lp_cve) |
310 | cve = self.exporter._make_cve_from_bug(bug) |
311 | - self.assertEqual(self.cve.sequence, cve.sequence) |
312 | - self.assertEqual(self.cve.crd, cve.crd) |
313 | - self.assertEqual(self.cve.public_date, cve.public_date) |
314 | - self.assertEqual(self.cve.public_date_at_USN, cve.public_date_at_USN) |
315 | - self.assertListEqual(self.cve.distro_packages, cve.distro_packages) |
316 | - self.assertListEqual(self.cve.series_packages, cve.series_packages) |
317 | - self.assertEqual(self.cve.importance, cve.importance) |
318 | - self.assertEqual(self.cve.status, cve.status) |
319 | - self.assertEqual(self.cve.assignee, cve.assignee) |
320 | - self.assertEqual(self.cve.discovered_by, cve.discovered_by) |
321 | - self.assertEqual(self.cve.description, cve.description) |
322 | - self.assertEqual(self.cve.ubuntu_description, cve.ubuntu_description) |
323 | - self.assertListEqual(self.cve.bug_urls, cve.bug_urls) |
324 | - self.assertListEqual(self.cve.references, cve.references) |
325 | - self.assertEqual(self.cve.notes, cve.notes) |
326 | - self.assertEqual(self.cve.mitigation, cve.mitigation) |
327 | - self.assertListEqual(self.cve.cvss, cve.cvss) |
328 | + self.checkCVE(self.cve, cve) |
329 | + |
330 | + def test_export_bug_to_uct_file(self): |
331 | + self.importer.import_cve(self.cve) |
332 | + bug = self.importer._find_existing_bug(self.cve, self.lp_cve) |
333 | + output_dir = Path(self.makeTemporaryDirectory()) |
334 | + cve_path = self.exporter.export_bug_to_uct_file(bug.id, output_dir) |
335 | + uct_record = UCTRecord.load(cve_path) |
336 | + cve = CVE.make_from_uct_record(uct_record) |
337 | + self.checkCVE(self.cve, cve) |
338 | diff --git a/lib/lp/bugs/scripts/uct/models.py b/lib/lp/bugs/scripts/uct/models.py |
339 | index 6c87f86..440f79d 100644 |
340 | --- a/lib/lp/bugs/scripts/uct/models.py |
341 | +++ b/lib/lp/bugs/scripts/uct/models.py |
342 | @@ -2,7 +2,7 @@ |
343 | # GNU Affero General Public License version 3 (see the file LICENSE). |
344 | |
345 | import logging |
346 | -from collections import defaultdict |
347 | +from collections import OrderedDict, defaultdict |
348 | from datetime import datetime |
349 | from enum import Enum |
350 | from pathlib import Path |
351 | @@ -13,11 +13,12 @@ import dateutil.parser |
352 | from contrib.cve_lib import load_cve |
353 | from zope.component import getUtility |
354 | |
355 | -from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
356 | from lp.bugs.enums import VulnerabilityStatus |
357 | from lp.bugs.interfaces.bugtask import BugTaskImportance, BugTaskStatus |
358 | +from lp.registry.interfaces.distribution import IDistributionSet |
359 | from lp.registry.interfaces.distroseries import IDistroSeriesSet |
360 | from lp.registry.interfaces.person import IPersonSet |
361 | +from lp.registry.interfaces.product import IProductSet |
362 | from lp.registry.interfaces.series import SeriesStatus |
363 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
364 | from lp.registry.model.distribution import Distribution |
365 | @@ -26,6 +27,7 @@ from lp.registry.model.distributionsourcepackage import ( |
366 | ) |
367 | from lp.registry.model.distroseries import DistroSeries |
368 | from lp.registry.model.person import Person |
369 | +from lp.registry.model.product import Product |
370 | from lp.registry.model.sourcepackage import SourcePackage |
371 | from lp.registry.model.sourcepackagename import SourcePackageName |
372 | from lp.services.propertycache import cachedproperty |
373 | @@ -73,10 +75,10 @@ class UCTRecord: |
374 | NEEDED = "needed" |
375 | PENDING = "pending" |
376 | |
377 | - DistroSeriesPackageStatus = NamedTuple( |
378 | - "DistroSeriesPackageStatus", |
379 | + SeriesPackageStatus = NamedTuple( |
380 | + "SeriesPackageStatus", |
381 | ( |
382 | - ("distroseries", str), |
383 | + ("series", str), |
384 | ("status", PackageStatus), |
385 | ("reason", str), |
386 | ("priority", Optional[Priority]), |
387 | @@ -95,7 +97,7 @@ class UCTRecord: |
388 | "Package", |
389 | ( |
390 | ("name", str), |
391 | - ("statuses", List[DistroSeriesPackageStatus]), |
392 | + ("statuses", List[SeriesPackageStatus]), |
393 | ("priority", Optional[Priority]), |
394 | ("tags", Set[str]), |
395 | ("patches", List[Patch]), |
396 | @@ -167,23 +169,23 @@ class UCTRecord: |
397 | cve_data, "pkgs" |
398 | ).items(): |
399 | statuses = [] |
400 | - for distroseries, (status, reason) in statuses_dict.items(): |
401 | - distroseries_priority = cls._pop_cve_property( |
402 | + for series, (status, reason) in statuses_dict.items(): |
403 | + series_priority = cls._pop_cve_property( |
404 | cve_data, |
405 | - "Priority_{package}_{distroseries}".format( |
406 | + "Priority_{package}_{series}".format( |
407 | package=package, |
408 | - distroseries=distroseries, |
409 | + series=series, |
410 | ), |
411 | required=False, |
412 | ) |
413 | statuses.append( |
414 | - cls.DistroSeriesPackageStatus( |
415 | - distroseries=distroseries, |
416 | + cls.SeriesPackageStatus( |
417 | + series=series, |
418 | status=cls.PackageStatus(status), |
419 | reason=reason, |
420 | priority=( |
421 | - cls.Priority(distroseries_priority) |
422 | - if distroseries_priority |
423 | + cls.Priority(series_priority) |
424 | + if series_priority |
425 | else None |
426 | ), |
427 | ) |
428 | @@ -334,7 +336,7 @@ class UCTRecord: |
429 | ) |
430 | for status in package.statuses: |
431 | self._write_field( |
432 | - "{}_{}".format(status.distroseries, package.name), |
433 | + "{}_{}".format(status.series, package.name), |
434 | ( |
435 | "{} ({})".format(status.status.value, status.reason) |
436 | if status.reason |
437 | @@ -351,9 +353,7 @@ class UCTRecord: |
438 | for status in package.statuses: |
439 | if status.priority: |
440 | self._write_field( |
441 | - "Priority_{}_{}".format( |
442 | - package.name, status.distroseries |
443 | - ), |
444 | + "Priority_{}_{}".format(package.name, status.series), |
445 | status.priority.value, |
446 | output, |
447 | ) |
448 | @@ -436,6 +436,16 @@ class CVE: |
449 | ), |
450 | ) |
451 | |
452 | + UpstreamPackage = NamedTuple( |
453 | + "UpstreamPackage", |
454 | + ( |
455 | + ("package", Product), |
456 | + ("importance", Optional[BugTaskImportance]), |
457 | + ("status", BugTaskStatus), |
458 | + ("status_explanation", str), |
459 | + ), |
460 | + ) |
461 | + |
462 | PRIORITY_MAP = { |
463 | UCTRecord.Priority.CRITICAL: BugTaskImportance.CRITICAL, |
464 | UCTRecord.Priority.HIGH: BugTaskImportance.HIGH, |
465 | @@ -478,6 +488,7 @@ class CVE: |
466 | public_date_at_USN: Optional[datetime], |
467 | distro_packages: List[DistroPackage], |
468 | series_packages: List[SeriesPackage], |
469 | + upstream_packages: List[UpstreamPackage], |
470 | importance: BugTaskImportance, |
471 | status: VulnerabilityStatus, |
472 | assignee: Optional[Person], |
473 | @@ -496,6 +507,7 @@ class CVE: |
474 | self.public_date_at_USN = public_date_at_USN |
475 | self.distro_packages = distro_packages |
476 | self.series_packages = series_packages |
477 | + self.upstream_packages = upstream_packages |
478 | self.importance = importance |
479 | self.status = status |
480 | self.assignee = assignee |
481 | @@ -518,6 +530,7 @@ class CVE: |
482 | |
483 | distro_packages = [] |
484 | series_packages = [] |
485 | + upstream_packages = [] |
486 | |
487 | spn_set = getUtility(ISourcePackageNameSet) |
488 | |
489 | @@ -530,11 +543,6 @@ class CVE: |
490 | ) |
491 | |
492 | for uct_package_status in uct_package.statuses: |
493 | - distro_series = cls.get_distro_series( |
494 | - uct_package_status.distroseries |
495 | - ) |
496 | - if distro_series is None: |
497 | - continue |
498 | |
499 | if uct_package_status.status not in cls.BUG_TASK_STATUS_MAP: |
500 | logger.warning( |
501 | @@ -543,6 +551,34 @@ class CVE: |
502 | ) |
503 | continue |
504 | |
505 | + series_package_importance = ( |
506 | + cls.PRIORITY_MAP[uct_package_status.priority] |
507 | + if uct_package_status.priority |
508 | + else None |
509 | + ) |
510 | + |
511 | + if uct_package_status.series == "upstream": |
512 | + product = cls.get_product(uct_package.name) |
513 | + if product is None: |
514 | + continue |
515 | + upstream_packages.append( |
516 | + cls.UpstreamPackage( |
517 | + package=product, |
518 | + importance=series_package_importance, |
519 | + status=cls.BUG_TASK_STATUS_MAP[ |
520 | + uct_package_status.status |
521 | + ], |
522 | + status_explanation=uct_package_status.reason, |
523 | + ) |
524 | + ) |
525 | + continue |
526 | + |
527 | + distro_series = cls.get_distro_series( |
528 | + uct_package_status.series |
529 | + ) |
530 | + if distro_series is None: |
531 | + continue |
532 | + |
533 | distro_package = cls.DistroPackage( |
534 | package=DistributionSourcePackage( |
535 | distribution=distro_series.distribution, |
536 | @@ -553,12 +589,6 @@ class CVE: |
537 | if distro_package not in distro_packages: |
538 | distro_packages.append(distro_package) |
539 | |
540 | - series_package_importance = ( |
541 | - cls.PRIORITY_MAP[uct_package_status.priority] |
542 | - if uct_package_status.priority |
543 | - else None |
544 | - ) |
545 | - |
546 | series_packages.append( |
547 | cls.SeriesPackage( |
548 | package=SourcePackage( |
549 | @@ -589,6 +619,7 @@ class CVE: |
550 | public_date_at_USN=uct_record.public_date_at_USN, |
551 | distro_packages=distro_packages, |
552 | series_packages=series_packages, |
553 | + upstream_packages=upstream_packages, |
554 | importance=cls.PRIORITY_MAP[uct_record.priority], |
555 | status=cls.infer_vulnerability_status(uct_record), |
556 | assignee=assignee, |
557 | @@ -616,23 +647,28 @@ class CVE: |
558 | series_package.package.sourcepackagename |
559 | ].append(series_package) |
560 | |
561 | - packages = [] # type: List[UCTRecord.Package] |
562 | + packages_by_name = OrderedDict() # type: Dict[str, UCTRecord.Package] |
563 | processed_packages = set() # type: Set[SourcePackageName] |
564 | for distro_package in self.distro_packages: |
565 | spn = distro_package.package.sourcepackagename |
566 | if spn in processed_packages: |
567 | continue |
568 | processed_packages.add(spn) |
569 | - statuses = [] # type: List[UCTRecord.DistroSeriesPackageStatus] |
570 | + statuses = [] # type: List[UCTRecord.SeriesPackageStatus] |
571 | for series_package in series_packages_by_name[spn]: |
572 | series = series_package.package.distroseries |
573 | if series.status == SeriesStatus.DEVELOPMENT: |
574 | series_name = "devel" |
575 | else: |
576 | series_name = series.name |
577 | + distro_name = distro_package.package.distribution.name |
578 | + if distro_name != "ubuntu": |
579 | + if distro_name == "ubuntu-esm": |
580 | + distro_name = "esm" |
581 | + series_name = "{}/{}".format(series_name, distro_name) |
582 | statuses.append( |
583 | - UCTRecord.DistroSeriesPackageStatus( |
584 | - distroseries=series_name, |
585 | + UCTRecord.SeriesPackageStatus( |
586 | + series=series_name, |
587 | status=self.BUG_TASK_STATUS_MAP_REVERSE[ |
588 | series_package.status |
589 | ], |
590 | @@ -647,19 +683,43 @@ class CVE: |
591 | ) |
592 | ) |
593 | |
594 | - packages.append( |
595 | - UCTRecord.Package( |
596 | - name=spn.name, |
597 | - statuses=statuses, |
598 | - priority=( |
599 | - self.PRIORITY_MAP_REVERSE[distro_package.importance] |
600 | - if distro_package.importance |
601 | - else None |
602 | - ), |
603 | + packages_by_name[spn.name] = UCTRecord.Package( |
604 | + name=spn.name, |
605 | + statuses=statuses, |
606 | + priority=( |
607 | + self.PRIORITY_MAP_REVERSE[distro_package.importance] |
608 | + if distro_package.importance |
609 | + else None |
610 | + ), |
611 | + tags=set(), |
612 | + patches=[], |
613 | + ) |
614 | + |
615 | + for upstream_package in self.upstream_packages: |
616 | + status = UCTRecord.SeriesPackageStatus( |
617 | + series="upstream", |
618 | + status=self.BUG_TASK_STATUS_MAP_REVERSE[ |
619 | + upstream_package.status |
620 | + ], |
621 | + reason=upstream_package.status_explanation, |
622 | + priority=( |
623 | + self.PRIORITY_MAP_REVERSE[upstream_package.importance] |
624 | + if upstream_package.importance |
625 | + else None |
626 | + ), |
627 | + ) |
628 | + package_name = upstream_package.package.name |
629 | + if package_name in packages_by_name: |
630 | + packages_by_name[package_name].statuses.append(status) |
631 | + else: |
632 | + packages_by_name[package_name] = UCTRecord.Package( |
633 | + name=package_name, |
634 | + statuses=[status], |
635 | + priority=None, |
636 | tags=set(), |
637 | patches=[], |
638 | ) |
639 | - ) |
640 | + |
641 | return UCTRecord( |
642 | parent_dir=self.VULNERABILITY_STATUS_MAP_REVERSE.get( |
643 | self.status, "" |
644 | @@ -678,7 +738,7 @@ class CVE: |
645 | priority=self.PRIORITY_MAP_REVERSE[self.importance], |
646 | references=self.references, |
647 | ubuntu_description=self.ubuntu_description, |
648 | - packages=packages, |
649 | + packages=list(packages_by_name.values()), |
650 | ) |
651 | |
652 | @property |
653 | @@ -722,20 +782,29 @@ class CVE: |
654 | if "/" in distro_series_name: |
655 | series_name, distro_name = distro_series_name.split("/", 1) |
656 | if distro_name == "esm": |
657 | - # TODO: ESM needs special handling |
658 | - pass |
659 | - return |
660 | + distro_name = "ubuntu-esm" |
661 | else: |
662 | + distro_name = "ubuntu" |
663 | series_name = distro_series_name |
664 | - distribution = getUtility(ILaunchpadCelebrities).ubuntu |
665 | - if series_name == "devel": |
666 | - distro_series = cls.get_devel_series(distribution) |
667 | - else: |
668 | - distro_series = getUtility(IDistroSeriesSet).queryByName( |
669 | - distribution, series_name |
670 | - ) |
671 | + distribution = getUtility(IDistributionSet).getByName(distro_name) |
672 | + if distribution is None: |
673 | + logger.warning("Could not find the distribution: %s", distro_name) |
674 | + return |
675 | + if series_name == "devel": |
676 | + distro_series = cls.get_devel_series(distribution) |
677 | + else: |
678 | + distro_series = getUtility(IDistroSeriesSet).queryByName( |
679 | + distribution, series_name |
680 | + ) |
681 | if not distro_series: |
682 | logger.warning( |
683 | "Could not find the distro series: %s", distro_series_name |
684 | ) |
685 | return distro_series |
686 | + |
687 | + @classmethod |
688 | + def get_product(cls, product_name: str) -> Optional[Product]: |
689 | + product = getUtility(IProductSet).getByName(product_name) |
690 | + if not product: |
691 | + logger.warning("Could not find the product: %s", product_name) |
692 | + return product |
693 | diff --git a/lib/lp/bugs/scripts/uct/uctexport.py b/lib/lp/bugs/scripts/uct/uctexport.py |
694 | index 17dcece..65e274c 100644 |
695 | --- a/lib/lp/bugs/scripts/uct/uctexport.py |
696 | +++ b/lib/lp/bugs/scripts/uct/uctexport.py |
697 | @@ -18,6 +18,7 @@ from lp.bugs.scripts.uct.models import CVE, CVSS |
698 | from lp.registry.model.distributionsourcepackage import ( |
699 | DistributionSourcePackage, |
700 | ) |
701 | +from lp.registry.model.product import Product |
702 | from lp.registry.model.sourcepackage import SourcePackage |
703 | |
704 | __all__ = [ |
705 | @@ -151,6 +152,26 @@ class UCTExporter: |
706 | ) |
707 | ) |
708 | |
709 | + upstream_packages = [] |
710 | + for bug_task in bug_tasks: |
711 | + target = removeSecurityProxy(bug_task.target) |
712 | + if not isinstance(target, Product): |
713 | + continue |
714 | + up_importance = bug_task.importance |
715 | + package_importance = package_importances.get(target.name) |
716 | + upstream_packages.append( |
717 | + CVE.UpstreamPackage( |
718 | + package=target, |
719 | + importance=( |
720 | + up_importance |
721 | + if up_importance != package_importance |
722 | + else None |
723 | + ), |
724 | + status=bug_task.status, |
725 | + status_explanation=bug_task.status_explanation, |
726 | + ) |
727 | + ) |
728 | + |
729 | return CVE( |
730 | sequence="CVE-{}".format(lp_cve.sequence), |
731 | crd=None, # TODO: fix this |
732 | @@ -158,6 +179,7 @@ class UCTExporter: |
733 | public_date_at_USN=None, # TODO: fix this |
734 | distro_packages=distro_packages, |
735 | series_packages=series_packages, |
736 | + upstream_packages=upstream_packages, |
737 | importance=cve_importance, |
738 | status=vulnerability.status, |
739 | assignee=bug_tasks[0].assignee, |
740 | diff --git a/lib/lp/bugs/scripts/uct/uctimport.py b/lib/lp/bugs/scripts/uct/uctimport.py |
741 | index 3bc3512..5f94703 100644 |
742 | --- a/lib/lp/bugs/scripts/uct/uctimport.py |
743 | +++ b/lib/lp/bugs/scripts/uct/uctimport.py |
744 | @@ -14,12 +14,21 @@ For each entry in UCT we: |
745 | 3. Create a Bug Task for each distribution/series package in the CVE entry |
746 | 4. Update the statuses of Bug Tasks based on the information in the CVE entry |
747 | 5. Update the information the related Launchpad's `Cve` model, if necessary |
748 | + |
749 | +Three types of bug tags are created: |
750 | + |
751 | +1. Bug tasks with a distribution package as a target - they represent |
752 | + importance of the package |
753 | +2. Bug tasks with distribution series packages as a target - they represent |
754 | + importance and status of the package in a particular series |
755 | +3. Bug tasks with a product as a target - they represent importance and |
756 | + status of the package in upstream. |
757 | """ |
758 | import logging |
759 | from datetime import timezone |
760 | from itertools import chain |
761 | from pathlib import Path |
762 | -from typing import List, Optional |
763 | +from typing import Dict, List, Optional |
764 | |
765 | import transaction |
766 | from zope.component import getUtility |
767 | @@ -156,10 +165,17 @@ class UCTImporter: |
768 | self._update_external_bug_urls(bug, cve.bug_urls) |
769 | |
770 | self._create_bug_tasks( |
771 | - bug, cve.distro_packages[1:], cve.series_packages |
772 | + bug, |
773 | + cve.distro_packages[1:], |
774 | + cve.series_packages, |
775 | + cve.upstream_packages, |
776 | ) |
777 | self._update_statuses_and_importances( |
778 | - bug, cve.importance, cve.distro_packages, cve.series_packages |
779 | + bug, |
780 | + cve.importance, |
781 | + cve.distro_packages, |
782 | + cve.series_packages, |
783 | + cve.upstream_packages, |
784 | ) |
785 | self._assign_bug_tasks(bug, cve.assignee) |
786 | |
787 | @@ -188,9 +204,18 @@ class UCTImporter: |
788 | """ |
789 | bug.description = self._make_bug_description(cve) |
790 | |
791 | - self._create_bug_tasks(bug, cve.distro_packages, cve.series_packages) |
792 | + self._create_bug_tasks( |
793 | + bug, |
794 | + cve.distro_packages, |
795 | + cve.series_packages, |
796 | + cve.upstream_packages, |
797 | + ) |
798 | self._update_statuses_and_importances( |
799 | - bug, cve.importance, cve.distro_packages, cve.series_packages |
800 | + bug, |
801 | + cve.importance, |
802 | + cve.distro_packages, |
803 | + cve.series_packages, |
804 | + cve.upstream_packages, |
805 | ) |
806 | self._assign_bug_tasks(bug, cve.assignee) |
807 | self._update_external_bug_urls(bug, cve.bug_urls) |
808 | @@ -228,6 +253,7 @@ class UCTImporter: |
809 | bug: BugModel, |
810 | distro_packages: List[CVE.DistroPackage], |
811 | series_packages: List[CVE.SeriesPackage], |
812 | + upstream_packages: List[CVE.UpstreamPackage], |
813 | ) -> None: |
814 | """ |
815 | Add bug tasks to the given `Bug` model based on the information |
816 | @@ -246,7 +272,8 @@ class UCTImporter: |
817 | bug_task_by_target = {t.target: t for t in bug_tasks} |
818 | bug_task_set = getUtility(IBugTaskSet) |
819 | for target in ( |
820 | - p.package for p in chain(distro_packages, series_packages) |
821 | + p.package |
822 | + for p in chain(distro_packages, series_packages, upstream_packages) |
823 | ): |
824 | if target not in bug_task_by_target: |
825 | bug_task_set.createTask(bug, self.bug_importer, target) |
826 | @@ -331,6 +358,7 @@ class UCTImporter: |
827 | cve_importance: BugTaskImportance, |
828 | distro_packages: List[CVE.DistroPackage], |
829 | series_packages: List[CVE.SeriesPackage], |
830 | + upstream_packages: List[CVE.UpstreamPackage], |
831 | ) -> None: |
832 | """ |
833 | Update statuses and importances of bug tasks according to the |
834 | @@ -350,19 +378,25 @@ class UCTImporter: |
835 | bug_tasks = bug.bugtasks # type: List[BugTask] |
836 | bug_task_by_target = {t.target: t for t in bug_tasks} |
837 | |
838 | - package_importances = {} |
839 | + package_importances = {} # type: Dict[str, BugTaskImportance] |
840 | |
841 | for dp in distro_packages: |
842 | task = bug_task_by_target[dp.package] |
843 | dp_importance = dp.importance or cve_importance |
844 | - package_importances[dp.package.sourcepackagename] = dp_importance |
845 | + package_importances[ |
846 | + dp.package.sourcepackagename.name |
847 | + ] = dp_importance |
848 | task.transitionToImportance(dp_importance) |
849 | |
850 | - for sp in series_packages: |
851 | + for sp in chain(series_packages, upstream_packages): |
852 | task = bug_task_by_target[sp.package] |
853 | - package_importance = package_importances[ |
854 | - sp.package.sourcepackagename |
855 | - ] |
856 | + if isinstance(sp, CVE.SeriesPackage): |
857 | + package_name = sp.package.sourcepackagename.name |
858 | + elif isinstance(sp, CVE.UpstreamPackage): |
859 | + package_name = sp.package.name |
860 | + else: |
861 | + raise AssertionError() |
862 | + package_importance = package_importances[package_name] |
863 | sp_importance = sp.importance or package_importance |
864 | task.transitionToImportance(sp_importance) |
865 | task.transitionToStatus(sp.status) |
866 | diff --git a/lib/lp/registry/model/distributionsourcepackage.py b/lib/lp/registry/model/distributionsourcepackage.py |
867 | index 6d31eab..fd9da20 100644 |
868 | --- a/lib/lp/registry/model/distributionsourcepackage.py |
869 | +++ b/lib/lp/registry/model/distributionsourcepackage.py |
870 | @@ -141,6 +141,9 @@ class DistributionSourcePackage( |
871 | self.distribution = distribution |
872 | self.sourcepackagename = sourcepackagename |
873 | |
874 | + def __repr__(self): |
875 | + return "<{} '{}'>".format(self.__class__.__name__, self.display_name) |
876 | + |
877 | @property |
878 | def name(self): |
879 | """See `IDistributionSourcePackage`.""" |
I'm OK with starting with this, but at some point soon we're going to need to change the package → product mapping to use the `Packaging` table rather than assuming that the source package name is equal to the product name, since that's often not the case. As discussed on Mattermost, this may require tightening up permissions on `Packaging` edits first, which to be fair we've wanted an excuse to do for a long time.