pulp-container 2.29.0__tar.gz → 2.29.2__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.
- {pulp_container-2.29.0 → pulp_container-2.29.2}/CHANGES.md +17 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/PKG-INFO +4 -4
- {pulp_container-2.29.0 → pulp_container-2.29.2}/functest_requirements.txt +1 -2
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/__init__.py +1 -1
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/authorization.py +1 -11
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/cache.py +10 -10
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/checks.py +30 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/downloaders.py +26 -14
- pulp_container-2.29.2/pulp_container/app/management/commands/container-repair-pull-through-distributions.py +73 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/models.py +3 -4
- pulp_container-2.29.2/pulp_container/app/pull_through.py +69 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/registry_api.py +7 -9
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/serializers.py +57 -15
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/viewsets.py +24 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/constants.py +2 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_domains.py +3 -2
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_pull_through_cache.py +28 -10
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_push_content.py +0 -1
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_push_signatures.py +44 -22
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_remote_filter_pull_through.py +3 -1
- pulp_container-2.29.2/pulp_container/tests/functional/api/test_sign_manifests.py +125 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_tagging_images.py +3 -4
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/conftest.py +151 -54
- pulp_container-2.29.2/pulp_container/tests/unit/test_pull_through_repair.py +142 -0
- pulp_container-2.29.2/pulp_container/tests/unit/test_serializers.py +280 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container.egg-info/PKG-INFO +4 -4
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container.egg-info/SOURCES.txt +3 -0
- pulp_container-2.29.2/pulp_container.egg-info/requires.txt +4 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pyproject.toml +5 -5
- pulp_container-2.29.0/pulp_container/tests/functional/api/test_sign_manifests.py +0 -57
- pulp_container-2.29.0/pulp_container/tests/unit/test_serializers.py +0 -105
- pulp_container-2.29.0/pulp_container.egg-info/requires.txt +0 -4
- {pulp_container-2.29.0 → pulp_container-2.29.2}/COMMITMENT +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/COPYRIGHT +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/LICENSE +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/MANIFEST.in +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/README.rst +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/__init__.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/access_policy.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/content.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/dynaconf_hooks.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/exceptions.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/fields.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/global_access_conditions.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/json_schemas.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/management/__init__.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/management/commands/__init__.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/management/commands/container-handle-image-data.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/management/commands/container-repair-media-type.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0001_initial.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0001_squashed_0032_upload_artifact.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0002_containerrepository.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0003_oci_mediatype.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0004_upload.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0005_contentredirectcontentguard.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0006_containerpushrepository.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0007_clear_tags_artifacts_refs.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0008_include_exclude_tags.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0009_container_namespace.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0010_remove_uploadchunk.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0011_add_container_repository_permissions.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0012_add_container_namespace_permissions.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0013_add_pull_push_permissions.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0014_containerdistribution_private.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0015_manage_tags_push_repo.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0016_add_delete_versions_permission.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0017_add_granular_perms.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0018_containerdistribution_description.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0019_DATA_distribution_model_swap.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0020_update_push_repo_perms.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0021_data_move_redirect_content_guard_to_core.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0022_delete_contentredirectcontentguard.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0023_manifestsignature.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0024_containerremote_sigstore.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0025_signature_stored_in_textfield.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0026_manifest_signing_service.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0027_data_translate_perms_to_roles.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0028_add_role_manage_permissions.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0029_remove_blob_media_type.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0030_enforce_tagged_manifest_reference.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0031_replace_charf_with_textf.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0032_upload_artifact.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0033_raise_warning_for_repair.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0034_translate_signed_schema.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0035_alter_blob_content_ptr_and_more.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0036_containerpushrepository_pending_blobs_manifests.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0037_create_pull_through_cache_models.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0038_add_manifest_metadata_fields.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0039_manifest_data.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0040_add_remote_repo_filter.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0041_add_pull_through_pull_permissions.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0042_add_manifest_nature_field.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0043_add_os_arch_image_size_manifest_fields.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0044_add_domain.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0045_alter_manifest_compressed_image_size.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0046_alter_manifest_listed_manifests.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0047_containernamespace_pulp_labels.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0048_containerremote_includes_excludes.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0049_manifestsignature_fingerprint.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/0050_alter_containernamespace_options.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/migrations/__init__.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/modelresource.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/redirects.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/registry.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/replica.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/settings.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/tasks/__init__.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/tasks/builder.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/tasks/download_image_data.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/tasks/migrate_push_repository.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/tasks/recursive_add.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/tasks/recursive_remove.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/tasks/sign.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/tasks/sync_stages.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/tasks/synchronize.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/tasks/tag.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/tasks/untag.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/token_verification.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/urls.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/utils.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/webserver_snippets/__init__.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/webserver_snippets/apache.conf +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/app/webserver_snippets/nginx.conf +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/__init__.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/__init__.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/__init__.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_build_images.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_cancel_blob_upload.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_content_cache.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_crud_distributions.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_crud_remotes.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_delete_blob.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_delete_manifest.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_flatpak.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_migrate_push_repository.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_pull_content.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_pulpimportexport.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_rbac_push_repositories.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_rbac_remotes.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_rbac_repo_content.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_rbac_repo_versions.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_rbac_sync_repositories.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_recursive_add.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_recursive_remove.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_repositories_list.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_sync.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_sync_signatures.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_token_authentication.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/constants.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/utils.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/unit/__init__.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/unit/fixtures/ryuk_0.13.0_manifest_list.json +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/unit/test_json_schemas.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/unit/test_models.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/unit/test_sync_stages.py +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container.egg-info/dependency_links.txt +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container.egg-info/entry_points.txt +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container.egg-info/top_level.txt +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/setup.cfg +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/test_requirements.txt +0 -0
- {pulp_container-2.29.0 → pulp_container-2.29.2}/unittest_requirements.txt +0 -0
|
@@ -8,6 +8,23 @@
|
|
|
8
8
|
|
|
9
9
|
[//]: # (towncrier release notes start)
|
|
10
10
|
|
|
11
|
+
## 2.29.2 (2026-10-08) {: #2.29.2 }
|
|
12
|
+
|
|
13
|
+
#### Bugfixes {: #2.29.2-bugfix }
|
|
14
|
+
|
|
15
|
+
- Fixed global auth token leakage. CVE-2026-103868
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 2.29.1 (2026-09-23) {: #2.29.1 }
|
|
20
|
+
|
|
21
|
+
#### Bugfixes {: #2.29.1-bugfix }
|
|
22
|
+
|
|
23
|
+
- 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.
|
|
24
|
+
[#2494](https://github.com/pulp/pulp_container/issues/2494)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
11
28
|
## 2.29.0 (2026-08-10) {: #2.29.0 }
|
|
12
29
|
|
|
13
30
|
#### Features {: #2.29.0-feature }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pulp-container
|
|
3
|
-
Version: 2.29.
|
|
3
|
+
Version: 2.29.2
|
|
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.130,>=3.
|
|
22
|
-
Requires-Dist: pyjwt[crypto]<2.
|
|
23
|
-
Requires-Dist: pysequoia<0.2.0,>=0.1.
|
|
21
|
+
Requires-Dist: pulpcore<3.130,>=3.118.0
|
|
22
|
+
Requires-Dist: pyjwt[crypto]<2.16,>=2.4
|
|
23
|
+
Requires-Dist: pysequoia<0.2.0,>=0.1.35
|
|
24
24
|
Dynamic: license-file
|
|
@@ -10,7 +10,6 @@ import jwt
|
|
|
10
10
|
from cryptography.hazmat.backends import default_backend
|
|
11
11
|
from cryptography.hazmat.primitives import serialization
|
|
12
12
|
from django.conf import settings
|
|
13
|
-
from django.db.models import F, Value
|
|
14
13
|
from django.http import HttpRequest
|
|
15
14
|
from rest_framework.request import Request
|
|
16
15
|
|
|
@@ -21,8 +20,8 @@ from pulp_container.app.access_policy import RegistryAccessPolicy
|
|
|
21
20
|
from pulp_container.app.models import (
|
|
22
21
|
ContainerDistribution,
|
|
23
22
|
ContainerNamespace,
|
|
24
|
-
ContainerPullThroughDistribution,
|
|
25
23
|
)
|
|
24
|
+
from pulp_container.app.pull_through import get_pull_through_distribution
|
|
26
25
|
|
|
27
26
|
TOKEN_EXPIRATION_TIME = getattr(settings, "TOKEN_EXPIRATION_TIME", 300)
|
|
28
27
|
|
|
@@ -209,15 +208,6 @@ class AuthorizationService:
|
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
|
|
212
|
-
def get_pull_through_distribution(path, domain):
|
|
213
|
-
return (
|
|
214
|
-
ContainerPullThroughDistribution.objects.annotate(path=Value(path))
|
|
215
|
-
.filter(pulp_domain=domain, path__startswith=F("base_path"))
|
|
216
|
-
.order_by("-base_path")
|
|
217
|
-
.first()
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
|
|
221
211
|
class PermissionChecker:
|
|
222
212
|
def __init__(self, user):
|
|
223
213
|
self.user = user
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
from django.core.exceptions import ObjectDoesNotExist
|
|
2
|
-
from django.db.models import F, Value
|
|
3
2
|
|
|
4
3
|
from pulpcore.plugin.cache import AsyncContentCache, CacheKeys, SyncContentCache
|
|
5
4
|
from pulpcore.plugin.util import cache_key, get_domain
|
|
6
5
|
|
|
7
6
|
from pulp_container.app.exceptions import RepositoryNotFound
|
|
8
|
-
from pulp_container.app.models import ContainerDistribution
|
|
7
|
+
from pulp_container.app.models import ContainerDistribution
|
|
8
|
+
from pulp_container.app.pull_through import (
|
|
9
|
+
get_pull_through_distribution,
|
|
10
|
+
get_pull_through_distribution_path,
|
|
11
|
+
)
|
|
9
12
|
|
|
10
13
|
ACCEPT_HEADER_KEY = "accept_header"
|
|
11
14
|
QUERY_KEY = "query"
|
|
@@ -37,9 +40,9 @@ class RegistryContentCache(RegistryCache, AsyncContentCache):
|
|
|
37
40
|
key = ":".join(all_keys[k] for k in self.keys)
|
|
38
41
|
return key
|
|
39
42
|
|
|
40
|
-
async def make_response(self, key, base_key):
|
|
43
|
+
async def make_response(self, key, base_key, *args, **kwargs):
|
|
41
44
|
"""Fix the Content-Type header to remove the charset."""
|
|
42
|
-
response = await super().make_response(key, base_key)
|
|
45
|
+
response = await super().make_response(key, base_key, *args, **kwargs)
|
|
43
46
|
if response is not None:
|
|
44
47
|
if content_type := response.headers.get("Content-Type"):
|
|
45
48
|
response.headers["Content-Type"] = content_type.split(";")[0]
|
|
@@ -83,15 +86,12 @@ def find_base_path_cached(request, cached):
|
|
|
83
86
|
try:
|
|
84
87
|
distro = ContainerDistribution.objects.get(base_path=path, pulp_domain=domain)
|
|
85
88
|
except ObjectDoesNotExist:
|
|
86
|
-
distro = (
|
|
87
|
-
ContainerPullThroughDistribution.objects.annotate(path=Value(path))
|
|
88
|
-
.filter(path__startswith=F("base_path"), pulp_domain=domain)
|
|
89
|
-
.order_by("-base_path")
|
|
90
|
-
.first()
|
|
91
|
-
)
|
|
89
|
+
distro = get_pull_through_distribution(path, domain)
|
|
92
90
|
if not distro:
|
|
93
91
|
raise RepositoryNotFound(name=path)
|
|
94
92
|
|
|
93
|
+
return cache_key(get_pull_through_distribution_path(distro))
|
|
94
|
+
|
|
95
95
|
return cache_key(distro.base_path)
|
|
96
96
|
|
|
97
97
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
2
|
from django.core.checks import Error as CheckError
|
|
3
|
+
from django.core.checks import Warning as CheckWarning
|
|
3
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
|
|
4
9
|
|
|
5
10
|
|
|
6
11
|
@register(deploy=True)
|
|
@@ -45,3 +50,28 @@ def container_settings_check(app_configs, **kwargs):
|
|
|
45
50
|
)
|
|
46
51
|
|
|
47
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
|
+
]
|
|
@@ -25,15 +25,12 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
25
25
|
Additionally, use custom headers from DeclarativeArtifact.extra_data['headers']
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
registry_auth = {"bearer": None, "basic": None}
|
|
29
|
-
token_lock = asyncio.Lock()
|
|
30
|
-
|
|
31
28
|
def __init__(self, *args, **kwargs):
|
|
32
29
|
"""
|
|
33
30
|
Initialize the downloader.
|
|
34
31
|
"""
|
|
35
32
|
self.remote = kwargs.pop("remote")
|
|
36
|
-
|
|
33
|
+
self.factory = kwargs.pop("factory")
|
|
37
34
|
super().__init__(*args, **kwargs)
|
|
38
35
|
|
|
39
36
|
async def _run(self, handle_401=True, extra_data=None):
|
|
@@ -57,8 +54,8 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
57
54
|
headers = extra_data.get("headers", headers)
|
|
58
55
|
repo_name = extra_data.get("repo_name", None)
|
|
59
56
|
http_method = extra_data.get("http_method", "get") if extra_data is not None else "get"
|
|
60
|
-
this_token = self.registry_auth["bearer"]
|
|
61
|
-
basic_auth = self.registry_auth["basic"]
|
|
57
|
+
this_token = self.factory.registry_auth["bearer"]
|
|
58
|
+
basic_auth = self.factory.registry_auth["basic"]
|
|
62
59
|
auth_headers = self.auth_header(this_token, basic_auth)
|
|
63
60
|
headers.update(auth_headers)
|
|
64
61
|
# aiohttps does not allow to send auth argument and auth header together
|
|
@@ -81,16 +78,16 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
81
78
|
if "bearer" in response_auth_header.lower():
|
|
82
79
|
# Token has not been updated during request
|
|
83
80
|
if (
|
|
84
|
-
self.registry_auth["bearer"] is None
|
|
85
|
-
or self.registry_auth["bearer"] == this_token
|
|
81
|
+
self.factory.registry_auth["bearer"] is None
|
|
82
|
+
or self.factory.registry_auth["bearer"] == this_token
|
|
86
83
|
):
|
|
87
|
-
self.registry_auth["bearer"] = None
|
|
84
|
+
self.factory.registry_auth["bearer"] = None
|
|
88
85
|
await self.update_token(response_auth_header, this_token, repo_name)
|
|
89
86
|
return await self._run(handle_401=False, extra_data=extra_data)
|
|
90
87
|
elif "basic" in response_auth_header.lower():
|
|
91
88
|
if self.remote.username:
|
|
92
89
|
basic = aiohttp.BasicAuth(self.remote.username, self.remote.password)
|
|
93
|
-
self.registry_auth["basic"] = basic.encode()
|
|
90
|
+
self.factory.registry_auth["basic"] = basic.encode()
|
|
94
91
|
return await self._run(handle_401=False, extra_data=extra_data)
|
|
95
92
|
else:
|
|
96
93
|
raise
|
|
@@ -111,10 +108,10 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
111
108
|
"""
|
|
112
109
|
Update the Bearer token to be used with all requests.
|
|
113
110
|
"""
|
|
114
|
-
async with self.token_lock:
|
|
111
|
+
async with self.factory.token_lock:
|
|
115
112
|
if (
|
|
116
|
-
self.registry_auth["bearer"] is not None
|
|
117
|
-
and self.registry_auth["bearer"] == used_token
|
|
113
|
+
self.factory.registry_auth["bearer"] is not None
|
|
114
|
+
and self.factory.registry_auth["bearer"] == used_token
|
|
118
115
|
):
|
|
119
116
|
return
|
|
120
117
|
log.info("Updating bearer token")
|
|
@@ -157,7 +154,7 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
157
154
|
|
|
158
155
|
token_js = json.loads(token_data)
|
|
159
156
|
token = token_js.get("token") or token_js.get("access_token")
|
|
160
|
-
self.registry_auth["bearer"] = token
|
|
157
|
+
self.factory.registry_auth["bearer"] = token
|
|
161
158
|
|
|
162
159
|
@staticmethod
|
|
163
160
|
def auth_header(token, basic_auth):
|
|
@@ -187,6 +184,21 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
187
184
|
)
|
|
188
185
|
|
|
189
186
|
|
|
187
|
+
class RegistryAuthDownloaderFactory(DownloaderFactory):
|
|
188
|
+
"""
|
|
189
|
+
A downloader factory for registry access that can also share the authentication tokens.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
def __init__(self, *args, **kwargs):
|
|
193
|
+
super().__init__(*args, **kwargs)
|
|
194
|
+
self.registry_auth = {"bearer": None, "basic": None}
|
|
195
|
+
self.token_lock = asyncio.Lock()
|
|
196
|
+
|
|
197
|
+
def _http_or_https(self, download_class, url, **kwargs):
|
|
198
|
+
kwargs.setdefault("factory", self)
|
|
199
|
+
return super()._http_or_https(download_class, url, **kwargs)
|
|
200
|
+
|
|
201
|
+
|
|
190
202
|
class NoAuthSignatureDownloader(HttpDownloader):
|
|
191
203
|
"""A downloader class suited for signature downloads."""
|
|
192
204
|
|
|
@@ -0,0 +1,73 @@
|
|
|
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_and_domain = []
|
|
26
|
+
distributions = ContainerPullThroughDistribution.objects.exclude(
|
|
27
|
+
pulp_labels__has_key=PULL_THROUGH_DISTRIBUTION_LABEL
|
|
28
|
+
).select_related("pulp_domain")
|
|
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_and_domain.append((old_base_path, distribution.pulp_domain))
|
|
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_and_domain:
|
|
58
|
+
if settings.DOMAIN_ENABLED:
|
|
59
|
+
old_base_paths = [
|
|
60
|
+
f"{domain.name}:{base_path}"
|
|
61
|
+
for base_path, domain in migrated_base_paths_and_domain
|
|
62
|
+
]
|
|
63
|
+
else:
|
|
64
|
+
old_base_paths = [base_path for base_path, _ in migrated_base_paths_and_domain]
|
|
65
|
+
SyncContentCache().delete(base_key=old_base_paths)
|
|
66
|
+
self.stdout.write(self.style.SUCCESS("Flushed repaired distribution cache entries."))
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _format_distribution(distribution):
|
|
70
|
+
return (
|
|
71
|
+
f" domain={distribution.pulp_domain.name!r} pk={distribution.pk} "
|
|
72
|
+
f"name={distribution.name!r} base_path={distribution.base_path!r}"
|
|
73
|
+
)
|
|
@@ -13,7 +13,6 @@ from django.shortcuts import redirect
|
|
|
13
13
|
from django_lifecycle import AFTER_CREATE, AFTER_DELETE, AFTER_UPDATE, hook
|
|
14
14
|
|
|
15
15
|
from pulpcore.plugin.cache import SyncContentCache
|
|
16
|
-
from pulpcore.plugin.download import DownloaderFactory
|
|
17
16
|
from pulpcore.plugin.models import (
|
|
18
17
|
Artifact,
|
|
19
18
|
AutoAddObjPermsMixin,
|
|
@@ -510,17 +509,17 @@ class ContainerRemote(Remote, AutoAddObjPermsMixin):
|
|
|
510
509
|
"""
|
|
511
510
|
Downloader Factory that maps to custom downloaders which support registry auth.
|
|
512
511
|
|
|
513
|
-
Upon first access, the
|
|
512
|
+
Upon first access, the RegistryAuthDownloaderFactory is instantiated and saved internally.
|
|
514
513
|
|
|
515
514
|
Returns:
|
|
516
|
-
DownloadFactory: The instantiated
|
|
515
|
+
DownloadFactory: The instantiated RegistryAuthDownloaderFactory to be used by
|
|
517
516
|
get_downloader()
|
|
518
517
|
|
|
519
518
|
"""
|
|
520
519
|
try:
|
|
521
520
|
return self._download_factory
|
|
522
521
|
except AttributeError:
|
|
523
|
-
self._download_factory =
|
|
522
|
+
self._download_factory = downloaders.RegistryAuthDownloaderFactory(
|
|
524
523
|
self,
|
|
525
524
|
downloader_overrides={
|
|
526
525
|
"http": downloaders.RegistryAuthHttpDownloader,
|
|
@@ -0,0 +1,69 @@
|
|
|
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, domain):
|
|
39
|
+
"""Find a pull-through distribution using the repaired scheme, then the legacy scheme."""
|
|
40
|
+
distributions = ContainerPullThroughDistribution.objects.filter(pulp_domain=domain)
|
|
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 = Distribution.objects.select_related("pulp_domain").order_by(
|
|
53
|
+
"pulp_domain_id", "base_path", "pk"
|
|
54
|
+
)
|
|
55
|
+
by_domain_and_path = {
|
|
56
|
+
(distribution.pulp_domain_id, distribution.base_path): distribution
|
|
57
|
+
for distribution in distributions.iterator()
|
|
58
|
+
}
|
|
59
|
+
overlaps = []
|
|
60
|
+
|
|
61
|
+
for distribution in by_domain_and_path.values():
|
|
62
|
+
parts = distribution.base_path.split("/")
|
|
63
|
+
for index in range(1, len(parts)):
|
|
64
|
+
parent_path = "/".join(parts[:index])
|
|
65
|
+
if parent := by_domain_and_path.get((distribution.pulp_domain_id, parent_path)):
|
|
66
|
+
if parent.pk in container_ids or distribution.pk in container_ids:
|
|
67
|
+
overlaps.append((parent, distribution))
|
|
68
|
+
|
|
69
|
+
return overlaps
|
|
@@ -75,6 +75,10 @@ from pulp_container.app.exceptions import (
|
|
|
75
75
|
RepositoryInvalid,
|
|
76
76
|
RepositoryNotFound,
|
|
77
77
|
)
|
|
78
|
+
from pulp_container.app.pull_through import (
|
|
79
|
+
get_pull_through_distribution,
|
|
80
|
+
get_pull_through_distribution_path,
|
|
81
|
+
)
|
|
78
82
|
from pulp_container.app.redirects import (
|
|
79
83
|
AzureStorageRedirects,
|
|
80
84
|
FileStorageRedirects,
|
|
@@ -366,18 +370,12 @@ class ContainerRegistryApiMixin:
|
|
|
366
370
|
|
|
367
371
|
def get_pull_through_drv(self, path):
|
|
368
372
|
domain = get_domain()
|
|
369
|
-
pull_through_cache_distribution = (
|
|
370
|
-
models.ContainerPullThroughDistribution.objects.annotate(path=Value(path))
|
|
371
|
-
.filter(path__startswith=F("base_path"), pulp_domain=domain)
|
|
372
|
-
.order_by("-base_path")
|
|
373
|
-
.first()
|
|
374
|
-
)
|
|
373
|
+
pull_through_cache_distribution = get_pull_through_distribution(path, domain)
|
|
375
374
|
if not pull_through_cache_distribution or not self.request.user.is_authenticated:
|
|
376
375
|
raise RepositoryNotFound(name=path)
|
|
377
376
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
)
|
|
377
|
+
pull_through_path = get_pull_through_distribution_path(pull_through_cache_distribution)
|
|
378
|
+
upstream_name = path.split(pull_through_path, maxsplit=1)[1].strip("/")
|
|
381
379
|
try:
|
|
382
380
|
pull_through_remote = models.ContainerPullThroughRemote.objects.get(
|
|
383
381
|
pk=pull_through_cache_distribution.remote_id
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from gettext import gettext as _
|
|
3
|
+
from uuid import uuid4
|
|
3
4
|
|
|
4
5
|
from django.core.validators import URLValidator
|
|
5
6
|
from pulp_file.app.models import FileContent
|
|
@@ -33,13 +34,31 @@ from pulpcore.plugin.util import get_domain
|
|
|
33
34
|
|
|
34
35
|
from pulp_container.app import fields, models
|
|
35
36
|
from pulp_container.app.utils import get_full_path
|
|
36
|
-
from pulp_container.constants import
|
|
37
|
+
from pulp_container.constants import (
|
|
38
|
+
PULL_THROUGH_DISTRIBUTION_LABEL,
|
|
39
|
+
SIGNATURE_TYPE,
|
|
40
|
+
)
|
|
37
41
|
|
|
38
42
|
VALID_SIGNATURE_NAME_REGEX = r"^sha256:[0-9a-f]{64}@[0-9a-f]{32}$"
|
|
39
43
|
VALID_TAG_REGEX = r"^[A-Za-z0-9][A-Za-z0-9._-]*$"
|
|
40
44
|
VALID_BASE_PATH_REGEX_COMPILED = re.compile(r"^[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9])*$")
|
|
41
45
|
|
|
42
46
|
|
|
47
|
+
def validate_registry_path(value):
|
|
48
|
+
"""Validate a path that will be exposed by the container registry."""
|
|
49
|
+
if len(value) > 255:
|
|
50
|
+
raise serializers.ValidationError(
|
|
51
|
+
_("The entered registry path cannot be longer than 255 characters.")
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if not all(re.match(VALID_BASE_PATH_REGEX_COMPILED, part) for part in value.split("/")):
|
|
55
|
+
raise serializers.ValidationError(
|
|
56
|
+
_("The provided registry path contains forbidden characters.")
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
|
|
43
62
|
class TagSerializer(NoArtifactContentSerializer):
|
|
44
63
|
"""
|
|
45
64
|
Serializer for Tags.
|
|
@@ -499,17 +518,9 @@ class ContainerDistributionSerializer(DistributionSerializer, GetOrCreateSeriali
|
|
|
499
518
|
|
|
500
519
|
def validate_base_path(self, value):
|
|
501
520
|
"""Check whether the passed repository base path is valid or not."""
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
)
|
|
506
|
-
|
|
507
|
-
if not all(re.match(VALID_BASE_PATH_REGEX_COMPILED, p) for p in value.split("/")):
|
|
508
|
-
raise serializers.ValidationError(
|
|
509
|
-
_("The provided base path contains forbidden characters.")
|
|
510
|
-
)
|
|
511
|
-
|
|
512
|
-
return value
|
|
521
|
+
validate_registry_path(value)
|
|
522
|
+
# Ensure we follow pulpcore's cross-distribution overlap validation in addition to the OCI rules.
|
|
523
|
+
return super().validate_base_path(value)
|
|
513
524
|
|
|
514
525
|
class Meta:
|
|
515
526
|
model = models.ContainerDistribution
|
|
@@ -556,18 +567,49 @@ class ContainerPullThroughDistributionSerializer(DistributionSerializer):
|
|
|
556
567
|
description = serializers.CharField(
|
|
557
568
|
help_text=_("An optional description."), required=False, allow_null=True
|
|
558
569
|
)
|
|
570
|
+
base_path = serializers.CharField(
|
|
571
|
+
help_text=_("The registry path exposed by this pull-through distribution.")
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
def validate_base_path(self, value):
|
|
575
|
+
"""Check whether the passed repository base path is valid or not."""
|
|
576
|
+
validate_registry_path(value)
|
|
577
|
+
# Ensure we follow pulpcore's cross-distribution overlap validation in addition to the OCI rules.
|
|
578
|
+
return super().validate_base_path(value)
|
|
559
579
|
|
|
560
580
|
def validate(self, data):
|
|
561
581
|
validated_data = super().validate(data)
|
|
582
|
+
registry_path = validated_data.get("base_path")
|
|
583
|
+
|
|
584
|
+
# This is the temporary fix for pull-through distros violating core's overlapping base-path rule
|
|
585
|
+
# Save the base_path in pulp_labels to avoid overlapping base-paths with this PT's child distros
|
|
586
|
+
# A permanent fix should have us move off this field or Distributions all together.
|
|
587
|
+
if self.instance is None:
|
|
588
|
+
base_path = validated_data.pop("base_path")
|
|
589
|
+
validated_data["base_path"] = str(uuid4())
|
|
590
|
+
validated_data.setdefault("pulp_labels", {})[PULL_THROUGH_DISTRIBUTION_LABEL] = (
|
|
591
|
+
base_path
|
|
592
|
+
)
|
|
593
|
+
elif (
|
|
594
|
+
"base_path" in validated_data and validated_data["base_path"] != self.instance.base_path
|
|
595
|
+
):
|
|
596
|
+
is_marked = (self.instance.pulp_labels or {}).get(PULL_THROUGH_DISTRIBUTION_LABEL)
|
|
597
|
+
if is_marked:
|
|
598
|
+
message = _("This value cannot be updated.")
|
|
599
|
+
else:
|
|
600
|
+
message = _(
|
|
601
|
+
"Run 'pulpcore-manager container-repair-pull-through-distributions' before "
|
|
602
|
+
"updating this value."
|
|
603
|
+
)
|
|
604
|
+
raise serializers.ValidationError({"base_path": message})
|
|
562
605
|
|
|
563
606
|
if "content_guard" not in validated_data:
|
|
564
607
|
validated_data["content_guard"] = ContentRedirectContentGuardSerializer.get_or_create(
|
|
565
608
|
{"name": "content redirect", "pulp_domain": get_domain()}
|
|
566
609
|
)
|
|
567
610
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
namespace_name = base_path.split("/")[0]
|
|
611
|
+
if registry_path:
|
|
612
|
+
namespace_name = registry_path.split("/")[0]
|
|
571
613
|
validated_data["namespace"] = ContainerNamespaceSerializer.get_or_create(
|
|
572
614
|
{"name": namespace_name, "pulp_domain": get_domain()}
|
|
573
615
|
)
|
|
@@ -11,6 +11,7 @@ from django_filters import CharFilter, MultipleChoiceFilter
|
|
|
11
11
|
from drf_spectacular.utils import extend_schema
|
|
12
12
|
from rest_framework import mixins
|
|
13
13
|
from rest_framework.decorators import action
|
|
14
|
+
from rest_framework.exceptions import ValidationError
|
|
14
15
|
|
|
15
16
|
from pulpcore.plugin.models import Content, PulpTemporaryFile
|
|
16
17
|
from pulpcore.plugin.serializers import AsyncOperationResponseSerializer
|
|
@@ -40,6 +41,7 @@ from pulpcore.plugin.viewsets import (
|
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
from pulp_container.app import models, serializers, tasks
|
|
44
|
+
from pulp_container.constants import PULL_THROUGH_DISTRIBUTION_LABEL
|
|
43
45
|
|
|
44
46
|
log = logging.getLogger(__name__)
|
|
45
47
|
|
|
@@ -1599,6 +1601,28 @@ class ContainerPullThroughDistributionViewSet(DistributionViewSet, RolesMixin):
|
|
|
1599
1601
|
],
|
|
1600
1602
|
}
|
|
1601
1603
|
|
|
1604
|
+
@action(
|
|
1605
|
+
detail=True,
|
|
1606
|
+
methods=["post"],
|
|
1607
|
+
serializer_class=DistributionViewSet.set_label.kwargs["serializer_class"],
|
|
1608
|
+
)
|
|
1609
|
+
def set_label(self, request, pk=None, **kwargs):
|
|
1610
|
+
"""Prevent clients from changing the internal pull-through marker."""
|
|
1611
|
+
if request.data.get("key") == PULL_THROUGH_DISTRIBUTION_LABEL:
|
|
1612
|
+
raise ValidationError("This label is managed by pulp_container.")
|
|
1613
|
+
return super().set_label(request, pk, **kwargs)
|
|
1614
|
+
|
|
1615
|
+
@action(
|
|
1616
|
+
detail=True,
|
|
1617
|
+
methods=["post"],
|
|
1618
|
+
serializer_class=DistributionViewSet.unset_label.kwargs["serializer_class"],
|
|
1619
|
+
)
|
|
1620
|
+
def unset_label(self, request, pk=None, **kwargs):
|
|
1621
|
+
"""Prevent clients from removing the internal pull-through marker."""
|
|
1622
|
+
if request.data.get("key") == PULL_THROUGH_DISTRIBUTION_LABEL:
|
|
1623
|
+
raise ValidationError("This label is managed by pulp_container.")
|
|
1624
|
+
return super().unset_label(request, pk, **kwargs)
|
|
1625
|
+
|
|
1602
1626
|
|
|
1603
1627
|
class ContainerNamespaceViewSet(
|
|
1604
1628
|
NamedModelViewSet,
|
{pulp_container-2.29.0 → pulp_container-2.29.2}/pulp_container/tests/functional/api/test_domains.py
RENAMED
|
@@ -237,11 +237,12 @@ def test_cross_domain_pulp_apis(
|
|
|
237
237
|
},
|
|
238
238
|
pulp_domain=domain1.name,
|
|
239
239
|
)
|
|
240
|
+
pt_distribution_name = str(uuid.uuid4())
|
|
240
241
|
with pytest.raises(container_bindings.ApiException) as e:
|
|
241
242
|
container_bindings.DistributionsPullThroughApi.create(
|
|
242
243
|
{
|
|
243
|
-
"name":
|
|
244
|
-
"base_path":
|
|
244
|
+
"name": pt_distribution_name,
|
|
245
|
+
"base_path": pt_distribution_name,
|
|
245
246
|
"remote": pt_remote.pulp_href,
|
|
246
247
|
},
|
|
247
248
|
pulp_domain=domain2.name,
|