Skip to content

Commit 342f81e

Browse files
committed
optimie creation
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent ebf2c95 commit 342f81e

2 files changed

Lines changed: 56 additions & 3 deletions

File tree

vulnerabilities/fetch.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ def fetch_for_packages(
8282
return results
8383

8484
vulnerablecode = VulnerableCode(dataspace)
85+
# Tracks advisory_uids created during this run to avoid re-updating them in later batches.
86+
created_advisory_uids = set()
8587

8688
for index, batch in enumerate(chunked_queryset(queryset, batch_size), start=1):
8789
batch_start = timer()
@@ -122,6 +124,7 @@ def fetch_for_packages(
122124
update,
123125
batch_results,
124126
vulnerability_cache,
127+
created_advisory_uids,
125128
log_func,
126129
verbosity,
127130
)
@@ -174,7 +177,15 @@ def batch_add_affected(affected_packages, vulnerabilities):
174177

175178

176179
def process_vc_entry(
177-
vc_entry, queryset, dataspace, update, results, vulnerability_cache, log_func=None, verbosity=1
180+
vc_entry,
181+
queryset,
182+
dataspace,
183+
update,
184+
results,
185+
vulnerability_cache,
186+
created_advisory_uids,
187+
log_func=None,
188+
verbosity=1,
178189
):
179190
"""
180191
Process a single VulnerableCode purl entry: find the matching packages in ``queryset``,
@@ -227,6 +238,7 @@ def process_vc_entry(
227238
update,
228239
results,
229240
vulnerability=vulnerability_cache.get(advisory_uid),
241+
created_advisory_uids=created_advisory_uids,
230242
)
231243
vulnerability_cache[advisory_uid] = vulnerability
232244
vulnerabilities.append(vulnerability)
@@ -242,22 +254,29 @@ def process_vc_entry(
242254

243255

244256
def create_or_update_vulnerability(
245-
vulnerability_data, dataspace, update, results, vulnerability=None
257+
vulnerability_data, dataspace, update, results, vulnerability=None, created_advisory_uids=None
246258
):
247259
"""
248260
Create or update a Vulnerability from ``vulnerability_data``.
249261
250262
``vulnerability`` is the already-resolved instance (looked up from the caller's
251263
``vulnerability_cache``), or ``None`` if not yet created. M2M linking is handled
252264
by the caller via ``batch_add_affected``.
265+
266+
``created_advisory_uids`` is a run-wide set of advisory_uids created during this fetch.
267+
Vulnerabilities in this set are skipped for updates to avoid spurious re-updates when the
268+
same advisory appears in multiple packages across different batches.
253269
"""
270+
advisory_uid = vulnerability_data["advisory_uid"]
254271
if not vulnerability:
255272
vulnerability = Vulnerability.create_from_data(
256273
dataspace=dataspace,
257274
data=vulnerability_data,
258275
)
259276
results["created"] += 1
260-
elif update:
277+
if created_advisory_uids is not None:
278+
created_advisory_uids.add(advisory_uid)
279+
elif update and advisory_uid not in (created_advisory_uids or ()):
261280
updated_fields = vulnerability.update_from_data(
262281
user=None,
263282
data=vulnerability_data,

vulnerabilities/tests/test_fetch.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,40 @@ def test_vulnerabilities_fetch_for_packages(self, mock_bulk_search_by_purl):
121121
self.assertEqual(Decimal("3.4"), package1.risk_score)
122122
self.assertEqual(Decimal("1.7"), pp1.weighted_risk_score)
123123

124+
@mock.patch("dejacode_toolkit.vulnerablecode.VulnerableCode.bulk_search_by_purl")
125+
def test_vulnerabilities_fetch_for_packages_cross_batch_no_spurious_update(
126+
self, mock_bulk_search_by_purl
127+
):
128+
# Two packages affected by the same advisory_uid, processed in separate batches.
129+
# The vulnerability must be created once and never "updated" within the same run.
130+
make_package(self.dataspace, package_url="pkg:pypi/idna@3.6")
131+
make_package(self.dataspace, package_url="pkg:pypi/idna@3.7")
132+
queryset = Package.objects.scope(self.dataspace)
133+
response_file = self.data / "vulnerabilities" / "idna_3.6_response.json"
134+
response_36 = json.loads(response_file.read_text())
135+
# Minimal response for idna@3.7: same advisory_uid as idna@3.6's first vulnerability.
136+
shared_vuln = response_36["results"][0]["affected_by_vulnerabilities"][0]
137+
response_37 = {
138+
"count": 1,
139+
"next": None,
140+
"previous": None,
141+
"results": [
142+
{
143+
"purl": "pkg:pypi/idna@3.7",
144+
"affected_by_vulnerabilities": [shared_vuln],
145+
"risk_score": 3.4,
146+
}
147+
],
148+
}
149+
mock_bulk_search_by_purl.side_effect = [response_36, response_37]
150+
151+
results = fetch_for_packages(
152+
queryset, self.dataspace, batch_size=1, update=True
153+
)
154+
# 2 vulnerabilities created from response_36; the shared one is NOT re-updated
155+
# when encountered in response_37's batch, because created_advisory_uids guards it.
156+
self.assertEqual(results, {"created": 2, "updated": 0})
157+
124158
@mock.patch("vulnerabilities.fetch.find_and_fire_hook")
125159
def test_vulnerabilities_fetch_notify_vulnerability_data_update(self, mock_fire_hook):
126160
notify_vulnerability_data_update(self.dataspace)

0 commit comments

Comments
 (0)