pulp-container 2.29.2__tar.gz → 2.29.3__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.2 → pulp_container-2.29.3}/CHANGES.md +14 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/PKG-INFO +1 -1
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/__init__.py +1 -1
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/downloaders.py +35 -14
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/registry_api.py +44 -7
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/viewsets.py +12 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_pull_content.py +61 -1
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_pull_through_cache.py +48 -22
- pulp_container-2.29.3/pulp_container/tests/unit/test_downloaders.py +33 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container.egg-info/PKG-INFO +1 -1
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container.egg-info/SOURCES.txt +1 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pyproject.toml +2 -2
- {pulp_container-2.29.2 → pulp_container-2.29.3}/COMMITMENT +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/COPYRIGHT +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/LICENSE +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/MANIFEST.in +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/README.rst +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/functest_requirements.txt +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/__init__.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/access_policy.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/authorization.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/cache.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/checks.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/content.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/dynaconf_hooks.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/exceptions.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/fields.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/global_access_conditions.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/json_schemas.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/management/__init__.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/management/commands/__init__.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/management/commands/container-handle-image-data.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/management/commands/container-repair-media-type.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/management/commands/container-repair-pull-through-distributions.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0001_initial.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0001_squashed_0032_upload_artifact.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0002_containerrepository.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0003_oci_mediatype.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0004_upload.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0005_contentredirectcontentguard.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0006_containerpushrepository.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0007_clear_tags_artifacts_refs.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0008_include_exclude_tags.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0009_container_namespace.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0010_remove_uploadchunk.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0011_add_container_repository_permissions.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0012_add_container_namespace_permissions.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0013_add_pull_push_permissions.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0014_containerdistribution_private.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0015_manage_tags_push_repo.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0016_add_delete_versions_permission.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0017_add_granular_perms.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0018_containerdistribution_description.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0019_DATA_distribution_model_swap.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0020_update_push_repo_perms.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0021_data_move_redirect_content_guard_to_core.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0022_delete_contentredirectcontentguard.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0023_manifestsignature.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0024_containerremote_sigstore.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0025_signature_stored_in_textfield.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0026_manifest_signing_service.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0027_data_translate_perms_to_roles.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0028_add_role_manage_permissions.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0029_remove_blob_media_type.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0030_enforce_tagged_manifest_reference.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0031_replace_charf_with_textf.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0032_upload_artifact.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0033_raise_warning_for_repair.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0034_translate_signed_schema.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0035_alter_blob_content_ptr_and_more.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0036_containerpushrepository_pending_blobs_manifests.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0037_create_pull_through_cache_models.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0038_add_manifest_metadata_fields.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0039_manifest_data.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0040_add_remote_repo_filter.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0041_add_pull_through_pull_permissions.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0042_add_manifest_nature_field.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0043_add_os_arch_image_size_manifest_fields.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0044_add_domain.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0045_alter_manifest_compressed_image_size.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0046_alter_manifest_listed_manifests.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0047_containernamespace_pulp_labels.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0048_containerremote_includes_excludes.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0049_manifestsignature_fingerprint.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0050_alter_containernamespace_options.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/__init__.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/modelresource.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/models.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/pull_through.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/redirects.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/registry.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/replica.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/serializers.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/settings.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/__init__.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/builder.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/download_image_data.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/migrate_push_repository.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/recursive_add.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/recursive_remove.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/sign.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/sync_stages.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/synchronize.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/tag.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/untag.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/token_verification.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/urls.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/utils.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/webserver_snippets/__init__.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/webserver_snippets/apache.conf +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/webserver_snippets/nginx.conf +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/constants.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/__init__.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/__init__.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/__init__.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_build_images.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_cancel_blob_upload.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_content_cache.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_crud_distributions.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_crud_remotes.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_delete_blob.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_delete_manifest.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_domains.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_flatpak.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_migrate_push_repository.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_pulpimportexport.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_push_content.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_push_signatures.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_rbac_push_repositories.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_rbac_remotes.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_rbac_repo_content.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_rbac_repo_versions.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_rbac_sync_repositories.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_recursive_add.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_recursive_remove.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_remote_filter_pull_through.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_repositories_list.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_sign_manifests.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_sync.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_sync_signatures.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_tagging_images.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_token_authentication.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/conftest.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/constants.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/utils.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/unit/__init__.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/unit/fixtures/ryuk_0.13.0_manifest_list.json +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/unit/test_json_schemas.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/unit/test_models.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/unit/test_pull_through_repair.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/unit/test_serializers.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/unit/test_sync_stages.py +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container.egg-info/dependency_links.txt +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container.egg-info/entry_points.txt +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container.egg-info/requires.txt +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container.egg-info/top_level.txt +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/setup.cfg +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/test_requirements.txt +0 -0
- {pulp_container-2.29.2 → pulp_container-2.29.3}/unittest_requirements.txt +0 -0
|
@@ -8,6 +8,20 @@
|
|
|
8
8
|
|
|
9
9
|
[//]: # (towncrier release notes start)
|
|
10
10
|
|
|
11
|
+
## 2.29.3 (2026-10-28) {: #2.29.3 }
|
|
12
|
+
|
|
13
|
+
#### Bugfixes {: #2.29.3-bugfix }
|
|
14
|
+
|
|
15
|
+
- Allow anonymous pull-through for public distributions
|
|
16
|
+
[#1976](https://github.com/pulp/pulp_container/issues/1976)
|
|
17
|
+
- Container blob downloads now respect each Domain's object storage redirect setting.
|
|
18
|
+
- Fixed pull-through signature lookups failing with a 500 when the upstream registry (e.g. gcr.io) sends unquoted values in its bearer auth challenge.
|
|
19
|
+
- Fixed the registry Authorization header leaking into a shared module-level headers dict, which could send one remote's credentials to another registry.
|
|
20
|
+
- Retried GHCR requests that fail with 403 using a bearer token, and fell back to GET when a registry (e.g. ECR Public) rejects HEAD on blob endpoints.
|
|
21
|
+
- Treat an explicitly empty Accept header like a missing header when retrieving registry blobs.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
11
25
|
## 2.29.2 (2026-10-08) {: #2.29.2 }
|
|
12
26
|
|
|
13
27
|
#### Bugfixes {: #2.29.2-bugfix }
|
|
@@ -33,7 +33,7 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
33
33
|
self.factory = kwargs.pop("factory")
|
|
34
34
|
super().__init__(*args, **kwargs)
|
|
35
35
|
|
|
36
|
-
async def _run(self,
|
|
36
|
+
async def _run(self, token_refresh_retries=2, extra_data=None):
|
|
37
37
|
"""
|
|
38
38
|
Download, validate, and compute digests on the `url`. This is a coroutine.
|
|
39
39
|
|
|
@@ -45,13 +45,13 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
45
45
|
:meth:`~pulpcore.plugin.download.BaseDownloader._run`.
|
|
46
46
|
|
|
47
47
|
Args:
|
|
48
|
-
|
|
48
|
+
token_refresh_retries(int): Number of times to retry the request
|
|
49
49
|
|
|
50
50
|
"""
|
|
51
51
|
headers = {}
|
|
52
52
|
repo_name = None
|
|
53
53
|
if extra_data is not None:
|
|
54
|
-
headers = extra_data.get("headers", headers)
|
|
54
|
+
headers = dict(extra_data.get("headers", headers))
|
|
55
55
|
repo_name = extra_data.get("repo_name", None)
|
|
56
56
|
http_method = extra_data.get("http_method", "get") if extra_data is not None else "get"
|
|
57
57
|
this_token = self.factory.registry_auth["bearer"]
|
|
@@ -73,7 +73,7 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
73
73
|
except ClientResponseError as e:
|
|
74
74
|
response_auth_header = response.headers.get("www-authenticate")
|
|
75
75
|
# Need to retry request
|
|
76
|
-
if
|
|
76
|
+
if token_refresh_retries and e.status == 401 and response_auth_header is not None:
|
|
77
77
|
# check if bearer or basic
|
|
78
78
|
if "bearer" in response_auth_header.lower():
|
|
79
79
|
# Token has not been updated during request
|
|
@@ -83,12 +83,22 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
83
83
|
):
|
|
84
84
|
self.factory.registry_auth["bearer"] = None
|
|
85
85
|
await self.update_token(response_auth_header, this_token, repo_name)
|
|
86
|
-
return await self._run(
|
|
86
|
+
return await self._run(
|
|
87
|
+
token_refresh_retries=token_refresh_retries - 1, extra_data=extra_data
|
|
88
|
+
)
|
|
87
89
|
elif "basic" in response_auth_header.lower():
|
|
88
90
|
if self.remote.username:
|
|
89
91
|
basic = aiohttp.BasicAuth(self.remote.username, self.remote.password)
|
|
90
92
|
self.factory.registry_auth["basic"] = basic.encode()
|
|
91
|
-
return await self._run(
|
|
93
|
+
return await self._run(
|
|
94
|
+
token_refresh_retries=token_refresh_retries - 1, extra_data=extra_data
|
|
95
|
+
)
|
|
96
|
+
elif token_refresh_retries and e.status == 403 and this_token is not None:
|
|
97
|
+
# handle 403 with bearer token, this is a special case for ghcr registry
|
|
98
|
+
self.factory.registry_auth["bearer"] = None
|
|
99
|
+
return await self._run(
|
|
100
|
+
token_refresh_retries=token_refresh_retries - 1, extra_data=extra_data
|
|
101
|
+
)
|
|
92
102
|
else:
|
|
93
103
|
raise
|
|
94
104
|
|
|
@@ -115,14 +125,7 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
115
125
|
):
|
|
116
126
|
return
|
|
117
127
|
log.info("Updating bearer token")
|
|
118
|
-
|
|
119
|
-
bearer_info_list = re.split(",(?=[^=,]+=)", bearer_info_string)
|
|
120
|
-
|
|
121
|
-
# The remaining string consists of comma seperated key=value pairs
|
|
122
|
-
auth_query_dict = {}
|
|
123
|
-
for key, value in (item.split("=") for item in bearer_info_list):
|
|
124
|
-
# The value is a string within a string, ex: '"value"'
|
|
125
|
-
auth_query_dict[key] = json.loads(value)
|
|
128
|
+
auth_query_dict = self.parse_auth_challenge(response_auth_header)
|
|
126
129
|
try:
|
|
127
130
|
token_base_url = auth_query_dict.pop("realm")
|
|
128
131
|
except KeyError:
|
|
@@ -156,6 +159,24 @@ class RegistryAuthHttpDownloader(HttpDownloader):
|
|
|
156
159
|
token = token_js.get("token") or token_js.get("access_token")
|
|
157
160
|
self.factory.registry_auth["bearer"] = token
|
|
158
161
|
|
|
162
|
+
@staticmethod
|
|
163
|
+
def parse_auth_challenge(response_auth_header):
|
|
164
|
+
"""
|
|
165
|
+
Parse the parameters of a Bearer auth challenge into a dictionary.
|
|
166
|
+
|
|
167
|
+
Values may be quoted strings or plain tokens (RFC 7235), e.g. gcr.io responds with
|
|
168
|
+
'Bearer realm="https://gcr.io/v2/token",service=gcr.io'.
|
|
169
|
+
"""
|
|
170
|
+
bearer_info_string = response_auth_header[len("Bearer ") :]
|
|
171
|
+
auth_query_dict = {}
|
|
172
|
+
for match in re.finditer(r'([^=,\s]+)=(?:"((?:[^"\\]|\\.)*)"|([^,]*))', bearer_info_string):
|
|
173
|
+
key, quoted, plain = match.groups()
|
|
174
|
+
if quoted is not None:
|
|
175
|
+
auth_query_dict[key] = re.sub(r"\\(.)", r"\1", quoted)
|
|
176
|
+
else:
|
|
177
|
+
auth_query_dict[key] = plain.strip()
|
|
178
|
+
return auth_query_dict
|
|
179
|
+
|
|
159
180
|
@staticmethod
|
|
160
181
|
def auth_header(token, basic_auth):
|
|
161
182
|
"""
|
|
@@ -32,6 +32,7 @@ from rest_framework.exceptions import (
|
|
|
32
32
|
Throttled,
|
|
33
33
|
)
|
|
34
34
|
from rest_framework.generics import ListAPIView
|
|
35
|
+
from rest_framework.negotiation import DefaultContentNegotiation
|
|
35
36
|
from rest_framework.pagination import BasePagination
|
|
36
37
|
from rest_framework.permissions import IsAuthenticated
|
|
37
38
|
from rest_framework.renderers import BaseRenderer, JSONRenderer
|
|
@@ -142,6 +143,15 @@ class ContentRenderer(BaseRenderer):
|
|
|
142
143
|
return data
|
|
143
144
|
|
|
144
145
|
|
|
146
|
+
class BlobContentNegotiation(DefaultContentNegotiation):
|
|
147
|
+
"""Treat an explicitly empty Accept header like a missing header for blobs."""
|
|
148
|
+
|
|
149
|
+
def get_accept_list(self, request):
|
|
150
|
+
if request.headers.get("Accept") == "":
|
|
151
|
+
return ["*/*"]
|
|
152
|
+
return super().get_accept_list(request)
|
|
153
|
+
|
|
154
|
+
|
|
145
155
|
class ManifestResponse(Response):
|
|
146
156
|
"""
|
|
147
157
|
An HTTP response class for returning Manifets.
|
|
@@ -371,7 +381,11 @@ class ContainerRegistryApiMixin:
|
|
|
371
381
|
def get_pull_through_drv(self, path):
|
|
372
382
|
domain = get_domain()
|
|
373
383
|
pull_through_cache_distribution = get_pull_through_distribution(path, domain)
|
|
374
|
-
|
|
384
|
+
# allow anonymous pull-through access for public distributions
|
|
385
|
+
if not pull_through_cache_distribution:
|
|
386
|
+
raise RepositoryNotFound(name=path)
|
|
387
|
+
# user must be authenticated when pull-through is private
|
|
388
|
+
if pull_through_cache_distribution.private and not self.request.user.is_authenticated:
|
|
375
389
|
raise RepositoryNotFound(name=path)
|
|
376
390
|
|
|
377
391
|
pull_through_path = get_pull_through_distribution_path(pull_through_cache_distribution)
|
|
@@ -1122,7 +1136,7 @@ class RedirectsMixin:
|
|
|
1122
1136
|
domain = get_domain()
|
|
1123
1137
|
if (
|
|
1124
1138
|
domain.storage_class == "pulpcore.app.models.storage.FileSystem"
|
|
1125
|
-
or not
|
|
1139
|
+
or not domain.redirect_to_object_storage
|
|
1126
1140
|
):
|
|
1127
1141
|
self.redirects_class = FileStorageRedirects
|
|
1128
1142
|
elif domain.storage_class == "storages.backends.s3boto3.S3Boto3Storage":
|
|
@@ -1139,6 +1153,7 @@ class Blobs(RedirectsMixin, ContainerRegistryApiMixin, ViewSet):
|
|
|
1139
1153
|
"""
|
|
1140
1154
|
|
|
1141
1155
|
renderer_classes = [ContentRenderer]
|
|
1156
|
+
content_negotiation_class = BlobContentNegotiation
|
|
1142
1157
|
|
|
1143
1158
|
def head(self, request, path, pk=None):
|
|
1144
1159
|
"""
|
|
@@ -1204,10 +1219,25 @@ class Blobs(RedirectsMixin, ContainerRegistryApiMixin, ViewSet):
|
|
|
1204
1219
|
relative_url = "/v2/{name}/blobs/{pk}".format(name=remote.namespaced_upstream_name, pk=pk)
|
|
1205
1220
|
blob_url = urljoin(remote.url, relative_url)
|
|
1206
1221
|
downloader = remote.get_downloader(url=blob_url)
|
|
1222
|
+
used_get_fallback = False
|
|
1207
1223
|
try:
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1224
|
+
try:
|
|
1225
|
+
response = downloader.fetch(
|
|
1226
|
+
extra_data={"headers": V2_ACCEPT_HEADERS, "http_method": "head"}
|
|
1227
|
+
)
|
|
1228
|
+
except ClientResponseError as response_error:
|
|
1229
|
+
if response_error.status in (401, 405):
|
|
1230
|
+
# Some registries (e.g. ECR Public) reject HEAD on blob endpoints
|
|
1231
|
+
# regardless of authentication; verify existence via GET instead
|
|
1232
|
+
used_get_fallback = True
|
|
1233
|
+
response = downloader.fetch(
|
|
1234
|
+
extra_data={
|
|
1235
|
+
"headers": {**V2_ACCEPT_HEADERS, "Range": "bytes=0-0"},
|
|
1236
|
+
"http_method": "get",
|
|
1237
|
+
}
|
|
1238
|
+
)
|
|
1239
|
+
else:
|
|
1240
|
+
raise
|
|
1211
1241
|
except ClientResponseError as response_error:
|
|
1212
1242
|
if response_error.status == 429:
|
|
1213
1243
|
# the client could request the blob outside the docker hub pull limit;
|
|
@@ -1221,7 +1251,10 @@ class Blobs(RedirectsMixin, ContainerRegistryApiMixin, ViewSet):
|
|
|
1221
1251
|
# The remote server is not available at the moment
|
|
1222
1252
|
raise GatewayTimeout()
|
|
1223
1253
|
else:
|
|
1224
|
-
|
|
1254
|
+
# Registries that reject HEAD answer the ranged GET with a redirect that
|
|
1255
|
+
# carries no docker-content-digest; the digest is verified later when the
|
|
1256
|
+
# artifact is actually downloaded.
|
|
1257
|
+
if not used_get_fallback and response.headers.get("docker-content-digest") != pk:
|
|
1225
1258
|
raise BlobNotFound(digest=pk)
|
|
1226
1259
|
return blob_url
|
|
1227
1260
|
|
|
@@ -1772,7 +1805,11 @@ class Signatures(ContainerRegistryApiMixin, ViewSet):
|
|
|
1772
1805
|
response_headers = {}
|
|
1773
1806
|
try:
|
|
1774
1807
|
ping_response = ping_downloader.fetch(
|
|
1775
|
-
extra_data={
|
|
1808
|
+
extra_data={
|
|
1809
|
+
"headers": V2_ACCEPT_HEADERS,
|
|
1810
|
+
"http_method": "get",
|
|
1811
|
+
"repo_name": remote.namespaced_upstream_name,
|
|
1812
|
+
}
|
|
1776
1813
|
)
|
|
1777
1814
|
response_headers = ping_response.headers
|
|
1778
1815
|
except ClientResponseError as e:
|
|
@@ -1396,6 +1396,12 @@ class ContainerDistributionViewSet(DistributionViewSet, RolesMixin):
|
|
|
1396
1396
|
"has_model_or_domain_or_obj_perms:container.manage_roles_containerdistribution"
|
|
1397
1397
|
],
|
|
1398
1398
|
},
|
|
1399
|
+
{
|
|
1400
|
+
"action": ["pull_new_content"],
|
|
1401
|
+
"principal": "*",
|
|
1402
|
+
"effect": "allow",
|
|
1403
|
+
"condition_expression": "not is_private",
|
|
1404
|
+
},
|
|
1399
1405
|
{
|
|
1400
1406
|
"action": ["pull_new_content"],
|
|
1401
1407
|
"principal": "authenticated",
|
|
@@ -1562,6 +1568,12 @@ class ContainerPullThroughDistributionViewSet(DistributionViewSet, RolesMixin):
|
|
|
1562
1568
|
"has_model_or_domain_or_obj_perms:container.manage_roles_containerpullthroughdistribution" # noqa
|
|
1563
1569
|
],
|
|
1564
1570
|
},
|
|
1571
|
+
{
|
|
1572
|
+
"action": ["pull_new_distribution"],
|
|
1573
|
+
"principal": "*",
|
|
1574
|
+
"effect": "allow",
|
|
1575
|
+
"condition_expression": "not is_private",
|
|
1576
|
+
},
|
|
1565
1577
|
{
|
|
1566
1578
|
"action": ["pull_new_distribution"],
|
|
1567
1579
|
"principal": "authenticated",
|
|
@@ -118,7 +118,7 @@ class TestPullContent:
|
|
|
118
118
|
)
|
|
119
119
|
assert content_response.content == digest_response.content
|
|
120
120
|
|
|
121
|
-
def test_create_empty_blob_on_the_fly(self, bindings_cfg, full_path, setup):
|
|
121
|
+
def test_create_empty_blob_on_the_fly(self, bindings_cfg, full_path, setup, local_registry):
|
|
122
122
|
"""
|
|
123
123
|
Test if empty blob getscreated and served on the fly.
|
|
124
124
|
"""
|
|
@@ -135,6 +135,66 @@ class TestPullContent:
|
|
|
135
135
|
header_digest = content_response.headers["docker-content-digest"].split(":")[1]
|
|
136
136
|
assert digest == header_digest
|
|
137
137
|
|
|
138
|
+
head, _ = local_registry.get_response("HEAD", blob_path)
|
|
139
|
+
assert head.status_code == 200
|
|
140
|
+
assert head.headers["Docker-Content-Digest"] == EMPTY_BLOB
|
|
141
|
+
assert int(head.headers["Content-Length"]) == len(content_response.content)
|
|
142
|
+
assert head.content == b""
|
|
143
|
+
|
|
144
|
+
@pytest.mark.parametrize("blob_type", ["config", "layer"])
|
|
145
|
+
@pytest.mark.parametrize("method", ["GET", "HEAD"])
|
|
146
|
+
@pytest.mark.parametrize("accept", [None, "", "*/*"])
|
|
147
|
+
def test_blob_accept_header(
|
|
148
|
+
self, blob_type, method, accept, bindings_cfg, full_path, setup, local_registry
|
|
149
|
+
):
|
|
150
|
+
"""Blob GET and HEAD redirect successfully for any supported Accept header."""
|
|
151
|
+
_, distribution, _ = setup
|
|
152
|
+
path = full_path(distribution)
|
|
153
|
+
tag = PULP_HELLO_WORLD_LINUX_TAG.lstrip(":")
|
|
154
|
+
manifest_headers = {
|
|
155
|
+
"Accept": f"{MEDIA_TYPE.MANIFEST_V2}, {MEDIA_TYPE.MANIFEST_OCI}, "
|
|
156
|
+
f"{MEDIA_TYPE.MANIFEST_LIST}, {MEDIA_TYPE.INDEX_OCI}"
|
|
157
|
+
}
|
|
158
|
+
manifest, _ = local_registry.get_response(
|
|
159
|
+
"GET", f"/v2/{path}/manifests/{tag}", headers=manifest_headers
|
|
160
|
+
)
|
|
161
|
+
manifest.raise_for_status()
|
|
162
|
+
manifest_data = manifest.json()
|
|
163
|
+
while "manifests" in manifest_data:
|
|
164
|
+
child_digest = manifest_data["manifests"][0]["digest"]
|
|
165
|
+
manifest, _ = local_registry.get_response(
|
|
166
|
+
"GET", f"/v2/{path}/manifests/{child_digest}", headers=manifest_headers
|
|
167
|
+
)
|
|
168
|
+
manifest.raise_for_status()
|
|
169
|
+
manifest_data = manifest.json()
|
|
170
|
+
descriptor = (
|
|
171
|
+
manifest_data["config"] if blob_type == "config" else manifest_data["layers"][0]
|
|
172
|
+
)
|
|
173
|
+
digest = descriptor["digest"]
|
|
174
|
+
blob_path = f"/v2/{path}/blobs/{digest}"
|
|
175
|
+
headers = {"Accept": accept}
|
|
176
|
+
|
|
177
|
+
response, auth = local_registry.get_response(
|
|
178
|
+
method, blob_path, headers=headers, allow_redirects=False
|
|
179
|
+
)
|
|
180
|
+
assert response.status_code == 302
|
|
181
|
+
assert response.headers["Location"]
|
|
182
|
+
|
|
183
|
+
if method == "HEAD":
|
|
184
|
+
head = requests.head(
|
|
185
|
+
urljoin(bindings_cfg.host, blob_path),
|
|
186
|
+
auth=auth,
|
|
187
|
+
headers=headers,
|
|
188
|
+
allow_redirects=True,
|
|
189
|
+
)
|
|
190
|
+
assert head.status_code == 200
|
|
191
|
+
assert int(head.headers["Content-Length"]) == descriptor["size"]
|
|
192
|
+
assert head.content == b""
|
|
193
|
+
|
|
194
|
+
download = requests.get(urljoin(bindings_cfg.host, blob_path), auth=auth, headers=headers)
|
|
195
|
+
download.raise_for_status()
|
|
196
|
+
assert f"sha256:{hashlib.sha256(download.content).hexdigest()}" == digest
|
|
197
|
+
|
|
138
198
|
def test_pull_image_from_repository(self, local_registry, registry_client, full_path, setup):
|
|
139
199
|
"""Verify that a client can pull the image from Pulp.
|
|
140
200
|
|
|
@@ -60,6 +60,9 @@ def pull_and_verify(
|
|
|
60
60
|
monitor_task,
|
|
61
61
|
):
|
|
62
62
|
def _pull_and_verify(images, pull_through_distribution):
|
|
63
|
+
if pull_through_distribution.private and settings.TOKEN_AUTH_DISABLED:
|
|
64
|
+
pytest.skip("RBAC cannot be tested when token authentication is disabled")
|
|
65
|
+
|
|
63
66
|
tags_to_verify = []
|
|
64
67
|
pull_through_path = pull_through_distribution.pulp_labels[PULL_THROUGH_DISTRIBUTION_LABEL]
|
|
65
68
|
for version, image_path in enumerate(images, start=1):
|
|
@@ -67,12 +70,14 @@ def pull_and_verify(
|
|
|
67
70
|
local_image_path = f"{pull_through_path}/{image_path}"
|
|
68
71
|
local_image_pull_path = full_path(local_image_path) # Handle if domain is enabled
|
|
69
72
|
|
|
70
|
-
#
|
|
71
|
-
|
|
73
|
+
# 1. Pull new content anonymously from public caches; private caches require auth.
|
|
74
|
+
if pull_through_distribution.private:
|
|
75
|
+
with anonymous_user, pytest.raises(CalledProcessError):
|
|
76
|
+
local_registry.pull(local_image_pull_path)
|
|
72
77
|
local_registry.pull(local_image_pull_path)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
else:
|
|
79
|
+
with anonymous_user:
|
|
80
|
+
local_registry.pull(local_image_pull_path)
|
|
76
81
|
local_image = local_registry.inspect(local_image_pull_path)
|
|
77
82
|
|
|
78
83
|
# when the client pulls the image, a repository, distribution, and remote is created in
|
|
@@ -119,9 +124,13 @@ def pull_and_verify(
|
|
|
119
124
|
tags = container_tag_api.list(repository_version=repository.latest_version_href).results
|
|
120
125
|
assert sorted(tags_to_verify) == sorted([tag.name for tag in tags])
|
|
121
126
|
|
|
122
|
-
# 6.
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
# 6. Cached content must retain the distribution's access restrictions.
|
|
128
|
+
if pull_through_distribution.private:
|
|
129
|
+
with anonymous_user, pytest.raises(CalledProcessError):
|
|
130
|
+
local_registry.pull(local_image_pull_path)
|
|
131
|
+
else:
|
|
132
|
+
with anonymous_user:
|
|
133
|
+
local_registry.pull(local_image_pull_path)
|
|
125
134
|
|
|
126
135
|
return _pull_and_verify
|
|
127
136
|
|
|
@@ -134,14 +143,18 @@ def test_pull_through_distribution_uses_internal_base_path(pull_through_distribu
|
|
|
134
143
|
assert distribution.pulp_labels[PULL_THROUGH_DISTRIBUTION_LABEL] != distribution.name
|
|
135
144
|
|
|
136
145
|
|
|
137
|
-
|
|
146
|
+
@pytest.mark.parametrize("private", [False, True])
|
|
147
|
+
def test_manifest_list_pull(
|
|
148
|
+
delete_orphans_pre, pull_through_distribution, pull_and_verify, private
|
|
149
|
+
):
|
|
138
150
|
images = [f"{PULP_HELLO_WORLD_REPO}:latest", f"{PULP_HELLO_WORLD_REPO}:linux"]
|
|
139
|
-
pull_and_verify(images, pull_through_distribution())
|
|
151
|
+
pull_and_verify(images, pull_through_distribution(private=private))
|
|
140
152
|
|
|
141
153
|
|
|
142
|
-
|
|
154
|
+
@pytest.mark.parametrize("private", [False, True])
|
|
155
|
+
def test_manifest_pull(delete_orphans_pre, pull_through_distribution, pull_and_verify, private):
|
|
143
156
|
images = [f"{PULP_FIXTURE_1}:manifest_a", f"{PULP_FIXTURE_1}:manifest_b"]
|
|
144
|
-
pull_and_verify(images, pull_through_distribution())
|
|
157
|
+
pull_and_verify(images, pull_through_distribution(private=private))
|
|
145
158
|
|
|
146
159
|
|
|
147
160
|
def test_pull_by_digest(
|
|
@@ -153,34 +166,47 @@ def test_pull_by_digest(
|
|
|
153
166
|
full_path,
|
|
154
167
|
gen_user,
|
|
155
168
|
):
|
|
169
|
+
if settings.TOKEN_AUTH_DISABLED:
|
|
170
|
+
pytest.skip("RBAC cannot be tested when token authentication is disabled")
|
|
171
|
+
|
|
156
172
|
# THIS TEST FAILS WHEN THE CONTENT IS ALREADY PRESENT IN PULP!!
|
|
157
173
|
image1 = f"{PULP_HELLO_WORLD_REPO}@{PULP_HELLO_WORLD_LINUX_AMD64_DIGEST}"
|
|
158
174
|
image2 = f"{PULP_FIXTURE_1}@{PULP_FIXTURE_1_MANIFEST_A_DIGEST}"
|
|
159
175
|
distribution1 = pull_through_distribution()
|
|
160
|
-
distribution2 = pull_through_distribution()
|
|
176
|
+
distribution2 = pull_through_distribution(private=True)
|
|
161
177
|
path1 = distribution1.pulp_labels[PULL_THROUGH_DISTRIBUTION_LABEL]
|
|
162
178
|
path2 = distribution2.pulp_labels[PULL_THROUGH_DISTRIBUTION_LABEL]
|
|
163
179
|
local_image_path1 = f"{full_path(path1)}/{image1}"
|
|
164
180
|
local_image_path2 = f"{full_path(path2)}/{image2}"
|
|
165
181
|
|
|
166
|
-
with anonymous_user
|
|
182
|
+
with anonymous_user:
|
|
167
183
|
local_registry.pull(local_image_path1)
|
|
168
184
|
|
|
169
|
-
local_registry.pull(local_image_path1)
|
|
170
|
-
|
|
171
185
|
add_pull_through_entities_to_cleanup(local_image_path1.split("@")[0])
|
|
172
186
|
|
|
173
187
|
with anonymous_user:
|
|
174
188
|
local_registry.pull(local_image_path1)
|
|
175
189
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
190
|
+
consumer = gen_user(
|
|
191
|
+
object_roles=[
|
|
192
|
+
("container.containerpullthroughdistribution_consumer", distribution2.pulp_href)
|
|
193
|
+
]
|
|
194
|
+
)
|
|
195
|
+
unprivileged_user = gen_user()
|
|
196
|
+
with anonymous_user, pytest.raises(CalledProcessError):
|
|
197
|
+
local_registry.pull(local_image_path2)
|
|
198
|
+
with unprivileged_user, pytest.raises(CalledProcessError):
|
|
199
|
+
local_registry.pull(local_image_path2)
|
|
200
|
+
with consumer:
|
|
180
201
|
local_registry.pull(local_image_path2)
|
|
181
202
|
|
|
182
|
-
|
|
183
|
-
|
|
203
|
+
add_pull_through_entities_to_cleanup(local_image_path2.split("@")[0])
|
|
204
|
+
|
|
205
|
+
with anonymous_user, pytest.raises(CalledProcessError):
|
|
206
|
+
local_registry.pull(local_image_path2)
|
|
207
|
+
with unprivileged_user, pytest.raises(CalledProcessError):
|
|
208
|
+
local_registry.pull(local_image_path2)
|
|
209
|
+
with consumer:
|
|
184
210
|
local_registry.pull(local_image_path2)
|
|
185
211
|
|
|
186
212
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from pulp_container.app.downloaders import RegistryAuthHttpDownloader
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.mark.parametrize(
|
|
7
|
+
"header,expected",
|
|
8
|
+
[
|
|
9
|
+
(
|
|
10
|
+
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io"',
|
|
11
|
+
{"realm": "https://auth.docker.io/token", "service": "registry.docker.io"},
|
|
12
|
+
),
|
|
13
|
+
(
|
|
14
|
+
'Bearer realm="https://gcr.io/v2/token",service=gcr.io',
|
|
15
|
+
{"realm": "https://gcr.io/v2/token", "service": "gcr.io"},
|
|
16
|
+
),
|
|
17
|
+
(
|
|
18
|
+
'Bearer realm="https://quay.io/v2/auth",service="quay.io",'
|
|
19
|
+
'scope="repository:foo/bar:pull,push"',
|
|
20
|
+
{
|
|
21
|
+
"realm": "https://quay.io/v2/auth",
|
|
22
|
+
"service": "quay.io",
|
|
23
|
+
"scope": "repository:foo/bar:pull,push",
|
|
24
|
+
},
|
|
25
|
+
),
|
|
26
|
+
(
|
|
27
|
+
'Bearer realm="https://example.com/token", service=example.com, error="a \\"b\\""',
|
|
28
|
+
{"realm": "https://example.com/token", "service": "example.com", "error": 'a "b"'},
|
|
29
|
+
),
|
|
30
|
+
],
|
|
31
|
+
)
|
|
32
|
+
def test_parse_auth_challenge(header, expected):
|
|
33
|
+
assert RegistryAuthHttpDownloader.parse_auth_challenge(header) == expected
|
|
@@ -148,6 +148,7 @@ pulp_container/tests/functional/api/test_sync_signatures.py
|
|
|
148
148
|
pulp_container/tests/functional/api/test_tagging_images.py
|
|
149
149
|
pulp_container/tests/functional/api/test_token_authentication.py
|
|
150
150
|
pulp_container/tests/unit/__init__.py
|
|
151
|
+
pulp_container/tests/unit/test_downloaders.py
|
|
151
152
|
pulp_container/tests/unit/test_json_schemas.py
|
|
152
153
|
pulp_container/tests/unit/test_models.py
|
|
153
154
|
pulp_container/tests/unit/test_pull_through_repair.py
|
|
@@ -7,7 +7,7 @@ build-backend = 'setuptools.build_meta'
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "pulp-container"
|
|
10
|
-
version = "2.29.
|
|
10
|
+
version = "2.29.3"
|
|
11
11
|
description = "Container plugin for the Pulp Project"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
authors = [
|
|
@@ -89,7 +89,7 @@ ignore = [
|
|
|
89
89
|
[tool.bumpversion]
|
|
90
90
|
# This section is managed by the plugin template. Do not edit manually.
|
|
91
91
|
|
|
92
|
-
current_version = "2.29.
|
|
92
|
+
current_version = "2.29.3"
|
|
93
93
|
commit = false
|
|
94
94
|
tag = false
|
|
95
95
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<alpha>0a)?(?P<patch>\\d+)(\\.(?P<release>[a-z]+))?"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/global_access_conditions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/management/commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0003_oci_mediatype.py
RENAMED
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0004_upload.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0039_manifest_data.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/migrations/0044_add_domain.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/download_image_data.py
RENAMED
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/migrate_push_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/tasks/recursive_remove.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/webserver_snippets/__init__.py
RENAMED
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/webserver_snippets/apache.conf
RENAMED
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/app/webserver_snippets/nginx.conf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_domains.py
RENAMED
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_flatpak.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/api/test_sync.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/functional/constants.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/unit/test_json_schemas.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/unit/test_serializers.py
RENAMED
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container/tests/unit/test_sync_stages.py
RENAMED
|
File without changes
|
{pulp_container-2.29.2 → pulp_container-2.29.3}/pulp_container.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|