launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #29070
[Merge] ~andrey-fedoseev/launchpad:uct-upstream into launchpad:master
Andrey Fedoseev has proposed merging ~andrey-fedoseev/launchpad:uct-upstream into launchpad:master with ~andrey-fedoseev/launchpad:uct-export as a prerequisite.
Commit message:
UCT import/export: handle upstream package status and ESM packages
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~andrey-fedoseev/launchpad/+git/launchpad/+merge/428893
This also includes the changes related to UCT export that were added in https://code.launchpad.net/~andrey-fedoseev/launchpad/+git/launchpad/+merge/428152
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~andrey-fedoseev/launchpad:uct-upstream into launchpad:master.
diff --git a/lib/lp/bugs/interfaces/bugtask.py b/lib/lp/bugs/interfaces/bugtask.py
index a8c4e45..8fb2ce2 100644
--- a/lib/lp/bugs/interfaces/bugtask.py
+++ b/lib/lp/bugs/interfaces/bugtask.py
@@ -846,7 +846,7 @@ class IBugTask(IHasBug, IBugTaskDelete):
@call_with(user=REQUEST_USER)
@export_write_operation()
@operation_for_version("beta")
- def transitionToMilestone(new_milestone, user=None):
+ def transitionToMilestone(new_milestone, user):
"""Set the BugTask milestone.
Set the bugtask milestone, making sure that the user is
@@ -859,7 +859,7 @@ class IBugTask(IHasBug, IBugTaskDelete):
@call_with(user=REQUEST_USER)
@export_write_operation()
@operation_for_version("beta")
- def transitionToImportance(new_importance, user=None):
+ def transitionToImportance(new_importance, user):
"""Set the BugTask importance.
Set the bugtask importance, making sure that the user is
@@ -883,7 +883,7 @@ class IBugTask(IHasBug, IBugTaskDelete):
@call_with(user=REQUEST_USER)
@export_write_operation()
@operation_for_version("beta")
- def transitionToStatus(new_status, user=None):
+ def transitionToStatus(new_status, user):
"""Perform a workflow transition to the new_status.
:new_status: new status from `BugTaskStatus`
diff --git a/lib/lp/bugs/model/bugtask.py b/lib/lp/bugs/model/bugtask.py
index 33d3647..90f4e36 100644
--- a/lib/lp/bugs/model/bugtask.py
+++ b/lib/lp/bugs/model/bugtask.py
@@ -933,9 +933,9 @@ class BugTask(StormBase):
# setter methods directly.
setattr(self, synched_attr, PassthroughValue(replica_attr_value))
- def transitionToMilestone(self, new_milestone, user=None):
+ def transitionToMilestone(self, new_milestone, user):
"""See `IBugTask`."""
- if user and not self.userHasBugSupervisorPrivileges(user):
+ if not self.userHasBugSupervisorPrivileges(user):
raise UserCannotEditBugTaskMilestone(
"User does not have sufficient permissions "
"to edit the bug task milestone."
@@ -945,9 +945,9 @@ class BugTask(StormBase):
# notified.
self.bug.clearBugNotificationRecipientsCache()
- def transitionToImportance(self, new_importance, user=None):
+ def transitionToImportance(self, new_importance, user):
"""See `IBugTask`."""
- if user and not self.userHasBugSupervisorPrivileges(user):
+ if not self.userHasBugSupervisorPrivileges(user):
raise UserCannotEditBugTaskImportance(
"User does not have sufficient permissions "
"to edit the bug task importance."
@@ -1030,9 +1030,9 @@ class BugTask(StormBase):
# Non-supervisors can transition to non-supervisor statuses.
return new_status not in BUG_SUPERVISOR_BUGTASK_STATUSES
- def transitionToStatus(self, new_status, user=None, when=None):
+ def transitionToStatus(self, new_status, user, when=None):
"""See `IBugTask`."""
- if not new_status:
+ if not new_status or user is None:
# This is mainly to facilitate tests which, unlike the
# normal status form, don't always submit a status when
# testing the edit form.
@@ -1040,7 +1040,7 @@ class BugTask(StormBase):
new_status = normalize_bugtask_status(new_status)
- if user and not self.canTransitionToStatus(new_status, user):
+ if not self.canTransitionToStatus(new_status, user):
raise UserCannotEditBugTaskStatus(
"Only Bug Supervisors may change status to %s."
% (new_status.title,)
diff --git a/lib/lp/bugs/scripts/bugimport.py b/lib/lp/bugs/scripts/bugimport.py
index 323c9b1..6694d86 100644
--- a/lib/lp/bugs/scripts/bugimport.py
+++ b/lib/lp/bugs/scripts/bugimport.py
@@ -356,10 +356,14 @@ class BugImporter:
# set up bug task
bugtask.datecreated = datecreated
bugtask.transitionToImportance(
- get_enum_value(BugTaskImportance, get_value(bugnode, "importance"))
+ get_enum_value(
+ BugTaskImportance, get_value(bugnode, "importance")
+ ),
+ self.bug_importer,
)
bugtask.transitionToStatus(
- get_enum_value(BugTaskStatus, get_value(bugnode, "status"))
+ get_enum_value(BugTaskStatus, get_value(bugnode, "status")),
+ self.bug_importer,
)
bugtask.transitionToAssignee(
self.getPerson(get_element(bugnode, "assignee"))
diff --git a/lib/lp/bugs/scripts/tests/test_uct.py b/lib/lp/bugs/scripts/tests/test_uct.py
index 47ff535..00f995d 100644
--- a/lib/lp/bugs/scripts/tests/test_uct.py
+++ b/lib/lp/bugs/scripts/tests/test_uct.py
@@ -91,20 +91,20 @@ class TestUCTRecord(TestCase):
UCTRecord.Package(
name="linux",
statuses=[
- UCTRecord.DistroSeriesPackageStatus(
- distroseries="upstream",
+ UCTRecord.SeriesPackageStatus(
+ series="upstream",
status=UCTRecord.PackageStatus.RELEASED,
reason="5.17~rc1",
priority=None,
),
- UCTRecord.DistroSeriesPackageStatus(
- distroseries="impish",
+ UCTRecord.SeriesPackageStatus(
+ series="impish",
status=UCTRecord.PackageStatus.RELEASED,
reason="5.13.0-37.42",
priority=UCTRecord.Priority.MEDIUM,
),
- UCTRecord.DistroSeriesPackageStatus(
- distroseries="devel",
+ UCTRecord.SeriesPackageStatus(
+ series="devel",
status=UCTRecord.PackageStatus.NOT_AFFECTED,
reason="5.15.0-25.25",
priority=UCTRecord.Priority.MEDIUM,
@@ -126,20 +126,20 @@ class TestUCTRecord(TestCase):
UCTRecord.Package(
name="linux-hwe",
statuses=[
- UCTRecord.DistroSeriesPackageStatus(
- distroseries="upstream",
+ UCTRecord.SeriesPackageStatus(
+ series="upstream",
status=UCTRecord.PackageStatus.RELEASED,
reason="5.17~rc1",
priority=None,
),
- UCTRecord.DistroSeriesPackageStatus(
- distroseries="impish",
+ UCTRecord.SeriesPackageStatus(
+ series="impish",
status=UCTRecord.PackageStatus.DOES_NOT_EXIST,
reason="",
priority=None,
),
- UCTRecord.DistroSeriesPackageStatus(
- distroseries="devel",
+ UCTRecord.SeriesPackageStatus(
+ series="devel",
status=UCTRecord.PackageStatus.DOES_NOT_EXIST,
reason="",
priority=None,
@@ -186,8 +186,14 @@ class TextCVE(TestCaseWithFactory):
status=SeriesStatus.DEVELOPMENT,
name="kinetic",
)
- dsp1 = self.factory.makeDistributionSourcePackage(distribution=ubuntu)
- dsp2 = self.factory.makeDistributionSourcePackage(distribution=ubuntu)
+ product_1 = self.factory.makeProduct()
+ product_2 = self.factory.makeProduct()
+ dsp1 = self.factory.makeDistributionSourcePackage(
+ sourcepackagename=product_1.name, distribution=ubuntu
+ )
+ dsp2 = self.factory.makeDistributionSourcePackage(
+ sourcepackagename=product_2.name, distribution=ubuntu
+ )
assignee = self.factory.makePerson()
self.uct_record = UCTRecord(
@@ -224,24 +230,30 @@ class TextCVE(TestCaseWithFactory):
UCTRecord.Package(
name=dsp1.sourcepackagename.name,
statuses=[
- UCTRecord.DistroSeriesPackageStatus(
- distroseries=supported_series.name,
+ UCTRecord.SeriesPackageStatus(
+ series=supported_series.name,
status=UCTRecord.PackageStatus.NOT_AFFECTED,
reason="reason 1",
priority=UCTRecord.Priority.MEDIUM,
),
- UCTRecord.DistroSeriesPackageStatus(
- distroseries=current_series.name,
+ UCTRecord.SeriesPackageStatus(
+ series=current_series.name,
status=UCTRecord.PackageStatus.RELEASED,
reason="reason 2",
priority=UCTRecord.Priority.MEDIUM,
),
- UCTRecord.DistroSeriesPackageStatus(
- distroseries="devel",
+ UCTRecord.SeriesPackageStatus(
+ series="devel",
status=UCTRecord.PackageStatus.RELEASED,
reason="reason 3",
priority=None,
),
+ UCTRecord.SeriesPackageStatus(
+ series="upstream",
+ status=UCTRecord.PackageStatus.RELEASED,
+ reason="reason 4",
+ priority=None,
+ ),
],
priority=None,
tags=set(),
@@ -250,20 +262,26 @@ class TextCVE(TestCaseWithFactory):
UCTRecord.Package(
name=dsp2.sourcepackagename.name,
statuses=[
- UCTRecord.DistroSeriesPackageStatus(
- distroseries=supported_series.name,
+ UCTRecord.SeriesPackageStatus(
+ series=supported_series.name,
status=UCTRecord.PackageStatus.DOES_NOT_EXIST,
reason="",
priority=None,
),
- UCTRecord.DistroSeriesPackageStatus(
- distroseries=current_series.name,
+ UCTRecord.SeriesPackageStatus(
+ series=current_series.name,
status=UCTRecord.PackageStatus.DOES_NOT_EXIST,
reason="",
priority=None,
),
- UCTRecord.DistroSeriesPackageStatus(
- distroseries="devel",
+ UCTRecord.SeriesPackageStatus(
+ series="devel",
+ status=UCTRecord.PackageStatus.RELEASED,
+ reason="",
+ priority=None,
+ ),
+ UCTRecord.SeriesPackageStatus(
+ series="upstream",
status=UCTRecord.PackageStatus.RELEASED,
reason="",
priority=None,
@@ -353,6 +371,20 @@ class TextCVE(TestCaseWithFactory):
status_explanation="",
),
],
+ upstream_packages=[
+ CVE.UpstreamPackage(
+ package=product_1,
+ importance=None,
+ status=BugTaskStatus.FIXRELEASED,
+ status_explanation="reason 4",
+ ),
+ CVE.SeriesPackage(
+ package=product_2,
+ importance=None,
+ status=BugTaskStatus.FIXRELEASED,
+ status_explanation="",
+ ),
+ ],
importance=BugTaskImportance.CRITICAL,
status=VulnerabilityStatus.ACTIVE,
assignee=assignee,
@@ -393,7 +425,7 @@ class TestUCTImporterExporter(TestCaseWithFactory):
super().setUp(*args, **kwargs)
celebrities = getUtility(ILaunchpadCelebrities)
self.ubuntu = celebrities.ubuntu
- self.esm = self.factory.makeDistribution("esm")
+ self.esm = self.factory.makeDistribution("ubuntu-esm")
self.bug_importer = celebrities.bug_importer
self.ubuntu_supported_series = self.factory.makeDistroSeries(
distribution=self.ubuntu,
@@ -418,11 +450,13 @@ class TestUCTImporterExporter(TestCaseWithFactory):
status=SeriesStatus.CURRENT,
name="trusty",
)
+ self.product_1 = self.factory.makeProduct()
+ self.product_2 = self.factory.makeProduct()
self.ubuntu_package = self.factory.makeDistributionSourcePackage(
- distribution=self.ubuntu
+ sourcepackagename=self.product_1.name, distribution=self.ubuntu
)
self.esm_package = self.factory.makeDistributionSourcePackage(
- distribution=self.esm
+ sourcepackagename=self.product_2.name, distribution=self.esm
)
for series in (
self.ubuntu_supported_series,
@@ -446,7 +480,9 @@ class TestUCTImporterExporter(TestCaseWithFactory):
)
self.lp_cve = self.factory.makeCVE("2022-23222")
- self.now = datetime.datetime.now(datetime.timezone.utc)
+ self.now = datetime.datetime.now(datetime.timezone.utc).replace(
+ microsecond=0
+ )
self.cve = CVE(
sequence="CVE-2022-23222",
crd=None,
@@ -509,6 +545,20 @@ class TestUCTImporterExporter(TestCaseWithFactory):
status_explanation="needs triage",
),
],
+ upstream_packages=[
+ CVE.UpstreamPackage(
+ package=self.product_1,
+ importance=BugTaskImportance.HIGH,
+ status=BugTaskStatus.FIXRELEASED,
+ status_explanation="fix released",
+ ),
+ CVE.UpstreamPackage(
+ package=self.product_2,
+ importance=BugTaskImportance.LOW,
+ status=BugTaskStatus.WONTFIX,
+ status_explanation="ignored",
+ ),
+ ],
importance=BugTaskImportance.MEDIUM,
status=VulnerabilityStatus.ACTIVE,
assignee=self.factory.makePerson(),
@@ -552,7 +602,10 @@ class TestUCTImporterExporter(TestCaseWithFactory):
bug_tasks = bug.bugtasks # type: List[BugTask]
self.assertEqual(
- len(cve.distro_packages) + len(cve.series_packages), len(bug_tasks)
+ len(cve.distro_packages)
+ + len(cve.series_packages)
+ + len(cve.upstream_packages),
+ len(bug_tasks),
)
bug_tasks_by_target = {t.target: t for t in bug_tasks}
@@ -563,7 +616,7 @@ class TestUCTImporterExporter(TestCaseWithFactory):
t = bug_tasks_by_target[distro_package.package]
package_importance = distro_package.importance or cve.importance
package_importances[
- distro_package.package.sourcepackagename
+ distro_package.package.sourcepackagename.name
] = package_importance
conjoined_primary = t.conjoined_primary
if conjoined_primary:
@@ -580,7 +633,7 @@ class TestUCTImporterExporter(TestCaseWithFactory):
self.assertIn(series_package.package, bug_tasks_by_target)
t = bug_tasks_by_target[series_package.package]
package_importance = package_importances[
- series_package.package.sourcepackagename
+ series_package.package.sourcepackagename.name
]
sp_importance = series_package.importance or package_importance
self.assertEqual(sp_importance, t.importance)
@@ -589,6 +642,19 @@ class TestUCTImporterExporter(TestCaseWithFactory):
series_package.status_explanation, t.status_explanation
)
+ for upstream_package in cve.upstream_packages:
+ self.assertIn(upstream_package.package, bug_tasks_by_target)
+ t = bug_tasks_by_target[upstream_package.package]
+ package_importance = package_importances[
+ upstream_package.package.name
+ ]
+ sp_importance = upstream_package.importance or package_importance
+ self.assertEqual(sp_importance, t.importance)
+ self.assertEqual(upstream_package.status, t.status)
+ self.assertEqual(
+ upstream_package.status_explanation, t.status_explanation
+ )
+
for t in bug_tasks:
self.assertEqual(cve.assignee, t.assignee)
@@ -625,6 +691,32 @@ class TestUCTImporterExporter(TestCaseWithFactory):
lp_cve.cvss,
)
+ def checkCVE(self, expected: CVE, actual: CVE):
+ self.assertEqual(expected.sequence, actual.sequence)
+ self.assertEqual(expected.crd, actual.crd)
+ self.assertEqual(expected.public_date, actual.public_date)
+ self.assertEqual(
+ expected.public_date_at_USN, actual.public_date_at_USN
+ )
+ self.assertListEqual(expected.distro_packages, actual.distro_packages)
+ self.assertListEqual(expected.series_packages, actual.series_packages)
+ self.assertListEqual(
+ expected.upstream_packages, actual.upstream_packages
+ )
+ self.assertEqual(expected.importance, actual.importance)
+ self.assertEqual(expected.status, actual.status)
+ self.assertEqual(expected.assignee, actual.assignee)
+ self.assertEqual(expected.discovered_by, actual.discovered_by)
+ self.assertEqual(expected.description, actual.description)
+ self.assertEqual(
+ expected.ubuntu_description, actual.ubuntu_description
+ )
+ self.assertListEqual(expected.bug_urls, actual.bug_urls)
+ self.assertListEqual(expected.references, actual.references)
+ self.assertEqual(expected.notes, actual.notes)
+ self.assertEqual(expected.mitigation, actual.mitigation)
+ self.assertListEqual(expected.cvss, actual.cvss)
+
def test_create_bug(self):
bug = self.importer.create_bug(self.cve, self.lp_cve)
@@ -891,20 +983,13 @@ class TestUCTImporterExporter(TestCaseWithFactory):
self.importer.import_cve(self.cve)
bug = self.importer._find_existing_bug(self.cve, self.lp_cve)
cve = self.exporter._make_cve_from_bug(bug)
- self.assertEqual(self.cve.sequence, cve.sequence)
- self.assertEqual(self.cve.crd, cve.crd)
- self.assertEqual(self.cve.public_date, cve.public_date)
- self.assertEqual(self.cve.public_date_at_USN, cve.public_date_at_USN)
- self.assertListEqual(self.cve.distro_packages, cve.distro_packages)
- self.assertListEqual(self.cve.series_packages, cve.series_packages)
- self.assertEqual(self.cve.importance, cve.importance)
- self.assertEqual(self.cve.status, cve.status)
- self.assertEqual(self.cve.assignee, cve.assignee)
- self.assertEqual(self.cve.discovered_by, cve.discovered_by)
- self.assertEqual(self.cve.description, cve.description)
- self.assertEqual(self.cve.ubuntu_description, cve.ubuntu_description)
- self.assertListEqual(self.cve.bug_urls, cve.bug_urls)
- self.assertListEqual(self.cve.references, cve.references)
- self.assertEqual(self.cve.notes, cve.notes)
- self.assertEqual(self.cve.mitigation, cve.mitigation)
- self.assertListEqual(self.cve.cvss, cve.cvss)
+ self.checkCVE(self.cve, cve)
+
+ def test_export_bug_to_uct_file(self):
+ self.importer.import_cve(self.cve)
+ bug = self.importer._find_existing_bug(self.cve, self.lp_cve)
+ output_dir = Path(self.makeTemporaryDirectory())
+ cve_path = self.exporter.export_bug_to_uct_file(bug.id, output_dir)
+ uct_record = UCTRecord.load(cve_path)
+ cve = CVE.make_from_uct_record(uct_record)
+ self.checkCVE(self.cve, cve)
diff --git a/lib/lp/bugs/scripts/uct/__init__.py b/lib/lp/bugs/scripts/uct/__init__.py
index 970e1fd..da0635b 100644
--- a/lib/lp/bugs/scripts/uct/__init__.py
+++ b/lib/lp/bugs/scripts/uct/__init__.py
@@ -1,9 +1,6 @@
# Copyright 2022 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
-from lp.bugs.scripts.uct.models import CVE, CVSS, UCTRecord # noqa: F401
-from lp.bugs.scripts.uct.uctexport import UCTExporter # noqa: F401
-from lp.bugs.scripts.uct.uctimport import ( # noqa: F401
- UCTImporter,
- UCTImportError,
-)
+from .models import CVE, CVSS, UCTRecord
+from .uctexport import UCTExporter
+from .uctimport import UCTImporter, UCTImportError
diff --git a/lib/lp/bugs/scripts/uct/models.py b/lib/lp/bugs/scripts/uct/models.py
index 6c87f86..440f79d 100644
--- a/lib/lp/bugs/scripts/uct/models.py
+++ b/lib/lp/bugs/scripts/uct/models.py
@@ -2,7 +2,7 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
import logging
-from collections import defaultdict
+from collections import OrderedDict, defaultdict
from datetime import datetime
from enum import Enum
from pathlib import Path
@@ -13,11 +13,12 @@ import dateutil.parser
from contrib.cve_lib import load_cve
from zope.component import getUtility
-from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.bugs.enums import VulnerabilityStatus
from lp.bugs.interfaces.bugtask import BugTaskImportance, BugTaskStatus
+from lp.registry.interfaces.distribution import IDistributionSet
from lp.registry.interfaces.distroseries import IDistroSeriesSet
from lp.registry.interfaces.person import IPersonSet
+from lp.registry.interfaces.product import IProductSet
from lp.registry.interfaces.series import SeriesStatus
from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
from lp.registry.model.distribution import Distribution
@@ -26,6 +27,7 @@ from lp.registry.model.distributionsourcepackage import (
)
from lp.registry.model.distroseries import DistroSeries
from lp.registry.model.person import Person
+from lp.registry.model.product import Product
from lp.registry.model.sourcepackage import SourcePackage
from lp.registry.model.sourcepackagename import SourcePackageName
from lp.services.propertycache import cachedproperty
@@ -73,10 +75,10 @@ class UCTRecord:
NEEDED = "needed"
PENDING = "pending"
- DistroSeriesPackageStatus = NamedTuple(
- "DistroSeriesPackageStatus",
+ SeriesPackageStatus = NamedTuple(
+ "SeriesPackageStatus",
(
- ("distroseries", str),
+ ("series", str),
("status", PackageStatus),
("reason", str),
("priority", Optional[Priority]),
@@ -95,7 +97,7 @@ class UCTRecord:
"Package",
(
("name", str),
- ("statuses", List[DistroSeriesPackageStatus]),
+ ("statuses", List[SeriesPackageStatus]),
("priority", Optional[Priority]),
("tags", Set[str]),
("patches", List[Patch]),
@@ -167,23 +169,23 @@ class UCTRecord:
cve_data, "pkgs"
).items():
statuses = []
- for distroseries, (status, reason) in statuses_dict.items():
- distroseries_priority = cls._pop_cve_property(
+ for series, (status, reason) in statuses_dict.items():
+ series_priority = cls._pop_cve_property(
cve_data,
- "Priority_{package}_{distroseries}".format(
+ "Priority_{package}_{series}".format(
package=package,
- distroseries=distroseries,
+ series=series,
),
required=False,
)
statuses.append(
- cls.DistroSeriesPackageStatus(
- distroseries=distroseries,
+ cls.SeriesPackageStatus(
+ series=series,
status=cls.PackageStatus(status),
reason=reason,
priority=(
- cls.Priority(distroseries_priority)
- if distroseries_priority
+ cls.Priority(series_priority)
+ if series_priority
else None
),
)
@@ -334,7 +336,7 @@ class UCTRecord:
)
for status in package.statuses:
self._write_field(
- "{}_{}".format(status.distroseries, package.name),
+ "{}_{}".format(status.series, package.name),
(
"{} ({})".format(status.status.value, status.reason)
if status.reason
@@ -351,9 +353,7 @@ class UCTRecord:
for status in package.statuses:
if status.priority:
self._write_field(
- "Priority_{}_{}".format(
- package.name, status.distroseries
- ),
+ "Priority_{}_{}".format(package.name, status.series),
status.priority.value,
output,
)
@@ -436,6 +436,16 @@ class CVE:
),
)
+ UpstreamPackage = NamedTuple(
+ "UpstreamPackage",
+ (
+ ("package", Product),
+ ("importance", Optional[BugTaskImportance]),
+ ("status", BugTaskStatus),
+ ("status_explanation", str),
+ ),
+ )
+
PRIORITY_MAP = {
UCTRecord.Priority.CRITICAL: BugTaskImportance.CRITICAL,
UCTRecord.Priority.HIGH: BugTaskImportance.HIGH,
@@ -478,6 +488,7 @@ class CVE:
public_date_at_USN: Optional[datetime],
distro_packages: List[DistroPackage],
series_packages: List[SeriesPackage],
+ upstream_packages: List[UpstreamPackage],
importance: BugTaskImportance,
status: VulnerabilityStatus,
assignee: Optional[Person],
@@ -496,6 +507,7 @@ class CVE:
self.public_date_at_USN = public_date_at_USN
self.distro_packages = distro_packages
self.series_packages = series_packages
+ self.upstream_packages = upstream_packages
self.importance = importance
self.status = status
self.assignee = assignee
@@ -518,6 +530,7 @@ class CVE:
distro_packages = []
series_packages = []
+ upstream_packages = []
spn_set = getUtility(ISourcePackageNameSet)
@@ -530,11 +543,6 @@ class CVE:
)
for uct_package_status in uct_package.statuses:
- distro_series = cls.get_distro_series(
- uct_package_status.distroseries
- )
- if distro_series is None:
- continue
if uct_package_status.status not in cls.BUG_TASK_STATUS_MAP:
logger.warning(
@@ -543,6 +551,34 @@ class CVE:
)
continue
+ series_package_importance = (
+ cls.PRIORITY_MAP[uct_package_status.priority]
+ if uct_package_status.priority
+ else None
+ )
+
+ if uct_package_status.series == "upstream":
+ product = cls.get_product(uct_package.name)
+ if product is None:
+ continue
+ upstream_packages.append(
+ cls.UpstreamPackage(
+ package=product,
+ importance=series_package_importance,
+ status=cls.BUG_TASK_STATUS_MAP[
+ uct_package_status.status
+ ],
+ status_explanation=uct_package_status.reason,
+ )
+ )
+ continue
+
+ distro_series = cls.get_distro_series(
+ uct_package_status.series
+ )
+ if distro_series is None:
+ continue
+
distro_package = cls.DistroPackage(
package=DistributionSourcePackage(
distribution=distro_series.distribution,
@@ -553,12 +589,6 @@ class CVE:
if distro_package not in distro_packages:
distro_packages.append(distro_package)
- series_package_importance = (
- cls.PRIORITY_MAP[uct_package_status.priority]
- if uct_package_status.priority
- else None
- )
-
series_packages.append(
cls.SeriesPackage(
package=SourcePackage(
@@ -589,6 +619,7 @@ class CVE:
public_date_at_USN=uct_record.public_date_at_USN,
distro_packages=distro_packages,
series_packages=series_packages,
+ upstream_packages=upstream_packages,
importance=cls.PRIORITY_MAP[uct_record.priority],
status=cls.infer_vulnerability_status(uct_record),
assignee=assignee,
@@ -616,23 +647,28 @@ class CVE:
series_package.package.sourcepackagename
].append(series_package)
- packages = [] # type: List[UCTRecord.Package]
+ packages_by_name = OrderedDict() # type: Dict[str, UCTRecord.Package]
processed_packages = set() # type: Set[SourcePackageName]
for distro_package in self.distro_packages:
spn = distro_package.package.sourcepackagename
if spn in processed_packages:
continue
processed_packages.add(spn)
- statuses = [] # type: List[UCTRecord.DistroSeriesPackageStatus]
+ statuses = [] # type: List[UCTRecord.SeriesPackageStatus]
for series_package in series_packages_by_name[spn]:
series = series_package.package.distroseries
if series.status == SeriesStatus.DEVELOPMENT:
series_name = "devel"
else:
series_name = series.name
+ distro_name = distro_package.package.distribution.name
+ if distro_name != "ubuntu":
+ if distro_name == "ubuntu-esm":
+ distro_name = "esm"
+ series_name = "{}/{}".format(series_name, distro_name)
statuses.append(
- UCTRecord.DistroSeriesPackageStatus(
- distroseries=series_name,
+ UCTRecord.SeriesPackageStatus(
+ series=series_name,
status=self.BUG_TASK_STATUS_MAP_REVERSE[
series_package.status
],
@@ -647,19 +683,43 @@ class CVE:
)
)
- packages.append(
- UCTRecord.Package(
- name=spn.name,
- statuses=statuses,
- priority=(
- self.PRIORITY_MAP_REVERSE[distro_package.importance]
- if distro_package.importance
- else None
- ),
+ packages_by_name[spn.name] = UCTRecord.Package(
+ name=spn.name,
+ statuses=statuses,
+ priority=(
+ self.PRIORITY_MAP_REVERSE[distro_package.importance]
+ if distro_package.importance
+ else None
+ ),
+ tags=set(),
+ patches=[],
+ )
+
+ for upstream_package in self.upstream_packages:
+ status = UCTRecord.SeriesPackageStatus(
+ series="upstream",
+ status=self.BUG_TASK_STATUS_MAP_REVERSE[
+ upstream_package.status
+ ],
+ reason=upstream_package.status_explanation,
+ priority=(
+ self.PRIORITY_MAP_REVERSE[upstream_package.importance]
+ if upstream_package.importance
+ else None
+ ),
+ )
+ package_name = upstream_package.package.name
+ if package_name in packages_by_name:
+ packages_by_name[package_name].statuses.append(status)
+ else:
+ packages_by_name[package_name] = UCTRecord.Package(
+ name=package_name,
+ statuses=[status],
+ priority=None,
tags=set(),
patches=[],
)
- )
+
return UCTRecord(
parent_dir=self.VULNERABILITY_STATUS_MAP_REVERSE.get(
self.status, ""
@@ -678,7 +738,7 @@ class CVE:
priority=self.PRIORITY_MAP_REVERSE[self.importance],
references=self.references,
ubuntu_description=self.ubuntu_description,
- packages=packages,
+ packages=list(packages_by_name.values()),
)
@property
@@ -722,20 +782,29 @@ class CVE:
if "/" in distro_series_name:
series_name, distro_name = distro_series_name.split("/", 1)
if distro_name == "esm":
- # TODO: ESM needs special handling
- pass
- return
+ distro_name = "ubuntu-esm"
else:
+ distro_name = "ubuntu"
series_name = distro_series_name
- distribution = getUtility(ILaunchpadCelebrities).ubuntu
- if series_name == "devel":
- distro_series = cls.get_devel_series(distribution)
- else:
- distro_series = getUtility(IDistroSeriesSet).queryByName(
- distribution, series_name
- )
+ distribution = getUtility(IDistributionSet).getByName(distro_name)
+ if distribution is None:
+ logger.warning("Could not find the distribution: %s", distro_name)
+ return
+ if series_name == "devel":
+ distro_series = cls.get_devel_series(distribution)
+ else:
+ distro_series = getUtility(IDistroSeriesSet).queryByName(
+ distribution, series_name
+ )
if not distro_series:
logger.warning(
"Could not find the distro series: %s", distro_series_name
)
return distro_series
+
+ @classmethod
+ def get_product(cls, product_name: str) -> Optional[Product]:
+ product = getUtility(IProductSet).getByName(product_name)
+ if not product:
+ logger.warning("Could not find the product: %s", product_name)
+ return product
diff --git a/lib/lp/bugs/scripts/uct/uctexport.py b/lib/lp/bugs/scripts/uct/uctexport.py
index 3e69302..45366a9 100644
--- a/lib/lp/bugs/scripts/uct/uctexport.py
+++ b/lib/lp/bugs/scripts/uct/uctexport.py
@@ -14,12 +14,14 @@ from lp.bugs.model.bug import Bug as BugModel
from lp.bugs.model.bugtask import BugTask
from lp.bugs.model.cve import Cve as CveModel
from lp.bugs.model.vulnerability import Vulnerability
-from lp.bugs.scripts.uct.models import CVE, CVSS
from lp.registry.model.distributionsourcepackage import (
DistributionSourcePackage,
)
+from lp.registry.model.product import Product
from lp.registry.model.sourcepackage import SourcePackage
+from .models import CVE, CVSS
+
__all__ = [
"UCTExporter",
]
@@ -70,7 +72,7 @@ class UCTExporter:
Create a `CVE` instances from a `Bug` model and the related
Vulnerabilities and `Cve`.
- `BugTasks` are converted to `CVE.DistroPackage` and `CVE.SeriesPackage`
+ `BugTasks` are converted to `CVE.DistroPackage` and `CVE.SEriesPackage`
objects.
Other `CVE` fields are populated from the information contained in the
@@ -141,6 +143,26 @@ class UCTExporter:
)
)
+ upstream_packages = []
+ for bug_task in bug_tasks:
+ target = removeSecurityProxy(bug_task.target)
+ if not isinstance(target, Product):
+ continue
+ up_importance = bug_task.importance
+ package_importance = package_importances.get(target.name)
+ upstream_packages.append(
+ CVE.UpstreamPackage(
+ package=target,
+ importance=(
+ up_importance
+ if up_importance != package_importance
+ else None
+ ),
+ status=bug_task.status,
+ status_explanation=bug_task.status_explanation,
+ )
+ )
+
return CVE(
sequence="CVE-{}".format(lp_cve.sequence),
crd=None, # TODO: fix this
@@ -148,6 +170,7 @@ class UCTExporter:
public_date_at_USN=None, # TODO: fix this
distro_packages=distro_packages,
series_packages=series_packages,
+ upstream_packages=upstream_packages,
importance=cve_importance,
status=vulnerability.status,
assignee=bug_tasks[0].assignee,
@@ -174,7 +197,7 @@ class UCTExporter:
Some `CVE` fields can't be mapped to Launchpad models.
They are saved to bug description.
- This method extracts those fields from the bug description.
+ This method extract those fields from the bug description.
:param bug_description: bug description
:return: parsed description
diff --git a/lib/lp/bugs/scripts/uct/uctimport.py b/lib/lp/bugs/scripts/uct/uctimport.py
index 3bc3512..13639b5 100644
--- a/lib/lp/bugs/scripts/uct/uctimport.py
+++ b/lib/lp/bugs/scripts/uct/uctimport.py
@@ -14,12 +14,21 @@ For each entry in UCT we:
3. Create a Bug Task for each distribution/series package in the CVE entry
4. Update the statuses of Bug Tasks based on the information in the CVE entry
5. Update the information the related Launchpad's `Cve` model, if necessary
+
+Three types of bug tags are created:
+
+1. Bug tasks with a distribution package as a target - they represent
+ importance of the package
+2. Bug tasks with distribution series packages as a target - they represent
+ importance and status of the package in a particular series
+3. Bug tasks with a product as a target - they represent importance and
+ status of the package in upstream.
"""
import logging
from datetime import timezone
from itertools import chain
from pathlib import Path
-from typing import List, Optional
+from typing import Dict, List, Optional
import transaction
from zope.component import getUtility
@@ -36,11 +45,12 @@ from lp.bugs.model.bug import Bug as BugModel
from lp.bugs.model.bugtask import BugTask
from lp.bugs.model.cve import Cve as CveModel
from lp.bugs.model.vulnerability import Vulnerability
-from lp.bugs.scripts.uct.models import CVE, UCTRecord
from lp.registry.model.distribution import Distribution
from lp.registry.model.person import Person
from lp.services.database.constants import UTC_NOW
+from .models import CVE, UCTRecord
+
__all__ = [
"UCTImporter",
"UCTImportError",
@@ -156,10 +166,17 @@ class UCTImporter:
self._update_external_bug_urls(bug, cve.bug_urls)
self._create_bug_tasks(
- bug, cve.distro_packages[1:], cve.series_packages
+ bug,
+ cve.distro_packages[1:],
+ cve.series_packages,
+ cve.upstream_packages,
)
self._update_statuses_and_importances(
- bug, cve.importance, cve.distro_packages, cve.series_packages
+ bug,
+ cve.importance,
+ cve.distro_packages,
+ cve.series_packages,
+ cve.upstream_packages,
)
self._assign_bug_tasks(bug, cve.assignee)
@@ -188,9 +205,18 @@ class UCTImporter:
"""
bug.description = self._make_bug_description(cve)
- self._create_bug_tasks(bug, cve.distro_packages, cve.series_packages)
+ self._create_bug_tasks(
+ bug,
+ cve.distro_packages,
+ cve.series_packages,
+ cve.upstream_packages,
+ )
self._update_statuses_and_importances(
- bug, cve.importance, cve.distro_packages, cve.series_packages
+ bug,
+ cve.importance,
+ cve.distro_packages,
+ cve.series_packages,
+ cve.upstream_packages,
)
self._assign_bug_tasks(bug, cve.assignee)
self._update_external_bug_urls(bug, cve.bug_urls)
@@ -228,6 +254,7 @@ class UCTImporter:
bug: BugModel,
distro_packages: List[CVE.DistroPackage],
series_packages: List[CVE.SeriesPackage],
+ upstream_packages: List[CVE.UpstreamPackage],
) -> None:
"""
Add bug tasks to the given `Bug` model based on the information
@@ -246,7 +273,8 @@ class UCTImporter:
bug_task_by_target = {t.target: t for t in bug_tasks}
bug_task_set = getUtility(IBugTaskSet)
for target in (
- p.package for p in chain(distro_packages, series_packages)
+ p.package
+ for p in chain(distro_packages, series_packages, upstream_packages)
):
if target not in bug_task_by_target:
bug_task_set.createTask(bug, self.bug_importer, target)
@@ -331,6 +359,7 @@ class UCTImporter:
cve_importance: BugTaskImportance,
distro_packages: List[CVE.DistroPackage],
series_packages: List[CVE.SeriesPackage],
+ upstream_packages: List[CVE.UpstreamPackage],
) -> None:
"""
Update statuses and importances of bug tasks according to the
@@ -350,23 +379,33 @@ class UCTImporter:
bug_tasks = bug.bugtasks # type: List[BugTask]
bug_task_by_target = {t.target: t for t in bug_tasks}
- package_importances = {}
+ package_importances = {} # type: Dict[str, BugTaskImportance]
for dp in distro_packages:
task = bug_task_by_target[dp.package]
dp_importance = dp.importance or cve_importance
- package_importances[dp.package.sourcepackagename] = dp_importance
- task.transitionToImportance(dp_importance)
+ package_importances[
+ dp.package.sourcepackagename.name
+ ] = dp_importance
+ if task.importance != dp_importance:
+ task.transitionToImportance(dp_importance, self.bug_importer)
- for sp in series_packages:
+ for sp in chain(series_packages, upstream_packages):
task = bug_task_by_target[sp.package]
- package_importance = package_importances[
- sp.package.sourcepackagename
- ]
+ if isinstance(sp, CVE.SeriesPackage):
+ package_name = sp.package.sourcepackagename.name
+ elif isinstance(sp, CVE.UpstreamPackage):
+ package_name = sp.package.name
+ else:
+ raise AssertionError()
+ package_importance = package_importances[package_name]
sp_importance = sp.importance or package_importance
- task.transitionToImportance(sp_importance)
- task.transitionToStatus(sp.status)
- task.status_explanation = sp.status_explanation
+ if task.importance != sp_importance:
+ task.transitionToImportance(sp_importance, self.bug_importer)
+ if task.status != sp.status:
+ task.transitionToStatus(sp.status, self.bug_importer)
+ if task.status_explanation != sp.status_explanation:
+ task.status_explanation = sp.status_explanation
def _update_external_bug_urls(
self, bug: BugModel, bug_urls: List[str]
diff --git a/lib/lp/registry/model/distributionsourcepackage.py b/lib/lp/registry/model/distributionsourcepackage.py
index 6d31eab..fd9da20 100644
--- a/lib/lp/registry/model/distributionsourcepackage.py
+++ b/lib/lp/registry/model/distributionsourcepackage.py
@@ -141,6 +141,9 @@ class DistributionSourcePackage(
self.distribution = distribution
self.sourcepackagename = sourcepackagename
+ def __repr__(self):
+ return "<{} '{}'>".format(self.__class__.__name__, self.display_name)
+
@property
def name(self):
"""See `IDistributionSourcePackage`."""
diff --git a/setup.cfg b/setup.cfg
index 367bb34..761ea15 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -209,6 +209,9 @@ ignore =
# operators, at least for now.
W503,
W504
+per-file-ignores =
+ # Ignore unused imports in `__init__` files
+ */__init__.py: F401
[isort]
# database/* have some implicit relative imports.