pulp-python 3.20.1__tar.gz → 3.21.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pulp_python-3.20.1 → pulp_python-3.21.0}/CHANGES.md +11 -2
- {pulp_python-3.20.1 → pulp_python-3.21.0}/PKG-INFO +3 -3
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/__init__.py +1 -1
- pulp_python-3.21.0/pulp_python/app/migrations/0017_pythonpackagecontent_size.py +50 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/models.py +1 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/pypi/views.py +14 -1
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/serializers.py +10 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/tasks/__init__.py +1 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/tasks/sync.py +2 -1
- pulp_python-3.21.0/pulp_python/app/tasks/vulnerability_report.py +30 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/utils.py +19 -5
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/viewsets.py +30 -2
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/pytest_plugin.py +1 -1
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_pypi_apis.py +5 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_pypi_simple_json_api.py +6 -2
- pulp_python-3.21.0/pulp_python/tests/functional/api/test_vulnerability_report.py +48 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/constants.py +5 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python.egg-info/PKG-INFO +3 -3
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python.egg-info/SOURCES.txt +3 -0
- pulp_python-3.21.0/pulp_python.egg-info/requires.txt +4 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pyproject.toml +4 -4
- pulp_python-3.20.1/pulp_python.egg-info/requires.txt +0 -4
- {pulp_python-3.20.1 → pulp_python-3.21.0}/COMMITMENT +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/COPYRIGHT +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/LICENSE +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/MANIFEST.in +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/README.md +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/functest_requirements.txt +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/__init__.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/global_access_conditions.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/management/__init__.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/management/commands/__init__.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/management/commands/repair-python-metadata.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0001_initial.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0001_squashed_0010_update_json_field.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0012_add_domain.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0016_pythonpackagecontent_metadata_sha256.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/__init__.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/modelresource.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/pypi/__init__.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/pypi/serializers.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/replica.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/settings.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/tasks/publish.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/tasks/repair.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/tasks/upload.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/urls.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/webserver_snippets/__init__.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/webserver_snippets/apache.conf +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/__init__.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/__init__.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/__init__.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_consume_content.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_crud_content_unit.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_crud_publications.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_crud_remotes.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_domains.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_download_content.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_export_import.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_full_mirror.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_rbac.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_repair.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_sync.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_upload.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/utils.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/unit/__init__.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/unit/test_models.py +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python.egg-info/dependency_links.txt +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python.egg-info/entry_points.txt +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python.egg-info/top_level.txt +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/setup.cfg +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/test_requirements.txt +0 -0
- {pulp_python-3.20.1 → pulp_python-3.21.0}/unittest_requirements.txt +0 -0
|
@@ -8,9 +8,18 @@
|
|
|
8
8
|
|
|
9
9
|
[//]: # (towncrier release notes start)
|
|
10
10
|
|
|
11
|
-
## 3.
|
|
11
|
+
## 3.21.0 (2025-11-18) {: #3.21.0 }
|
|
12
12
|
|
|
13
|
-
####
|
|
13
|
+
#### Features {: #3.21.0-feature }
|
|
14
|
+
|
|
15
|
+
- Added ability to serve a specific repository version of a PyPI index.
|
|
16
|
+
[#982](https://github.com/pulp/pulp_python/issues/982)
|
|
17
|
+
- Implemented PEP 700 support, adding `versions`, `size` and `upload-time` to the Simple JSON API.
|
|
18
|
+
[#996](https://github.com/pulp/pulp_python/issues/996)
|
|
19
|
+
- Added the new /scan endpoint to the RepositoryVersion viewset to generate vulnerability reports.
|
|
20
|
+
[#1012](https://github.com/pulp/pulp_python/issues/1012)
|
|
21
|
+
|
|
22
|
+
#### Bugfixes {: #3.21.0-bugfix }
|
|
14
23
|
|
|
15
24
|
- Fixed pull-through caching not checking the repository if package was not present on remote.
|
|
16
25
|
[#1004](https://github.com/pulp/pulp_python/issues/1004)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pulp-python
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.21.0
|
|
4
4
|
Summary: pulp-python plugin for the Pulp Project
|
|
5
5
|
Author-email: Pulp Team <pulp-list@redhat.com>
|
|
6
6
|
Project-URL: Homepage, https://pulpproject.org
|
|
@@ -20,9 +20,9 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
20
20
|
Requires-Python: >=3.11
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: pulpcore<3.100,>=3.85.
|
|
23
|
+
Requires-Dist: pulpcore<3.100,>=3.85.3
|
|
24
24
|
Requires-Dist: pkginfo<1.13.0,>=1.12.0
|
|
25
|
-
Requires-Dist: bandersnatch<6.
|
|
25
|
+
Requires-Dist: bandersnatch<6.7,>=6.6.0
|
|
26
26
|
Requires-Dist: pypi-simple<2.0,>=1.5.0
|
|
27
27
|
Dynamic: license-file
|
|
28
28
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Generated by Django 4.2.26 on 2025-11-11 21:43
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models, transaction
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def add_size_to_current_models(apps, schema_editor):
|
|
7
|
+
"""Adds the size to current PythonPackageContent models."""
|
|
8
|
+
PythonPackageContent = apps.get_model("python", "PythonPackageContent")
|
|
9
|
+
RemoteArtifact = apps.get_model("core", "RemoteArtifact")
|
|
10
|
+
package_bulk = []
|
|
11
|
+
for python_package in PythonPackageContent.objects.only("pk", "size").iterator():
|
|
12
|
+
content_artifact = python_package.contentartifact_set.first()
|
|
13
|
+
if content_artifact.artifact:
|
|
14
|
+
artifact = content_artifact.artifact
|
|
15
|
+
else:
|
|
16
|
+
artifact = RemoteArtifact.objects.filter(content_artifact=content_artifact).first()
|
|
17
|
+
python_package.size = artifact.size or 0
|
|
18
|
+
package_bulk.append(python_package)
|
|
19
|
+
if len(package_bulk) == 100000:
|
|
20
|
+
with transaction.atomic():
|
|
21
|
+
PythonPackageContent.objects.bulk_update(
|
|
22
|
+
package_bulk,
|
|
23
|
+
[
|
|
24
|
+
"size",
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
package_bulk = []
|
|
28
|
+
with transaction.atomic():
|
|
29
|
+
PythonPackageContent.objects.bulk_update(
|
|
30
|
+
package_bulk,
|
|
31
|
+
[
|
|
32
|
+
"size",
|
|
33
|
+
],
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Migration(migrations.Migration):
|
|
38
|
+
|
|
39
|
+
dependencies = [
|
|
40
|
+
("python", "0016_pythonpackagecontent_metadata_sha256"),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
operations = [
|
|
44
|
+
migrations.AddField(
|
|
45
|
+
model_name="pythonpackagecontent",
|
|
46
|
+
name="size",
|
|
47
|
+
field=models.BigIntegerField(default=0),
|
|
48
|
+
),
|
|
49
|
+
migrations.RunPython(add_size_to_current_models, migrations.RunPython.noop, elidable=True),
|
|
50
|
+
]
|
|
@@ -193,6 +193,7 @@ class PythonPackageContent(Content):
|
|
|
193
193
|
python_version = models.TextField()
|
|
194
194
|
sha256 = models.CharField(db_index=True, max_length=64)
|
|
195
195
|
metadata_sha256 = models.CharField(max_length=64, null=True)
|
|
196
|
+
size = models.BigIntegerField(default=0)
|
|
196
197
|
# yanked and yanked_reason are not implemented because they are mutable
|
|
197
198
|
|
|
198
199
|
# From pulpcore
|
|
@@ -105,10 +105,13 @@ class PyPIMixin:
|
|
|
105
105
|
"""Finds the repository version this distribution is serving."""
|
|
106
106
|
pub = distribution.publication
|
|
107
107
|
rep = distribution.repository
|
|
108
|
+
rep_version = distribution.repository_version
|
|
108
109
|
if pub:
|
|
109
110
|
return pub.repository_version or pub.repository.latest_version()
|
|
110
111
|
elif rep:
|
|
111
112
|
return rep.latest_version()
|
|
113
|
+
elif rep_version:
|
|
114
|
+
return rep_version
|
|
112
115
|
else:
|
|
113
116
|
raise Http404("No repository associated with this index")
|
|
114
117
|
|
|
@@ -302,6 +305,9 @@ class SimpleView(PackageUploadMixin, ViewSet):
|
|
|
302
305
|
"sha256": release_package.digests.get("sha256", ""),
|
|
303
306
|
"requires_python": release_package.requires_python,
|
|
304
307
|
"metadata_sha256": (release_package.metadata_digests or {}).get("sha256"),
|
|
308
|
+
"size": release_package.size,
|
|
309
|
+
"upload_time": release_package.upload_time,
|
|
310
|
+
"version": release_package.version,
|
|
305
311
|
}
|
|
306
312
|
|
|
307
313
|
rfilter = get_remote_package_filter(remote)
|
|
@@ -343,12 +349,19 @@ class SimpleView(PackageUploadMixin, ViewSet):
|
|
|
343
349
|
return redirect(urljoin(self.base_content_url, f"{path}/simple/{normalized}/"))
|
|
344
350
|
if content:
|
|
345
351
|
packages = content.filter(name__normalize=normalized).values(
|
|
346
|
-
"filename",
|
|
352
|
+
"filename",
|
|
353
|
+
"sha256",
|
|
354
|
+
"metadata_sha256",
|
|
355
|
+
"requires_python",
|
|
356
|
+
"size",
|
|
357
|
+
"pulp_created",
|
|
358
|
+
"version",
|
|
347
359
|
)
|
|
348
360
|
local_releases = {
|
|
349
361
|
p["filename"]: {
|
|
350
362
|
**p,
|
|
351
363
|
"url": urljoin(self.base_content_url, f"{path}/{p['filename']}"),
|
|
364
|
+
"upload_time": p["pulp_created"],
|
|
352
365
|
}
|
|
353
366
|
for p in packages
|
|
354
367
|
}
|
|
@@ -53,6 +53,9 @@ class PythonDistributionSerializer(core_serializers.DistributionSerializer):
|
|
|
53
53
|
queryset=core_models.Publication.objects.exclude(complete=False),
|
|
54
54
|
allow_null=True,
|
|
55
55
|
)
|
|
56
|
+
repository_version = core_serializers.RepositoryVersionRelatedField(
|
|
57
|
+
required=False, help_text=_("RepositoryVersion to be served."), allow_null=True
|
|
58
|
+
)
|
|
56
59
|
base_url = serializers.SerializerMethodField(read_only=True)
|
|
57
60
|
allow_uploads = serializers.BooleanField(
|
|
58
61
|
default=True, help_text=_("Allow packages to be uploaded to this index.")
|
|
@@ -74,6 +77,7 @@ class PythonDistributionSerializer(core_serializers.DistributionSerializer):
|
|
|
74
77
|
class Meta:
|
|
75
78
|
fields = core_serializers.DistributionSerializer.Meta.fields + (
|
|
76
79
|
"publication",
|
|
80
|
+
"repository_version",
|
|
77
81
|
"allow_uploads",
|
|
78
82
|
"remote",
|
|
79
83
|
)
|
|
@@ -277,6 +281,10 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
|
|
|
277
281
|
),
|
|
278
282
|
read_only=True,
|
|
279
283
|
)
|
|
284
|
+
size = serializers.IntegerField(
|
|
285
|
+
help_text=_("The size of the package in bytes."),
|
|
286
|
+
read_only=True,
|
|
287
|
+
)
|
|
280
288
|
sha256 = serializers.CharField(
|
|
281
289
|
default="",
|
|
282
290
|
help_text=_("The SHA256 digest of this package."),
|
|
@@ -368,6 +376,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
|
|
|
368
376
|
"filename",
|
|
369
377
|
"packagetype",
|
|
370
378
|
"python_version",
|
|
379
|
+
"size",
|
|
371
380
|
"sha256",
|
|
372
381
|
"metadata_sha256",
|
|
373
382
|
)
|
|
@@ -421,6 +430,7 @@ class PythonPackageContentUploadSerializer(PythonPackageContentSerializer):
|
|
|
421
430
|
data["artifact"] = artifact
|
|
422
431
|
data["sha256"] = artifact.sha256
|
|
423
432
|
data["relative_path"] = filename
|
|
433
|
+
data["size"] = artifact.size
|
|
424
434
|
data.update(parse_project_metadata(vars(metadata)))
|
|
425
435
|
# Overwrite filename from metadata
|
|
426
436
|
data["filename"] = filename
|
|
@@ -59,9 +59,10 @@ def sync(remote_pk, repository_pk, mirror):
|
|
|
59
59
|
|
|
60
60
|
def create_bandersnatch_config(remote):
|
|
61
61
|
"""Modifies the global Bandersnatch config state for this sync"""
|
|
62
|
-
config = BandersnatchConfig()
|
|
62
|
+
config = BandersnatchConfig()
|
|
63
63
|
config["mirror"]["master"] = remote.url
|
|
64
64
|
config["mirror"]["workers"] = str(remote.download_concurrency)
|
|
65
|
+
config["mirror"]["allow_non_https"] = "true"
|
|
65
66
|
if not config.has_section("plugins"):
|
|
66
67
|
config.add_section("plugins")
|
|
67
68
|
config["plugins"]["enabled"] = "blocklist_release\n"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pulpcore.plugin.models import RepositoryVersion
|
|
2
|
+
from pulpcore.plugin.sync import sync_to_async_iterable
|
|
3
|
+
|
|
4
|
+
from pulp_python.app.models import PythonPackageContent
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def get_repo_version_content(repo_version_pk: str):
|
|
8
|
+
"""
|
|
9
|
+
Retrieve Python package content from a repository version for vulnerability scanning.
|
|
10
|
+
"""
|
|
11
|
+
repo_version = await RepositoryVersion.objects.aget(pk=repo_version_pk)
|
|
12
|
+
content_units = PythonPackageContent.objects.filter(pk__in=repo_version.content).only(
|
|
13
|
+
"name", "version"
|
|
14
|
+
)
|
|
15
|
+
ecosystem = "PyPI"
|
|
16
|
+
async for content in sync_to_async_iterable(content_units):
|
|
17
|
+
repo_content_osv_data = _build_osv_data(content.name, ecosystem, content.version)
|
|
18
|
+
repo_content_osv_data["repo_version"] = repo_version
|
|
19
|
+
repo_content_osv_data["content"] = content
|
|
20
|
+
yield repo_content_osv_data
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _build_osv_data(name, ecosystem, version=None):
|
|
24
|
+
"""
|
|
25
|
+
Build an OSV data structure for vulnerability queries.
|
|
26
|
+
"""
|
|
27
|
+
osv_data = {"package": {"name": name, "ecosystem": ecosystem}}
|
|
28
|
+
if version:
|
|
29
|
+
osv_data["version"] = version
|
|
30
|
+
return osv_data
|
|
@@ -7,6 +7,7 @@ import zipfile
|
|
|
7
7
|
import json
|
|
8
8
|
from collections import defaultdict
|
|
9
9
|
from django.conf import settings
|
|
10
|
+
from django.utils import timezone
|
|
10
11
|
from jinja2 import Template
|
|
11
12
|
from packaging.utils import canonicalize_name
|
|
12
13
|
from packaging.requirements import Requirement
|
|
@@ -18,7 +19,7 @@ PYPI_LAST_SERIAL = "X-PYPI-LAST-SERIAL"
|
|
|
18
19
|
"""TODO This serial constant is temporary until Python repositories implements serials"""
|
|
19
20
|
PYPI_SERIAL_CONSTANT = 1000000000
|
|
20
21
|
|
|
21
|
-
SIMPLE_API_VERSION = "1.
|
|
22
|
+
SIMPLE_API_VERSION = "1.1"
|
|
22
23
|
|
|
23
24
|
simple_index_template = """<!DOCTYPE html>
|
|
24
25
|
<html>
|
|
@@ -161,6 +162,7 @@ def parse_metadata(project, version, distribution):
|
|
|
161
162
|
package["sha256"] = distribution.get("digests", {}).get("sha256") or ""
|
|
162
163
|
package["python_version"] = distribution.get("python_version") or ""
|
|
163
164
|
package["requires_python"] = distribution.get("requires_python") or ""
|
|
165
|
+
package["size"] = distribution.get("size") or 0
|
|
164
166
|
|
|
165
167
|
return package
|
|
166
168
|
|
|
@@ -223,6 +225,7 @@ def artifact_to_python_content_data(filename, artifact, domain=None):
|
|
|
223
225
|
metadata = get_project_metadata_from_file(temp_file.name)
|
|
224
226
|
data = parse_project_metadata(vars(metadata))
|
|
225
227
|
data["sha256"] = artifact.sha256
|
|
228
|
+
data["size"] = artifact.size
|
|
226
229
|
data["filename"] = filename
|
|
227
230
|
data["pulp_domain"] = domain or artifact.pulp_domain
|
|
228
231
|
data["_pulp_domain"] = data["pulp_domain"]
|
|
@@ -403,7 +406,6 @@ def python_content_to_download_info(content, base_path, domain=None):
|
|
|
403
406
|
components.insert(2, domain.name)
|
|
404
407
|
url = "/".join(components)
|
|
405
408
|
md5 = artifact.md5 if artifact and artifact.md5 else ""
|
|
406
|
-
size = artifact.size if artifact and artifact.size else 0
|
|
407
409
|
return {
|
|
408
410
|
"comment_text": "",
|
|
409
411
|
"digests": {"md5": md5, "sha256": content.sha256},
|
|
@@ -414,7 +416,7 @@ def python_content_to_download_info(content, base_path, domain=None):
|
|
|
414
416
|
"packagetype": content.packagetype,
|
|
415
417
|
"python_version": content.python_version,
|
|
416
418
|
"requires_python": content.requires_python or None,
|
|
417
|
-
"size": size,
|
|
419
|
+
"size": content.size,
|
|
418
420
|
"upload_time": str(content.pulp_created),
|
|
419
421
|
"upload_time_iso_8601": str(content.pulp_created.isoformat()),
|
|
420
422
|
"url": url,
|
|
@@ -471,20 +473,32 @@ def write_simple_detail_json(project_name, project_packages):
|
|
|
471
473
|
{"sha256": package["metadata_sha256"]} if package["metadata_sha256"] else False
|
|
472
474
|
),
|
|
473
475
|
# yanked and yanked_reason are not implemented because they are mutable
|
|
476
|
+
# (v1.1, PEP 700)
|
|
477
|
+
"size": package["size"],
|
|
478
|
+
"upload-time": format_upload_time(package["upload_time"]),
|
|
474
479
|
# TODO in the future:
|
|
475
|
-
# size, upload-time (v1.1, PEP 700)
|
|
476
480
|
# core-metadata (PEP 7.14)
|
|
477
481
|
# provenance (v1.3, PEP 740)
|
|
478
482
|
}
|
|
479
483
|
for package in project_packages
|
|
480
484
|
],
|
|
485
|
+
# (v1.1, PEP 700)
|
|
486
|
+
"versions": sorted(set(package["version"] for package in project_packages)),
|
|
481
487
|
# TODO in the future:
|
|
482
|
-
# versions (v1.1, PEP 700)
|
|
483
488
|
# alternate-locations (v1.2, PEP 708)
|
|
484
489
|
# project-status (v1.4, PEP 792 - pypi and docs differ)
|
|
485
490
|
}
|
|
486
491
|
|
|
487
492
|
|
|
493
|
+
def format_upload_time(upload_time):
|
|
494
|
+
"""Formats the upload time to be in Zulu time. UTC with Z suffix"""
|
|
495
|
+
if upload_time:
|
|
496
|
+
if upload_time.tzinfo:
|
|
497
|
+
dt = upload_time.astimezone(timezone.utc)
|
|
498
|
+
return dt.isoformat().replace("+00:00", "Z")
|
|
499
|
+
return None
|
|
500
|
+
|
|
501
|
+
|
|
488
502
|
class PackageIncludeFilter:
|
|
489
503
|
"""A special class to help filter Package's based on a remote's include/exclude"""
|
|
490
504
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from bandersnatch.configuration import BandersnatchConfig
|
|
2
2
|
from django.db import transaction
|
|
3
3
|
from drf_spectacular.utils import extend_schema
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
from rest_framework import status
|
|
5
6
|
from rest_framework.decorators import action
|
|
6
7
|
from rest_framework.response import Response
|
|
@@ -12,7 +13,7 @@ from pulpcore.plugin.serializers import (
|
|
|
12
13
|
AsyncOperationResponseSerializer,
|
|
13
14
|
RepositorySyncURLSerializer,
|
|
14
15
|
)
|
|
15
|
-
from pulpcore.plugin.tasking import dispatch
|
|
16
|
+
from pulpcore.plugin.tasking import check_content, dispatch
|
|
16
17
|
|
|
17
18
|
from pulp_python.app import models as python_models
|
|
18
19
|
from pulp_python.app import serializers as python_serializers
|
|
@@ -205,9 +206,36 @@ class PythonRepositoryVersionViewSet(core_viewsets.RepositoryVersionViewSet):
|
|
|
205
206
|
"has_repository_model_or_domain_or_obj_perms:python.view_pythonrepository",
|
|
206
207
|
],
|
|
207
208
|
},
|
|
209
|
+
{
|
|
210
|
+
"action": ["scan"],
|
|
211
|
+
"principal": "authenticated",
|
|
212
|
+
"effect": "allow",
|
|
213
|
+
"condition": [
|
|
214
|
+
"has_repository_model_or_domain_or_obj_perms:python.view_pythonrepository",
|
|
215
|
+
],
|
|
216
|
+
},
|
|
208
217
|
],
|
|
209
218
|
}
|
|
210
219
|
|
|
220
|
+
@extend_schema(
|
|
221
|
+
summary="Generate vulnerability report", responses={202: AsyncOperationResponseSerializer}
|
|
222
|
+
)
|
|
223
|
+
@action(detail=True, methods=["post"], serializer_class=None)
|
|
224
|
+
def scan(self, request, repository_pk, **kwargs):
|
|
225
|
+
"""
|
|
226
|
+
Scan a repository version for vulnerabilities.
|
|
227
|
+
"""
|
|
228
|
+
repository_version = self.get_object()
|
|
229
|
+
func = (
|
|
230
|
+
f"{tasks.get_repo_version_content.__module__}.{tasks.get_repo_version_content.__name__}"
|
|
231
|
+
)
|
|
232
|
+
task = dispatch(
|
|
233
|
+
check_content,
|
|
234
|
+
shared_resources=[repository_version.repository],
|
|
235
|
+
args=[func, [repository_version.pk]],
|
|
236
|
+
)
|
|
237
|
+
return core_viewsets.OperationPostponedResponse(task, request)
|
|
238
|
+
|
|
211
239
|
|
|
212
240
|
class PythonDistributionViewSet(core_viewsets.DistributionViewSet, core_viewsets.RolesMixin):
|
|
213
241
|
"""
|
|
@@ -496,7 +524,7 @@ class PythonRemoteViewSet(core_viewsets.RemoteViewSet, core_viewsets.RolesMixin)
|
|
|
496
524
|
bander_config_file = serializer.validated_data.get("config")
|
|
497
525
|
name = serializer.validated_data.get("name")
|
|
498
526
|
policy = serializer.validated_data.get("policy")
|
|
499
|
-
bander_config = BandersnatchConfig(bander_config_file.file.name)
|
|
527
|
+
bander_config = BandersnatchConfig(Path(bander_config_file.file.name))
|
|
500
528
|
data = {
|
|
501
529
|
"name": name,
|
|
502
530
|
"policy": policy,
|
|
@@ -73,7 +73,7 @@ def python_distribution_factory(python_bindings, gen_object_with_cleanup):
|
|
|
73
73
|
ver_href = f"{repo_href}versions/{version}/"
|
|
74
74
|
else:
|
|
75
75
|
ver_href = get_href(version)
|
|
76
|
-
body
|
|
76
|
+
body["repository_version"] = ver_href
|
|
77
77
|
else:
|
|
78
78
|
body["repository"] = repo_href
|
|
79
79
|
kwargs = {}
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_pypi_apis.py
RENAMED
|
@@ -267,6 +267,11 @@ def test_pypi_json(python_remote_factory, python_repo_with_sync, python_distribu
|
|
|
267
267
|
distro = python_distribution_factory(repository=repo)
|
|
268
268
|
response = requests.get(urljoin(distro.base_url, "pypi/shelf-reader/json"))
|
|
269
269
|
assert_pypi_json(response.json())
|
|
270
|
+
# Test serving a repository version
|
|
271
|
+
distro = python_distribution_factory(repository=repo, version="1")
|
|
272
|
+
assert distro.repository is None
|
|
273
|
+
response = requests.get(urljoin(distro.base_url, "pypi/shelf-reader/json"))
|
|
274
|
+
assert_pypi_json(response.json())
|
|
270
275
|
|
|
271
276
|
|
|
272
277
|
@pytest.mark.parallel
|
|
@@ -11,7 +11,7 @@ from pulp_python.tests.functional.constants import (
|
|
|
11
11
|
PYTHON_WHEEL_URL,
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
API_VERSION = "1.
|
|
14
|
+
API_VERSION = "1.1"
|
|
15
15
|
PYPI_SERIAL_CONSTANT = 1000000000
|
|
16
16
|
|
|
17
17
|
PYPI_TEXT_HTML = "text/html"
|
|
@@ -69,6 +69,7 @@ def test_simple_json_detail_api(
|
|
|
69
69
|
assert data["meta"] == {"api-version": API_VERSION, "_last-serial": PYPI_SERIAL_CONSTANT}
|
|
70
70
|
assert data["name"] == "shelf-reader"
|
|
71
71
|
assert data["files"]
|
|
72
|
+
assert data["versions"] == ["0.1"]
|
|
72
73
|
|
|
73
74
|
# Check data of a wheel
|
|
74
75
|
file_whl = next(
|
|
@@ -83,7 +84,8 @@ def test_simple_json_detail_api(
|
|
|
83
84
|
assert file_whl["data-dist-info-metadata"] == {
|
|
84
85
|
"sha256": "ed333f0db05d77e933a157b7225b403ada9a2f93318d77b41b662eba78bac350"
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
+
assert file_whl["size"] == 22455
|
|
88
|
+
assert file_whl["upload-time"] is not None
|
|
87
89
|
# Check data of a tarball
|
|
88
90
|
file_tar = next((i for i in data["files"] if i["filename"] == "shelf-reader-0.1.tar.gz"), None)
|
|
89
91
|
assert file_tar is not None, "tar file not found"
|
|
@@ -93,6 +95,8 @@ def test_simple_json_detail_api(
|
|
|
93
95
|
}
|
|
94
96
|
assert file_tar["requires-python"] is None
|
|
95
97
|
assert file_tar["data-dist-info-metadata"] is False
|
|
98
|
+
assert file_tar["size"] == 19097
|
|
99
|
+
assert file_tar["upload-time"] is not None
|
|
96
100
|
|
|
97
101
|
|
|
98
102
|
@pytest.mark.parallel
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from pulp_python.tests.functional.constants import (
|
|
4
|
+
PYPI_URL,
|
|
5
|
+
VULNERABILITY_REPORT_TEST_PACKAGE_NAME,
|
|
6
|
+
VULNERABILITY_REPORT_TEST_PACKAGES,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.parallel
|
|
11
|
+
def test_vulnerability_report(
|
|
12
|
+
pulpcore_bindings, python_bindings, python_repo, python_remote_factory, monitor_task
|
|
13
|
+
):
|
|
14
|
+
|
|
15
|
+
# Sync the test repository.
|
|
16
|
+
remote = python_remote_factory(url=PYPI_URL, includes=VULNERABILITY_REPORT_TEST_PACKAGES)
|
|
17
|
+
sync_data = dict(remote=remote.pulp_href)
|
|
18
|
+
response = python_bindings.RepositoriesPythonApi.sync(python_repo.pulp_href, sync_data)
|
|
19
|
+
monitor_task(response.task)
|
|
20
|
+
|
|
21
|
+
# get repo latest version
|
|
22
|
+
repo = python_bindings.RepositoriesPythonApi.read(python_repo.pulp_href)
|
|
23
|
+
latest_version_href = repo.latest_version_href
|
|
24
|
+
|
|
25
|
+
# scan
|
|
26
|
+
response = python_bindings.RepositoriesPythonVersionsApi.scan(
|
|
27
|
+
python_python_repository_version_href=latest_version_href
|
|
28
|
+
)
|
|
29
|
+
monitor_task(response.task)
|
|
30
|
+
|
|
31
|
+
# checks
|
|
32
|
+
vulns_list = pulpcore_bindings.VulnReportApi.list()
|
|
33
|
+
assert len(vulns_list.results) > 0
|
|
34
|
+
for results in vulns_list.results:
|
|
35
|
+
assert len(results.vulns) > 0
|
|
36
|
+
for vuln in results.vulns:
|
|
37
|
+
assert VULNERABILITY_REPORT_TEST_PACKAGE_NAME.lower() in (
|
|
38
|
+
affected["package"]["name"] for affected in vuln["affected"]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
repo_version = python_bindings.RepositoriesPythonVersionsApi.read(latest_version_href)
|
|
42
|
+
assert repo_version.vuln_report is not None
|
|
43
|
+
|
|
44
|
+
python_packages = python_bindings.ContentPackagesApi.list(
|
|
45
|
+
name=VULNERABILITY_REPORT_TEST_PACKAGE_NAME, repository_version=latest_version_href
|
|
46
|
+
)
|
|
47
|
+
for content in python_packages.results:
|
|
48
|
+
assert content.vuln_report is not None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pulp-python
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.21.0
|
|
4
4
|
Summary: pulp-python plugin for the Pulp Project
|
|
5
5
|
Author-email: Pulp Team <pulp-list@redhat.com>
|
|
6
6
|
Project-URL: Homepage, https://pulpproject.org
|
|
@@ -20,9 +20,9 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
20
20
|
Requires-Python: >=3.11
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: pulpcore<3.100,>=3.85.
|
|
23
|
+
Requires-Dist: pulpcore<3.100,>=3.85.3
|
|
24
24
|
Requires-Dist: pkginfo<1.13.0,>=1.12.0
|
|
25
|
-
Requires-Dist: bandersnatch<6.
|
|
25
|
+
Requires-Dist: bandersnatch<6.7,>=6.6.0
|
|
26
26
|
Requires-Dist: pypi-simple<2.0,>=1.5.0
|
|
27
27
|
Dynamic: license-file
|
|
28
28
|
|
|
@@ -46,6 +46,7 @@ pulp_python/app/migrations/0013_add_rbac_permissions.py
|
|
|
46
46
|
pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py
|
|
47
47
|
pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py
|
|
48
48
|
pulp_python/app/migrations/0016_pythonpackagecontent_metadata_sha256.py
|
|
49
|
+
pulp_python/app/migrations/0017_pythonpackagecontent_size.py
|
|
49
50
|
pulp_python/app/migrations/__init__.py
|
|
50
51
|
pulp_python/app/pypi/__init__.py
|
|
51
52
|
pulp_python/app/pypi/serializers.py
|
|
@@ -55,6 +56,7 @@ pulp_python/app/tasks/publish.py
|
|
|
55
56
|
pulp_python/app/tasks/repair.py
|
|
56
57
|
pulp_python/app/tasks/sync.py
|
|
57
58
|
pulp_python/app/tasks/upload.py
|
|
59
|
+
pulp_python/app/tasks/vulnerability_report.py
|
|
58
60
|
pulp_python/app/webserver_snippets/__init__.py
|
|
59
61
|
pulp_python/app/webserver_snippets/apache.conf
|
|
60
62
|
pulp_python/app/webserver_snippets/nginx.conf
|
|
@@ -78,5 +80,6 @@ pulp_python/tests/functional/api/test_rbac.py
|
|
|
78
80
|
pulp_python/tests/functional/api/test_repair.py
|
|
79
81
|
pulp_python/tests/functional/api/test_sync.py
|
|
80
82
|
pulp_python/tests/functional/api/test_upload.py
|
|
83
|
+
pulp_python/tests/functional/api/test_vulnerability_report.py
|
|
81
84
|
pulp_python/tests/unit/__init__.py
|
|
82
85
|
pulp_python/tests/unit/test_models.py
|
|
@@ -7,7 +7,7 @@ build-backend = 'setuptools.build_meta'
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "pulp-python"
|
|
10
|
-
version = "3.
|
|
10
|
+
version = "3.21.0"
|
|
11
11
|
description = "pulp-python plugin for the Pulp Project"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
authors = [
|
|
@@ -26,9 +26,9 @@ classifiers=[
|
|
|
26
26
|
]
|
|
27
27
|
requires-python = ">=3.11"
|
|
28
28
|
dependencies = [
|
|
29
|
-
"pulpcore>=3.85.
|
|
29
|
+
"pulpcore>=3.85.3,<3.100",
|
|
30
30
|
"pkginfo>=1.12.0,<1.13.0",
|
|
31
|
-
"bandersnatch>=6.
|
|
31
|
+
"bandersnatch>=6.6.0,<6.7",
|
|
32
32
|
"pypi-simple>=1.5.0,<2.0",
|
|
33
33
|
]
|
|
34
34
|
|
|
@@ -76,7 +76,7 @@ ignore = [
|
|
|
76
76
|
[tool.bumpversion]
|
|
77
77
|
# This section is managed by the plugin template. Do not edit manually.
|
|
78
78
|
|
|
79
|
-
current_version = "3.
|
|
79
|
+
current_version = "3.21.0"
|
|
80
80
|
commit = false
|
|
81
81
|
tag = false
|
|
82
82
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<alpha>0a)?(?P<patch>\\d+)(\\.(?P<release>[a-z]+))?"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0003_new_sync_filters.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0010_update_json_field.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/app/migrations/0013_add_rbac_permissions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_auto_publish.py
RENAMED
|
File without changes
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_consume_content.py
RENAMED
|
File without changes
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_crud_content_unit.py
RENAMED
|
File without changes
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_crud_publications.py
RENAMED
|
File without changes
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_crud_remotes.py
RENAMED
|
File without changes
|
|
File without changes
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_download_content.py
RENAMED
|
File without changes
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_export_import.py
RENAMED
|
File without changes
|
{pulp_python-3.20.1 → pulp_python-3.21.0}/pulp_python/tests/functional/api/test_full_mirror.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|