← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~enriqueesanchz/launchpad:fix-update-cve-exception into launchpad:master

 

Enrique Sánchez has proposed merging ~enriqueesanchz/launchpad:fix-update-cve-exception into launchpad:master.

Commit message:
Fix empty delta zip raising an Exception
    
There are some times where the delta zip release from
CVEProject/cvelistV5 is empty because there were no changes. We avoid
raising an LaunchpadScriptFailure due to that.
Default cve references content to url.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~enriqueesanchz/launchpad/+git/launchpad/+merge/491899
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~enriqueesanchz/launchpad:fix-update-cve-exception into launchpad:master.
diff --git a/lib/lp/bugs/scripts/cveimport.py b/lib/lp/bugs/scripts/cveimport.py
index 66a6dcd..99fe66b 100644
--- a/lib/lp/bugs/scripts/cveimport.py
+++ b/lib/lp/bugs/scripts/cveimport.py
@@ -374,6 +374,16 @@ class CVEUpdater(LaunchpadCronScript):
             # extract the outer zip file
             with zipfile.ZipFile(outer_zip_path) as outer_zf:
                 if delta:
+                    target_dir = os.path.join(temp_dir, "deltaCves")
+
+                    # If there are no delta cves, we return an empty dir
+                    if not outer_zf.namelist():
+                        self.logger.info(
+                            "Zip file is empty: there are no delta changes"
+                        )
+                        os.mkdir(target_dir)
+                        return target_dir
+
                     # for delta, extract deltacves directory
                     members = [
                         m
@@ -381,7 +391,6 @@ class CVEUpdater(LaunchpadCronScript):
                         if m.startswith("deltaCves/")
                     ]
                     outer_zf.extractall(temp_dir, members=members)
-                    target_dir = os.path.join(temp_dir, "deltaCves")
                 else:
                     # for baseline, handle nested zip structure
                     outer_zf.extract("cves.zip", temp_dir)
@@ -729,6 +738,9 @@ class CVEUpdater(LaunchpadCronScript):
             source = "external"  # default source
             content = ref.get("name", "")
 
+            if not content:
+                content = url
+
             # look for existing reference
             was_there_previously = False
             for old_ref in old_references:
diff --git a/lib/lp/bugs/scripts/tests/test_cveimport.py b/lib/lp/bugs/scripts/tests/test_cveimport.py
index f39c7e6..07f4550 100644
--- a/lib/lp/bugs/scripts/tests/test_cveimport.py
+++ b/lib/lp/bugs/scripts/tests/test_cveimport.py
@@ -4,8 +4,10 @@
 import gzip
 import io
 import json
+import os
 import shutil
 import tempfile
+import zipfile
 from datetime import datetime, timezone
 from pathlib import Path
 
@@ -203,6 +205,20 @@ class TestCVEUpdater(TestCase):
         self.assertIsNotNone(cve)
         self.assertEqual("Delta CVE", cve.description)
 
+    def test_process_delta_directory_empty(self):
+        """Test processing an empty directory of delta CVE files."""
+        # Create empty test delta directory
+        delta_dir = Path(self.temp_dir) / "deltaCves"
+        delta_dir.mkdir()
+
+        # Process the directory using the script infrastructure
+        updater = self.make_updater([str(delta_dir)])
+        processed, errors = updater.process_delta_directory(str(delta_dir))
+
+        # Verify results
+        self.assertEqual(0, processed)
+        self.assertEqual(0, errors)
+
     def test_construct_github_url(self):
         """Test GitHub URL construction for different scenarios."""
         updater = CVEUpdater(
@@ -262,3 +278,66 @@ class TestCVEUpdater(TestCase):
         # Verify the update
         updated_cve = cveset["2024-0004"]
         self.assertEqual(new_desc, updated_cve.description)
+
+    def test_extract_github_zip(self):
+        """Test extract_github_zip for complete releases."""
+        updater = self.make_updater()
+        outer_buffer = io.BytesIO()
+
+        with zipfile.ZipFile(outer_buffer, "w") as outer_zip:
+            # create inner cves.zip in memory
+            inner_buffer = io.BytesIO()
+            with zipfile.ZipFile(inner_buffer, "w") as inner_zip:
+                inner_zip.writestr("cves/CVE-2025-8941.json", "CVE data")
+            outer_zip.writestr("cves.zip", inner_buffer.getvalue())
+
+        target_dir = updater.extract_github_zip(outer_buffer.getvalue())
+        self.assertTrue(target_dir.endswith("cves"))
+        self.assertEqual(os.listdir(target_dir), ["CVE-2025-8941.json"])
+
+    def test_extract_empty_github_zip(self):
+        """Test that extract_github_zip for complete releases raises
+        LaunchpadScriptFailure when the zip is empty.
+        """
+        updater = self.make_updater()
+        buffer = io.BytesIO()
+
+        # Empty zipfile buffer
+        with zipfile.ZipFile(buffer, "w"):
+            pass
+
+        self.assertRaisesWithContent(
+            LaunchpadScriptFailure,
+            "Failed to extract ZIP files: \"There is no item named 'cves.zip' "
+            'in the archive"',
+            updater.extract_github_zip,
+            buffer.getvalue(),
+        )
+
+    def test_extract_delta_github_zip(self):
+        """Test extract_github_zip for delta releases."""
+        updater = self.make_updater()
+        buffer = io.BytesIO()
+
+        with zipfile.ZipFile(buffer, "w") as zf:
+            zf.writestr("deltaCves/CVE-2025-8941.json", "delta CVE data")
+
+        empty_dir = updater.extract_github_zip(buffer.getvalue(), delta=True)
+        self.assertTrue(empty_dir.endswith("deltaCves"))
+        self.assertEqual(os.listdir(empty_dir), ["CVE-2025-8941.json"])
+
+    def test_extract_empty_delta_github_zip(self):
+        """Test that extract_github_zip for delta releases returns an empty dir
+        if the zip is empty. There can be hours when no cves are updated so we
+        will return an empty dir and will not import cves.
+        """
+        updater = self.make_updater()
+        buffer = io.BytesIO()
+
+        # Empty zipfile buffer
+        with zipfile.ZipFile(buffer, "w"):
+            pass
+
+        empty_dir = updater.extract_github_zip(buffer.getvalue(), delta=True)
+        self.assertTrue(empty_dir.endswith("deltaCves"))
+        self.assertEqual(os.listdir(empty_dir), [])