Skip to content

Commit 75f92e0

Browse files
committed
Migrate Advisory aliases field to M2M relationship
Signed-off-by: Keshav Priyadarshi <git@keshav.space>
1 parent 432a7d4 commit 75f92e0

File tree

3 files changed

+128
-7
lines changed

3 files changed

+128
-7
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
from aboutcode.pipeline import LoopProgress
11+
from django.db import migrations
12+
from django.db import models
13+
14+
"""
15+
Model and data migration for converting the Advisory aliases
16+
JSON field to a concrete M2M Advisory Alias relationship.
17+
"""
18+
19+
def bulk_update(model, items, fields, logger):
20+
item_count = 0
21+
if items:
22+
try:
23+
model.objects.bulk_update(objs=items, fields=fields)
24+
item_count += len(items)
25+
except Exception as e:
26+
logger(f"Error updating Advisory: {e}")
27+
items.clear()
28+
return item_count
29+
30+
31+
class Migration(migrations.Migration):
32+
33+
dependencies = [
34+
("vulnerabilities", "0088_fix_alpine_purl_type"),
35+
]
36+
37+
def populate_new_advisory_aliases_field(apps, schema_editor):
38+
Advisory = apps.get_model("vulnerabilities", "Advisory")
39+
Alias = apps.get_model("vulnerabilities", "Alias")
40+
advisories = Advisory.objects.all()
41+
42+
chunk_size = 10000
43+
advisories_count = advisories.count()
44+
print(f"\nPopulate new advisory aliases relationship.")
45+
progress = LoopProgress(
46+
total_iterations=advisories_count,
47+
logger=print,
48+
progress_step=1,
49+
)
50+
for advisory in progress.iter(advisories.iterator(chunk_size=chunk_size)):
51+
aliases = Alias.objects.filter(alias__in=advisory.old_aliases)
52+
advisory.aliases.set(aliases)
53+
54+
def reverse_populate_new_advisory_aliases_field(apps, schema_editor):
55+
Advisory = apps.get_model("vulnerabilities", "Advisory")
56+
advisories = Advisory.objects.all()
57+
58+
updated_advisory_count = 0
59+
batch_size = 10000
60+
chunk_size = 10000
61+
updated_advisory = []
62+
progress = LoopProgress(
63+
total_iterations=advisories.count(),
64+
logger=print,
65+
progress_step=1,
66+
)
67+
for advisory in progress.iter(advisories.iterator(chunk_size=chunk_size)):
68+
aliases = advisory.aliases.all()
69+
advisory.old_aliases = [alias.alias for alias in aliases]
70+
updated_advisory.append(advisory)
71+
72+
if len(updated_advisory) > batch_size:
73+
updated_advisory_count += bulk_update(
74+
model=Advisory,
75+
items=updated_advisory,
76+
fields=["old_aliases"],
77+
logger=print,
78+
)
79+
80+
updated_advisory_count += bulk_update(
81+
model=Advisory,
82+
items=updated_advisory,
83+
fields=["old_aliases"],
84+
logger=print,
85+
)
86+
87+
operations = [
88+
# Rename aliases field to old_aliases
89+
migrations.AlterModelOptions(
90+
name="advisory",
91+
options={"ordering": ["date_published", "unique_content_id"]},
92+
),
93+
migrations.AlterUniqueTogether(
94+
name="advisory",
95+
unique_together={("unique_content_id", "date_published", "url")},
96+
),
97+
migrations.RenameField(
98+
model_name="advisory",
99+
old_name="aliases",
100+
new_name="old_aliases",
101+
),
102+
migrations.AddField(
103+
model_name="advisory",
104+
name="aliases",
105+
field=models.ManyToManyField(related_name="advisories", to="vulnerabilities.alias"),
106+
),
107+
# Populate the new M2M aliases relation
108+
migrations.RunPython(
109+
code=populate_new_advisory_aliases_field,
110+
reverse_code=reverse_populate_new_advisory_aliases_field,
111+
),
112+
# Delete old_alias field
113+
migrations.RemoveField(
114+
model_name="advisory",
115+
name="old_aliases",
116+
),
117+
]

vulnerabilities/models.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,7 +1318,10 @@ class Advisory(models.Model):
13181318
max_length=32,
13191319
blank=True,
13201320
)
1321-
aliases = models.JSONField(blank=True, default=list, help_text="A list of alias strings")
1321+
aliases = models.ManyToManyField(
1322+
Alias,
1323+
related_name="advisories",
1324+
)
13221325
summary = models.TextField(
13231326
blank=True,
13241327
)
@@ -1353,8 +1356,8 @@ class Advisory(models.Model):
13531356
objects = AdvisoryQuerySet.as_manager()
13541357

13551358
class Meta:
1356-
unique_together = ["aliases", "unique_content_id", "date_published", "url"]
1357-
ordering = ["aliases", "date_published", "unique_content_id"]
1359+
unique_together = ["unique_content_id", "date_published", "url"]
1360+
ordering = ["date_published", "unique_content_id"]
13581361

13591362
def save(self, *args, **kwargs):
13601363
checksum = hashlib.md5()

vulnerabilities/tests/test_changelog.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from vulnerabilities import models
1818
from vulnerabilities.importer import AffectedPackage
1919
from vulnerabilities.pipelines.npm_importer import NpmImporterPipeline
20+
from vulnerabilities.pipes.advisory import get_or_create_aliases
2021

2122

2223
@pytest.mark.django_db
@@ -37,8 +38,8 @@ def test_package_changelog():
3738
fixed_version=SemverVersion("1.0"),
3839
).to_dict()
3940
],
40-
aliases=["CVE-123"],
4141
)
42+
adv.aliases.add(*get_or_create_aliases(["CVE-123"]))
4243
NpmImporterPipeline().import_advisory(advisory=adv)
4344
assert models.PackageChangeLog.objects.filter(package=pkg).count() == 1
4445
NpmImporterPipeline().import_advisory(advisory=adv)
@@ -65,8 +66,8 @@ def test_package_changelog():
6566
affected_version_range=NpmVersionRange.from_native(">=2.0"),
6667
).to_dict()
6768
],
68-
aliases=["CVE-145"],
6969
)
70+
adv.aliases.add(*get_or_create_aliases(["CVE-123"]))
7071
NpmImporterPipeline().import_advisory(advisory=adv)
7172
assert models.PackageChangeLog.objects.filter(package=pkg1).count() == 1
7273
NpmImporterPipeline().import_advisory(advisory=adv)
@@ -96,8 +97,8 @@ def test_vulnerability_changelog():
9697
fixed_version=SemverVersion("1.0"),
9798
).to_dict()
9899
],
99-
aliases=["CVE-TEST-1234"],
100100
)
101+
adv.aliases.add(*get_or_create_aliases(["CVE-TEST-1234"]))
101102
NpmImporterPipeline().import_advisory(advisory=adv)
102103
# 1 Changelogs is expected here:
103104
# 1 for importing vuln details
@@ -129,8 +130,8 @@ def test_vulnerability_changelog_software_version():
129130
fixed_version=SemverVersion("1.0"),
130131
).to_dict()
131132
],
132-
aliases=["CVE-TEST-1234"],
133133
)
134+
adv.aliases.add(*get_or_create_aliases(["CVE-TEST-1234"]))
134135
NpmImporterPipeline().import_advisory(advisory=adv)
135136
npm_vulnerability_log = models.VulnerabilityChangeLog.objects.first()
136137

0 commit comments

Comments
 (0)