pulp-python 3.28.2__py3-none-any.whl → 3.29.0__py3-none-any.whl
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/app/__init__.py +1 -1
- pulp_python/app/migrations/0022_pythonblocklistentry.py +48 -0
- pulp_python/app/models.py +64 -0
- pulp_python/app/serializers.py +122 -1
- pulp_python/app/viewsets.py +78 -2
- pulp_python/tests/functional/api/test_blocklist.py +151 -0
- {pulp_python-3.28.2.dist-info → pulp_python-3.29.0.dist-info}/METADATA +1 -1
- {pulp_python-3.28.2.dist-info → pulp_python-3.29.0.dist-info}/RECORD +12 -10
- {pulp_python-3.28.2.dist-info → pulp_python-3.29.0.dist-info}/WHEEL +0 -0
- {pulp_python-3.28.2.dist-info → pulp_python-3.29.0.dist-info}/entry_points.txt +0 -0
- {pulp_python-3.28.2.dist-info → pulp_python-3.29.0.dist-info}/licenses/LICENSE +0 -0
- {pulp_python-3.28.2.dist-info → pulp_python-3.29.0.dist-info}/top_level.txt +0 -0
pulp_python/app/__init__.py
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Generated by Django 5.2.10 on 2026-04-16 14:00
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
import django_lifecycle.mixins
|
|
5
|
+
import pulpcore.app.models.base
|
|
6
|
+
from django.db import migrations, models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
("python", "0021_pythonrepository_upload_duplicate_filenames"),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.CreateModel(
|
|
17
|
+
name="PythonBlocklistEntry",
|
|
18
|
+
fields=[
|
|
19
|
+
(
|
|
20
|
+
"pulp_id",
|
|
21
|
+
models.UUIDField(
|
|
22
|
+
default=pulpcore.app.models.base.pulp_uuid,
|
|
23
|
+
editable=False,
|
|
24
|
+
primary_key=True,
|
|
25
|
+
serialize=False,
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
("pulp_created", models.DateTimeField(auto_now_add=True)),
|
|
29
|
+
("pulp_last_updated", models.DateTimeField(auto_now=True, null=True)),
|
|
30
|
+
("name", models.TextField(default=None, null=True)),
|
|
31
|
+
("version", models.TextField(default=None, null=True)),
|
|
32
|
+
("filename", models.TextField(default=None, null=True)),
|
|
33
|
+
("added_by", models.TextField(default="")),
|
|
34
|
+
(
|
|
35
|
+
"repository",
|
|
36
|
+
models.ForeignKey(
|
|
37
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
38
|
+
related_name="blocklist_entries",
|
|
39
|
+
to="python.pythonrepository",
|
|
40
|
+
),
|
|
41
|
+
),
|
|
42
|
+
],
|
|
43
|
+
options={
|
|
44
|
+
"default_related_name": "%(app_label)s_%(model_name)s",
|
|
45
|
+
},
|
|
46
|
+
bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model),
|
|
47
|
+
),
|
|
48
|
+
]
|
pulp_python/app/models.py
CHANGED
|
@@ -14,6 +14,7 @@ from django_lifecycle import (
|
|
|
14
14
|
from rest_framework.serializers import ValidationError
|
|
15
15
|
from pulpcore.plugin.models import (
|
|
16
16
|
AutoAddObjPermsMixin,
|
|
17
|
+
BaseModel,
|
|
17
18
|
Content,
|
|
18
19
|
Publication,
|
|
19
20
|
Distribution,
|
|
@@ -399,9 +400,12 @@ class PythonRepository(Repository, AutoAddObjPermsMixin):
|
|
|
399
400
|
|
|
400
401
|
When allow_package_substitution is False, reject any new version that would implicitly
|
|
401
402
|
replace existing content with different checksums (content substitution).
|
|
403
|
+
|
|
404
|
+
Also checks newly added content against the repository's blocklist entries.
|
|
402
405
|
"""
|
|
403
406
|
if not self.allow_package_substitution:
|
|
404
407
|
self._check_for_package_substitution(new_version)
|
|
408
|
+
self._check_blocklist(new_version)
|
|
405
409
|
remove_duplicates(new_version)
|
|
406
410
|
validate_repo_version(new_version)
|
|
407
411
|
|
|
@@ -418,3 +422,63 @@ class PythonRepository(Repository, AutoAddObjPermsMixin):
|
|
|
418
422
|
"To allow this, set 'allow_package_substitution' to True on the repository. "
|
|
419
423
|
f"Conflicting packages: {duplicates}"
|
|
420
424
|
)
|
|
425
|
+
|
|
426
|
+
def _check_blocklist(self, new_version):
|
|
427
|
+
"""
|
|
428
|
+
Check newly added content in a repository version against the blocklist.
|
|
429
|
+
"""
|
|
430
|
+
added_content = PythonPackageContent.objects.filter(
|
|
431
|
+
pk__in=new_version.added().values_list("pk", flat=True)
|
|
432
|
+
).only("filename", "name_normalized", "version")
|
|
433
|
+
if added_content.exists():
|
|
434
|
+
self.check_blocklist_for_packages(added_content)
|
|
435
|
+
|
|
436
|
+
def check_blocklist_for_packages(self, packages):
|
|
437
|
+
"""
|
|
438
|
+
Raise a ValidationError if any of the given packages match a blocklist entry.
|
|
439
|
+
"""
|
|
440
|
+
entries = PythonBlocklistEntry.objects.filter(repository=self)
|
|
441
|
+
if not entries.exists():
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
blocked = []
|
|
445
|
+
for pkg in packages:
|
|
446
|
+
for entry in entries:
|
|
447
|
+
if entry.filename and entry.filename == pkg.filename:
|
|
448
|
+
blocked.append(pkg.filename)
|
|
449
|
+
break
|
|
450
|
+
if entry.name == pkg.name_normalized:
|
|
451
|
+
if not entry.version or entry.version == pkg.version:
|
|
452
|
+
blocked.append(pkg.filename)
|
|
453
|
+
break
|
|
454
|
+
if blocked:
|
|
455
|
+
raise ValidationError(
|
|
456
|
+
"Blocklisted packages cannot be added to this repository: "
|
|
457
|
+
"{}".format(", ".join(blocked))
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class PythonBlocklistEntry(BaseModel):
|
|
462
|
+
"""
|
|
463
|
+
An entry in a PythonRepository's package blocklist.
|
|
464
|
+
|
|
465
|
+
Blocklist entries prevent packages from being added to the repository.
|
|
466
|
+
Entries can match by package `name` (all versions), package `name` + `version`,
|
|
467
|
+
or exact `filename`. Exactly one of `name` or `filename` must be provided.
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
name = models.TextField(null=True, default=None)
|
|
471
|
+
version = models.TextField(null=True, default=None)
|
|
472
|
+
filename = models.TextField(null=True, default=None)
|
|
473
|
+
added_by = models.TextField(default="")
|
|
474
|
+
repository = models.ForeignKey(
|
|
475
|
+
PythonRepository, on_delete=models.CASCADE, related_name="blocklist_entries"
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
def __str__(self):
|
|
479
|
+
if self.filename:
|
|
480
|
+
return f"<{self._meta.object_name}: {self.filename}>"
|
|
481
|
+
return f"<{self._meta.object_name}: {self.name} [{self.version or 'all'}]>"
|
|
482
|
+
|
|
483
|
+
class Meta:
|
|
484
|
+
default_related_name = "%(app_label)s_%(model_name)s"
|
pulp_python/app/serializers.py
CHANGED
|
@@ -6,6 +6,7 @@ from django.conf import settings
|
|
|
6
6
|
from django.db.utils import IntegrityError
|
|
7
7
|
from drf_spectacular.utils import extend_schema_serializer
|
|
8
8
|
from packaging.requirements import Requirement
|
|
9
|
+
from packaging.version import Version, InvalidVersion
|
|
9
10
|
from rest_framework import serializers
|
|
10
11
|
from pypi_attestations import AttestationError
|
|
11
12
|
from pydantic import TypeAdapter, ValidationError
|
|
@@ -13,9 +14,10 @@ from urllib.parse import urljoin
|
|
|
13
14
|
|
|
14
15
|
from pulpcore.plugin import models as core_models
|
|
15
16
|
from pulpcore.plugin import serializers as core_serializers
|
|
16
|
-
from pulpcore.plugin.util import get_domain, get_prn, get_current_authenticated_user
|
|
17
|
+
from pulpcore.plugin.util import get_domain, get_prn, get_current_authenticated_user, reverse
|
|
17
18
|
|
|
18
19
|
from pulp_python.app import models as python_models
|
|
20
|
+
from pulp_python.app.utils import canonicalize_name
|
|
19
21
|
from pulp_python.app.provenance import (
|
|
20
22
|
Attestation,
|
|
21
23
|
Provenance,
|
|
@@ -53,6 +55,11 @@ class PythonRepositorySerializer(core_serializers.RepositorySerializer):
|
|
|
53
55
|
default=False,
|
|
54
56
|
required=False,
|
|
55
57
|
)
|
|
58
|
+
blocklist_entries_href = serializers.SerializerMethodField(
|
|
59
|
+
help_text=_("URL to the blocklist entries for this repository."),
|
|
60
|
+
read_only=True,
|
|
61
|
+
)
|
|
62
|
+
|
|
56
63
|
allow_package_substitution = serializers.BooleanField(
|
|
57
64
|
help_text=_(
|
|
58
65
|
"Whether to allow package substitution (replacing existing packages with packages "
|
|
@@ -65,10 +72,15 @@ class PythonRepositorySerializer(core_serializers.RepositorySerializer):
|
|
|
65
72
|
required=False,
|
|
66
73
|
)
|
|
67
74
|
|
|
75
|
+
def get_blocklist_entries_href(self, obj):
|
|
76
|
+
repo_href = reverse("repositories-python/python-detail", kwargs={"pk": obj.pk})
|
|
77
|
+
return f"{repo_href}blocklist_entries/"
|
|
78
|
+
|
|
68
79
|
class Meta:
|
|
69
80
|
fields = core_serializers.RepositorySerializer.Meta.fields + (
|
|
70
81
|
"autopublish",
|
|
71
82
|
"allow_package_substitution",
|
|
83
|
+
"blocklist_entries_href",
|
|
72
84
|
)
|
|
73
85
|
model = python_models.PythonRepository
|
|
74
86
|
|
|
@@ -780,6 +792,115 @@ class PythonRemoteSerializer(core_serializers.RemoteSerializer):
|
|
|
780
792
|
model = python_models.PythonRemote
|
|
781
793
|
|
|
782
794
|
|
|
795
|
+
class PythonBlocklistEntrySerializer(core_serializers.ModelSerializer):
|
|
796
|
+
"""
|
|
797
|
+
Serializer for PythonBlocklistEntry.
|
|
798
|
+
|
|
799
|
+
The `repository` is supplied by the URL (not the request body) and is injected
|
|
800
|
+
by the viewset before saving.
|
|
801
|
+
"""
|
|
802
|
+
|
|
803
|
+
pulp_href = serializers.SerializerMethodField(
|
|
804
|
+
read_only=True,
|
|
805
|
+
help_text=_("The URL of this blocklist entry."),
|
|
806
|
+
)
|
|
807
|
+
repository = core_serializers.DetailRelatedField(
|
|
808
|
+
read_only=True,
|
|
809
|
+
view_name_pattern=r"repositories(-.*/.*)?-detail",
|
|
810
|
+
help_text=_("Repository this blocklist entry belongs to."),
|
|
811
|
+
)
|
|
812
|
+
name = serializers.CharField(
|
|
813
|
+
required=False,
|
|
814
|
+
allow_null=True,
|
|
815
|
+
default=None,
|
|
816
|
+
help_text=_(
|
|
817
|
+
"Package name to block (for all versions). Compared after PEP 503 normalization. "
|
|
818
|
+
"Required when 'filename' is not provided."
|
|
819
|
+
),
|
|
820
|
+
)
|
|
821
|
+
version = serializers.CharField(
|
|
822
|
+
required=False,
|
|
823
|
+
allow_null=True,
|
|
824
|
+
default=None,
|
|
825
|
+
help_text=_("Exact version string to block (e.g. '1.0'). Only used when 'name' is set."),
|
|
826
|
+
)
|
|
827
|
+
filename = serializers.CharField(
|
|
828
|
+
required=False,
|
|
829
|
+
allow_null=True,
|
|
830
|
+
default=None,
|
|
831
|
+
help_text=_("Exact filename to block. Required when 'name' is not provided."),
|
|
832
|
+
)
|
|
833
|
+
added_by = serializers.CharField(
|
|
834
|
+
read_only=True,
|
|
835
|
+
help_text=_("PRN of the user who added this blocklist entry."),
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
def get_pulp_href(self, obj):
|
|
839
|
+
repo_href = reverse("repositories-python/python-detail", kwargs={"pk": obj.repository_id})
|
|
840
|
+
return f"{repo_href}blocklist_entries/{obj.pk}/"
|
|
841
|
+
|
|
842
|
+
def validate(self, data):
|
|
843
|
+
"""
|
|
844
|
+
Validate that the blocklist entry is well-formed and not a duplicate.
|
|
845
|
+
"""
|
|
846
|
+
name = data.get("name")
|
|
847
|
+
filename = data.get("filename")
|
|
848
|
+
version = data.get("version")
|
|
849
|
+
|
|
850
|
+
if version and filename:
|
|
851
|
+
raise serializers.ValidationError(_("'version' cannot be used with 'filename'."))
|
|
852
|
+
if version and not name:
|
|
853
|
+
raise serializers.ValidationError(_("'version' requires 'name' to be provided."))
|
|
854
|
+
if bool(name) == bool(filename):
|
|
855
|
+
raise serializers.ValidationError(
|
|
856
|
+
_("Exactly one of 'name' or 'filename' must be provided.")
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
if version:
|
|
860
|
+
try:
|
|
861
|
+
Version(version)
|
|
862
|
+
except InvalidVersion:
|
|
863
|
+
raise serializers.ValidationError(
|
|
864
|
+
{"version": _("'{}' is not a valid version.").format(version)}
|
|
865
|
+
)
|
|
866
|
+
if name:
|
|
867
|
+
data["name"] = canonicalize_name(name)
|
|
868
|
+
name = data["name"]
|
|
869
|
+
|
|
870
|
+
repository = self.context.get("repository")
|
|
871
|
+
if repository:
|
|
872
|
+
qs = python_models.PythonBlocklistEntry.objects.filter(repository=repository)
|
|
873
|
+
if name and qs.filter(name=name, version=version).exists():
|
|
874
|
+
raise serializers.ValidationError(
|
|
875
|
+
_("A blocklist entry with this name and version already exists.")
|
|
876
|
+
)
|
|
877
|
+
if filename and qs.filter(filename=filename).exists():
|
|
878
|
+
raise serializers.ValidationError(
|
|
879
|
+
_("A blocklist entry with this filename already exists.")
|
|
880
|
+
)
|
|
881
|
+
data["repository"] = repository
|
|
882
|
+
|
|
883
|
+
return data
|
|
884
|
+
|
|
885
|
+
def create(self, validated_data):
|
|
886
|
+
"""
|
|
887
|
+
Create a new blocklist entry, recording the authenticated user in `added_by`.
|
|
888
|
+
"""
|
|
889
|
+
user = get_current_authenticated_user()
|
|
890
|
+
validated_data["added_by"] = get_prn(user) if user else ""
|
|
891
|
+
return super().create(validated_data)
|
|
892
|
+
|
|
893
|
+
class Meta:
|
|
894
|
+
fields = core_serializers.ModelSerializer.Meta.fields + (
|
|
895
|
+
"repository",
|
|
896
|
+
"name",
|
|
897
|
+
"version",
|
|
898
|
+
"filename",
|
|
899
|
+
"added_by",
|
|
900
|
+
)
|
|
901
|
+
model = python_models.PythonBlocklistEntry
|
|
902
|
+
|
|
903
|
+
|
|
783
904
|
class PythonBanderRemoteSerializer(serializers.Serializer):
|
|
784
905
|
"""
|
|
785
906
|
A Serializer for the initial step of creating a Python Remote from a Bandersnatch config file
|
pulp_python/app/viewsets.py
CHANGED
|
@@ -7,6 +7,12 @@ from packaging.utils import canonicalize_name
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from rest_framework import status
|
|
9
9
|
from rest_framework.decorators import action
|
|
10
|
+
from rest_framework.mixins import (
|
|
11
|
+
CreateModelMixin,
|
|
12
|
+
DestroyModelMixin,
|
|
13
|
+
ListModelMixin,
|
|
14
|
+
RetrieveModelMixin,
|
|
15
|
+
)
|
|
10
16
|
from rest_framework.response import Response
|
|
11
17
|
from rest_framework.serializers import ValidationError
|
|
12
18
|
|
|
@@ -143,15 +149,20 @@ class PythonRepositoryViewSet(
|
|
|
143
149
|
If allow_package_substitution is False and the request is **only** adding packages, then a
|
|
144
150
|
package substitution check is performed to provide a quicker error response. Otherwise, the
|
|
145
151
|
check is delegated to the task.
|
|
152
|
+
|
|
153
|
+
Also performs an early blocklist check on added packages.
|
|
146
154
|
"""
|
|
147
155
|
repository = self.get_object()
|
|
156
|
+
add_content_units = request.data.get("add_content_units", [])
|
|
157
|
+
content_ids = [extract_pk(x) for x in add_content_units]
|
|
158
|
+
|
|
159
|
+
self._early_blocklist_check(repository, content_ids)
|
|
160
|
+
|
|
148
161
|
if not repository.allow_package_substitution:
|
|
149
162
|
remove_content_units = request.data.get("remove_content_units", [])
|
|
150
163
|
if remove_content_units or "base_version" in request.data:
|
|
151
164
|
return super().modify(request, pk)
|
|
152
165
|
rvc = repository.latest_version().content
|
|
153
|
-
add_content_units = request.data.get("add_content_units", [])
|
|
154
|
-
content_ids = [extract_pk(x) for x in add_content_units]
|
|
155
166
|
packages = (
|
|
156
167
|
python_models.PythonPackageContent.objects.filter(pk__in=content_ids)
|
|
157
168
|
.exclude(pk__in=rvc)
|
|
@@ -167,6 +178,17 @@ class PythonRepositoryViewSet(
|
|
|
167
178
|
)
|
|
168
179
|
return super().modify(request, pk)
|
|
169
180
|
|
|
181
|
+
def _early_blocklist_check(self, repository, content_ids):
|
|
182
|
+
"""
|
|
183
|
+
Raise early if any added packages match a blocklist entry.
|
|
184
|
+
"""
|
|
185
|
+
if not content_ids:
|
|
186
|
+
return
|
|
187
|
+
packages = python_models.PythonPackageContent.objects.filter(pk__in=content_ids).only(
|
|
188
|
+
"filename", "name_normalized", "version"
|
|
189
|
+
)
|
|
190
|
+
repository.check_blocklist_for_packages(packages)
|
|
191
|
+
|
|
170
192
|
@extend_schema(
|
|
171
193
|
summary="Repair metadata",
|
|
172
194
|
responses={202: AsyncOperationResponseSerializer},
|
|
@@ -216,6 +238,60 @@ class PythonRepositoryViewSet(
|
|
|
216
238
|
return core_viewsets.OperationPostponedResponse(result, request)
|
|
217
239
|
|
|
218
240
|
|
|
241
|
+
class PythonBlocklistEntryViewSet(
|
|
242
|
+
core_viewsets.NamedModelViewSet,
|
|
243
|
+
CreateModelMixin,
|
|
244
|
+
RetrieveModelMixin,
|
|
245
|
+
ListModelMixin,
|
|
246
|
+
DestroyModelMixin,
|
|
247
|
+
):
|
|
248
|
+
"""
|
|
249
|
+
ViewSet for managing blocklist entries on a PythonRepository.
|
|
250
|
+
|
|
251
|
+
Blocklist entries prevent packages from being added to the repository.
|
|
252
|
+
Entries can match by package `name` (all versions), package `name` + `version`,
|
|
253
|
+
or exact `filename`. Exactly one of `name` or `filename` must be provided.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
endpoint_name = "blocklist_entries"
|
|
257
|
+
router_lookup = "pythonblocklistentry"
|
|
258
|
+
parent_viewset = PythonRepositoryViewSet
|
|
259
|
+
parent_lookup_kwargs = {"repository_pk": "repository__pk"}
|
|
260
|
+
serializer_class = python_serializers.PythonBlocklistEntrySerializer
|
|
261
|
+
queryset = python_models.PythonBlocklistEntry.objects.all()
|
|
262
|
+
ordering = ("-pulp_created",)
|
|
263
|
+
|
|
264
|
+
DEFAULT_ACCESS_POLICY = {
|
|
265
|
+
"statements": [
|
|
266
|
+
{
|
|
267
|
+
"action": ["list", "retrieve"],
|
|
268
|
+
"principal": "authenticated",
|
|
269
|
+
"effect": "allow",
|
|
270
|
+
"condition": "has_repository_model_or_domain_or_obj_perms:python.view_pythonrepository", # noqa: E501
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"action": ["create", "destroy"],
|
|
274
|
+
"principal": "authenticated",
|
|
275
|
+
"effect": "allow",
|
|
276
|
+
"condition": [
|
|
277
|
+
"has_repository_model_or_domain_or_obj_perms:python.modify_pythonrepository",
|
|
278
|
+
"has_repository_model_or_domain_or_obj_perms:python.view_pythonrepository",
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
def get_serializer_context(self):
|
|
285
|
+
"""
|
|
286
|
+
Inject the parent repository into the serializer context so that `validate()` can check for
|
|
287
|
+
duplicate entries. The guard on `repository_pk` prevents errors during schema generation.
|
|
288
|
+
"""
|
|
289
|
+
context = super().get_serializer_context()
|
|
290
|
+
if self.kwargs.get("repository_pk"):
|
|
291
|
+
context["repository"] = self.get_parent_object()
|
|
292
|
+
return context
|
|
293
|
+
|
|
294
|
+
|
|
219
295
|
class PythonRepositoryVersionViewSet(core_viewsets.RepositoryVersionViewSet):
|
|
220
296
|
"""
|
|
221
297
|
PythonRepositoryVersion represents a single Python repository version.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from pulpcore.tests.functional.utils import PulpTaskError
|
|
4
|
+
from pulp_python.tests.functional.constants import PYTHON_EGG_FILENAME, PYTHON_EGG_URL
|
|
5
|
+
|
|
6
|
+
CONTENT_BODY = {"relative_path": PYTHON_EGG_FILENAME, "file_url": PYTHON_EGG_URL}
|
|
7
|
+
BLOCKED_MSG = "Blocklisted packages cannot be added to this repository"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.parallel
|
|
11
|
+
def test_crd_entry(python_bindings, python_repo):
|
|
12
|
+
"""
|
|
13
|
+
CRD operations on blocklist entries return correct fields and update the entry count.
|
|
14
|
+
"""
|
|
15
|
+
entries_data = [
|
|
16
|
+
({"name": "shelf-reader"}, "shelf-reader", None, None),
|
|
17
|
+
({"name": "shelf-reader", "version": "0.1"}, "shelf-reader", "0.1", None),
|
|
18
|
+
({"filename": PYTHON_EGG_FILENAME}, None, None, PYTHON_EGG_FILENAME),
|
|
19
|
+
]
|
|
20
|
+
for body_kwargs, name, version, filename in entries_data:
|
|
21
|
+
entry = python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
|
|
22
|
+
python_repo.pulp_href, python_bindings.PythonPythonBlocklistEntry(**body_kwargs)
|
|
23
|
+
)
|
|
24
|
+
assert entry.name == name
|
|
25
|
+
assert entry.version == version
|
|
26
|
+
assert entry.filename == filename
|
|
27
|
+
assert entry.added_by == "prn:auth.user:1"
|
|
28
|
+
assert entry.pulp_href is not None
|
|
29
|
+
assert entry.prn is not None
|
|
30
|
+
|
|
31
|
+
result = python_bindings.RepositoriesPythonBlocklistEntriesApi.list(python_repo.pulp_href)
|
|
32
|
+
assert result.count == 3
|
|
33
|
+
|
|
34
|
+
entry = result.results[0]
|
|
35
|
+
python_bindings.RepositoriesPythonBlocklistEntriesApi.read(entry.pulp_href)
|
|
36
|
+
|
|
37
|
+
python_bindings.RepositoriesPythonBlocklistEntriesApi.delete(entry.pulp_href)
|
|
38
|
+
result = python_bindings.RepositoriesPythonBlocklistEntriesApi.list(python_repo.pulp_href)
|
|
39
|
+
assert result.count == 2
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.parallel
|
|
43
|
+
@pytest.mark.parametrize(
|
|
44
|
+
"body_kwargs, expected_msg",
|
|
45
|
+
[
|
|
46
|
+
({"name": "shelf-reader"}, "this name and version already exists"),
|
|
47
|
+
({"name": "shelf-reader", "version": "0.1"}, "this name and version already exists"),
|
|
48
|
+
({"filename": PYTHON_EGG_FILENAME}, "this filename already exists"),
|
|
49
|
+
],
|
|
50
|
+
)
|
|
51
|
+
def test_duplicate_entry_rejected(python_bindings, python_repo, body_kwargs, expected_msg):
|
|
52
|
+
"""
|
|
53
|
+
Creating a duplicate entry should fail.
|
|
54
|
+
"""
|
|
55
|
+
python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
|
|
56
|
+
python_repo.pulp_href, python_bindings.PythonPythonBlocklistEntry(**body_kwargs)
|
|
57
|
+
)
|
|
58
|
+
with pytest.raises(python_bindings.ApiException) as ctx:
|
|
59
|
+
python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
|
|
60
|
+
python_repo.pulp_href, python_bindings.PythonPythonBlocklistEntry(**body_kwargs)
|
|
61
|
+
)
|
|
62
|
+
assert ctx.value.status == 400
|
|
63
|
+
assert expected_msg in ctx.value.body
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.mark.parallel
|
|
67
|
+
@pytest.mark.parametrize(
|
|
68
|
+
"body_kwargs, expected_msg",
|
|
69
|
+
[
|
|
70
|
+
({"version": "0.1", "filename": PYTHON_EGG_FILENAME}, "version' cannot be used with"),
|
|
71
|
+
({"version": "0.1"}, "version' requires 'name'"),
|
|
72
|
+
({"name": "shelf-reader", "filename": PYTHON_EGG_FILENAME}, "Exactly one of"),
|
|
73
|
+
({}, "Exactly one of"),
|
|
74
|
+
({"name": "shelf-reader", "version": "not-a-version"}, "not a valid version"),
|
|
75
|
+
],
|
|
76
|
+
)
|
|
77
|
+
def test_invalid_entry_rejected(python_bindings, python_repo, body_kwargs, expected_msg):
|
|
78
|
+
"""
|
|
79
|
+
Creating an entry with invalid data should fail.
|
|
80
|
+
"""
|
|
81
|
+
with pytest.raises(python_bindings.ApiException) as ctx:
|
|
82
|
+
python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
|
|
83
|
+
python_repo.pulp_href, python_bindings.PythonPythonBlocklistEntry(**body_kwargs)
|
|
84
|
+
)
|
|
85
|
+
assert ctx.value.status == 400
|
|
86
|
+
assert expected_msg in ctx.value.body
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.mark.parallel
|
|
90
|
+
def test_upload_blocked(monitor_task, python_bindings, python_repo):
|
|
91
|
+
"""
|
|
92
|
+
Uploading a package matching a blocklist entry is rejected.
|
|
93
|
+
"""
|
|
94
|
+
python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
|
|
95
|
+
python_repo.pulp_href,
|
|
96
|
+
python_bindings.PythonPythonBlocklistEntry(name="shelf-reader", version="0.1"),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
with pytest.raises(PulpTaskError) as exc:
|
|
100
|
+
response = python_bindings.ContentPackagesApi.create(
|
|
101
|
+
repository=python_repo.pulp_href, **CONTENT_BODY
|
|
102
|
+
)
|
|
103
|
+
monitor_task(response.task)
|
|
104
|
+
assert BLOCKED_MSG in exc.value.task.error["description"]
|
|
105
|
+
|
|
106
|
+
repo = python_bindings.RepositoriesPythonApi.read(python_repo.pulp_href)
|
|
107
|
+
assert repo.latest_version_href.endswith("/0/")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@pytest.mark.parallel
|
|
111
|
+
def test_upload_allowed(monitor_task, python_bindings, python_repo):
|
|
112
|
+
"""
|
|
113
|
+
Uploading a package is allowed when the blocklist entry targets a different version.
|
|
114
|
+
"""
|
|
115
|
+
python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
|
|
116
|
+
python_repo.pulp_href,
|
|
117
|
+
python_bindings.PythonPythonBlocklistEntry(name="shelf-reader", version="9.9"),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
response = python_bindings.ContentPackagesApi.create(
|
|
121
|
+
repository=python_repo.pulp_href, **CONTENT_BODY
|
|
122
|
+
)
|
|
123
|
+
monitor_task(response.task)
|
|
124
|
+
|
|
125
|
+
repo = python_bindings.RepositoriesPythonApi.read(python_repo.pulp_href)
|
|
126
|
+
assert repo.latest_version_href.endswith("/1/")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@pytest.mark.parallel
|
|
130
|
+
def test_modify_blocked(monitor_task, python_bindings, python_repo):
|
|
131
|
+
"""
|
|
132
|
+
Adding a blocklisted package via repository modify is rejected.
|
|
133
|
+
"""
|
|
134
|
+
python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
|
|
135
|
+
python_repo.pulp_href,
|
|
136
|
+
python_bindings.PythonPythonBlocklistEntry(name="shelf-reader", version="0.1"),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
response = python_bindings.ContentPackagesApi.create(**CONTENT_BODY)
|
|
140
|
+
task = monitor_task(response.task)
|
|
141
|
+
content = python_bindings.ContentPackagesApi.read(task.created_resources[0])
|
|
142
|
+
|
|
143
|
+
with pytest.raises(python_bindings.ApiException) as exc:
|
|
144
|
+
python_bindings.RepositoriesPythonApi.modify(
|
|
145
|
+
python_repo.pulp_href, {"add_content_units": [content.pulp_href]}
|
|
146
|
+
)
|
|
147
|
+
assert exc.value.status == 400
|
|
148
|
+
assert BLOCKED_MSG in exc.value.body
|
|
149
|
+
|
|
150
|
+
repo = python_bindings.RepositoriesPythonApi.read(python_repo.pulp_href)
|
|
151
|
+
assert repo.latest_version_href.endswith("/0/")
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
pulp_python/__init__.py,sha256=GIuTLoBTc-07dSLJUh8xrZPRz8x-jJ61pfR0J1IjnzI,65
|
|
2
2
|
pulp_python/pytest_plugin.py,sha256=EBivPJa39ZE1QaDWea9candeeJL_W_cBk6Q7Onhw4bE,8604
|
|
3
|
-
pulp_python/app/__init__.py,sha256=
|
|
3
|
+
pulp_python/app/__init__.py,sha256=rWQatXxN5F1fdkajaunRkP5PUdVrWTH0qCZGU8jRHuw,2488
|
|
4
4
|
pulp_python/app/global_access_conditions.py,sha256=MZJtyoVsr-4hRaty6mKDqh3caOHd5UKJjEWLV2crOLs,1080
|
|
5
5
|
pulp_python/app/modelresource.py,sha256=dogoBWibBmQyFpcV-Hp1lu7D2WwSECa5PEShWSIg7mg,1248
|
|
6
|
-
pulp_python/app/models.py,sha256=
|
|
6
|
+
pulp_python/app/models.py,sha256=1lyO4Po_vTS8prrtBL1Lo5qh12fUQpvX6H3x2kVOCrY,17685
|
|
7
7
|
pulp_python/app/provenance.py,sha256=iyhkuNahHiTDK0Djrd4-PlgErA5SJVI0uQOIPj46tEI,2352
|
|
8
8
|
pulp_python/app/replica.py,sha256=W60Kt9CwZSfztRi3-f82Rfp0F_zhzpOTiz35bvRFBBE,1881
|
|
9
|
-
pulp_python/app/serializers.py,sha256=
|
|
9
|
+
pulp_python/app/serializers.py,sha256=Pulopm6BFfDrH4sUmt2x1ZI7mLzHtrQrOp3K9ILrxlw,33853
|
|
10
10
|
pulp_python/app/settings.py,sha256=Cyc_p6U0HQjKpyrRL6JFrK3R7RMQJ9MAgNMJCfzPEiA,255
|
|
11
11
|
pulp_python/app/urls.py,sha256=3-UG9-bkPW_bs8362XBnxbpGqQetyuMOfPaayadMYFo,1387
|
|
12
12
|
pulp_python/app/utils.py,sha256=2MEHK_fuLpZA9w4ohbhcs1D2ii478gwW_iAuSTsu-kw,26740
|
|
13
|
-
pulp_python/app/viewsets.py,sha256=
|
|
13
|
+
pulp_python/app/viewsets.py,sha256=8-FPEgNGY_KEaoJbdE_N5hQg-KOe--OXvxQZ6p6wFhE,34120
|
|
14
14
|
pulp_python/app/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
pulp_python/app/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
pulp_python/app/management/commands/repair-python-metadata.py,sha256=i-mC3UYxIiHJoknRV4xZZ7cQ44lyqWshpbaeYi7as8E,4457
|
|
@@ -36,6 +36,7 @@ pulp_python/app/migrations/0018_packageprovenance.py,sha256=FU67aw6u77TjlPuzxRrR
|
|
|
36
36
|
pulp_python/app/migrations/0019_create_missing_metadata_artifacts.py,sha256=glHecwu0X17RdRHo1MvjDRvrqpJ7EgxvYXy1FOXJvn4,817
|
|
37
37
|
pulp_python/app/migrations/0020_pythonpackagecontent_name_normalized.py,sha256=n-gtLKmhDiydgkM5Fv8OisoFZYPPOhOa8-9nExCCq14,1317
|
|
38
38
|
pulp_python/app/migrations/0021_pythonrepository_upload_duplicate_filenames.py,sha256=Yx-Cgk8DR9J9e9ht00xy0s6ogh2Rh5JS7AbdzSngKLY,384
|
|
39
|
+
pulp_python/app/migrations/0022_pythonblocklistentry.py,sha256=EbtjZuN65myTAHVWrJYbt4mWgWL7EElEYs1XulHB6Bo,1732
|
|
39
40
|
pulp_python/app/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
41
|
pulp_python/app/pypi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
42
|
pulp_python/app/pypi/serializers.py,sha256=w6_doLpaO8Dnzur57MYrTIqEP4I3LI8biu9ipLC66fk,5028
|
|
@@ -56,6 +57,7 @@ pulp_python/tests/functional/utils.py,sha256=ZmOVSa94o0KvH7l42YwuUAcNJo8Eb733QBH
|
|
|
56
57
|
pulp_python/tests/functional/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
58
|
pulp_python/tests/functional/api/test_attestations.py,sha256=_opUZT65c5H2j_8ous-nKGHmDOw8EMjxdooTIQhiXQ8,8640
|
|
58
59
|
pulp_python/tests/functional/api/test_auto_publish.py,sha256=uCIt4LsO61oMk3bDs3LMQDJI8zkKqvY0b1uX16bTxzM,1747
|
|
60
|
+
pulp_python/tests/functional/api/test_blocklist.py,sha256=ichJo5UxDdWcR3pnuRouAsPLVBZ7Wcss7oB-AJEV-s8,5993
|
|
59
61
|
pulp_python/tests/functional/api/test_consume_content.py,sha256=QUOZ_bQ_Ortzitc7sjlMEJzRhPm00wayxjZvEeK18jI,1000
|
|
60
62
|
pulp_python/tests/functional/api/test_crud_content_unit.py,sha256=IjkyVC761ZTOhdM3ksT_5zvvWhMk326BSJlUyql0vFM,13793
|
|
61
63
|
pulp_python/tests/functional/api/test_crud_publications.py,sha256=uoTGHbhKq_mYOYtNV8Tt9cyhzY0zsoG1DhYrfIdYcMc,5646
|
|
@@ -75,9 +77,9 @@ pulp_python/tests/functional/assets/shelf-reader-0.1.tar.gz.publish.attestation,
|
|
|
75
77
|
pulp_python/tests/functional/assets/shelf_reader-0.1-py2-none-any.whl.publish.attestation,sha256=muTQ8dqYSSdx76DlaPjB1REcNIS-aak-Na0TkASxu8M,10426
|
|
76
78
|
pulp_python/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
77
79
|
pulp_python/tests/unit/test_models.py,sha256=TBI0yKsrdbnJSPeBFfxSqhXK7zaNvR6qg5JehGH3Pds,229
|
|
78
|
-
pulp_python-3.
|
|
79
|
-
pulp_python-3.
|
|
80
|
-
pulp_python-3.
|
|
81
|
-
pulp_python-3.
|
|
82
|
-
pulp_python-3.
|
|
83
|
-
pulp_python-3.
|
|
80
|
+
pulp_python-3.29.0.dist-info/licenses/LICENSE,sha256=2ylvL381vKOhdO-w6zkrOxe9lLNBhRQpo9_0EbHC_HM,18046
|
|
81
|
+
pulp_python-3.29.0.dist-info/METADATA,sha256=alLnLZtMqhz4pl-EyaETY-NOOrRG7ziRJEQFO4bcfms,1744
|
|
82
|
+
pulp_python-3.29.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
83
|
+
pulp_python-3.29.0.dist-info/entry_points.txt,sha256=HvqLEXjw_dS5jqAwnE5JiRZFE6f-y5SRtitKLPml2To,115
|
|
84
|
+
pulp_python-3.29.0.dist-info/top_level.txt,sha256=X0hXgXc_bpbiKqVrkt8jD5_QEiQviKbHDwveQcOcJjo,12
|
|
85
|
+
pulp_python-3.29.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|