1313)
1414from pulpcore .plugin .models import (
1515 AutoAddObjPermsMixin ,
16+ BaseModel ,
1617 Content ,
1718 Publication ,
1819 Distribution ,
@@ -399,9 +400,12 @@ def finalize_new_version(self, new_version):
399400
400401 When allow_package_substitution is False, reject any new version that would implicitly
401402 replace existing content with different checksums (content substitution).
403+
404+ Also checks newly added content against the repository's blocklist entries.
402405 """
403406 if not self .allow_package_substitution :
404407 self ._check_for_package_substitution (new_version )
408+ self ._check_blocklist (new_version )
405409 remove_duplicates (new_version )
406410 validate_repo_version (new_version )
407411
@@ -413,4 +417,68 @@ def _check_for_package_substitution(self, new_version):
413417 qs = PythonPackageContent .objects .filter (pk__in = new_version .content )
414418 duplicates = collect_duplicates (qs , ("filename" ,))
415419 if duplicates :
416- raise PackageSubstitutionError (duplicates )
420+ raise ValidationError (
421+ "Found duplicate packages being added with the same filename but different checksums. " # noqa: E501
422+ "To allow this, set 'allow_package_substitution' to True on the repository. "
423+ f"Conflicting packages: { duplicates } "
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"
0 commit comments