Skip to content

Commit 5bf23bd

Browse files
JonoYangtdruez
andauthored
448 none package uid (#454)
* Scope the packages QuerySet by current project #448 Signed-off-by: Thomas Druez <[email protected]> * Bump scancode to 31.0.0rc2 #448 * The new scancode version provides a fix to the error encountered in #448 * Update expected test results Signed-off-by: Jono Yang <[email protected]> * Ensure package_uid unique and indexed #448 * TODO: Do we want to make blank and null true on the field since we have existing empty values and the pipelines do not set a package_uid when creating a DiscoveredPackage? Signed-off-by: Jono Yang <[email protected]> * Make DiscoveredPackage.package_uid unique within a Project #448 Signed-off-by: Thomas Druez <[email protected]> * Simplify update_or_create_package and fix unit test #448 Add a override option to update_from_data Signed-off-by: Thomas Druez <[email protected]> Co-authored-by: Thomas Druez <[email protected]>
1 parent 14d3b71 commit 5bf23bd

10 files changed

+70
-39
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 4.0.5 on 2022-06-17 06:58
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('scanpipe', '0016_discoveredpackage_package_uid'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='discoveredpackage',
15+
name='package_uid',
16+
field=models.CharField(blank=True, db_index=True, help_text='Unique identifier for this package.', max_length=1024),
17+
),
18+
migrations.AddConstraint(
19+
model_name='discoveredpackage',
20+
constraint=models.UniqueConstraint(condition=models.Q(('package_uid', ''), _negated=True), fields=('project', 'package_uid'), name='scanpipe_discoveredpackage_unique_package_uid_within_project'),
21+
),
22+
]

