pulp-container 2.22.9__tar.gz → 2.22.11__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 (146) hide show
  1. {pulp_container-2.22.9 → pulp_container-2.22.11}/CHANGES.md +18 -0
  2. {pulp_container-2.22.9 → pulp_container-2.22.11}/PKG-INFO +2 -2
  3. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/__init__.py +4 -3
  4. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/authorization.py +5 -18
  5. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/cache.py +9 -9
  6. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/checks.py +32 -1
  7. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/content.py +1 -0
  8. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/management/commands/container-handle-image-data.py +2 -6
  9. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/management/commands/container-repair-media-type.py +1 -3
  10. pulp_container-2.22.11/pulp_container/app/management/commands/container-repair-pull-through-distributions.py +66 -0
  11. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/modelresource.py +3 -2
  12. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/models.py +9 -10
  13. pulp_container-2.22.11/pulp_container/app/pull_through.py +66 -0
  14. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/registry.py +10 -13
  15. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/registry_api.py +31 -37
  16. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/serializers.py +68 -41
  17. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/tasks/builder.py +8 -7
  18. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/tasks/recursive_remove.py +2 -1
  19. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/tasks/sign.py +0 -1
  20. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/tasks/sync_stages.py +19 -18
  21. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/tasks/synchronize.py +4 -3
  22. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/tasks/tag.py +1 -0
  23. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/tasks/untag.py +1 -0
  24. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/token_verification.py +3 -5
  25. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/urls.py +1 -0
  26. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/utils.py +14 -15
  27. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/viewsets.py +29 -8
  28. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/constants.py +2 -0
  29. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_content_cache.py +4 -6
  30. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_crud_distributions.py +5 -6
  31. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_flatpak.py +2 -1
  32. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_pull_content.py +19 -30
  33. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_pull_through_cache.py +25 -17
  34. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_push_content.py +13 -16
  35. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_push_signatures.py +2 -1
  36. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_rbac_push_repositories.py +0 -2
  37. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_rbac_remotes.py +3 -4
  38. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_rbac_repo_content.py +4 -7
  39. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_rbac_repo_versions.py +4 -7
  40. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_rbac_sync_repositories.py +0 -2
  41. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_recursive_add.py +8 -9
  42. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_recursive_remove.py +9 -10
  43. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_remote_filter_pull_through.py +7 -4
  44. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_repositories_list.py +13 -15
  45. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_sign_manifests.py +0 -1
  46. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_sync.py +3 -2
  47. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_tagging_images.py +11 -12
  48. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_token_authentication.py +17 -18
  49. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/conftest.py +78 -59
  50. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/utils.py +24 -21
  51. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/unit/test_json_schemas.py +2 -3
  52. pulp_container-2.22.11/pulp_container/tests/unit/test_pull_through_repair.py +134 -0
  53. pulp_container-2.22.11/pulp_container/tests/unit/test_serializers.py +278 -0
  54. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container.egg-info/PKG-INFO +2 -2
  55. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container.egg-info/SOURCES.txt +3 -0
  56. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container.egg-info/requires.txt +1 -1
  57. {pulp_container-2.22.9 → pulp_container-2.22.11}/pyproject.toml +30 -1
  58. {pulp_container-2.22.9 → pulp_container-2.22.11}/requirements.txt +1 -1
  59. {pulp_container-2.22.9 → pulp_container-2.22.11}/setup.py +1 -1
  60. pulp_container-2.22.9/pulp_container/tests/unit/test_serializers.py +0 -103
  61. {pulp_container-2.22.9 → pulp_container-2.22.11}/COMMITMENT +0 -0
  62. {pulp_container-2.22.9 → pulp_container-2.22.11}/COPYRIGHT +0 -0
  63. {pulp_container-2.22.9 → pulp_container-2.22.11}/LICENSE +0 -0
  64. {pulp_container-2.22.9 → pulp_container-2.22.11}/MANIFEST.in +0 -0
  65. {pulp_container-2.22.9 → pulp_container-2.22.11}/README.rst +0 -0
  66. {pulp_container-2.22.9 → pulp_container-2.22.11}/functest_requirements.txt +0 -0
  67. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/__init__.py +0 -0
  68. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/access_policy.py +0 -0
  69. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/downloaders.py +3 -3
  70. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/exceptions.py +0 -0
  71. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/fields.py +1 -1
  72. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/global_access_conditions.py +0 -0
  73. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/json_schemas.py +0 -0
  74. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/management/__init__.py +0 -0
  75. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/management/commands/__init__.py +0 -0
  76. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0001_initial.py +0 -0
  77. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0002_containerrepository.py +0 -0
  78. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0003_oci_mediatype.py +0 -0
  79. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0004_upload.py +0 -0
  80. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0005_contentredirectcontentguard.py +0 -0
  81. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0006_containerpushrepository.py +0 -0
  82. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0007_clear_tags_artifacts_refs.py +0 -0
  83. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0008_include_exclude_tags.py +0 -0
  84. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0009_container_namespace.py +0 -0
  85. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0010_remove_uploadchunk.py +0 -0
  86. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0011_add_container_repository_permissions.py +0 -0
  87. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0012_add_container_namespace_permissions.py +0 -0
  88. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0013_add_pull_push_permissions.py +0 -0
  89. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0014_containerdistribution_private.py +0 -0
  90. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0015_manage_tags_push_repo.py +0 -0
  91. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0016_add_delete_versions_permission.py +0 -0
  92. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0017_add_granular_perms.py +0 -0
  93. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0018_containerdistribution_description.py +0 -0
  94. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0019_DATA_distribution_model_swap.py +0 -0
  95. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0020_update_push_repo_perms.py +0 -0
  96. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0021_data_move_redirect_content_guard_to_core.py +0 -0
  97. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0022_delete_contentredirectcontentguard.py +0 -0
  98. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0023_manifestsignature.py +0 -0
  99. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0024_containerremote_sigstore.py +0 -0
  100. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0025_signature_stored_in_textfield.py +0 -0
  101. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0026_manifest_signing_service.py +0 -0
  102. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0027_data_translate_perms_to_roles.py +0 -0
  103. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0028_add_role_manage_permissions.py +0 -0
  104. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0029_remove_blob_media_type.py +0 -0
  105. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0030_enforce_tagged_manifest_reference.py +0 -0
  106. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0031_replace_charf_with_textf.py +0 -0
  107. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0032_upload_artifact.py +0 -0
  108. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0033_raise_warning_for_repair.py +0 -0
  109. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0034_translate_signed_schema.py +0 -0
  110. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0035_alter_blob_content_ptr_and_more.py +0 -0
  111. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0036_containerpushrepository_pending_blobs_manifests.py +0 -0
  112. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0037_create_pull_through_cache_models.py +0 -0
  113. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0038_add_manifest_metadata_fields.py +0 -0
  114. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0039_manifest_data.py +0 -0
  115. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0040_add_remote_repo_filter.py +0 -0
  116. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0041_add_pull_through_pull_permissions.py +0 -0
  117. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0042_add_manifest_nature_field.py +0 -0
  118. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/0043_add_os_arch_image_size_manifest_fields.py +0 -0
  119. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/migrations/__init__.py +0 -0
  120. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/redirects.py +1 -1
  121. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/replica.py +3 -3
  122. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/settings.py +0 -0
  123. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/tasks/__init__.py +0 -0
  124. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/tasks/download_image_data.py +1 -1
  125. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/tasks/recursive_add.py +1 -1
  126. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/webserver_snippets/__init__.py +0 -0
  127. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/webserver_snippets/apache.conf +0 -0
  128. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/app/webserver_snippets/nginx.conf +0 -0
  129. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/__init__.py +0 -0
  130. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/__init__.py +0 -0
  131. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/__init__.py +0 -0
  132. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/rbac_base.py +0 -0
  133. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_build_images.py +4 -4
  134. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_crud_remotes.py +4 -4
  135. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_pulpimportexport.py +2 -2
  136. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/api/test_sync_signatures.py +1 -1
  137. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/functional/constants.py +1 -1
  138. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/unit/__init__.py +0 -0
  139. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/unit/fixtures/ryuk_0.13.0_manifest_list.json +0 -0
  140. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container/tests/unit/test_models.py +0 -0
  141. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container.egg-info/dependency_links.txt +0 -0
  142. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container.egg-info/entry_points.txt +0 -0
  143. {pulp_container-2.22.9 → pulp_container-2.22.11}/pulp_container.egg-info/top_level.txt +0 -0
  144. {pulp_container-2.22.9 → pulp_container-2.22.11}/setup.cfg +0 -0
  145. {pulp_container-2.22.9 → pulp_container-2.22.11}/test_requirements.txt +0 -0
  146. {pulp_container-2.22.9 → pulp_container-2.22.11}/unittest_requirements.txt +0 -0
@@ -8,6 +8,24 @@
8
8
 
9
9
  [//]: # (towncrier release notes start)
10
10
 
11
+ ## 2.22.11 (2026-09-23) {: #2.22.11 }
12
+
13
+ #### Bugfixes {: #2.22.11-bugfix }
14
+
15
+ - Prevent container distributions from overlapping other distribution base paths. Pull-through distributions **NEED** to be fixed before upgrading pulpcore; run `pulpcore-manager container-repair-pull-through-distributions` to repair existing distributions.
16
+ [#2494](https://github.com/pulp/pulp_container/issues/2494)
17
+
18
+ ---
19
+
20
+ ## 2.22.10 (2026-07-23) {: #2.22.10 }
21
+
22
+ #### Bugfixes {: #2.22.10-bugfix }
23
+
24
+ - Fixed OS and architecture filters being silently ignored when filtering manifest lists in the flatpak index.
25
+ - Fixed a serialization issue with the FlatpakIndex cache.
26
+
27
+ ---
28
+
11
29
  ## 2.22.9 (2026-07-21) {: #2.22.9 }
12
30
 
13
31
  #### Bugfixes {: #2.22.9-bugfix }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-container
3
- Version: 2.22.9
3
+ Version: 2.22.11
4
4
  Summary: Container plugin for the Pulp Project
5
5
  Home-page: https://pulpproject.org/
6
6
  Author: Pulp Team
@@ -18,7 +18,7 @@ Requires-Python: >=3.8
18
18
  License-File: LICENSE
19
19
  Requires-Dist: jsonschema<4.24,>=4.4
20
20
  Requires-Dist: pulpcore<3.70,>=3.49.0
21
- Requires-Dist: pyjwt[crypto]<2.10,>=2.4
21
+ Requires-Dist: pyjwt[crypto]<2.14,>=2.4
22
22
  Requires-Dist: pycares<4.9
23
23
  Requires-Dist: pysequoia<0.2.0,>=0.1.33
24
24
  Dynamic: author
@@ -1,7 +1,8 @@
1
- from pulpcore.plugin import PulpPluginAppConfig
2
1
  from django.db import connection
3
2
  from django.db.models.signals import post_migrate
4
3
 
4
+ from pulpcore.plugin import PulpPluginAppConfig
5
+
5
6
  update_sequences_to_bigint = """
6
7
  ALTER TABLE container_blobmanifest ALTER COLUMN id TYPE bigint;
7
8
  ALTER TABLE container_manifestlistmanifest ALTER COLUMN id TYPE bigint;
@@ -23,7 +24,7 @@ class PulpContainerPluginAppConfig(PulpPluginAppConfig):
23
24
 
24
25
  name = "pulp_container.app"
25
26
  label = "container"
26
- version = "2.22.9"
27
+ version = "2.22.11"
27
28
  python_package_name = "pulp-container"
28
29
 
29
30
  @staticmethod
@@ -34,6 +35,6 @@ class PulpContainerPluginAppConfig(PulpPluginAppConfig):
34
35
 
35
36
  def ready(self):
36
37
  super().ready()
37
- from . import checks
38
+ from . import checks # noqa: F401
38
39
 
39
40
  post_migrate.connect(PulpContainerPluginAppConfig.update_sequences, sender=self)
@@ -2,27 +2,23 @@ import base64
2
2
  import hashlib
3
3
  import random
4
4
  import uuid
5
-
6
- import jwt
7
-
8
5
  from collections import defaultdict, namedtuple
9
6
  from datetime import datetime
10
7
  from functools import partial
11
8
 
9
+ import jwt
10
+ from cryptography.hazmat.backends import default_backend
11
+ from cryptography.hazmat.primitives import serialization
12
12
  from django.conf import settings
13
13
  from django.http import HttpRequest
14
- from django.db.models import F, Value
15
14
  from rest_framework.request import Request
16
15
 
17
- from cryptography.hazmat.backends import default_backend
18
- from cryptography.hazmat.primitives import serialization
19
-
16
+ from pulp_container.app.access_policy import RegistryAccessPolicy
20
17
  from pulp_container.app.models import (
21
18
  ContainerDistribution,
22
19
  ContainerNamespace,
23
- ContainerPullThroughDistribution,
24
20
  )
25
- from pulp_container.app.access_policy import RegistryAccessPolicy
21
+ from pulp_container.app.pull_through import get_pull_through_distribution
26
22
 
27
23
  TOKEN_EXPIRATION_TIME = settings.get("TOKEN_EXPIRATION_TIME", 300)
28
24
 
@@ -209,15 +205,6 @@ class AuthorizationService:
209
205
  }
210
206
 
211
207
 
212
- def get_pull_through_distribution(path):
213
- return (
214
- ContainerPullThroughDistribution.objects.annotate(path=Value(path))
215
- .filter(path__startswith=F("base_path"))
216
- .order_by("-base_path")
217
- .first()
218
- )
219
-
220
-
221
208
  class PermissionChecker:
222
209
  def __init__(self, user):
223
210
  self.user = user
@@ -1,10 +1,13 @@
1
1
  from django.core.exceptions import ObjectDoesNotExist
2
- from django.db.models import F, Value
3
2
 
4
- from pulpcore.plugin.cache import CacheKeys, AsyncContentCache, SyncContentCache
3
+ from pulpcore.plugin.cache import AsyncContentCache, CacheKeys, SyncContentCache
5
4
 
6
- from pulp_container.app.models import ContainerDistribution, ContainerPullThroughDistribution
7
5
  from pulp_container.app.exceptions import RepositoryNotFound
6
+ from pulp_container.app.models import ContainerDistribution
7
+ from pulp_container.app.pull_through import (
8
+ get_pull_through_distribution,
9
+ get_pull_through_distribution_path,
10
+ )
8
11
 
9
12
  ACCEPT_HEADER_KEY = "accept_header"
10
13
  QUERY_KEY = "query"
@@ -72,15 +75,12 @@ def find_base_path_cached(request, cached):
72
75
  try:
73
76
  distro = ContainerDistribution.objects.get(base_path=path)
74
77
  except ObjectDoesNotExist:
75
- distro = (
76
- ContainerPullThroughDistribution.objects.annotate(path=Value(path))
77
- .filter(path__startswith=F("base_path"))
78
- .order_by("-base_path")
79
- .first()
80
- )
78
+ distro = get_pull_through_distribution(path)
81
79
  if not distro:
82
80
  raise RepositoryNotFound(name=path)
83
81
 
82
+ return get_pull_through_distribution_path(distro)
83
+
84
84
  return distro.base_path
85
85
 
86
86
 
@@ -1,5 +1,11 @@
1
1
  from django.conf import settings
2
- from django.core.checks import Error as CheckError, register
2
+ from django.core.checks import Error as CheckError
3
+ from django.core.checks import Warning as CheckWarning
4
+ from django.core.checks import register
5
+ from django.db import OperationalError, ProgrammingError
6
+
7
+ from pulp_container.app.models import ContainerPullThroughDistribution
8
+ from pulp_container.constants import PULL_THROUGH_DISTRIBUTION_LABEL
3
9
 
4
10
 
5
11
  @register(deploy=True)
@@ -44,3 +50,28 @@ def container_settings_check(app_configs, **kwargs):
44
50
  )
45
51
 
46
52
  return errors
53
+
54
+
55
+ @register()
56
+ def pull_through_distribution_check(app_configs, **kwargs):
57
+ """Warn when legacy pull-through distributions need repair."""
58
+ try:
59
+ needs_repair = ContainerPullThroughDistribution.objects.exclude(
60
+ pulp_labels__has_key=PULL_THROUGH_DISTRIBUTION_LABEL
61
+ ).exists()
62
+ except (OperationalError, ProgrammingError):
63
+ return []
64
+
65
+ if not needs_repair:
66
+ return []
67
+
68
+ return [
69
+ CheckWarning(
70
+ "Pull-through distributions need base-path repair.",
71
+ hint=(
72
+ "Run 'pulpcore-manager container-repair-pull-through-distributions' and "
73
+ "resolve any distributions it reports."
74
+ ),
75
+ id="pulp_container.W001",
76
+ )
77
+ ]
@@ -1,6 +1,7 @@
1
1
  from aiohttp import web
2
2
 
3
3
  from pulpcore.plugin.content import app
4
+
4
5
  from pulp_container.app.registry import Registry
5
6
 
6
7
  registry = Registry()
@@ -1,8 +1,6 @@
1
- from json.decoder import JSONDecodeError
2
-
3
- from gettext import gettext as _
4
-
5
1
  from contextlib import suppress
2
+ from gettext import gettext as _
3
+ from json.decoder import JSONDecodeError
6
4
 
7
5
  from django.conf import settings
8
6
  from django.core.exceptions import ObjectDoesNotExist
@@ -12,9 +10,7 @@ from django.db.models import Q
12
10
  from pulpcore.plugin.cache import SyncContentCache
13
11
 
14
12
  from pulp_container.app.models import ContainerDistribution, Manifest
15
-
16
13
  from pulp_container.app.utils import get_content_data
17
-
18
14
  from pulp_container.constants import MEDIA_TYPE
19
15
 
20
16
 
@@ -1,5 +1,4 @@
1
1
  import json
2
-
3
2
  from gettext import gettext as _
4
3
 
5
4
  from django.conf import settings
@@ -7,9 +6,8 @@ from django.core.management import BaseCommand
7
6
 
8
7
  from pulpcore.plugin.cache import SyncContentCache
9
8
 
10
- from pulp_container.app.models import Manifest, ContainerDistribution
9
+ from pulp_container.app.models import ContainerDistribution, Manifest
11
10
  from pulp_container.app.utils import determine_media_type_from_json
12
-
13
11
  from pulp_container.constants import MEDIA_TYPE
14
12
 
15
13
 
@@ -0,0 +1,66 @@
1
+ from gettext import gettext as _
2
+ from uuid import uuid4
3
+
4
+ from django.conf import settings
5
+ from django.core.management import BaseCommand
6
+ from django.db import transaction
7
+
8
+ from pulpcore.plugin.cache import SyncContentCache
9
+
10
+ from pulp_container.app.models import ContainerPullThroughDistribution
11
+ from pulp_container.app.pull_through import (
12
+ PULL_THROUGH_DISTRIBUTION_LABEL,
13
+ find_container_distribution_overlaps,
14
+ )
15
+
16
+
17
+ class Command(BaseCommand):
18
+ """Repair pull-through distributions whose registry path is stored as their base path."""
19
+
20
+ help = _("Repair pull-through distribution base paths and report remaining overlaps.")
21
+
22
+ def handle(self, *args, **options):
23
+ """Move eligible pull-through base paths and report unresolved overlaps."""
24
+ repaired = 0
25
+ migrated_base_paths = []
26
+ distributions = ContainerPullThroughDistribution.objects.exclude(
27
+ pulp_labels__has_key=PULL_THROUGH_DISTRIBUTION_LABEL
28
+ )
29
+
30
+ for distribution in distributions.iterator():
31
+ old_base_path = distribution.base_path
32
+ distribution.base_path = str(uuid4())
33
+ distribution.pulp_labels = {
34
+ **(distribution.pulp_labels or {}),
35
+ PULL_THROUGH_DISTRIBUTION_LABEL: old_base_path,
36
+ }
37
+ migrated_base_paths.append(old_base_path)
38
+ with transaction.atomic():
39
+ distribution.save(update_fields=["base_path", "pulp_labels"])
40
+ repaired += 1
41
+
42
+ self.stdout.write(self.style.SUCCESS(f"Repaired {repaired} pull-through distributions."))
43
+
44
+ overlaps = find_container_distribution_overlaps()
45
+ if overlaps:
46
+ self.stdout.write(
47
+ self.style.WARNING(
48
+ "The following container distributions have overlapping base paths:"
49
+ )
50
+ )
51
+ for distribution, other in overlaps:
52
+ self.stdout.write(
53
+ f" {self._format_distribution(distribution).strip()} overlaps "
54
+ f"{self._format_distribution(other).strip()}"
55
+ )
56
+
57
+ if settings.CACHE_ENABLED and migrated_base_paths:
58
+ SyncContentCache().delete(base_key=migrated_base_paths)
59
+ self.stdout.write(self.style.SUCCESS("Flushed repaired distribution cache entries."))
60
+
61
+ @staticmethod
62
+ def _format_distribution(distribution):
63
+ return (
64
+ f" pk={distribution.pk} name={distribution.name!r} "
65
+ f"base_path={distribution.base_path!r}"
66
+ )
@@ -1,11 +1,12 @@
1
1
  from import_export import fields, widgets
2
- from pulpcore.plugin.importexport import QueryModelResource, BaseContentResource
2
+
3
+ from pulpcore.plugin.importexport import BaseContentResource, QueryModelResource
3
4
  from pulpcore.plugin.modelresources import RepositoryResource
4
5
 
5
6
  from pulp_container.app.models import (
6
7
  Blob,
7
- ContainerRepository,
8
8
  ContainerPushRepository,
9
+ ContainerRepository,
9
10
  Manifest,
10
11
  ManifestListManifest,
11
12
  ManifestSignature,
@@ -1,17 +1,15 @@
1
- from gettext import gettext as _
2
-
3
1
  import json
4
2
  import os
5
3
  import re
6
4
  import tempfile
7
- import time
5
+ from gettext import gettext as _
8
6
  from logging import getLogger
9
7
 
10
- from django.db import models
11
8
  from django.conf import settings
12
9
  from django.contrib.postgres import fields
10
+ from django.db import models
13
11
  from django.shortcuts import redirect
14
- from django_lifecycle import hook, AFTER_CREATE, AFTER_DELETE, AFTER_UPDATE
12
+ from django_lifecycle import AFTER_CREATE, AFTER_DELETE, AFTER_UPDATE, hook
15
13
 
16
14
  from pulpcore.plugin.cache import SyncContentCache
17
15
  from pulpcore.plugin.download import DownloaderFactory
@@ -20,17 +18,17 @@ from pulpcore.plugin.models import (
20
18
  AutoAddObjPermsMixin,
21
19
  BaseModel,
22
20
  Content,
21
+ Distribution,
23
22
  Remote,
24
23
  Repository,
25
- Distribution,
26
24
  SigningService,
25
+ )
26
+ from pulpcore.plugin.models import (
27
27
  Upload as CoreUpload,
28
28
  )
29
29
  from pulpcore.plugin.repo_version_utils import remove_duplicates, validate_repo_version
30
30
  from pulpcore.plugin.util import gpg_verify
31
31
 
32
-
33
- from . import downloaders
34
32
  from pulp_container.app.utils import get_content_data, integer_overflow_safe
35
33
  from pulp_container.constants import (
36
34
  COSIGN_MEDIA_TYPES,
@@ -41,6 +39,8 @@ from pulp_container.constants import (
41
39
  SIGNATURE_TYPE,
42
40
  )
43
41
 
42
+ from . import downloaders
43
+
44
44
  logger = getLogger(__name__)
45
45
 
46
46
 
@@ -936,8 +936,7 @@ INCOMPLETE_EXT = ".part"
936
936
 
937
937
  def generate_filename(instance, filename):
938
938
  """Method for generating upload file name"""
939
- filename = os.path.join(instance.upload_dir, str(instance.pk) + INCOMPLETE_EXT)
940
- return time.strftime(filename)
939
+ return os.path.join(instance.upload_dir, str(instance.pk) + INCOMPLETE_EXT)
941
940
 
942
941
 
943
942
  class Upload(CoreUpload):
@@ -0,0 +1,66 @@
1
+ from django.db.models import F, Q, TextField, Value
2
+ from django.db.models.functions import Concat
3
+
4
+ from pulpcore.plugin.models import Distribution
5
+
6
+ from pulp_container.app.models import ContainerDistribution, ContainerPullThroughDistribution
7
+ from pulp_container.constants import (
8
+ PULL_THROUGH_DISTRIBUTION_LABEL,
9
+ )
10
+
11
+
12
+ def is_pull_through_distribution_marked(distribution):
13
+ """Return whether a pull-through distribution stores its registry path in pulp_labels."""
14
+ return (distribution.pulp_labels or {}).get(PULL_THROUGH_DISTRIBUTION_LABEL)
15
+
16
+
17
+ def get_pull_through_distribution_path(distribution):
18
+ """Return the registry path represented by a pull-through distribution."""
19
+ if base_path := is_pull_through_distribution_marked(distribution):
20
+ return base_path
21
+ return distribution.base_path
22
+
23
+
24
+ def _match_path(queryset, path, field):
25
+ """Return the longest segment-aware path match from a queryset."""
26
+ prefix_field = f"{field}_prefix"
27
+ return (
28
+ queryset.annotate(
29
+ requested_path=Value(path, output_field=TextField()),
30
+ **{prefix_field: Concat(F(field), Value("/"), output_field=TextField())},
31
+ )
32
+ .filter(Q(requested_path=F(field)) | Q(requested_path__startswith=F(prefix_field)))
33
+ .order_by(f"-{field}")
34
+ .first()
35
+ )
36
+
37
+
38
+ def get_pull_through_distribution(path):
39
+ """Find a pull-through distribution using the repaired scheme, then the legacy scheme."""
40
+ distributions = ContainerPullThroughDistribution.objects.all()
41
+ marked = distributions.filter(pulp_labels__has_key=PULL_THROUGH_DISTRIBUTION_LABEL)
42
+ if distribution := _match_path(marked, path, f"pulp_labels__{PULL_THROUGH_DISTRIBUTION_LABEL}"):
43
+ return distribution
44
+
45
+ legacy = distributions.exclude(pulp_labels__has_key=PULL_THROUGH_DISTRIBUTION_LABEL)
46
+ return _match_path(legacy, path, "base_path")
47
+
48
+
49
+ def find_container_distribution_overlaps():
50
+ """Return pairs where a container distribution overlaps any distribution."""
51
+ container_ids = set(ContainerDistribution.objects.values_list("pk", flat=True))
52
+ distributions = list(Distribution.objects.order_by("base_path", "pk"))
53
+ by_path = {}
54
+ for distribution in distributions:
55
+ by_path.setdefault(distribution.base_path, []).append(distribution)
56
+ overlaps = []
57
+
58
+ for distribution in distributions:
59
+ parts = distribution.base_path.split("/")
60
+ for index in range(1, len(parts)):
61
+ parent_path = "/".join(parts[:index])
62
+ for parent in by_path.get(parent_path, []):
63
+ if parent.pk in container_ids or distribution.pk in container_ids:
64
+ overlaps.append((parent, distribution))
65
+
66
+ return overlaps
@@ -1,35 +1,32 @@
1
1
  import json
2
2
  import logging
3
3
  import os
4
-
5
- from asgiref.sync import sync_to_async
6
-
7
4
  from contextlib import suppress
8
5
  from urllib.parse import urljoin
9
6
 
10
7
  from aiohttp import web
11
- from aiohttp.client_exceptions import ClientResponseError, ClientConnectionError
8
+ from aiohttp.client_exceptions import ClientConnectionError, ClientResponseError
12
9
  from aiohttp.web_exceptions import HTTPTooManyRequests
13
- from django_guid import set_guid
14
- from django_guid.utils import generate_guid
10
+ from asgiref.sync import sync_to_async
15
11
  from django.core.exceptions import ObjectDoesNotExist
16
12
  from django.db import IntegrityError
13
+ from django_guid import set_guid
14
+ from django_guid.utils import generate_guid
17
15
  from multidict import MultiDict
18
16
 
19
- from pulpcore.plugin.content import Handler, PathNotResolved
20
- from pulpcore.plugin.util import get_domain
21
- from pulpcore.plugin.models import RemoteArtifact, Content, ContentArtifact
22
- from pulpcore.plugin.content import ArtifactResponse
23
- from pulpcore.plugin.tasking import dispatch
17
+ from pulpcore.plugin.content import ArtifactResponse, Handler, PathNotResolved
24
18
  from pulpcore.plugin.exceptions import TimeoutException
19
+ from pulpcore.plugin.models import Content, ContentArtifact, RemoteArtifact
20
+ from pulpcore.plugin.tasking import dispatch
21
+ from pulpcore.plugin.util import get_domain
25
22
 
26
23
  from pulp_container.app.cache import RegistryContentCache
27
- from pulp_container.app.models import ContainerDistribution, Tag, Blob, Manifest, BlobManifest
24
+ from pulp_container.app.models import Blob, BlobManifest, ContainerDistribution, Manifest, Tag
28
25
  from pulp_container.app.tasks import download_image_data
29
26
  from pulp_container.app.utils import (
30
27
  calculate_digest,
31
- get_accepted_media_types,
32
28
  determine_media_type,
29
+ get_accepted_media_types,
33
30
  save_artifact,
34
31
  )
35
32
  from pulp_container.constants import BLOB_CONTENT_TYPE, EMPTY_BLOB, MEDIA_TYPE, V2_ACCEPT_HEADERS
@@ -7,36 +7,26 @@ Check `Plugin Writer's Guide`_ for more details.
7
7
 
8
8
  import base64
9
9
  import binascii
10
+ import hashlib
10
11
  import json
11
12
  import logging
12
- import hashlib
13
13
  import re
14
-
15
- from aiohttp.client_exceptions import ClientResponseError, ClientConnectionError
16
14
  from itertools import chain
17
- from urllib.parse import urljoin, urlparse, urlunparse, parse_qs, urlencode
18
15
  from tempfile import NamedTemporaryFile
16
+ from urllib.parse import parse_qs, urlencode, urljoin, urlparse, urlunparse
19
17
 
18
+ from aiohttp.client_exceptions import ClientConnectionError, ClientResponseError
19
+ from django.conf import settings
20
20
  from django.core.exceptions import ObjectDoesNotExist
21
21
  from django.core.files.base import ContentFile, File
22
22
  from django.db import IntegrityError, transaction
23
- from django.db.models import F, Value
24
23
  from django.forms.models import model_to_dict
25
24
  from django.shortcuts import get_object_or_404
26
-
27
- from django.conf import settings
28
-
29
- from pulpcore.plugin.models import Artifact, ContentArtifact, UploadChunk
30
- from pulpcore.plugin.files import PulpTemporaryUploadedFile
31
- from pulpcore.plugin.tasking import add_and_remove, dispatch
32
- from pulpcore.plugin.util import get_objects_for_user, get_url, get_domain
33
- from pulpcore.plugin.exceptions import TimeoutException
34
-
35
25
  from rest_framework.exceptions import (
36
26
  AuthenticationFailed,
37
27
  NotAuthenticated,
38
- PermissionDenied,
39
28
  ParseError,
29
+ PermissionDenied,
40
30
  Throttled,
41
31
  )
42
32
  from rest_framework.generics import ListAPIView
@@ -46,38 +36,48 @@ from rest_framework.renderers import BaseRenderer, JSONRenderer
46
36
  from rest_framework.response import Response
47
37
  from rest_framework.serializers import ModelSerializer
48
38
  from rest_framework.settings import api_settings
49
- from rest_framework.viewsets import ViewSet
50
- from rest_framework.views import APIView, exception_handler
51
39
  from rest_framework.status import HTTP_401_UNAUTHORIZED
40
+ from rest_framework.views import APIView, exception_handler
41
+ from rest_framework.viewsets import ViewSet
42
+
43
+ from pulpcore.plugin.exceptions import TimeoutException
44
+ from pulpcore.plugin.files import PulpTemporaryUploadedFile
45
+ from pulpcore.plugin.models import Artifact, ContentArtifact, UploadChunk
46
+ from pulpcore.plugin.tasking import add_and_remove, dispatch
47
+ from pulpcore.plugin.util import get_domain, get_objects_for_user, get_url
52
48
 
53
49
  from pulp_container.app import models, serializers
54
50
  from pulp_container.app.authorization import AuthorizationService, PermissionChecker
55
51
  from pulp_container.app.cache import (
56
- find_base_path_cached,
57
52
  FlatpakIndexStaticCache,
58
53
  RegistryApiCache,
54
+ find_base_path_cached,
59
55
  )
60
56
  from pulp_container.app.exceptions import (
61
57
  BadGateway,
58
+ BlobInvalid,
59
+ BlobNotFound,
62
60
  GatewayTimeout,
63
61
  InvalidRequest,
64
- RepositoryNotFound,
65
- RepositoryInvalid,
66
- BlobNotFound,
67
- BlobInvalid,
68
- ManifestNotFound,
69
62
  ManifestInvalid,
63
+ ManifestNotFound,
70
64
  ManifestSignatureInvalid,
65
+ RepositoryInvalid,
66
+ RepositoryNotFound,
67
+ )
68
+ from pulp_container.app.pull_through import (
69
+ get_pull_through_distribution,
70
+ get_pull_through_distribution_path,
71
71
  )
72
72
  from pulp_container.app.redirects import (
73
+ AzureStorageRedirects,
73
74
  FileStorageRedirects,
74
75
  S3StorageRedirects,
75
- AzureStorageRedirects,
76
76
  )
77
77
  from pulp_container.app.token_verification import (
78
78
  RegistryAuthentication,
79
- TokenAuthentication,
80
79
  RegistryPermission,
80
+ TokenAuthentication,
81
81
  TokenPermission,
82
82
  )
83
83
  from pulp_container.app.utils import (
@@ -309,18 +309,12 @@ class ContainerRegistryApiMixin:
309
309
  return distribution, repository, repository_version
310
310
 
311
311
  def get_pull_through_drv(self, path):
312
- pull_through_cache_distribution = (
313
- models.ContainerPullThroughDistribution.objects.annotate(path=Value(path))
314
- .filter(path__startswith=F("base_path"))
315
- .order_by("-base_path")
316
- .first()
317
- )
312
+ pull_through_cache_distribution = get_pull_through_distribution(path)
318
313
  if not pull_through_cache_distribution or not self.request.user.is_authenticated:
319
314
  raise RepositoryNotFound(name=path)
320
315
 
321
- upstream_name = path.split(pull_through_cache_distribution.base_path, maxsplit=1)[1].strip(
322
- "/"
323
- )
316
+ pull_through_path = get_pull_through_distribution_path(pull_through_cache_distribution)
317
+ upstream_name = path.split(pull_through_path, maxsplit=1)[1].strip("/")
324
318
  try:
325
319
  pull_through_remote = models.ContainerPullThroughRemote.objects.get(
326
320
  pk=pull_through_cache_distribution.remote_id
@@ -571,9 +565,9 @@ class FlatpakIndexDynamicView(APIView):
571
565
  elif manifest.media_type in (models.MEDIA_TYPE.MANIFEST_LIST, models.MEDIA_TYPE.INDEX_OCI):
572
566
  mlms = manifest.listed_manifests.through.objects.filter(image_manifest__pk=manifest.pk)
573
567
  if oss:
574
- mlms.filter(os__in=oss)
568
+ mlms = mlms.filter(os__in=oss)
575
569
  if architectures:
576
- mlms.filter(architecture__in=architectures)
570
+ mlms = mlms.filter(architecture__in=architectures)
577
571
  for mlm in mlms:
578
572
  self.recurse_through_manifest_lists(
579
573
  tag, mlm.manifest_list, oss, architectures, manifests
@@ -679,7 +673,7 @@ class FlatpakIndexDynamicView(APIView):
679
673
  continue
680
674
  images.append(
681
675
  {
682
- "Tags": tagged,
676
+ "Tags": list(tagged),
683
677
  "Digest": manifest.digest,
684
678
  "MediaType": manifest.media_type,
685
679
  "OS": os,