pulp-container 2.28.0__tar.gz → 2.29.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.
Files changed (155) hide show
  1. {pulp_container-2.28.0 → pulp_container-2.29.0}/CHANGES.md +134 -2
  2. {pulp_container-2.28.0 → pulp_container-2.29.0}/MANIFEST.in +4 -2
  3. {pulp_container-2.28.0 → pulp_container-2.29.0}/PKG-INFO +2 -2
  4. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/__init__.py +1 -1
  5. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/authorization.py +1 -2
  6. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/exceptions.py +33 -0
  7. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/global_access_conditions.py +3 -2
  8. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/json_schemas.py +1 -0
  9. pulp_container-2.29.0/pulp_container/app/migrations/0050_alter_containernamespace_options.py +17 -0
  10. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/models.py +3 -3
  11. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/registry_api.py +119 -19
  12. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/serializers.py +11 -4
  13. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/tasks/__init__.py +1 -0
  14. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/tasks/builder.py +5 -4
  15. pulp_container-2.29.0/pulp_container/app/tasks/migrate_push_repository.py +133 -0
  16. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/tasks/sync_stages.py +35 -10
  17. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/utils.py +19 -3
  18. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/viewsets.py +184 -122
  19. pulp_container-2.29.0/pulp_container/tests/functional/api/test_cancel_blob_upload.py +71 -0
  20. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_content_cache.py +1 -1
  21. pulp_container-2.29.0/pulp_container/tests/functional/api/test_delete_blob.py +123 -0
  22. pulp_container-2.29.0/pulp_container/tests/functional/api/test_delete_manifest.py +97 -0
  23. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_domains.py +22 -4
  24. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_flatpak.py +2 -2
  25. pulp_container-2.29.0/pulp_container/tests/functional/api/test_migrate_push_repository.py +181 -0
  26. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_pull_content.py +40 -1
  27. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_push_content.py +83 -18
  28. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_push_signatures.py +2 -2
  29. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_rbac_push_repositories.py +86 -2
  30. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_rbac_repo_content.py +32 -39
  31. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_rbac_repo_versions.py +71 -3
  32. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_recursive_remove.py +9 -2
  33. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_sign_manifests.py +3 -3
  34. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_tagging_images.py +2 -2
  35. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/conftest.py +31 -0
  36. pulp_container-2.29.0/pulp_container/tests/unit/fixtures/ryuk_0.13.0_manifest_list.json +154 -0
  37. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/unit/test_json_schemas.py +29 -0
  38. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container.egg-info/PKG-INFO +2 -2
  39. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container.egg-info/SOURCES.txt +8 -1
  40. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container.egg-info/requires.txt +1 -1
  41. {pulp_container-2.28.0 → pulp_container-2.29.0}/pyproject.toml +5 -4
  42. {pulp_container-2.28.0 → pulp_container-2.29.0}/COMMITMENT +0 -0
  43. {pulp_container-2.28.0 → pulp_container-2.29.0}/COPYRIGHT +0 -0
  44. {pulp_container-2.28.0 → pulp_container-2.29.0}/LICENSE +0 -0
  45. {pulp_container-2.28.0 → pulp_container-2.29.0}/README.rst +0 -0
  46. {pulp_container-2.28.0 → pulp_container-2.29.0}/functest_requirements.txt +0 -0
  47. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/__init__.py +0 -0
  48. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/access_policy.py +0 -0
  49. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/cache.py +0 -0
  50. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/checks.py +0 -0
  51. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/content.py +0 -0
  52. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/downloaders.py +0 -0
  53. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/dynaconf_hooks.py +0 -0
  54. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/fields.py +0 -0
  55. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/management/__init__.py +0 -0
  56. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/management/commands/__init__.py +0 -0
  57. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/management/commands/container-handle-image-data.py +0 -0
  58. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/management/commands/container-repair-media-type.py +0 -0
  59. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0001_initial.py +0 -0
  60. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0001_squashed_0032_upload_artifact.py +0 -0
  61. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0002_containerrepository.py +0 -0
  62. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0003_oci_mediatype.py +0 -0
  63. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0004_upload.py +0 -0
  64. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0005_contentredirectcontentguard.py +0 -0
  65. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0006_containerpushrepository.py +0 -0
  66. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0007_clear_tags_artifacts_refs.py +0 -0
  67. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0008_include_exclude_tags.py +0 -0
  68. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0009_container_namespace.py +0 -0
  69. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0010_remove_uploadchunk.py +0 -0
  70. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0011_add_container_repository_permissions.py +0 -0
  71. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0012_add_container_namespace_permissions.py +0 -0
  72. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0013_add_pull_push_permissions.py +0 -0
  73. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0014_containerdistribution_private.py +0 -0
  74. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0015_manage_tags_push_repo.py +0 -0
  75. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0016_add_delete_versions_permission.py +0 -0
  76. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0017_add_granular_perms.py +0 -0
  77. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0018_containerdistribution_description.py +0 -0
  78. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0019_DATA_distribution_model_swap.py +0 -0
  79. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0020_update_push_repo_perms.py +0 -0
  80. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0021_data_move_redirect_content_guard_to_core.py +0 -0
  81. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0022_delete_contentredirectcontentguard.py +0 -0
  82. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0023_manifestsignature.py +0 -0
  83. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0024_containerremote_sigstore.py +0 -0
  84. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0025_signature_stored_in_textfield.py +0 -0
  85. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0026_manifest_signing_service.py +0 -0
  86. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0027_data_translate_perms_to_roles.py +0 -0
  87. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0028_add_role_manage_permissions.py +0 -0
  88. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0029_remove_blob_media_type.py +0 -0
  89. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0030_enforce_tagged_manifest_reference.py +0 -0
  90. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0031_replace_charf_with_textf.py +0 -0
  91. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0032_upload_artifact.py +0 -0
  92. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0033_raise_warning_for_repair.py +0 -0
  93. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0034_translate_signed_schema.py +0 -0
  94. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0035_alter_blob_content_ptr_and_more.py +0 -0
  95. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0036_containerpushrepository_pending_blobs_manifests.py +0 -0
  96. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0037_create_pull_through_cache_models.py +0 -0
  97. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0038_add_manifest_metadata_fields.py +0 -0
  98. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0039_manifest_data.py +0 -0
  99. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0040_add_remote_repo_filter.py +0 -0
  100. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0041_add_pull_through_pull_permissions.py +0 -0
  101. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0042_add_manifest_nature_field.py +0 -0
  102. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0043_add_os_arch_image_size_manifest_fields.py +0 -0
  103. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0044_add_domain.py +0 -0
  104. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0045_alter_manifest_compressed_image_size.py +0 -0
  105. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0046_alter_manifest_listed_manifests.py +0 -0
  106. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0047_containernamespace_pulp_labels.py +0 -0
  107. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0048_containerremote_includes_excludes.py +0 -0
  108. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/0049_manifestsignature_fingerprint.py +0 -0
  109. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/migrations/__init__.py +0 -0
  110. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/modelresource.py +0 -0
  111. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/redirects.py +0 -0
  112. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/registry.py +0 -0
  113. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/replica.py +0 -0
  114. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/settings.py +0 -0
  115. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/tasks/download_image_data.py +0 -0
  116. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/tasks/recursive_add.py +0 -0
  117. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/tasks/recursive_remove.py +0 -0
  118. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/tasks/sign.py +0 -0
  119. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/tasks/synchronize.py +0 -0
  120. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/tasks/tag.py +0 -0
  121. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/tasks/untag.py +0 -0
  122. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/token_verification.py +0 -0
  123. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/urls.py +0 -0
  124. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/webserver_snippets/__init__.py +0 -0
  125. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/webserver_snippets/apache.conf +0 -0
  126. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/app/webserver_snippets/nginx.conf +0 -0
  127. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/constants.py +0 -0
  128. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/__init__.py +0 -0
  129. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/__init__.py +0 -0
  130. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/__init__.py +0 -0
  131. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_build_images.py +0 -0
  132. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_crud_distributions.py +0 -0
  133. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_crud_remotes.py +0 -0
  134. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_pull_through_cache.py +0 -0
  135. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_pulpimportexport.py +0 -0
  136. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_rbac_remotes.py +0 -0
  137. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_rbac_sync_repositories.py +0 -0
  138. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_recursive_add.py +0 -0
  139. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_remote_filter_pull_through.py +0 -0
  140. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_repositories_list.py +0 -0
  141. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_sync.py +0 -0
  142. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_sync_signatures.py +0 -0
  143. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/api/test_token_authentication.py +0 -0
  144. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/constants.py +0 -0
  145. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/functional/utils.py +0 -0
  146. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/unit/__init__.py +0 -0
  147. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/unit/test_models.py +0 -0
  148. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/unit/test_serializers.py +0 -0
  149. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container/tests/unit/test_sync_stages.py +0 -0
  150. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container.egg-info/dependency_links.txt +0 -0
  151. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container.egg-info/entry_points.txt +0 -0
  152. {pulp_container-2.28.0 → pulp_container-2.29.0}/pulp_container.egg-info/top_level.txt +0 -0
  153. {pulp_container-2.28.0 → pulp_container-2.29.0}/setup.cfg +0 -0
  154. {pulp_container-2.28.0 → pulp_container-2.29.0}/test_requirements.txt +0 -0
  155. {pulp_container-2.28.0 → pulp_container-2.29.0}/unittest_requirements.txt +0 -0
