pulp-container 2.27.2__tar.gz → 2.27.4__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 (145) hide show
  1. {pulp_container-2.27.2 → pulp_container-2.27.4}/CHANGES.md +21 -0
  2. {pulp_container-2.27.2 → pulp_container-2.27.4}/PKG-INFO +2 -1
  3. pulp_container-2.27.4/pulp_container/app/__init__.py +40 -0
  4. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/models.py +0 -1
  5. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/registry_api.py +13 -8
  6. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/serializers.py +8 -17
  7. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/tasks/sign.py +4 -1
  8. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/tasks/sync_stages.py +4 -2
  9. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/tasks/synchronize.py +0 -1
  10. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/utils.py +55 -26
  11. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/viewsets.py +0 -1
  12. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/constants.py +0 -1
  13. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_build_images.py +4 -8
  14. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_crud_remotes.py +0 -1
  15. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_flatpak.py +0 -1
  16. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_sync_signatures.py +66 -0
  17. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container.egg-info/PKG-INFO +2 -1
  18. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container.egg-info/requires.txt +1 -0
  19. {pulp_container-2.27.2 → pulp_container-2.27.4}/pyproject.toml +5 -2
  20. pulp_container-2.27.2/pulp_container/app/__init__.py +0 -15
  21. {pulp_container-2.27.2 → pulp_container-2.27.4}/COMMITMENT +0 -0
  22. {pulp_container-2.27.2 → pulp_container-2.27.4}/COPYRIGHT +0 -0
  23. {pulp_container-2.27.2 → pulp_container-2.27.4}/LICENSE +0 -0
  24. {pulp_container-2.27.2 → pulp_container-2.27.4}/MANIFEST.in +0 -0
  25. {pulp_container-2.27.2 → pulp_container-2.27.4}/README.rst +0 -0
  26. {pulp_container-2.27.2 → pulp_container-2.27.4}/functest_requirements.txt +0 -0
  27. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/__init__.py +0 -0
  28. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/access_policy.py +0 -0
  29. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/authorization.py +0 -0
  30. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/cache.py +0 -0
  31. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/checks.py +0 -0
  32. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/content.py +0 -0
  33. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/downloaders.py +0 -0
  34. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/dynaconf_hooks.py +0 -0
  35. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/exceptions.py +0 -0
  36. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/fields.py +0 -0
  37. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/global_access_conditions.py +0 -0
  38. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/json_schemas.py +0 -0
  39. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/management/__init__.py +0 -0
  40. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/management/commands/__init__.py +0 -0
  41. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/management/commands/container-handle-image-data.py +0 -0
  42. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/management/commands/container-repair-media-type.py +0 -0
  43. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0001_initial.py +0 -0
  44. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0001_squashed_0032_upload_artifact.py +0 -0
  45. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0002_containerrepository.py +0 -0
  46. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0003_oci_mediatype.py +0 -0
  47. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0004_upload.py +0 -0
  48. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0005_contentredirectcontentguard.py +0 -0
  49. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0006_containerpushrepository.py +0 -0
  50. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0007_clear_tags_artifacts_refs.py +0 -0
  51. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0008_include_exclude_tags.py +0 -0
  52. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0009_container_namespace.py +0 -0
  53. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0010_remove_uploadchunk.py +0 -0
  54. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0011_add_container_repository_permissions.py +0 -0
  55. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0012_add_container_namespace_permissions.py +0 -0
  56. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0013_add_pull_push_permissions.py +0 -0
  57. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0014_containerdistribution_private.py +0 -0
  58. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0015_manage_tags_push_repo.py +0 -0
  59. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0016_add_delete_versions_permission.py +0 -0
  60. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0017_add_granular_perms.py +0 -0
  61. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0018_containerdistribution_description.py +0 -0
  62. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0019_DATA_distribution_model_swap.py +0 -0
  63. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0020_update_push_repo_perms.py +0 -0
  64. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0021_data_move_redirect_content_guard_to_core.py +0 -0
  65. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0022_delete_contentredirectcontentguard.py +0 -0
  66. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0023_manifestsignature.py +0 -0
  67. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0024_containerremote_sigstore.py +0 -0
  68. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0025_signature_stored_in_textfield.py +0 -0
  69. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0026_manifest_signing_service.py +0 -0
  70. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0027_data_translate_perms_to_roles.py +0 -0
  71. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0028_add_role_manage_permissions.py +0 -0
  72. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0029_remove_blob_media_type.py +0 -0
  73. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0030_enforce_tagged_manifest_reference.py +0 -0
  74. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0031_replace_charf_with_textf.py +0 -0
  75. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0032_upload_artifact.py +0 -0
  76. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0033_raise_warning_for_repair.py +0 -0
  77. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0034_translate_signed_schema.py +0 -0
  78. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0035_alter_blob_content_ptr_and_more.py +0 -0
  79. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0036_containerpushrepository_pending_blobs_manifests.py +0 -0
  80. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0037_create_pull_through_cache_models.py +0 -0
  81. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0038_add_manifest_metadata_fields.py +0 -0
  82. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0039_manifest_data.py +0 -0
  83. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0040_add_remote_repo_filter.py +0 -0
  84. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0041_add_pull_through_pull_permissions.py +0 -0
  85. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0042_add_manifest_nature_field.py +0 -0
  86. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0043_add_os_arch_image_size_manifest_fields.py +0 -0
  87. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0044_add_domain.py +0 -0
  88. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0045_alter_manifest_compressed_image_size.py +0 -0
  89. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/0046_alter_manifest_listed_manifests.py +0 -0
  90. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/migrations/__init__.py +0 -0
  91. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/modelresource.py +0 -0
  92. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/redirects.py +0 -0
  93. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/registry.py +0 -0
  94. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/replica.py +0 -0
  95. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/settings.py +0 -0
  96. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/tasks/__init__.py +0 -0
  97. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/tasks/builder.py +0 -0
  98. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/tasks/download_image_data.py +0 -0
  99. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/tasks/recursive_add.py +0 -0
  100. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/tasks/recursive_remove.py +0 -0
  101. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/tasks/tag.py +0 -0
  102. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/tasks/untag.py +0 -0
  103. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/token_verification.py +0 -0
  104. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/urls.py +0 -0
  105. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/webserver_snippets/__init__.py +0 -0
  106. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/webserver_snippets/apache.conf +0 -0
  107. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/app/webserver_snippets/nginx.conf +0 -0
  108. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/__init__.py +0 -0
  109. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/__init__.py +0 -0
  110. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/__init__.py +0 -0
  111. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_content_cache.py +0 -0
  112. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_crud_distributions.py +0 -0
  113. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_domains.py +0 -0
  114. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_pull_content.py +0 -0
  115. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_pull_through_cache.py +0 -0
  116. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_pulpimportexport.py +0 -0
  117. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_push_content.py +0 -0
  118. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_push_signatures.py +0 -0
  119. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_rbac_push_repositories.py +0 -0
  120. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_rbac_remotes.py +0 -0
  121. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_rbac_repo_content.py +0 -0
  122. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_rbac_repo_versions.py +0 -0
  123. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_rbac_sync_repositories.py +0 -0
  124. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_recursive_add.py +0 -0
  125. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_recursive_remove.py +0 -0
  126. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_remote_filter_pull_through.py +0 -0
  127. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_repositories_list.py +0 -0
  128. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_sign_manifests.py +0 -0
  129. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_sync.py +0 -0
  130. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_tagging_images.py +0 -0
  131. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/api/test_token_authentication.py +0 -0
  132. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/conftest.py +0 -0
  133. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/constants.py +0 -0
  134. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/functional/utils.py +0 -0
  135. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/unit/__init__.py +0 -0
  136. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/unit/test_json_schemas.py +0 -0
  137. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/unit/test_models.py +0 -0
  138. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container/tests/unit/test_serializers.py +0 -0
  139. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container.egg-info/SOURCES.txt +0 -0
  140. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container.egg-info/dependency_links.txt +0 -0
  141. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container.egg-info/entry_points.txt +0 -0
  142. {pulp_container-2.27.2 → pulp_container-2.27.4}/pulp_container.egg-info/top_level.txt +0 -0
  143. {pulp_container-2.27.2 → pulp_container-2.27.4}/setup.cfg +0 -0
  144. {pulp_container-2.27.2 → pulp_container-2.27.4}/test_requirements.txt +0 -0
  145. {pulp_container-2.27.2 → pulp_container-2.27.4}/unittest_requirements.txt +0 -0