scanpipe/models.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,6 +1729,7 @@ class DiscoveredPackage(
17291729
package_uid = models.CharField(
17301730
max_length=1024,
17311731
blank=True,
1732+
db_index=True,
17321733
help_text=_("Unique identifier for this package."),
17331734
)
17341735

@@ -1740,6 +1741,13 @@ class DiscoveredPackage(
17401741

17411742
class Meta:
17421743
ordering = ["uuid"]
1744+
constraints = [
1745+
models.UniqueConstraint(
1746+
fields=["project", "package_uid"],
1747+
condition=~Q(package_uid=""),
1748+
name="%(app_label)s_%(class)s_unique_package_uid_within_project",
1749+
),
1750+
]
17431751

17441752
def __str__(self):
17451753
return self.package_url or str(self.uuid)
@@ -1807,7 +1815,7 @@ def create_from_data(cls, project, package_data):
18071815
discovered_package.save(save_error=False, capture_exception=False)
18081816
return discovered_package
18091817

1810-
def update_from_data(self, package_data):
1818+
def update_from_data(self, package_data, override=False):
18111819
"""
18121820
Update this discovered package instance with the provided `package_data`.
18131821
The `save()` is called only if at least one field was modified.
@@ -1825,11 +1833,9 @@ def update_from_data(self, package_data):
18251833
continue
18261834

18271835
current_value = getattr(self, field_name, None)
1828-
if not current_value:
1836+
if not current_value or (current_value != value and override):
18291837
setattr(self, field_name, value)
18301838
updated_fields.append(field_name)
1831-
elif current_value != value:
1832-
pass # TODO: handle this case
18331839

18341840
if updated_fields:
18351841
self.save()

scanpipe/pipes/__init__.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,22 +74,18 @@ def update_or_create_package(project, package_data, codebase_resource=None):
7474
DiscoveredPackage using its Package URL and package_uid as a unique key.
7575
"""
7676
purl_data = DiscoveredPackage.extract_purl_data(package_data)
77-
package_uid = package_data.get("package_uid")
78-
purl_data_and_package_uid = {
79-
**purl_data,
80-
"package_uid": package_uid,
81-
}
8277

8378
try:
8479
package = DiscoveredPackage.objects.get(
85-
project=project, **purl_data_and_package_uid
80+
project=project,
81+
package_uid=package_data.get("package_uid"),
82+
**purl_data,
8683
)
8784
except DiscoveredPackage.DoesNotExist:
8885
package = None
8986

9087
if package:
9188
package.update_from_data(package_data)
92-
9389
else:
9490
if codebase_resource:
9591
package = codebase_resource.create_and_add_package(package_data)

scanpipe/pipes/scancode.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343

4444
from scanpipe import pipes
4545
from scanpipe.models import CodebaseResource
46-
from scanpipe.models import DiscoveredPackage
4746

4847
logger = logging.getLogger("scanpipe.pipes")
4948

@@ -392,13 +391,14 @@ def create_codebase_resources(project, scanned_codebase):
392391
defaults=resource_data,
393392
)
394393

395-
# Associate DiscoveredPackage to CodebaseResource, if applicable
396-
if hasattr(scanned_resource, "for_packages"):
397-
for package_uid in scanned_resource.for_packages:
398-
package = DiscoveredPackage.objects.get(package_uid=package_uid)
399-
set_codebase_resource_for_package(
400-
codebase_resource=codebase_resource, discovered_package=package
401-
)
394+
for_packages = getattr(scanned_resource, "for_packages", [])
395+
for package_uid in for_packages:
396+
logger.debug(f"Assign {package_uid} to {codebase_resource}")
397+
package = project.discoveredpackages.get(package_uid=package_uid)
398+
set_codebase_resource_for_package(
399+
codebase_resource=codebase_resource,
400+
discovered_package=package,
401+
)
402402

403403

404404
def create_discovered_packages(project, scanned_codebase):

scanpipe/templates/scanpipe/package_list.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<tbody>
2828
{% for package in object_list %}
2929
<tr class="break-word">
30-
<td style="min-width: 500px;">
30+
<td style="min-width: 500px;" title="{{ package.package_uid }}">
3131
{{ package.package_url }}
3232
</td>
3333
<td style="min-width: 300px; max-width: 400px;">

scanpipe/tests/data/is-npm-1.0.0_scan_package.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"errors": [],
2121
"warnings": [],
2222
"extra_data": {
23-
"spdx_license_list_version": "3.16",
23+
"spdx_license_list_version": "3.17",
2424
"files_count": 3
2525
}
2626
}
@@ -131,7 +131,6 @@
131131
"md5": null,
132132
"sha256": null,
133133
"mime_type": null,
134-
"file_type": null,
135134
"programming_language": null,
136135
"is_binary": false,
137136
"is_text": false,
@@ -170,7 +169,6 @@
170169
"md5": null,
171170
"sha256": null,
172171
"mime_type": null,
173-
"file_type": null,
174172
"programming_language": null,
175173
"is_binary": false,
176174
"is_text": false,
@@ -209,7 +207,6 @@
209207
"md5": "bc4b18b0c8c32b94883d6fc1d675e919",
210208
"sha256": "4044efe5626e2fbc40d3d7ce8b263b831d7644ac179e20cdf15b2794f8934030",
211209
"mime_type": "text/plain",
212-
"file_type": "ASCII text",
213210
"programming_language": "JavaScript",
214211
"is_binary": false,
215212
"is_text": true,
@@ -250,7 +247,6 @@
250247
"md5": "c843e88ecb274d5d573c71be330bff8b",
251248
"sha256": "522879426298e078881de533b9b3a82fa3bece1336a84bddacf4620008b5d0a3",
252249
"mime_type": "application/json",
253-
"file_type": "JSON data",
254250
"programming_language": null,
255251
"is_binary": false,
256252
"is_text": true,
@@ -408,7 +404,6 @@
408404
"md5": "a743e0abf08c28a37ecc4bef4dc02f8c",
409405
"sha256": "e98b263545fe62a00c73b57412cbdbcd0792e013a76f9c88342623ab5e8467c1",
410406
"mime_type": "text/plain",
411-
"file_type": "UTF-8 Unicode text",
412407
"programming_language": null,
413408
"is_binary": false,
414409
"is_text": true,

scanpipe/tests/data/multiple-is-npm-1.0.0_scan_package.json

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"errors": [],
2121
"warnings": [],
2222
"extra_data": {
23-
"spdx_license_list_version": "3.16",
23+
"spdx_license_list_version": "3.17",
2424
"files_count": 6
2525
}
2626
}
@@ -200,7 +200,6 @@
200200
"md5": null,
201201
"sha256": null,
202202
"mime_type": null,
203-
"file_type": null,
204203
"programming_language": null,
205204
"is_binary": false,
206205
"is_text": false,
@@ -239,7 +238,6 @@
239238
"md5": null,
240239
"sha256": null,
241240
"mime_type": null,
242-
"file_type": null,
243241
"programming_language": null,
244242
"is_binary": false,
245243
"is_text": false,
@@ -278,7 +276,6 @@
278276
"md5": "bc4b18b0c8c32b94883d6fc1d675e919",
279277
"sha256": "4044efe5626e2fbc40d3d7ce8b263b831d7644ac179e20cdf15b2794f8934030",
280278
"mime_type": "text/plain",
281-
"file_type": "ASCII text",
282279
"programming_language": "JavaScript",
283280
"is_binary": false,
284281
"is_text": true,
@@ -319,7 +316,6 @@
319316
"md5": "c843e88ecb274d5d573c71be330bff8b",
320317
"sha256": "522879426298e078881de533b9b3a82fa3bece1336a84bddacf4620008b5d0a3",
321318
"mime_type": "application/json",
322-
"file_type": "JSON data",
323319
"programming_language": null,
324320
"is_binary": false,
325321
"is_text": true,
@@ -477,7 +473,6 @@
477473
"md5": "a743e0abf08c28a37ecc4bef4dc02f8c",
478474
"sha256": "e98b263545fe62a00c73b57412cbdbcd0792e013a76f9c88342623ab5e8467c1",
479475
"mime_type": "text/plain",
480-
"file_type": "UTF-8 Unicode text",
481476
"programming_language": null,
482477
"is_binary": false,
483478
"is_text": true,
@@ -593,7 +588,6 @@
593588
"md5": null,
594589
"sha256": null,
595590
"mime_type": null,
596-
"file_type": null,
597591
"programming_language": null,
598592
"is_binary": false,
599593
"is_text": false,
@@ -632,7 +626,6 @@
632626
"md5": null,
633627
"sha256": null,
634628
"mime_type": null,
635-
"file_type": null,
636629
"programming_language": null,
637630
"is_binary": false,
638631
"is_text": false,
@@ -671,7 +664,6 @@
671664
"md5": "bc4b18b0c8c32b94883d6fc1d675e919",
672665
"sha256": "4044efe5626e2fbc40d3d7ce8b263b831d7644ac179e20cdf15b2794f8934030",
673666
"mime_type": "text/plain",
674-
"file_type": "ASCII text",
675667
"programming_language": "JavaScript",
676668
"is_binary": false,
677669
"is_text": true,
@@ -712,7 +704,6 @@
712704
"md5": "c843e88ecb274d5d573c71be330bff8b",
713705
"sha256": "522879426298e078881de533b9b3a82fa3bece1336a84bddacf4620008b5d0a3",
714706
"mime_type": "application/json",
715-
"file_type": "JSON data",
716707
"programming_language": null,
717708
"is_binary": false,
718709
"is_text": true,
@@ -870,7 +861,6 @@
870861
"md5": "a743e0abf08c28a37ecc4bef4dc02f8c",
871862
"sha256": "e98b263545fe62a00c73b57412cbdbcd0792e013a76f9c88342623ab5e8467c1",
872863
"mime_type": "text/plain",
873-
"file_type": "UTF-8 Unicode text",
874864
"programming_language": null,
875865
"is_binary": false,
876866
"is_text": true,

scanpipe/tests/test_models.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from django.core.files.uploadedfile import SimpleUploadedFile
3636
from django.core.management import call_command
3737
from django.db import DataError
38+
from django.db import IntegrityError
3839
from django.db import connection
3940
from django.test import TestCase
4041
from django.test import TransactionTestCase
@@ -50,7 +51,6 @@
5051
from scanpipe.models import ProjectError
5152
from scanpipe.models import Run
5253
from scanpipe.models import RunInProgressError
53-
from scanpipe.models import WebhookSubscription
5454
from scanpipe.models import get_project_work_directory
5555
from scanpipe.pipes.fetch import Download
5656
from scanpipe.pipes.input import copy_input
@@ -1261,6 +1261,10 @@ def test_scanpipe_discovered_package_model_update_from_data(self):
12611261
# Already a value, not updated
12621262
self.assertEqual(package_data1["description"], package.description)
12631263

1264+
updated_fields = package.update_from_data(new_data, override=True)
1265+
self.assertEqual(["description"], updated_fields)
1266+
self.assertEqual(new_data["description"], package.description)
1267+
12641268
def test_scanpipe_model_create_user_creates_auth_token(self):
12651269
basic_user = User.objects.create_user(username="basic_user")
12661270
self.assertTrue(basic_user.auth_token.key)
@@ -1412,6 +1416,23 @@ def test_scanpipe_discovered_package_model_create_from_data(self):
14121416
self.assertEqual(package_count, DiscoveredPackage.objects.count())
14131417
self.assertEqual(project_error_count, ProjectError.objects.count())
14141418

1419+
def test_scanpipe_discovered_package_model_unique_package_uid_in_project(self):
1420+
project1 = Project.objects.create(name="Analysis")
1421+
1422+
self.assertTrue(package_data1["package_uid"])
1423+
package = DiscoveredPackage.create_from_data(project1, package_data1)
1424+
self.assertTrue(package.package_uid)
1425+
1426+
with self.assertRaises(IntegrityError):
1427+
DiscoveredPackage.create_from_data(project1, package_data1)
1428+
1429+
package_data_no_uid = package_data1.copy()
1430+
package_data_no_uid.pop("package_uid")
1431+
package2 = DiscoveredPackage.create_from_data(project1, package_data_no_uid)
1432+
self.assertFalse(package2.package_uid)
1433+
package3 = DiscoveredPackage.create_from_data(project1, package_data_no_uid)
1434+
self.assertFalse(package3.package_uid)
1435+
14151436
@skipIf(connection.vendor == "sqlite", "No max_length constraints on SQLite.")
14161437
def test_scanpipe_codebase_resource_create_and_add_package_errors(self):
14171438
project1 = Project.objects.create(name="Analysis")

scanpipe/tests/test_pipes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,7 @@ def test_scanpipe_pipes_update_or_create_package(self):
11791179
resource1 = CodebaseResource.objects.create(project=p1, path="filename.ext")
11801180
package_data2 = dict(package_data1)
11811181
package_data2["name"] = "new name"
1182+
package_data2["package_uid"] = ""
11821183
package2 = update_or_create_package(p1, package_data2, resource1)
11831184
self.assertNotEqual(package.pk, package2.pk)
11841185
self.assertIn(resource1, package2.codebase_resources.all())

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ install_requires =
7070
# Docker
7171
container_inspector==31.0.0
7272
# ScanCode-toolkit
73-
scancode-toolkit[packages]==31.0.0rc1
73+
scancode-toolkit[packages]==31.0.0rc2
7474
extractcode[full]==31.0.0
7575
commoncode==31.0.0b4
7676
# FetchCode

0 commit comments

Comments
 (0)