From 69de4077d85b25ee21291095376a1a91ac22f845 Mon Sep 17 00:00:00 2001 From: David Davis Date: Wed, 20 May 2026 16:07:18 +0000 Subject: [PATCH] Add support for overwrite when adding content Plumb the new pulpcore `overwrite` parameter to sign_and_create and signed_add_and_remove and override AptRepository.check_content_overwrite to exempt incoming packages already in the version that were produced by the signing workflow (tracked via DebPackageSigningResult). Assisted-By: GitHub Copilot (Claude) --- CHANGES/+overwrite-signing-noop.feature | 4 + pulp_deb/app/models/repository.py | 23 ++++++ pulp_deb/app/tasks/signing.py | 10 ++- .../functional/api/test_package_signing.py | 76 +++++++++++++++++++ pyproject.toml | 2 +- 5 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 CHANGES/+overwrite-signing-noop.feature diff --git a/CHANGES/+overwrite-signing-noop.feature b/CHANGES/+overwrite-signing-noop.feature new file mode 100644 index 00000000..31f2331f --- /dev/null +++ b/CHANGES/+overwrite-signing-noop.feature @@ -0,0 +1,4 @@ +Added support for the new `overwrite` parameter on the APT repository modify +endpoint. Packages already produced by Pulp's signing workflow (tracked via +`DebPackageSigningResult`) and present in the repository version are exempted +from the overwrite check so the operation remains a NOOP. diff --git a/pulp_deb/app/models/repository.py b/pulp_deb/app/models/repository.py index 9f5089c7..77b58824 100644 --- a/pulp_deb/app/models/repository.py +++ b/pulp_deb/app/models/repository.py @@ -34,6 +34,7 @@ SourcePackage, SourcePackageReleaseComponent, ) +from pulp_deb.app.models.signing_service import DebPackageSigningResult log = logging.getLogger(__name__) @@ -148,6 +149,28 @@ def package_signing_fingerprint_release_overrides_map(self): for override in self.package_signing_fingerprint_release_overrides.all() } + def check_content_overwrite(self, version, add_content_pks, remove_content_pks=None): + """ + Exempt signing-NOOP packages from the overwrite check. + + A previously-signed package returned from the signing-result cache may already + be in the version, making the add a NOOP. Skip those so genuine overwrites are + still rejected. + """ + # If package signing is enabled, filter previously signed packages from the add list. + if self.package_signing_service_id is not None: + existing_pks = set(version.content.values_list("pk", flat=True)) + signing_noop_pks = set( + DebPackageSigningResult.objects.filter( + result__in=[pk for pk in add_content_pks if pk in existing_pks], + ).values_list("result", flat=True) + ) + add_content_pks = [pk for pk in add_content_pks if pk not in signing_noop_pks] + + super().check_content_overwrite( + version, add_content_pks, remove_content_pks=remove_content_pks + ) + def initialize_new_version(self, new_version): """ Remove old metadata from the repo before performing anything else for the new version. This diff --git a/pulp_deb/app/tasks/signing.py b/pulp_deb/app/tasks/signing.py index 27f237a7..969946dd 100644 --- a/pulp_deb/app/tasks/signing.py +++ b/pulp_deb/app/tasks/signing.py @@ -206,7 +206,7 @@ def _sign_package(package, signing_service, signing_fingerprint, package_release def signed_add_and_remove( - repository_pk, add_content_units, remove_content_units, base_version_pk=None + repository_pk, add_content_units, remove_content_units, base_version_pk=None, overwrite=True ): repo = AptRepository.objects.get(pk=repository_pk) @@ -270,4 +270,10 @@ async def _bounded_sign(pkg_tuple): if str(new_prc.pk) not in add_content_units: add_content_units.append(str(new_prc.pk)) - return add_and_remove(repository_pk, add_content_units, remove_content_units, base_version_pk) + return add_and_remove( + repository_pk, + add_content_units, + remove_content_units, + base_version_pk, + overwrite=overwrite, + ) diff --git a/pulp_deb/tests/functional/api/test_package_signing.py b/pulp_deb/tests/functional/api/test_package_signing.py index c66d9dd2..bf9202fd 100644 --- a/pulp_deb/tests/functional/api/test_package_signing.py +++ b/pulp_deb/tests/functional/api/test_package_signing.py @@ -353,6 +353,82 @@ def test_signed_repo_modify( assert [signed_package_href] == [pkg.pulp_href for pkg in results] +def test_signed_repo_modify_overwrite_false_noop( + tmp_path, + monitor_task, + signing_gpg_metadata, + deb_package_signing_service, + deb_repository_factory, + deb_package_factory, + deb_release_component_factory, + deb_package_release_component_factory, + apt_repository_api, + apt_package_api, +): + """ + Re-adding an unsigned package with overwrite=False should NOOP, not raise. + + The first add transparently signs the package and caches the result. A second + add of the same unsigned package would normally produce the same signed + package (already in the version) and trigger the pulpcore overwrite check. + The deb-specific override should exempt this signing-NOOP case. + """ + _, fingerprint, _ = signing_gpg_metadata + + repository = deb_repository_factory( + package_signing_service=deb_package_signing_service.pulp_href, + package_signing_fingerprint=fingerprint, + ) + + file_to_upload = shutil.copy( + get_local_package_absolute_path("frigg_1.0_ppc64.deb"), + tmp_path, + ) + created_package = deb_package_factory(file=file_to_upload) + package_href = created_package.pulp_href + + release_component = deb_release_component_factory( + distribution=str(uuid.uuid4()), component="main" + ).pulp_href + prc = deb_package_release_component_factory( + package=package_href, + release_component=release_component, + ).pulp_href + + # First add: package gets signed and the result gets stored. + monitor_task( + apt_repository_api.modify( + repository.pulp_href, + { + "add_content_units": [package_href, release_component, prc], + "overwrite": False, + }, + ).task + ) + repository = apt_repository_api.read(repository.pulp_href) + signed_package = apt_package_api.list( + repository_version=repository.latest_version_href + ).results[0] + first_version_href = repository.latest_version_href + + # Second add of the same unsigned package: should NOOP rather than raise + # ContentOverwriteError, because the already signed package is already present. + task_result = monitor_task( + apt_repository_api.modify( + repository.pulp_href, + { + "add_content_units": [package_href, release_component, prc], + "overwrite": False, + }, + ).task + ) + repository = apt_repository_api.read(repository.pulp_href) + assert repository.latest_version_href == first_version_href + assert task_result.created_resources == [] + results = apt_package_api.list(repository_version=repository.latest_version_href).results + assert [signed_package.pulp_href] == [pkg.pulp_href for pkg in results] + + def test_already_signed_package( tmp_path, add_package_to_repo, diff --git a/pyproject.toml b/pyproject.toml index 7969914b..a4d25f91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ requires-python = ">=3.11" dependencies = [ # All things django and asyncio are deliberately left to pulpcore # Example transitive requirements: asgiref, asyncio, aiohttp - "pulpcore>=3.107.0,<3.115", + "pulpcore>=3.111.1,<3.115", "python-debian>=0.1.44,<0.2.0", "python-gnupg>=0.5,<0.6", "jsonschema>=4.6,<5.0",