@@ -8,6 +8,27 @@
8
8
 
9
9
  [//]: # (towncrier release notes start)
10
10
 
11
+ ## 2.27.4 (2026-03-30) {: #2.27.4 }
12
+
13
+ #### Bugfixes {: #2.27.4-bugfix }
14
+
15
+ - Don't blow up on encountering PQC signatures.
16
+ [#2237](https://github.com/pulp/pulp_container/issues/2237)
17
+
18
+ ---
19
+
20
+ ## 2.27.3 (2026-03-18) {: #2.27.3 }
21
+
22
+ #### Bugfixes {: #2.27.3-bugfix }
23
+
24
+ - Altered several id-fields and their related sequences.
25
+
26
+ INTEGER AutoField sequences can "run out" on large/old instances, update to BIGINT.
27
+ [#2080](https://github.com/pulp/pulp_container/issues/2080)
28
+ - Fixed memory usage when pushing large images with monolithic upload.
29
+
30
+ ---
31
+
11
32
  ## 2.27.2 (2026-02-24) {: #2.27.2 }
12
33
 
13
34
  #### Bugfixes {: #2.27.2-bugfix }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-container
3
- Version: 2.27.2
3
+ Version: 2.27.4
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
@@ -20,4 +20,5 @@ License-File: LICENSE
20
20
  Requires-Dist: jsonschema<4.26,>=4.4
21
21
  Requires-Dist: pulpcore<3.115,>=3.73.2
22
22
  Requires-Dist: pyjwt[crypto]<2.11,>=2.4
23
+ Requires-Dist: pysequoia==0.1.32
23
24
  Dynamic: license-file
@@ -0,0 +1,40 @@
1
+ from pulpcore.plugin import PulpPluginAppConfig
2
+ from django.db import connection
3
+ from django.db.models.signals import post_migrate
4
+
5
+ update_sequences_to_bigint = """
6
+ ALTER TABLE container_blobmanifest ALTER COLUMN id TYPE bigint;
7
+ ALTER TABLE container_manifestlistmanifest ALTER COLUMN id TYPE bigint;
8
+ ALTER TABLE container_containerpushrepository_pending_blobs ALTER COLUMN id TYPE bigint;
9
+ ALTER TABLE container_containerpushrepository_pending_manifests ALTER COLUMN id TYPE bigint;
10
+ ALTER TABLE container_containerrepository_pending_manifests ALTER COLUMN id TYPE bigint;
11
+ ALTER TABLE container_containerrepository_pending_blobs ALTER COLUMN id TYPE bigint;
12
+ ALTER SEQUENCE container_blobmanifest_id_seq AS BIGINT;
13
+ ALTER SEQUENCE container_manifestlistmanifest_id_seq AS BIGINT;
14
+ ALTER SEQUENCE container_containerpushrepository_pending_blobs_id_seq AS BIGINT;
15
+ ALTER SEQUENCE container_containerpushrepository_pending_manifests_id_seq AS BIGINT;
16
+ ALTER SEQUENCE container_containerrepository_pending_blobs_id_seq AS BIGINT;
17
+ ALTER SEQUENCE container_containerrepository_pending_manifests_id_seq AS BIGINT;
18
+ """
19
+
20
+
21
+ class PulpContainerPluginAppConfig(PulpPluginAppConfig):
22
+ """Entry point for the container plugin."""
23
+
24
+ name = "pulp_container.app"
25
+ label = "container"
26
+ version = "2.27.4"
27
+ python_package_name = "pulp-container"
28
+ domain_compatible = True
29
+
30
+ @staticmethod
31
+ def update_sequences(sender, **kwargs):
32
+ """Update database sequences to bigint type after migrations."""
33
+ with connection.cursor() as cursor:
34
+ cursor.execute(update_sequences_to_bigint)
35
+
36
+ def ready(self):
37
+ super().ready()
38
+ from . import checks
39
+
40
+ post_migrate.connect(PulpContainerPluginAppConfig.update_sequences, sender=self)
@@ -41,7 +41,6 @@ from pulp_container.constants import (
41
41
  SIGNATURE_TYPE,
42
42
  )
43
43
 
44
-
45
44
  logger = getLogger(__name__)
46
45
 
47
46
 
@@ -967,24 +967,27 @@ class BlobUploads(ContainerRegistryApiMixin, ViewSet):
967
967
  """
968
968
  Create a blob from uploaded chunks.
969
969
 
970
- This request makes the upload complete. It can whether carry a zero-length
971
- body or last chunk can be uploaded.
970
+ This request makes the upload complete. It can either carry a zero-length
971
+ body or contain the last chunk.
972
972
 
973
973
  """
974
974
  _, repository = self.get_dr_push(request, path)
975
975
 
976
976
  digest = request.query_params["digest"]
977
977
  chunk = request.META["wsgi.input"]
978
- # last chunk (and the only one) from monolitic upload
979
- # or last chunk from chunked upload
980
- last_chunk = ContentFile(chunk.read())
981
978
  upload = get_object_or_404(models.Upload, pk=pk, repository=repository)
982
979
 
983
- if artifact := upload.artifact:
980
+ if upload.size == 0:
981
+ # monolithic upload to be completed in PUT
982
+ artifact = self.create_single_chunk_artifact(chunk)
983
+ elif artifact := upload.artifact:
984
+ # chunked upload was completed in PATCH
984
985
  if artifact.sha256 != digest[len("sha256:") :]:
985
986
  raise Exception("The digest did not match")
986
987
  artifact.touch()
987
988
  else:
989
+ # chunked upload to be completed in PUT
990
+ last_chunk = ContentFile(chunk.read())
988
991
  chunks = UploadChunk.objects.filter(upload=upload).order_by("offset")
989
992
  with NamedTemporaryFile("ab") as temp_file:
990
993
  for chunk in chunks:
@@ -1541,8 +1544,10 @@ class Signatures(ContainerRegistryApiMixin, ViewSet):
1541
1544
  except binascii.Error:
1542
1545
  raise ManifestSignatureInvalid(digest=pk)
1543
1546
 
1544
- signature_json = extract_data_from_signature(signature_raw, manifest.digest)
1545
- if signature_json is None:
1547
+ try:
1548
+ signature_json = extract_data_from_signature(signature_raw, manifest.digest)
1549
+ except ValueError as exc:
1550
+ log.warning("Error processing signature on upload: {}".format(exc))
1546
1551
  raise ManifestSignatureInvalid(digest=pk)
1547
1552
 
1548
1553
  sig_digest = hashlib.sha256(signature_raw).hexdigest()
@@ -34,7 +34,6 @@ from pulp_container.app import models, fields
34
34
  from pulp_container.constants import SIGNATURE_TYPE
35
35
  from pulp_container.app.utils import get_full_path
36
36
 
37
-
38
37
  VALID_SIGNATURE_NAME_REGEX = r"^sha256:[0-9a-f]{64}@[0-9a-f]{32}$"
39
38
  VALID_TAG_REGEX = r"^[A-Za-z0-9][A-Za-z0-9._-]*$"
40
39
  VALID_BASE_PATH_REGEX_COMPILED = re.compile(r"^[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9])*$")
@@ -299,25 +298,21 @@ class ContainerRemoteSerializer(RemoteSerializer):
299
298
  child=serializers.CharField(max_length=255),
300
299
  allow_null=True,
301
300
  required=False,
302
- help_text=_(
303
- """
301
+ help_text=_("""
304
302
  A list of tags to include during sync.
305
303
  Wildcards *, ? are recognized.
306
304
  'include_tags' is evaluated before 'exclude_tags'.
307
- """
308
- ),
305
+ """),
309
306
  )
310
307
  exclude_tags = serializers.ListField(
311
308
  child=serializers.CharField(max_length=255),
312
309
  allow_null=True,
313
310
  required=False,
314
- help_text=_(
315
- """
311
+ help_text=_("""
316
312
  A list of tags to exclude during sync.
317
313
  Wildcards *, ? are recognized.
318
314
  'exclude_tags' is evaluated after 'include_tags'.
319
- """
320
- ),
315
+ """),
321
316
  )
322
317
 
323
318
  policy = serializers.ChoiceField(
@@ -357,25 +352,21 @@ class ContainerPullThroughRemoteSerializer(RemoteSerializer):
357
352
  child=serializers.CharField(max_length=255),
358
353
  allow_null=True,
359
354
  required=False,
360
- help_text=_(
361
- """
355
+ help_text=_("""
362
356
  A list of remotes to include during pull-through caching.
363
357
  Wildcards *, ? are recognized.
364
358
  'includes' is evaluated before 'excludes'.
365
- """
366
- ),
359
+ """),
367
360
  )
368
361
  excludes = serializers.ListField(
369
362
  child=serializers.CharField(max_length=255),
370
363
  allow_null=True,
371
364
  required=False,
372
- help_text=_(
373
- """
365
+ help_text=_("""
374
366
  A list of remotes to exclude during pull-through caching.
375
367
  Wildcards *, ? are recognized.
376
368
  'excludes' is evaluated after 'includes'.
377
- """
378
- ),
369
+ """),
379
370
  )
380
371
 
381
372
  class Meta:
@@ -118,7 +118,10 @@ async def create_signature(manifest, reference, signing_service):
118
118
  data = sig_fp.read()
119
119
  encoded_sig = base64.b64encode(data).decode()
120
120
  sig_digest = hashlib.sha256(data).hexdigest()
121
- sig_json = extract_data_from_signature(data, manifest.digest)
121
+ try:
122
+ sig_json = extract_data_from_signature(data, manifest.digest)
123
+ except ValueError:
124
+ raise
122
125
  manifest_digest = sig_json["critical"]["image"]["docker-manifest-digest"]
123
126
 
124
127
  signature = ManifestSignature(
@@ -391,8 +391,10 @@ class ContainerFirstStage(Stage):
391
391
  def _create_signature_declarative_content(
392
392
  self, signature_raw, man_dc, name=None, signature_b64=None
393
393
  ):
394
- signature_json = extract_data_from_signature(signature_raw, man_dc.content.digest)
395
- if signature_json is None:
394
+ try:
395
+ signature_json = extract_data_from_signature(signature_raw, man_dc.content.digest)
396
+ except ValueError as exc:
397
+ log.warning("Error processing signature on sync: {}".format(str(exc)))
396
398
  return
397
399
 
398
400
  sig_digest = hashlib.sha256(signature_raw).hexdigest()
@@ -13,7 +13,6 @@ from pulpcore.plugin.stages import (
13
13
  from .sync_stages import ContainerFirstStage, ContainerContentSaver
14
14
  from pulp_container.app.models import ContainerRemote, ContainerRepository
15
15
 
16
-
17
16
  log = logging.getLogger(__name__)
18
17
 
19
18
 
@@ -1,9 +1,6 @@
1
1
  import base64
2
2
  import hashlib
3
3
  import fnmatch
4
- import re
5
- import subprocess
6
- import gnupg
7
4
  import json
8
5
  import logging
9
6
  import time
@@ -15,6 +12,8 @@ from django.db import IntegrityError
15
12
  from functools import partial
16
13
  from rest_framework.exceptions import Throttled
17
14
 
15
+ from pysequoia.packet import PacketPile, Tag
16
+
18
17
  from pulpcore.plugin.models import Artifact, Task
19
18
  from pulpcore.plugin.util import get_domain
20
19
 
@@ -32,9 +31,6 @@ from pulp_container.app.json_schemas import (
32
31
  SIGNATURE_SCHEMA,
33
32
  )
34
33
 
35
- KEY_ID_REGEX_COMPILED = re.compile(r"keyid ([0-9A-F]+)")
36
- TIMESTAMP_REGEX_COMPILED = re.compile(r"created ([0-9]+)")
37
-
38
34
  signature_validator = Draft7Validator(SIGNATURE_SCHEMA)
39
35
 
40
36
  log = logging.getLogger(__name__)
@@ -86,6 +82,20 @@ def urlpath_sanitize(*args):
86
82
  return "/".join(segments)
87
83
 
88
84
 
85
+ def keyid_from_fingerprint(fingerprint):
86
+ """Derive a key ID from an OpenPGP fingerprint.
87
+
88
+ For v4 fingerprints (40 hex chars / 20 bytes), the key ID is the last 8 bytes.
89
+ For v6 fingerprints (64 hex chars / 32 bytes), the key ID is the first 8 bytes.
90
+ """
91
+ if len(fingerprint) == 40:
92
+ return fingerprint[-16:]
93
+ elif len(fingerprint) == 64:
94
+ return fingerprint[:16]
95
+ else:
96
+ raise ValueError(f"Unexpected fingerprint length: {len(fingerprint)}")
97
+
98
+
89
99
  def extract_data_from_signature(signature_raw, man_digest):
90
100
  """
91
101
  Extract data from an "integrated" signature, aka a signed non-encrypted document.
@@ -98,37 +108,56 @@ def extract_data_from_signature(signature_raw, man_digest):
98
108
  dict: JSON representation of the document and available data about signature
99
109
 
100
110
  """
101
- gpg = gnupg.GPG()
102
- crypt_obj = gpg.decrypt(signature_raw, extra_args=["--skip-verify"])
103
- if not crypt_obj.data:
104
- log.info(
105
- "It is not possible to read the signed document, GPG error: {}".format(crypt_obj.stderr)
111
+ try:
112
+ pile = PacketPile.from_bytes(signature_raw)
113
+ except Exception as exc:
114
+ raise ValueError(
115
+ "Signed document for manifest {} is un-parseable: {}".format(man_digest, str(exc))
106
116
  )
107
- return
117
+
118
+ literal_data = None
119
+ signing_key_id = None
120
+ signing_key_fingerprint = None
121
+ signature_timestamp = None
122
+
123
+ for packet in pile:
124
+ if packet.tag == Tag.Literal:
125
+ literal_data = bytes(packet.literal_data)
126
+ elif packet.tag == Tag.Signature:
127
+ if packet.issuer_key_id is not None:
128
+ signing_key_id = packet.issuer_key_id.upper()
129
+ elif packet.issuer_fingerprint is not None:
130
+ signing_key_fingerprint = packet.issuer_fingerprint.upper()
131
+ signing_key_id = keyid_from_fingerprint(signing_key_fingerprint)
132
+ else:
133
+ raise ValueError(
134
+ "Signature for manifest {} has no fingerprint or key_id".format(man_digest)
135
+ )
136
+ if packet.signature_created is not None:
137
+ signature_timestamp = int(packet.signature_created.timestamp())
138
+
139
+ if not literal_data:
140
+ raise ValueError("Signature for manifest {} has no literal data".format(man_digest))
108
141
 
109
142
  try:
110
- sig_json = json.loads(crypt_obj.data)
143
+ sig_json = json.loads(literal_data)
111
144
  except Exception as exc:
112
- log.info(
113
- "Signed document cannot be parsed to create a signature for {}."
114
- " Error: {}".format(man_digest, str(exc))
145
+ raise ValueError(
146
+ "Signed document cannot be parsed to create a signature for {}. Error: {}".format(
147
+ man_digest, str(exc)
148
+ )
115
149
  )
116
- return
117
150
 
118
151
  errors = []
119
152
  for error in signature_validator.iter_errors(sig_json):
120
153
  errors.append(f'{".".join(error.path)}: {error.message}')
121
154
 
122
155
  if errors:
123
- log.info("The signature for {} is not synced due to: {}".format(man_digest, errors))
124
- return
125
-
126
- # decrypted and unverified signatures do not have prepopulated the key_id and timestamp
127
- # fields; thus, it is necessary to use the debugging utilities of gpg to extract these
128
- # fields since they are not encrypted and still readable without decrypting the signature first
129
- packets = subprocess.check_output(["gpg", "--list-packets"], input=signature_raw).decode()
130
- sig_json["signing_key_id"] = KEY_ID_REGEX_COMPILED.search(packets).group(1)
131
- sig_json["signature_timestamp"] = TIMESTAMP_REGEX_COMPILED.search(packets).group(1)
156
+ raise ValueError("The signature for {} is not synced due to: {}".format(man_digest, errors))
157
+
158
+ sig_json["signing_key_id"] = signing_key_id
159
+ sig_json["signing_key_fingerprint"] = signing_key_fingerprint
160
+ sig_json["signature_timestamp"] = signature_timestamp
132
161
 
133
162
  return sig_json
134
163
 
@@ -45,7 +45,6 @@ from pulpcore.plugin.viewsets import (
45
45
 
46
46
  from pulp_container.app import models, serializers, tasks
47
47
 
48
-
49
48
  log = logging.getLogger(__name__)
50
49
 
51
50
 
@@ -1,6 +1,5 @@
1
1
  from types import SimpleNamespace
2
2
 
3
-
4
3
  MEDIA_TYPE = SimpleNamespace(
5
4
  MANIFEST_V1="application/vnd.docker.distribution.manifest.v1+json",
6
5
  MANIFEST_V1_SIGNED="application/vnd.docker.distribution.manifest.v1+prettyjws",
@@ -10,13 +10,11 @@ from pulp_container.constants import MANIFEST_TYPE
10
10
  def containerfile_name():
11
11
  """A fixture for a basic container file used for building images."""
12
12
  with NamedTemporaryFile() as containerfile:
13
- containerfile.write(
14
- b"""FROM quay.io/quay/busybox:latest
13
+ containerfile.write(b"""FROM quay.io/quay/busybox:latest
15
14
  # Copy a file using COPY statement. Use the relative path specified in the 'artifacts' parameter.
16
15
  COPY foo/bar/example.txt /tmp/inside-image.txt
17
16
  # Print the content of the file when the container starts
18
- CMD ["cat", "/tmp/inside-image.txt"]"""
19
- )
17
+ CMD ["cat", "/tmp/inside-image.txt"]""")
20
18
  containerfile.flush()
21
19
  yield containerfile.name
22
20
 
@@ -215,11 +213,9 @@ def test_without_build_context(
215
213
 
216
214
  def containerfile_without_context_files():
217
215
  with NamedTemporaryFile() as containerfile:
218
- containerfile.write(
219
- b"""FROM quay.io/quay/busybox:latest
216
+ containerfile.write(b"""FROM quay.io/quay/busybox:latest
220
217
  # Print the content of the file when the container starts
221
- CMD ["ls", "/"]"""
222
- )
218
+ CMD ["ls", "/"]""")
223
219
  containerfile.flush()
224
220
  yield containerfile.name
225
221
 
@@ -6,7 +6,6 @@ import uuid
6
6
 
7
7
  from pulp_container.tests.functional.conftest import gen_container_remote
8
8
 
9
-
10
9
  ON_DEMAND_DOWNLOAD_POLICIES = ("on_demand", "streamed")
11
10
 
12
11
 
@@ -5,7 +5,6 @@ import subprocess
5
5
 
6
6
  from pulp_container.tests.functional.constants import REGISTRY_V2
7
7
 
8
-
9
8
  pytestmark = pytest.mark.skip(reason="TLS is broken currently. TODO: Fix")
10
9
 
11
10
 
@@ -9,6 +9,9 @@ IMAGE_MANIFEST_TAG = "7.9-511-source"
9
9
  MANIFEST_LIST_TAG = "7.9"
10
10
  SIGSTORE_URL = "https://access.redhat.com/webassets/docker/content/sigstore"
11
11
 
12
+ UBI10_MICRO_REPOSITORY_NAME = "ubi10-micro"
13
+ UBI10_MICRO_TAG = "latest"
14
+
12
15
 
13
16
  @pytest.fixture
14
17
  def synced_repository(
@@ -120,3 +123,66 @@ def test_sync_images_without_sigstore_requiring_signatures(container_bindings, s
120
123
  repository_version=synced_repository.latest_version_href
121
124
  ).results
122
125
  assert len(tags) == 0
126
+
127
+
128
+ def test_sync_image_with_pqc_signatures(
129
+ delete_orphans_pre,
130
+ container_repo,
131
+ container_remote_factory,
132
+ container_bindings,
133
+ monitor_task,
134
+ ):
135
+ """Sync ubi10-micro:latest from registry.access.redhat.com with all signatures."""
136
+ data = gen_container_remote(
137
+ url=REDHAT_REGISTRY_V2,
138
+ upstream_name=UBI10_MICRO_REPOSITORY_NAME,
139
+ policy="on_demand",
140
+ include_tags=[UBI10_MICRO_TAG],
141
+ sigstore=SIGSTORE_URL,
142
+ )
143
+ remote = container_remote_factory(**data)
144
+
145
+ sync_data = {"remote": remote.pulp_href, "signed_only": False}
146
+ response = container_bindings.RepositoriesContainerApi.sync(container_repo.pulp_href, sync_data)
147
+ monitor_task(response.task)
148
+
149
+ repo = container_bindings.RepositoriesContainerApi.read(container_repo.pulp_href)
150
+
151
+ tags = container_bindings.ContentTagsApi.list(
152
+ repository_version=repo.latest_version_href
153
+ ).results
154
+ assert len(tags) == 1
155
+ assert tags[0].name == UBI10_MICRO_TAG
156
+
157
+ signatures = container_bindings.ContentSignaturesApi.list(
158
+ repository_version=repo.latest_version_href
159
+ ).results
160
+ assert len(signatures) > 0
161
+
162
+ # Assert that a signature using one of the "old" Red Hat signing release keys exist
163
+ expected_key_ids = ["199E2F91FD431D51", "E60D446E63405576"]
164
+ assert any(s.key_id in expected_key_ids for s in signatures), (
165
+ f"No signature found with key_ids {expected_key_ids}; "
166
+ f"found key_ids: {sorted({s.key_id for s in signatures})}"
167
+ )
168
+
169
+ # Assert that a signature using the Red Hat PQC (ML-DSA-87) signing key exists
170
+ # Fingerprint: FCD355B305707A62DA143AB6E422397E50FE8467A2A95343D246D6276AFEDF8F
171
+ # Key ID => first 8 bytes (16 hex chars)
172
+ expected_key_id = "FCD355B305707A62"
173
+ assert any(s.key_id == expected_key_id for s in signatures), (
174
+ f"No signature found with key_id {expected_key_id!r}; "
175
+ f"found key_ids: {sorted({s.key_id for s in signatures})}"
176
+ )
177
+
178
+ # ubi10-micro:latest is a manifest list; collect all listed manifests and verify
179
+ # that each has at least one signature
180
+ manifest_list = container_bindings.ContentManifestsApi.read(tags[0].tagged_manifest)
181
+ listed_manifests = [
182
+ container_bindings.ContentManifestsApi.read(lm_href)
183
+ for lm_href in manifest_list.listed_manifests
184
+ ]
185
+ for lm in listed_manifests:
186
+ lm_signatures = [s for s in signatures if s.signed_manifest == lm.pulp_href]
187
+ assert len(lm_signatures) > 0, f"No signatures found for manifest {lm.digest}"
188
+ assert all(s.name.startswith(lm.digest) for s in lm_signatures)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-container
3
- Version: 2.27.2
3
+ Version: 2.27.4
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
@@ -20,4 +20,5 @@ License-File: LICENSE
20
20
  Requires-Dist: jsonschema<4.26,>=4.4
21
21
  Requires-Dist: pulpcore<3.115,>=3.73.2
22
22
  Requires-Dist: pyjwt[crypto]<2.11,>=2.4
23
+ Requires-Dist: pysequoia==0.1.32
23
24
  Dynamic: license-file
@@ -1,3 +1,4 @@
1
1
  jsonschema<4.26,>=4.4
2
2
  pulpcore<3.115,>=3.73.2
3
3
  pyjwt[crypto]<2.11,>=2.4
4
+ pysequoia==0.1.32
@@ -7,7 +7,7 @@ build-backend = 'setuptools.build_meta'
7
7
 
8
8
  [project]
9
9
  name = "pulp-container"
10
- version = "2.27.2"
10
+ version = "2.27.4"
11
11
  description = "Container plugin for the Pulp Project"
12
12
  readme = "README.md"
13
13
  authors = [
@@ -26,6 +26,7 @@ dependencies = [
26
26
  "jsonschema>=4.4,<4.26",
27
27
  "pulpcore>=3.73.2,<3.115",
28
28
  "pyjwt[crypto]>=2.4,<2.11",
29
+ "pysequoia==0.1.32",
29
30
  ]
30
31
 
31
32
  [project.urls]
@@ -81,12 +82,14 @@ ignore = [
81
82
  ".github/**",
82
83
  "lint_requirements.txt",
83
84
  ".flake8",
85
+ "AGENTS.md",
86
+ "CLAUDE.md",
84
87
  ]
85
88
 
86
89
  [tool.bumpversion]
87
90
  # This section is managed by the plugin template. Do not edit manually.
88
91
 
89
- current_version = "2.27.2"
92
+ current_version = "2.27.4"
90
93
  commit = false
91
94
  tag = false
92
95
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<alpha>0a)?(?P<patch>\\d+)(\\.(?P<release>[a-z]+))?"
@@ -1,15 +0,0 @@
1
- from pulpcore.plugin import PulpPluginAppConfig
2
-
3
-
4
- class PulpContainerPluginAppConfig(PulpPluginAppConfig):
5
- """Entry point for the container plugin."""
6
-
7
- name = "pulp_container.app"
8
- label = "container"
9
- version = "2.27.2"
10
- python_package_name = "pulp-container"
11
- domain_compatible = True
12
-
13
- def ready(self):
14
- super().ready()
15
- from . import checks
File without changes