← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~enriqueesanchz/launchpad:soss-export-parsing into launchpad:master

 

Enrique Sánchez has proposed merging ~enriqueesanchz/launchpad:soss-export-parsing into launchpad:master.

Commit message:
Add soss-cve-tracker export parsing layer

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~enriqueesanchz/launchpad/+git/launchpad/+merge/487080
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~enriqueesanchz/launchpad:soss-export-parsing into launchpad:master.
diff --git a/lib/lp/bugs/scripts/soss/models.py b/lib/lp/bugs/scripts/soss/models.py
index 4d63a2c..dd942e3 100644
--- a/lib/lp/bugs/scripts/soss/models.py
+++ b/lib/lp/bugs/scripts/soss/models.py
@@ -50,6 +50,9 @@ class SOSSRecord:
             if not VALID_CHANNEL_REGEX.match(self.value):
                 raise ValueError(f"Invalid channel format: {self.value}")
 
+        def __str__(self):
+            return self.value
+
     @dataclass
     class CVSS:
         source: str
@@ -67,6 +70,14 @@ class SOSSRecord:
                     f"Invalid base severity: {self.base_severity}"
                 )
 
+        def to_dict(self):
+            return {
+                "source": self.source,
+                "vector": self.vector,
+                "baseScore": self.base_score,
+                "baseSeverity": self.base_severity,
+            }
+
     @dataclass
     class Package:
         name: str
@@ -75,6 +86,15 @@ class SOSSRecord:
         status: "SOSSRecord.PackageStatusEnum"
         note: str
 
+        def to_dict(self):
+            return {
+                "Name": self.name,
+                "Channel": str(self.channel),
+                "Repositories": self.repositories,
+                "Status": self.status.value,
+                "Note": self.note,
+            }
+
     references: List[str]
     notes: List[str]
     priority: PriorityEnum
@@ -144,3 +164,32 @@ class SOSSRecord:
             cvss=cvss_list,
             public_date=public_date,
         )
+
+    def to_dict(self) -> dict:
+        serialized = {
+            "References": self.references,
+            "Notes": self.notes,
+            "Priority": self.priority.value,
+            "Priority-Reason": self.priority_reason,
+            "Assigned-To": self.assigned_to,
+            "Packages": {
+                key.value: [p.to_dict() for p in pkg_list]
+                for key, pkg_list in self.packages.items()
+            },
+        }
+
+        if self.candidate:
+            serialized["Candidate"] = self.candidate
+        if self.description:
+            serialized["Description"] = self.description
+        if self.cvss:
+            serialized["CVSS"] = [cvss.to_dict() for cvss in self.cvss]
+        if self.public_date:
+            serialized["PublicDate"] = self.public_date.isoformat(
+                sep="T", timespec="milliseconds"
+            )
+
+        return serialized
+
+    def to_yaml(self) -> str:
+        return yaml.dump(self.to_dict(), sort_keys=False)
diff --git a/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2005-1544 b/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2005-1544
new file mode 100644
index 0000000..8788e47
--- /dev/null
+++ b/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2005-1544
@@ -0,0 +1,33 @@
+References: []
+Notes: []
+Priority: Needs-triage
+Priority-Reason: ''
+Assigned-To: ''
+Packages:
+  conda:
+  - Name: libtiff
+    Channel: focal:4.5.0-h6adf6a1/stable
+    Repositories:
+    - soss-conda-candidate-local
+    Status: ignored
+    Note: ''
+  - Name: opencv
+    Channel: focal:4.5.3-py39hf3d152e/stable
+    Repositories:
+    - soss-conda-stable-local
+    - soss-conda-candidate-local
+    Status: ignored
+    Note: ''
+  unpackaged:
+  - Name: opencv
+    Channel: jammy:4.8.0/stable
+    Repositories:
+    - soss-src-stable-local
+    Status: ignored
+    Note: ''
+  - Name: opencv
+    Channel: jammy:4.7.0/stable
+    Repositories:
+    - soss-src-stable-local
+    Status: ignored
+    Note: ''
diff --git a/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2011-5000 b/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2011-5000
new file mode 100644
index 0000000..e92427b
--- /dev/null
+++ b/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2011-5000
@@ -0,0 +1,14 @@
+References: []
+Notes: []
+Priority: Needs-triage
+Priority-Reason: ''
+Assigned-To: ''
+Packages:
+  conda:
+  - Name: openssh
+    Channel: focal:8.6p1-h1fa914a/stable
+    Repositories:
+    - soss-conda-src-local
+    - soss-conda-stable-local
+    Status: ignored
+    Note: ''
diff --git a/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2021-21300 b/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2021-21300
new file mode 100644
index 0000000..0a68496
--- /dev/null
+++ b/lib/lp/bugs/scripts/soss/tests/sampledata/CVE-2021-21300
@@ -0,0 +1,13 @@
+References: []
+Notes: []
+Priority: Needs-triage
+Priority-Reason: ''
+Assigned-To: ''
+Packages:
+  conda:
+  - Name: git
+    Channel: focal:2.32.0-pl5321hc30692c/stable
+    Repositories:
+    - soss-conda-candidate-local
+    Status: ignored
+    Note: was ignored
diff --git a/lib/lp/bugs/scripts/soss/tests/test_sossrecord.py b/lib/lp/bugs/scripts/soss/tests/test_sossrecord.py
index a26cad7..eb1fec5 100644
--- a/lib/lp/bugs/scripts/soss/tests/test_sossrecord.py
+++ b/lib/lp/bugs/scripts/soss/tests/test_sossrecord.py
@@ -1,5 +1,6 @@
 #  Copyright 2025 Canonical Ltd.  This software is licensed under the
 #  GNU Affero General Public License version 3 (see the file LICENSE).
+import os
 from datetime import datetime
 from pathlib import Path
 
@@ -10,6 +11,10 @@ from lp.testing import TestCase
 class TestSOSSRecord(TestCase):
     maxDiff = None
 
+    def get_sample_files(self):
+        directory = Path(__file__).parent / "sampledata"
+        return [directory / f for f in os.listdir(directory)]
+
     def setUp(self, *args, **kwargs):
         super().setUp(*args, **kwargs)
 
@@ -273,3 +278,29 @@ class TestSOSSRecord(TestCase):
             soss_record = SOSSRecord.from_yaml(f)
 
         self.assertEqual(self.soss_record, soss_record)
+
+    def test_to_dict(self):
+        self.assertDictEqual(
+            self.soss_record.to_dict(),
+            self.soss_record_dict,
+        )
+
+    def test_to_yaml(self):
+        load_from = Path(__file__).parent / "sampledata" / "CVE-2025-1979-full"
+        with open(load_from) as f:
+            sample_data = f.read()
+
+        self.assertEqual(self.soss_record.to_yaml(), sample_data),
+
+    def parse_import_export_yaml(self, file):
+        with open(file) as f:
+            soss_record_read = f.read()
+
+        soss_record = SOSSRecord.from_yaml(soss_record_read)
+        self.assertEqual(soss_record_read, soss_record.to_yaml())
+
+    def test_parse_import_export_yaml(self):
+        files = self.get_sample_files()
+
+        for f in files:
+            self.parse_import_export_yaml(f)