@@ -8,6 +8,45 @@
8
8
 
9
9
  [//]: # (towncrier release notes start)
10
10
 
11
+ ## 2.29.0 (2026-08-10) {: #2.29.0 }
12
+
13
+ #### Features {: #2.29.0-feature }
14
+
15
+ - Enable DELETE on the Docker v2 manifest endpoint so users can delete manifests by digest.
16
+ [#480](https://github.com/pulp/pulp_container/issues/480)
17
+ - Enable DELETE on the Docker v2 blob endpoint so users can delete blobs by digest.
18
+ [#481](https://github.com/pulp/pulp_container/issues/481)
19
+ - Added support for retrieving blob upload status via `GET /v2/<name>/blobs/uploads/<uuid>`.
20
+ [#483](https://github.com/pulp/pulp_container/issues/483)
21
+ - Added a `migrate` endpoint on push repositories that converts a legacy `ContainerPushRepository`
22
+ into a `ContainerRepository` in place. The repository primary key and version history are
23
+ preserved; only the repository type changes.
24
+ - Added support for cancelling blob uploads via `DELETE /v2/<name>/blobs/uploads/<uuid>`.
25
+ - Changed the default created repository on push to be a ContainerRepository instead of a ContainerPushRepository. ContainerPushRepository will eventually be phased out in future releases.
26
+
27
+ #### Bugfixes {: #2.29.0-bugfix }
28
+
29
+ - Fixed tag-based manifest pulls returning 404 when the client's `Accept` header listed the tagged manifest's media type together with parameters such as q-values, since the media type comparison did not strip them and had no support for the `*/*` wildcard.
30
+ [#2417](https://github.com/pulp/pulp_container/issues/2417)
31
+ - Add transparent repair code during Manifest sync to redownload the raw data in case it was lost during an upgrade.
32
+ - Added OCI manifest as an allowed media type in the Docker manifest list schema.
33
+ - Raised a proper `PulpException` subclass instead of a bare `Exception` when building or pushing an OCI image fails, so the error is not sanitized away by pulpcore in a future release.
34
+
35
+ #### Improved Documentation {: #2.29.0-doc }
36
+
37
+ - Rewrote the RBAC documentation to be more comprehensive and detailed.
38
+
39
+ ---
40
+
41
+ ## 2.28.1 (2026-07-22) {: #2.28.1 }
42
+
43
+ #### Bugfixes {: #2.28.1-bugfix }
44
+
45
+ - Add transparent repair code during Manifest sync to redownload the raw data in case it was lost during an upgrade.
46
+ - Added OCI manifest as an allowed media type in the Docker manifest list schema.
47
+
48
+ ---
49
+
11
50
  ## 2.28.0 (2026-05-29) {: #2.28.0 }
12
51
 
13
52
  #### Features {: #2.28.0-feature }
@@ -59,6 +98,21 @@
59
98
 
60
99
  ---
61
100
 
101
+ ## 2.27.11 (2026-07-29) {: #2.27.11 }
102
+
103
+ No significant changes.
104
+
105
+ ---
106
+
107
+ ## 2.27.10 (2026-06-08) {: #2.27.10 }
108
+
109
+ #### Bugfixes {: #2.27.10-bugfix }
110
+
111
+ - Add transparent repair code during Manifest sync to redownload the raw data in case it was lost during an upgrade.
112
+ - Added OCI manifest as an allowed media type in the Docker manifest list schema.
113
+
114
+ ---
115
+
62
116
  ## 2.27.9 (2026-05-14) {: #2.27.9 }
63
117
 
64
118
  #### Bugfixes {: #2.27.9-bugfix }
@@ -163,7 +217,22 @@ No significant changes.
163
217
 
164
218
  #### Misc {: #2.27.0-misc }
165
219
 
166
- -
220
+
221
+ ---
222
+
223
+ ## 2.26.16 (2026-06-08) {: #2.26.16 }
224
+
225
+ #### Bugfixes {: #2.26.16-bugfix }
226
+
227
+ - Added OCI manifest as an allowed media type in the Docker manifest list schema.
228
+
229
+ ---
230
+
231
+ ## 2.26.15 (2026-06-02) {: #2.26.15 }
232
+
233
+ #### Bugfixes {: #2.26.15-bugfix }
234
+
235
+ - Readded backwards compatibility for manifests that haven't been migrated to new data field. Sync will try to update them to the new format and repair manifests that are missing their artifact, but it is recommended that you run the `pulpcore-manager container-handle-image-data` command to fix all manifests at once. The command has been updated to report broken manifests by repository. Broken manifests in pushed repositories need to be deleted and re-pushed.
167
236
 
168
237
  ---
169
238
 
@@ -292,6 +361,8 @@ No significant changes.
292
361
 
293
362
  ## 2.26.0 (2025-08-14) {: #2.26.0 }
294
363
 
364
+ **WARNING:** If your installation pre-dates pulp_container 2.20, make sure that you run `pulpcore-manager container-handle-image-data` before upgrading to (or past) this release. If your installation began after pulp_container 2.20, or if you have already run this command at some point, this is not necessary.
365
+
295
366
  #### Features {: #2.26.0-feature }
296
367
 
297
368
  - Bump pulpcore upperbound to <3.100. pulp_container is now a Python >=3.11 project.
@@ -329,6 +400,8 @@ No significant changes.
329
400
 
330
401
  ## 2.25.0 (2025-04-15) {: #2.25.0 }
331
402
 
403
+ **WARNING:** If your installation pre-dates pulp_container 2.20, make sure that you run `pulpcore-manager container-handle-image-data` before upgrading to (or past) this release. If your installation began after pulp_container 2.20, or if you have already run this command at some point, this is not necessary.
404
+
332
405
  #### Features {: #2.25.0-feature }
333
406
 
334
407
  - Added full support for domains.
@@ -353,6 +426,14 @@ No significant changes.
353
426
 
354
427
  ---
355
428
 
429
+ ## 2.24.12 (2026-07-21) {: #2.24.12 }
430
+
431
+ #### Bugfixes {: #2.24.12-bugfix }
432
+
433
+ - Added OCI manifest as an allowed media type in the Docker manifest list schema.
434
+
435
+ ---
436
+
356
437
  ## 2.24.11 (2026-04-29) {: #2.24.11 }
357
438
 
358
439
  #### Bugfixes {: #2.24.11-bugfix }
@@ -483,6 +564,23 @@ No significant changes.
483
564
 
484
565
  ---
485
566
 
567
+ ## 2.22.10 (2026-07-23) {: #2.22.10 }
568
+
569
+ #### Bugfixes {: #2.22.10-bugfix }
570
+
571
+ - Fixed OS and architecture filters being silently ignored when filtering manifest lists in the flatpak index.
572
+ - Fixed a serialization issue with the FlatpakIndex cache.
573
+
574
+ ---
575
+
576
+ ## 2.22.9 (2026-07-21) {: #2.22.9 }
577
+
578
+ #### Bugfixes {: #2.22.9-bugfix }
579
+
580
+ - Added OCI manifest as an allowed media type in the Docker manifest list schema.
581
+
582
+ ---
583
+
486
584
  ## 2.22.8 (2026-04-24) {: #2.22.8 }
487
585
 
488
586
  No significant changes.
@@ -659,6 +757,23 @@ No significant changes.
659
757
 
660
758
  ---
661
759
 
760
+ ## 2.20.11 (2026-07-27) {: #2.20.11 }
761
+
762
+ #### Bugfixes {: #2.20.11-bugfix }
763
+
764
+ - Fixed OS and architecture filters being silently ignored when filtering manifest lists in the flatpak index.
765
+ - Fixed a serialization issue with the FlatpakIndex cache.
766
+
767
+ ---
768
+
769
+ ## 2.20.10 (2026-07-21) {: #2.20.10 }
770
+
771
+ #### Bugfixes {: #2.20.10-bugfix }
772
+
773
+ - Added OCI manifest as an allowed media type in the Docker manifest list schema.
774
+
775
+ ---
776
+
662
777
  ## 2.20.9 (2026-04-24) {: #2.20.9 }
663
778
 
664
779
  No significant changes.
@@ -749,6 +864,8 @@ No significant changes.
749
864
 
750
865
  ## 2.20.0 (2024-05-06) {: #2.20.0 }
751
866
 
867
+ **NOTICE:** If you are upgrading from a previous version of pulp_container, make sure that you run `pulpcore-manager container-handle-image-data` at some point before or shortly after upgrading. This is required in order to complete the transition away from storing Manifest data as artifacts. This must be done to avoid the potential for issues with later releases of pulp_container.
868
+
752
869
  ### Features
753
870
 
754
871
  - Updated the Manifest model to no longer rely on artifacts, storing all manifest data internally
@@ -777,7 +894,22 @@ No significant changes.
777
894
 
778
895
  ### Misc
779
896
 
780
- -
897
+ -
898
+
899
+ ---
900
+
901
+ ## 2.19.13 (2026-07-27) {: #2.19.13 }
902
+
903
+ #### Bugfixes {: #2.19.13-bugfix }
904
+
905
+ - Fixed OS and architecture filters being silently ignored when filtering manifest lists in the flatpak index.
906
+ - Fixed a serialization issue with the FlatpakIndex cache.
907
+
908
+ ---
909
+
910
+ ## 2.19.12 (2026-06-08) {: #2.19.12 }
911
+
912
+ No significant changes.
781
913
 
782
914
  ---
783
915
 
@@ -1,11 +1,13 @@
1
- include LICENSE
2
1
  include pulp_container/app/webserver_snippets/*
3
- include pyproject.toml
2
+ include pulp_container/tests/unit/fixtures/*
3
+ include LICENSE
4
4
  include CHANGES.md
5
5
  include COMMITMENT
6
6
  include COPYRIGHT
7
+ include pyproject.toml
7
8
  include functest_requirements.txt
8
9
  include test_requirements.txt
9
10
  include unittest_requirements.txt
10
11
  exclude CONTRIBUTING.md
11
12
  exclude releasing.md
13
+ exclude Makefile
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-container
3
- Version: 2.28.0
3
+ Version: 2.29.0
4
4
  Summary: Container plugin for the Pulp Project
5
5
  Author-email: Pulp Team <pulp-list@redhat.com>
6
6
  Project-URL: Homepage, https://pulpproject.org
@@ -18,7 +18,7 @@ Requires-Python: >=3.11
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: jsonschema<4.27,>=4.4
21
- Requires-Dist: pulpcore<3.115,>=3.111.0
21
+ Requires-Dist: pulpcore<3.130,>=3.111.0
22
22
  Requires-Dist: pyjwt[crypto]<2.14,>=2.4
23
23
  Requires-Dist: pysequoia<0.2.0,>=0.1.33
24
24
  Dynamic: license-file
@@ -24,7 +24,7 @@ class PulpContainerPluginAppConfig(PulpPluginAppConfig):
24
24
 
25
25
  name = "pulp_container.app"
26
26
  label = "container"
27
- version = "2.28.0"
27
+ version = "2.29.0"
28
28
  python_package_name = "pulp-container"
29
29
  domain_compatible = True
30
30
 
@@ -24,7 +24,7 @@ from pulp_container.app.models import (
24
24
  ContainerPullThroughDistribution,
25
25
  )
26
26
 
27
- TOKEN_EXPIRATION_TIME = settings.get("TOKEN_EXPIRATION_TIME", 300)
27
+ TOKEN_EXPIRATION_TIME = getattr(settings, "TOKEN_EXPIRATION_TIME", 300)
28
28
 
29
29
  FakeView = namedtuple("FakeView", "action, get_object, get_serializer")
30
30
 
@@ -295,7 +295,6 @@ class PermissionChecker:
295
295
  if not domain:
296
296
  return False
297
297
 
298
- print("Checking push permissions for path ", path, "and domain ", domain.name)
299
298
  try:
300
299
  distribution = ContainerDistribution.objects.get(base_path=path, pulp_domain=domain)
301
300
  except ContainerDistribution.DoesNotExist:
@@ -1,5 +1,7 @@
1
1
  from rest_framework.exceptions import APIException, NotFound, ParseError
2
2
 
3
+ from pulpcore.plugin.exceptions import PulpException
4
+
3
5
 
4
6
  class BadGateway(APIException):
5
7
  status_code = 502
@@ -90,6 +92,24 @@ class BlobInvalid(ParseError):
90
92
  )
91
93
 
92
94
 
95
+ class BlobUploadUnknown(NotFound):
96
+ """Exception to render a 404 with the code 'BLOB_UPLOAD_UNKNOWN'"""
97
+
98
+ def __init__(self, uuid):
99
+ """Initialize the exception with the upload uuid."""
100
+ super().__init__(
101
+ detail={
102
+ "errors": [
103
+ {
104
+ "code": "BLOB_UPLOAD_UNKNOWN",
105
+ "message": "blob upload unknown to registry",
106
+ "detail": {"uuid": uuid},
107
+ }
108
+ ]
109
+ }
110
+ )
111
+
112
+
93
113
  class ManifestNotFound(NotFound):
94
114
  """Exception to render a 404 with the code 'MANIFEST_UNKNOWN'"""
95
115
 
@@ -144,6 +164,19 @@ class ManifestSignatureInvalid(ParseError):
144
164
  )
145
165
 
146
166
 
167
+ class ContainerBuildError(PulpException):
168
+ """Exception to signal that building or pushing an OCI image failed."""
169
+
170
+ error_code = "CON0001"
171
+
172
+ def __init__(self, message):
173
+ """Initialize the exception with the decoded stderr of the failed podman command."""
174
+ self.message = message
175
+
176
+ def __str__(self):
177
+ return self.message
178
+
179
+
147
180
  class InvalidRequest(ParseError):
148
181
  """An exception to render an HTTP 400 response."""
149
182
 
@@ -28,9 +28,10 @@ def has_namespace_obj_perms(request, view, action, permission):
28
28
  if type(obj) is models.ContainerDistribution:
29
29
  namespace = obj.namespace
30
30
  return request.user.has_perm(permission, namespace)
31
- elif type(obj) is models.ContainerPushRepository:
31
+ elif type(obj) is models.ContainerPushRepository or type(obj) is models.ContainerRepository:
32
32
  for dist in obj.distributions.all():
33
- if request.user.has_perm(permission, dist.cast().namespace):
33
+ namespace = dist.cast().namespace
34
+ if namespace and request.user.has_perm(permission, namespace):
34
35
  return True
35
36
  elif type(obj) is models.ContainerPullThroughDistribution:
36
37
  namespace = obj.namespace
@@ -124,6 +124,7 @@ DOCKER_MANIFEST_LIST_V2_SCHEMA = {
124
124
  "enum": [
125
125
  MEDIA_TYPE.MANIFEST_V2,
126
126
  MEDIA_TYPE.MANIFEST_V1,
127
+ MEDIA_TYPE.MANIFEST_OCI,
127
128
  ],
128
129
  },
129
130
  "size": {"type": "number"},
@@ -0,0 +1,17 @@
1
+ # Generated by Django 5.2.13 on 2026-06-03 19:16
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('container', '0049_manifestsignature_fingerprint'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterModelOptions(
14
+ name='containernamespace',
15
+ options={'permissions': [('namespace_add_containerdistribution', 'Add any distribution to a namespace'), ('namespace_delete_containerdistribution', 'Delete any distribution from a namespace'), ('namespace_view_containerdistribution', 'View any distribution in a namespace'), ('namespace_pull_containerdistribution', 'Pull from any distribution in a namespace'), ('namespace_push_containerdistribution', 'Push to any distribution in a namespace'), ('namespace_change_containerdistribution', 'Change any distribution in a namespace'), ('namespace_view_containerpushrepository', 'View any push repository in a namespace'), ('namespace_view_containerrepository', 'View any repository in a namespace'), ('namespace_modify_content_containerpushrepository', 'Modify content in any push repository in a namespace'), ('namespace_modify_content_containerrepository', 'Modify content in any repository in a namespace'), ('namespace_change_containerpushrepository', 'Update any existing push repository in a namespace'), ('namespace_change_containerrepository', 'Change any repository in a namespace'), ('manage_roles_containernamespace', 'Can manage role assignments on container namespace')]},
16
+ ),
17
+ ]
@@ -2,7 +2,6 @@ import json
2
2
  import os
3
3
  import re
4
4
  import tempfile
5
- import time
6
5
  from gettext import gettext as _
7
6
  from logging import getLogger
8
7
 
@@ -458,6 +457,7 @@ class ContainerNamespace(BaseModel, AutoAddObjPermsMixin):
458
457
  ("namespace_push_containerdistribution", "Push to any distribution in a namespace"),
459
458
  ("namespace_change_containerdistribution", "Change any distribution in a namespace"),
460
459
  ("namespace_view_containerpushrepository", "View any push repository in a namespace"),
460
+ ("namespace_view_containerrepository", "View any repository in a namespace"),
461
461
  (
462
462
  "namespace_modify_content_containerpushrepository",
463
463
  "Modify content in any push repository in a namespace",
@@ -470,6 +470,7 @@ class ContainerNamespace(BaseModel, AutoAddObjPermsMixin):
470
470
  "namespace_change_containerpushrepository",
471
471
  "Update any existing push repository in a namespace",
472
472
  ),
473
+ ("namespace_change_containerrepository", "Change any repository in a namespace"),
473
474
  (
474
475
  "manage_roles_containernamespace",
475
476
  "Can manage role assignments on container namespace",
@@ -947,8 +948,7 @@ INCOMPLETE_EXT = ".part"
947
948
 
948
949
  def generate_filename(instance, filename):
949
950
  """Method for generating upload file name"""
950
- filename = os.path.join(instance.upload_dir, str(instance.pk) + INCOMPLETE_EXT)
951
- return time.strftime(filename)
951
+ return os.path.join(instance.upload_dir, str(instance.pk) + INCOMPLETE_EXT)
952
952
 
953
953
 
954
954
  class Upload(CoreUpload):
@@ -45,7 +45,13 @@ from rest_framework.viewsets import ViewSet
45
45
  from pulpcore.plugin import pulp_hashlib
46
46
  from pulpcore.plugin.exceptions import TimeoutException
47
47
  from pulpcore.plugin.files import PulpTemporaryUploadedFile
48
- from pulpcore.plugin.models import Artifact, ContentArtifact, RemoteArtifact, UploadChunk
48
+ from pulpcore.plugin.models import (
49
+ Artifact,
50
+ ContentArtifact,
51
+ RemoteArtifact,
52
+ Repository,
53
+ UploadChunk,
54
+ )
49
55
  from pulpcore.plugin.tasking import dispatch
50
56
  from pulpcore.plugin.util import get_domain, get_objects_for_user, get_url
51
57
 
@@ -60,6 +66,7 @@ from pulp_container.app.exceptions import (
60
66
  BadGateway,
61
67
  BlobInvalid,
62
68
  BlobNotFound,
69
+ BlobUploadUnknown,
63
70
  GatewayTimeout,
64
71
  InvalidRequest,
65
72
  ManifestInvalid,
@@ -73,7 +80,7 @@ from pulp_container.app.redirects import (
73
80
  FileStorageRedirects,
74
81
  S3StorageRedirects,
75
82
  )
76
- from pulp_container.app.tasks import aadd_and_remove, download_image_data
83
+ from pulp_container.app.tasks import aadd_and_remove, download_image_data, recursive_remove_content
77
84
  from pulp_container.app.token_verification import (
78
85
  RegistryAuthentication,
79
86
  RegistryPermission,
@@ -88,6 +95,7 @@ from pulp_container.app.utils import (
88
95
  get_accepted_media_types,
89
96
  get_full_path,
90
97
  has_task_completed,
98
+ is_media_type_accepted,
91
99
  validate_manifest,
92
100
  )
93
101
  from pulp_container.constants import (
@@ -159,11 +167,11 @@ class ManifestResponse(Response):
159
167
  manifest_media_type = manifest.media_type
160
168
  if request:
161
169
  accepted_media_types = get_accepted_media_types(request.headers)
162
- if (
163
- manifest_media_type not in accepted_media_types
164
- and manifest_media_type != MEDIA_TYPE.MANIFEST_V1
170
+ if accepted_media_types and not is_media_type_accepted(
171
+ accepted_media_types, manifest_media_type
165
172
  ):
166
- raise ManifestNotFound(reference=tag.name)
173
+ if manifest_media_type != MEDIA_TYPE.MANIFEST_V1:
174
+ raise ManifestNotFound(reference=tag.name)
167
175
 
168
176
  # Schema v1 manifests are always returned with the signed content type
169
177
  if manifest_media_type == MEDIA_TYPE.MANIFEST_V1:
@@ -279,7 +287,7 @@ class ContainerRegistryApiMixin:
279
287
  This allows deployments to use custom authentication backends (e.g. remote
280
288
  header-based auth) alongside standard Basic auth for container registry operations.
281
289
  """
282
- if settings.get("TOKEN_AUTH_DISABLED", False):
290
+ if getattr(settings, "TOKEN_AUTH_DISABLED", False):
283
291
  return [RegistryAuthentication, *api_settings.DEFAULT_AUTHENTICATION_CLASSES]
284
292
  return [TokenAuthentication]
285
293
 
@@ -288,7 +296,7 @@ class ContainerRegistryApiMixin:
288
296
  """
289
297
  List of permission classes to check for this view.
290
298
  """
291
- if settings.get("TOKEN_AUTH_DISABLED", False):
299
+ if getattr(settings, "TOKEN_AUTH_DISABLED", False):
292
300
  return [RegistryPermission]
293
301
  return [TokenPermission]
294
302
 
@@ -436,11 +444,9 @@ class ContainerRegistryApiMixin:
436
444
  repository = distribution.repository
437
445
  if repository:
438
446
  repository = repository.cast()
439
- if not repository.PUSH_ENABLED:
440
- raise RepositoryInvalid(name=path, message="Repository is read-only.")
441
447
  elif create:
442
448
  with transaction.atomic():
443
- repository = serializers.ContainerPushRepositorySerializer.get_or_create(
449
+ repository = serializers.ContainerRepositorySerializer.get_or_create(
444
450
  {"name": path, "pulp_domain": domain}
445
451
  )
446
452
  distribution.repository = repository
@@ -451,11 +457,22 @@ class ContainerRegistryApiMixin:
451
457
 
452
458
  def create_dr(self, path, request):
453
459
  domain = get_domain()
460
+ repository_types = [
461
+ models.ContainerRepository.get_pulp_type(),
462
+ models.ContainerPushRepository.get_pulp_type(),
463
+ ]
454
464
  with transaction.atomic():
455
465
  try:
456
- repository = serializers.ContainerPushRepositorySerializer.get_or_create(
457
- {"name": path, "pulp_domain": domain}
458
- )
466
+ # Handle new default of ContainerRepository and fallback old ContainerPushRepository
467
+ repository = Repository.objects.filter(name=path, pulp_domain=domain).first()
468
+ if repository:
469
+ if repository.pulp_type not in repository_types:
470
+ raise RepositoryInvalid(name=path, message="Repository is read-only.")
471
+ repository = repository.cast()
472
+ else:
473
+ repository = serializers.ContainerRepositorySerializer.get_or_create(
474
+ {"name": path, "pulp_domain": domain}
475
+ )
459
476
  distribution = serializers.ContainerDistributionSerializer.get_or_create(
460
477
  {"base_path": path, "name": path, "pulp_domain": domain},
461
478
  {"repository": get_url(repository)},
@@ -464,9 +481,10 @@ class ContainerRegistryApiMixin:
464
481
  raise RepositoryInvalid(name=path, message="Repository is read-only.")
465
482
 
466
483
  if distribution.repository:
467
- dist_repository = distribution.repository.cast()
468
- if not dist_repository.PUSH_ENABLED or repository != dist_repository:
469
- raise RepositoryInvalid(name=path, message="Repository is read-only.")
484
+ if repository.pk != distribution.repository.pk:
485
+ raise RepositoryInvalid(
486
+ name=path, message="Repository is not available for push."
487
+ )
470
488
  else:
471
489
  distribution.repository = repository
472
490
  distribution.save()
@@ -517,7 +535,7 @@ class VersionView(ContainerRegistryApiMixin, APIView):
517
535
  """
518
536
  List of permission classes to check for this view.
519
537
  """
520
- if settings.get("TOKEN_AUTH_DISABLED", False):
538
+ if getattr(settings, "TOKEN_AUTH_DISABLED", False):
521
539
  return [IsAuthenticated]
522
540
  return [TokenPermission]
523
541
 
@@ -609,7 +627,7 @@ class CatalogView(ContainerRegistryApiMixin, ListAPIView):
609
627
  distribution_permission = "container.pull_containerdistribution"
610
628
  namespace_permission = "container.namespace_pull_containerdistribution"
611
629
 
612
- if settings.get("TOKEN_AUTH_DISABLED", False):
630
+ if getattr(settings, "TOKEN_AUTH_DISABLED", False):
613
631
  return queryset
614
632
 
615
633
  public_repositories = queryset.filter(private=False)
@@ -1014,6 +1032,30 @@ class BlobUploads(ContainerRegistryApiMixin, ViewSet):
1014
1032
 
1015
1033
  return UploadResponse(upload=upload, path=path, request=request)
1016
1034
 
1035
+ def get(self, request, path, pk=None):
1036
+ """
1037
+ Retrieve the status of an upload.
1038
+ """
1039
+ _, repository = self.get_dr_push(request, path)
1040
+ upload = get_object_or_404(models.Upload, repository=repository, pk=pk)
1041
+ return UploadResponse(upload=upload, path=path, request=request, status=204)
1042
+
1043
+ def head(self, request, path, pk=None):
1044
+ """Respond to HEAD requests about blob uploads."""
1045
+ return self.get(request, path, pk=pk)
1046
+
1047
+ def destroy(self, request, path, pk=None):
1048
+ """
1049
+ Cancel an outstanding blob upload.
1050
+ """
1051
+ _, repository = self.get_dr_push(request, path)
1052
+ try:
1053
+ upload = models.Upload.objects.get(repository=repository, pk=pk)
1054
+ except models.Upload.DoesNotExist:
1055
+ raise BlobUploadUnknown(uuid=pk)
1056
+ upload.delete()
1057
+ return Response(status=204)
1058
+
1017
1059
  def put(self, request, path, pk=None):
1018
1060
  """
1019
1061
  Create a blob from uploaded chunks.
@@ -1185,6 +1227,34 @@ class Blobs(RedirectsMixin, ContainerRegistryApiMixin, ViewSet):
1185
1227
  raise BlobNotFound(digest=pk)
1186
1228
  return blob_url
1187
1229
 
1230
+ def destroy(self, request, path, pk=None):
1231
+ """
1232
+ Delete a blob identified by digest.
1233
+ """
1234
+ if not pk.startswith("sha256:"):
1235
+ raise InvalidRequest(message="A blob can only be deleted by digest.")
1236
+
1237
+ _, repository = self.get_dr_push(request, path)
1238
+ latest_version = repository.latest_version()
1239
+
1240
+ if blob := models.Blob.objects.filter(digest=pk, pk__in=latest_version.content).first():
1241
+ dispatch(
1242
+ aadd_and_remove,
1243
+ exclusive_resources=[repository],
1244
+ kwargs={
1245
+ "repository_pk": str(repository.pk),
1246
+ "add_content_units": [],
1247
+ "remove_content_units": [str(blob.pk)],
1248
+ },
1249
+ immediate=True,
1250
+ deferred=True,
1251
+ )
1252
+ elif pending_blob := repository.pending_blobs.filter(digest=pk).first():
1253
+ repository.pending_blobs.remove(pending_blob)
1254
+ else:
1255
+ raise BlobNotFound(digest=pk)
1256
+ return Response(status=202)
1257
+
1188
1258
 
1189
1259
  class Manifests(RedirectsMixin, ContainerRegistryApiMixin, ViewSet):
1190
1260
  """
@@ -1329,6 +1399,36 @@ class Manifests(RedirectsMixin, ContainerRegistryApiMixin, ViewSet):
1329
1399
  # Fallthrough catchall, no manifest or tag found
1330
1400
  raise ManifestNotFound(reference=pk)
1331
1401
 
1402
+ def destroy(self, request, path, pk=None):
1403
+ """
1404
+ Delete a manifest identified by digest.
1405
+ """
1406
+ if not pk.startswith("sha256:"):
1407
+ raise InvalidRequest(message="A manifest can only be deleted by digest.")
1408
+
1409
+ _, repository = self.get_dr_push(request, path)
1410
+ latest_version = repository.latest_version()
1411
+
1412
+ manifest = models.Manifest.objects.filter(digest=pk, pk__in=latest_version.content).first()
1413
+ if not manifest:
1414
+ manifest = repository.pending_manifests.filter(digest=pk).first()
1415
+ if not manifest:
1416
+ raise ManifestNotFound(reference=pk)
1417
+ repository.pending_manifests.remove(manifest)
1418
+
1419
+ tags = models.Tag.objects.filter(tagged_manifest=manifest, pk__in=latest_version.content)
1420
+ content_units = [str(manifest.pk)] + [str(tag.pk) for tag in tags]
1421
+
1422
+ dispatch(
1423
+ recursive_remove_content,
1424
+ exclusive_resources=[repository],
1425
+ kwargs={
1426
+ "repository_pk": str(repository.pk),
1427
+ "content_units": content_units,
1428
+ },
1429
+ )
1430
+ return Response(status=202)
1431
+
1332
1432
  def get_content_units_to_add(self, manifest, tag=None):
1333
1433
  add_content_units = [str(manifest.pk)]
1334
1434
  if tag: