diff --git a/CHANGES/+add-pulp-exceptions.feature b/CHANGES/+add-pulp-exceptions.feature new file mode 100644 index 00000000..b0fd6437 --- /dev/null +++ b/CHANGES/+add-pulp-exceptions.feature @@ -0,0 +1 @@ +Add more Pulp Exceptions. diff --git a/pulp_deb/app/exceptions.py b/pulp_deb/app/exceptions.py new file mode 100644 index 00000000..2aa6f92c --- /dev/null +++ b/pulp_deb/app/exceptions.py @@ -0,0 +1,178 @@ +from gettext import gettext as _ + +from pulpcore.plugin.exceptions import PulpException + + +class NoReleaseFile(PulpException): + """ + Raised when no Release file can be found at the expected URL. + """ + + error_code = "DEB0001" + + def __init__(self, url): + super().__init__() + self.url = url + + def __str__(self): + return f"[{self.error_code}] " + _( + "Could not find a Release file at '{url}', try checking the 'url' and " + "'distributions' option on your remote" + ).format(url=self.url) + + +class NoValidSignatureForKey(PulpException): + """ + Raised when verification of a Release file with the provided GPG key fails. + """ + + error_code = "DEB0002" + + def __init__(self, url): + super().__init__() + self.url = url + + def __str__(self): + return f"[{self.error_code}] " + _( + "Unable to verify any Release files from '{url}' using the GPG key provided." + ).format(url=self.url) + + +class NoPackageIndexFile(PulpException): + """ + Raised when no suitable package index file can be found. + """ + + error_code = "DEB0003" + + def __init__(self, relative_dir): + super().__init__() + self.relative_dir = relative_dir + + def __str__(self): + return f"[{self.error_code}] " + _( + "No suitable package index files found in '{relative_dir}'. If you are syncing from " + "a partial mirror, you can ignore this error for individual remotes " + "(ignore_missing_package_indices='True') or system wide " + "(FORCE_IGNORE_MISSING_PACKAGE_INDICES setting)." + ).format(relative_dir=self.relative_dir) + + +class MissingReleaseFileField(PulpException): + """ + Raised when an upstream Release file is missing a required field. + """ + + error_code = "DEB0004" + + def __init__(self, distribution, field): + super().__init__() + self.distribution = distribution + self.field = field + + def __str__(self): + return f"[{self.error_code}] " + _( + "The release file for distribution '{distribution}' is missing " + "the required field '{field}'." + ).format(distribution=self.distribution, field=self.field) + + +class UnknownNoSupportForArchitectureAllValue(PulpException): + """ + Raised when a Release file contains an unknown 'No-Support-for-Architecture-all' value. + """ + + error_code = "DEB0005" + + def __init__(self, release_file_path, unknown_value): + super().__init__() + self.release_file_path = release_file_path + self.unknown_value = unknown_value + + def __str__(self): + return f"[{self.error_code}] " + _( + "The Release file at '{release_file_path}' contains the " + "'No-Support-for-Architecture-all' field, with unknown value '{unknown_value}'! " + "pulp_deb currently only understands the value 'Packages' for this field, please " + "open an issue at https://github.com/pulp/pulp_deb/issues specifying the remote " + "you are attempting to sync, so that we can improve pulp_deb!" + ).format( + release_file_path=self.release_file_path, + unknown_value=self.unknown_value, + ) + + +class DuplicateReleaseFile(PulpException): + """ + Raised when multiple ReleaseFile objects exist where only one is expected. + """ + + error_code = "DEB0006" + + def __init__(self, count): + super().__init__() + self.count = count + + def __str__(self): + return f"[{self.error_code}] " + _( + "Previous ReleaseFile count: {count}. There should only be one." + ).format(count=self.count) + + +class DuplicatePackageIndex(PulpException): + """ + Raised when multiple PackageIndex objects exist where only one is expected. + """ + + error_code = "DEB0007" + + def __init__(self, count): + super().__init__() + self.count = count + + def __str__(self): + return f"[{self.error_code}] " + _( + "Previous PackageIndex count: {count}. There should only be one." + ).format(count=self.count) + + +class SourceSyncNotSupported(PulpException): + """ + Raised when attempting to sync source repositories, which is not yet implemented. + """ + + error_code = "DEB0008" + + def __str__(self): + return f"[{self.error_code}] " + _("Syncing source repositories is not yet implemented.") + + +class DependencySolvingNotSupported(PulpException): + """ + Raised when advanced copy with dependency solving is requested. + """ + + error_code = "DEB0009" + + def __str__(self): + return f"[{self.error_code}] " + _( + "Advanced copy with dependency solving is not yet implemented." + ) + + +class DuplicatePackageChecksumError(PulpException): + """ + Raised when newly added packages have the same name, version, and architecture + but different checksums. + """ + + error_code = "DEB0011" + + def __str__(self): + return f"[{self.error_code}] " + _( + "Cannot create repository version since there are newly added packages with the " + "same name, version, and architecture, but a different checksum. If the log level " + "is DEBUG, you can find a list of affected packages in the Pulp log. You can often " + "work around this issue by restricting syncs to only those distirbution component " + "combinations, that do not contain colliding duplicates!" + ) diff --git a/pulp_deb/app/models/repository.py b/pulp_deb/app/models/repository.py index f2889a34..48e12fab 100644 --- a/pulp_deb/app/models/repository.py +++ b/pulp_deb/app/models/repository.py @@ -15,6 +15,7 @@ ) from pulpcore.plugin.util import batch_qs, get_domain_pk +from pulp_deb.app.exceptions import DuplicatePackageChecksumError from pulp_deb.app.models import ( AptReleaseSigningService, AptRemote, @@ -225,14 +226,7 @@ def handle_duplicate_packages(new_version): ) log.debug(message.format(package_fields, distribution_components)) - message = _( - "Cannot create repository version since there are newly added packages with the " - "same name, version, and architecture, but a different checksum. If the log level " - "is DEBUG, you can find a list of affected packages in the Pulp log. You can often " - "work around this issue by restricting syncs to only those distirbution component " - "combinations, that do not contain colliding duplicates!" - ) - raise ValueError(message) + raise DuplicatePackageChecksumError() # Now remove existing packages that are duplicates of any packages added to new_version if package_qs_added.count() and content_qs_existing.count(): diff --git a/pulp_deb/app/tasks/copy.py b/pulp_deb/app/tasks/copy.py index 576bea44..cdbaa714 100644 --- a/pulp_deb/app/tasks/copy.py +++ b/pulp_deb/app/tasks/copy.py @@ -7,6 +7,7 @@ from pulpcore.plugin.models import RepositoryVersion from pulpcore.plugin.util import get_domain_pk +from pulp_deb.app.exceptions import DependencySolvingNotSupported from pulp_deb.app.models import ( AptRepository, Package, @@ -105,7 +106,7 @@ def process_entry(entry): ) if dependency_solving: - raise NotImplementedError("Advanced copy with dependency solving is not yet implemented.") + raise DependencySolvingNotSupported() for entry in config: ( diff --git a/pulp_deb/app/tasks/synchronizing.py b/pulp_deb/app/tasks/synchronizing.py index e9a2e1a7..6a2ae8d2 100644 --- a/pulp_deb/app/tasks/synchronizing.py +++ b/pulp_deb/app/tasks/synchronizing.py @@ -21,7 +21,7 @@ from django.db.utils import IntegrityError from rest_framework.exceptions import ValidationError -from pulpcore.plugin.exceptions import DigestValidationError +from pulpcore.plugin.exceptions import DigestValidationError, SyncError from pulpcore.plugin.models import ( Artifact, ProgressReport, @@ -47,6 +47,16 @@ CHECKSUM_TYPE_MAP, NO_MD5_WARNING_MESSAGE, ) +from pulp_deb.app.exceptions import ( + DuplicatePackageIndex, + DuplicateReleaseFile, + MissingReleaseFileField, + NoPackageIndexFile, + NoReleaseFile, + NoValidSignatureForKey, + SourceSyncNotSupported, + UnknownNoSupportForArchitectureAllValue, +) from pulp_deb.app.models import ( AptRemote, AptRepository, @@ -73,92 +83,6 @@ log = logging.getLogger(__name__) -class NoReleaseFile(Exception): - """ - Exception to signal, that no file representing a release is present. - """ - - def __init__(self, url, *args, **kwargs): - """ - Exception to signal, that no file representing a release is present. - """ - super().__init__( - "Could not find a Release file at '{}', try checking the 'url' and " - "'distributions' option on your remote".format(url), - *args, - **kwargs, - ) - - -class NoValidSignatureForKey(Exception): - """ - Exception to signal, that verification of release file with provided GPG key fails. - """ - - def __init__(self, url, *args, **kwargs): - """ - Exception to signal, that verification of release file with provided GPG key fails. - """ - super().__init__( - "Unable to verify any Release files from '{}' using the GPG key provided.".format(url), - *args, - **kwargs, - ) - - -class NoPackageIndexFile(Exception): - """ - Exception to signal, that no file representing a package index is present. - """ - - def __init__(self, relative_dir, *args, **kwargs): - """ - Exception to signal, that no file representing a package index is present. - """ - self.relative_dir = relative_dir - message = ( - "No suitable package index files found in '{}'. If you are syncing from a partial " - "mirror, you can ignore this error for individual remotes " - "(ignore_missing_package_indices='True') or system wide " - "(FORCE_IGNORE_MISSING_PACKAGE_INDICES setting)." - ) - super().__init__(_(message).format(relative_dir), *args, **kwargs) - - pass - - -class MissingReleaseFileField(Exception): - """ - Exception signifying that the upstream release file is missing a required field. - """ - - def __init__(self, distribution, field, *args, **kwargs): - """ - The upstream release file is missing a required field. - """ - message = "The release file for distribution '{}' is missing the required field '{}'." - super().__init__(_(message).format(distribution, field), *args, **kwargs) - - -class UnknownNoSupportForArchitectureAllValue(Exception): - """ - Exception Signifying that the Release file contains the 'No-Support-for-Architecture-all' field, - but with a value other than 'Packages'. We interpret this as an error since this would likely - signify some unknown repo format, that pulp_deb is more likely to get wrong than right! - """ - - def __init__(self, release_file_path, unknown_value, *args, **kwargs): - message = ( - "The Release file at '{}' contains the 'No-Support-for-Architecture-all' field, with " - "unknown value '{}'! pulp_deb currently only understands the value 'Packages' for " - "this field, please open an issue at https://github.com/pulp/pulp_deb/issues " - "specifying the remote you are attempting to sync, so that we can improve pulp_deb!" - ) - super().__init__(_(message).format(unknown_value), *args, **kwargs) - - pass - - def synchronize(remote_pk, repository_pk, mirror, optimize): """ Sync content from the remote repository. @@ -180,7 +104,7 @@ def synchronize(remote_pk, repository_pk, mirror, optimize): previous_repo_version = repository.latest_version() if not remote.url: - raise ValueError(_("A remote must have a url specified to synchronize.")) + raise SyncError(_("A remote must have a url specified to synchronize.")) if optimize and mirror: skip_dist = [] @@ -902,7 +826,7 @@ async def _handle_flat_repo( # Handle source package index if self.remote.sync_sources: - raise NotImplementedError("Syncing source repositories is not yet implemented.") + raise SourceSyncNotSupported() # Await all tasks await asyncio.gather(*pending_tasks) @@ -1315,8 +1239,7 @@ def get_previous_release_file(previous_version, distribution): ReleaseFile.objects.filter(distribution=distribution) ) if previous_release_file_qs.count() > 1: - message = "Previous ReleaseFile count: {}. There should only be one." - raise Exception(message.format(previous_release_file_qs.count())) + raise DuplicateReleaseFile(count=previous_release_file_qs.count()) return previous_release_file_qs.first() @@ -1329,8 +1252,7 @@ def _get_previous_package_index(previous_version, relative_path): PackageIndex.objects.filter(relative_path=relative_path) ) if previous_package_index_qs.count() > 1: - message = "Previous PackageIndex count: {}. There should only be one." - raise Exception(message.format(previous_package_index_qs.count())) + raise DuplicatePackageIndex(count=previous_package_index_qs.count()) return previous_package_index_qs.first() diff --git a/pulp_deb/tests/functional/api/test_duplicate_packages.py b/pulp_deb/tests/functional/api/test_duplicate_packages.py index 62d6fcb4..a5da44c2 100644 --- a/pulp_deb/tests/functional/api/test_duplicate_packages.py +++ b/pulp_deb/tests/functional/api/test_duplicate_packages.py @@ -133,6 +133,7 @@ def test_add_duplicates_to_repo( deb_modify_repository(repository, {"add_content_units": [href1, href2]}) # Assert the error message. + assert "[DEB0011]" in str(exception.value.task.error["description"]) assert "Cannot create repository version since there are newly added packages with" in str( exception.value.task.error["description"] ) diff --git a/pulp_deb/tests/functional/api/test_sync.py b/pulp_deb/tests/functional/api/test_sync.py index 9e477c87..bfdd584e 100644 --- a/pulp_deb/tests/functional/api/test_sync.py +++ b/pulp_deb/tests/functional/api/test_sync.py @@ -80,14 +80,14 @@ def test_sync( "architectures": "ppc64", "ignore_missing_package_indices": False, }, - ["No suitable package index files", "ppc64"], + ["[DEB0003]", "No suitable package index files", "ppc64"], ), ( { "architectures": "armeb", "ignore_missing_package_indices": False, }, - ["No suitable package index files", "armeb"], + ["[DEB0003]", "No suitable package index files", "armeb"], ), ], ) @@ -126,7 +126,7 @@ def test_sync_missing_package_indices( ( DEB_FIXTURE_STANDARD_REPOSITORY_NAME, {"distributions": "no_dist"}, - ["Could not find a Release file at"], + ["[DEB0001]", "Could not find a Release file at"], ), ( DEB_FIXTURE_INVALID_REPOSITORY_NAME, @@ -134,7 +134,7 @@ def test_sync_missing_package_indices( "distributions": "nosuite", "gpgkey": DEB_SIGNING_KEY, }, - ["Unable to verify any Release files from", "using the GPG key provided."], + ["[DEB0002]", "Unable to verify any Release files from", "using the GPG key provided."], ), ], ) diff --git a/pulp_deb/tests/unit/test_release_file_helpers.py b/pulp_deb/tests/unit/test_release_file_helpers.py index f3160abe..5d761158 100644 --- a/pulp_deb/tests/unit/test_release_file_helpers.py +++ b/pulp_deb/tests/unit/test_release_file_helpers.py @@ -3,8 +3,8 @@ import pytest from django.test import override_settings +from pulp_deb.app.exceptions import MissingReleaseFileField from pulp_deb.app.tasks.synchronizing import ( - MissingReleaseFileField, _collect_release_artifacts, _parse_release_file_attributes, ) @@ -138,6 +138,7 @@ def test_parse_release_file_attributes_missing_fields_nonflat( with pytest.raises(MissingReleaseFileField) as exc: _parse_release_file_attributes(mock_d_content, mock_main_artifact) + assert "[DEB0004]" in str(exc.value) assert field_name in str(exc.value) @@ -213,4 +214,5 @@ def test_parse_release_file_attributes_permissive_disabled_compoent( with pytest.raises(MissingReleaseFileField) as exc: _parse_release_file_attributes(mock_d_content, mock_main_artifact) + assert "[DEB0004]" in str(exc.value) assert f"missing the required field '{field_name}'" in str(exc.value) diff --git a/pyproject.toml b/pyproject.toml index 4ec0d4f7..00ae36a3 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.85.0,<3.115", + "pulpcore>=3.105.0,<3.115", "python-debian>=0.1.44,<0.2.0", "python-gnupg>=0.5,<0.6", "jsonschema>=4.6,<5.0",