Fix tag-based manifest pulls 404ing while digest-based pulls succeed - #2438
Open
wussh wants to merge 6 commits into
Open
Fix tag-based manifest pulls 404ing while digest-based pulls succeed#2438wussh wants to merge 6 commits into
wussh wants to merge 6 commits into
Conversation
Author
|
Verified this fix end-to-end against the live cluster that originally hit #2417 (Domains + on-demand + follow-latest distribution, S3 storage, pulpcore 3.113.0 / pulp_container 2.28.0):
|
wussh
force-pushed
the
fix-2417-tag-manifest-404
branch
from
July 26, 2026 17:11
319a366 to
75a7a9f
Compare
Contributor
jobselko
reviewed
Jul 28, 2026
ManifestResponse.from_tag rejected requests based on the client Accept header, raising ManifestNotFound when the tag's manifest media type wasn't explicitly listed. from_manifest (the digest lookup path) never had this check, so identical content served 200 by digest and 404 by tag — most visible with manifest-list/OCI-index tags whose media type clients often omit from Accept. This was introduced in 8479d52 (pulp#1974) when manifest serving moved from a content-app redirect (which ignored Accept) into the registry API view. from_tag now matches from_manifest: it always serves the tagged manifest, only rewriting schema v1 to the signed content type. Fixes pulp#2417
Fixes a ruff UP032 finding introduced by the new test added in this branch.
pulpcore treats any task exception that isn't a PulpException subclass as deprecated: it logs a pulpcore.deprecation warning today and will sanitize the error message away entirely in pulpcore 3.130, losing the actual podman/buildah stderr. build_image_from_scratch and the deprecated build_image_from_containerfile both raised bare Exception on a failed podman build/push, tripping this path — surfaced as CI noise in the "deprecations" check, but a real forward-compat bug. Add ContainerBuildError(PulpException) and raise it with the decoded stderr instead; pytest.raises(PulpTaskError) assertions in test_build_images.py are unaffected since the exception still propagates through task.error the same way.
ContainerNamespace has a PROTECT foreign key to Domain. cdomain_factory's teardown cleared content-redirect guards but never ContainerNamespace objects, so any domain-scoped ContainerDistribution left one behind (auto-created by ContainerNamespaceSerializer.get_or_create). When pulpcore's own domain_factory teardown then deleted the domain, Django raised ProtectedError — sanitized into a pulpcore.deprecation warning that failed the "deprecations" CI check. test_pull_in_domain creates two distributions and cleaned up neither namespace, making it the concrete trigger. Query-and-delete namespaces by domain in cdomain_factory's teardown instead of relying on each test to remember; safe to run even for tests that already clean their own namespace via add_to_cleanup since it re-lists current state.
PulpException.__init__(error_code=...) is itself deprecated in current pulpcore — passing error_code positionally now logs its own pulpcore.deprecation warning. error_code must be a predefined class attribute instead.
- error_code: use the CON00XX naming convention (CON0001) jobselko and gerrod3 agreed on, instead of PLP_CONTAINER_BUILD_0001. - CHANGES fragment: use single backticks, since they render as markdown. - cdomain_factory: adopt gerrod3's version of the namespace cleanup from pulp#2424 (suppress errors per-namespace/guard instead of letting one failure abort the rest, and wait on the delete task via monitor_task). - Drop the ruff<0.16 pin: no longer needed after rebasing on main, where the 0.16 lint findings were already fixed elsewhere.
wussh
force-pushed
the
fix-2417-tag-manifest-404
branch
from
July 31, 2026 10:28
806b30a to
5fae027
Compare
Author
|
Addressed the mechanical review feedback:
Still not addressed: your point about the accept-header check itself (removing it from |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ManifestResponse.from_tagrejected requests based on the clientAcceptheader, raisingManifestNotFoundwhen the tag's manifest media type wasn't explicitly listed.from_manifest(the digest lookup path) never had this check, so the identical content served200by digest and404by tag — most visible for manifest-list/OCI-index tags whose media type clients often omit fromAccept.8479d527), which moved manifest serving from a content-app redirect (that ignoredAccept) into the registry API view.from_tagnow mirrorsfrom_manifest: it always serves the tagged manifest, only rewriting schema v1 to the signed content type.Fixes #2417
Test plan
test_pull_content.py::test_api_performes_schema_conversion(previously asserted a 4xx with the comment "I don't understand what this is testing") intotest_api_serves_tag_regardless_of_accept_header, which pulls the same manifest by tag and by digest with a narrowAcceptheader and asserts identical200responses.registry_api.pyout of the runningpulp-apipod, applied only thefrom_tagdiff from this PR on top of it (no unrelated version drift), wrote it back into the running container, and reloaded gunicorn workers (no pod restart). Results:404, digest pull200(matches the issue exactly).200, digest pull200,tags/list200, and the tag/digest response bodies are byte-identical.404baseline — no lasting changes were left on the cluster.