fix: TagAsync to buffer stream for authentication retry#355
fix: TagAsync to buffer stream for authentication retry#355wangxiaoxuan273 wants to merge 2 commits intooras-project:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes ManifestStore.TagAsync failing during authentication retries by ensuring the pushed manifest content is seekable (buffered) so Auth.Client can rewind/clone the request content when a 401 triggers retry.
Changes:
- Buffer
FetchAsync’s (non-seekable) response stream into aMemoryStreambefore callingDoPushAsyncinTagAsync. - Add unit tests covering TagAsync behavior with auth retry, buffering integrity across retries, and the no-retry path.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/OrasProject.Oras/Registry/Remote/ManifestStore.cs |
Buffers fetched manifest content into a seekable stream before pushing to support auth retry. |
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs |
Adds new TagAsync tests (auth retry, buffering integrity, and basic success without retry). |
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #355 +/- ##
==========================================
+ Coverage 91.76% 91.77% +0.01%
==========================================
Files 64 64
Lines 2755 2759 +4
Branches 364 365 +1
==========================================
+ Hits 2528 2532 +4
Misses 138 138
Partials 89 89 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
340cce4 to
86358ec
Compare
86358ec to
6d343d4
Compare
6d343d4 to
0adacb9
Compare
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
0adacb9 to
89bfa09
Compare
89bfa09 to
56cab2b
Compare
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
The TagAsync method was passing a non-seekable HTTP response stream to DoPushAsync. When authentication retry was needed (e.g., with Azure Container Registry), Auth.Client would clone the request which fails for non-seekable streams. This fix: - Enforces size limit early (before FetchAsync) to fail fast - Buffers the content stream into a seekable MemoryStream using ReadAllAsync - Ensures retries can successfully rewind and re-read the stream content Also adds comprehensive unit tests for TagAsync auth retry scenarios. Signed-off-by: Xiaoxuan Wang <wangxiaoxuan119@gmail.com>
56cab2b to
9f08a4b
Compare
akashsinghal
left a comment
There was a problem hiding this comment.
Caution
This review was generated by Copilot using Claude Opus 4.6, mimicking the review style of shizhMSFT. This is not the real shizhMSFT.
The fix is correct and the approach is consistent with PushWithIndexingAsync. SGTM overall.
tests/OrasProject.Oras.Tests/Registry/Remote/ManifestStoreTest.cs
Outdated
Show resolved
Hide resolved
|
Overall, PR looks good to me. @wangxiaoxuan273 can you confirm if you've verified manually that the APIs that were affected by the 2 issues you mentioned are actually resolved now? |
Yes, I have verified manually that this change can fix the two issues. |
…note SHA-256 limitation - Consolidate ManifestStore_TagAsync_WithAuthRetry and ManifestStore_TagAsync_BuffersStreamForRetry into a single test that captures content from both PUT attempts and verifies retry count and content integrity - Switch ManifestStore_TagAsync_SucceedsWithoutRetry to use NonSeekableStream for GET response to validate buffering path - Add comment noting ReadAllAsync SHA-256 limitation is pre-existing and tracked separately - Shorten fully-qualified Auth types using using directive - Ensure all new/modified lines stay within 120-column limit Signed-off-by: Xiaoxuan Wang <wangxiaoxuan119@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
You can also share your feedback on Copilot code review. Take the survey.
|
Caution This review was generated by Copilot using Claude Opus 4.6, mimicking the review style of shizhMSFT. This is not the real shizhMSFT. Second-round review of PR #355 All 20 prior review threads addressed — the fix is clean and the tests are comprehensive. Two observations: 1. Missing negative test for size guard ( 2. Inline SHA-256 comment should reference an issue ( Otherwise SGTM — nice work addressing all the previous feedback cleanly. |
Fixes #327
Fixes #333
Root cause of the bug:
The
TagAsyncpasses a non-seekable HTTP response stream directly toDoPushAsync, which creates an HTTP PUT request with that stream as the body. When the initial request receives a 401 Unauthorized response, theClient.SendCoreAsyncmethod attempts to retry the request with authentication credentials by cloning the original request. Cloning requires rewinding the content stream, but HTTP response streams (network streams) are non-seekable, causing the exception.The fix:
This PR wraps the original stream with a rewindable stream before sending it out.