Skip to content

fix: fix errors during repeated pull and rm operations; add tag validation on pull to ensure idempotency. - #484

Open
PikaByter wants to merge 2 commits into
modelpack:mainfrom
PikaByter:main
Open

PikaByter wants to merge 2 commits into
modelpack:mainfrom
PikaByter:main

Conversation

@PikaByter

@PikaByter PikaByter commented Apr 1, 2026 •

Copy link
Copy Markdown

related issue:#483

FIx:
add tag validation when pulling

Test:

make build
chmod +x ./output/modctl
cd output
./modctl pull <image>
./modctl rmi <image>
./modctl pulll <image>
./modctl rmi <image>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a StatTag method to the Storage interface and its distribution implementation to verify tag existence. This new method is utilized in the pullIfNotExist function to ensure manifests are pushed when a tag is missing, even if the underlying blob is already present. The changes also include minor formatting adjustments to model file constants and updated mocks. A review comment suggested using errors.As for more robust error handling of wrapped ErrTagUnknown errors.

Comment on lines +293 to +299
if err != nil {
switch err.(type) {
case distribution.ErrTagUnknown:
return false, nil
}
return false, err
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using switch err.(type) to check for distribution.ErrTagUnknown may fail if the error is wrapped. Since the project uses wrapped errors (e.g., %w), it is safer and more idiomatic to use errors.As to check for specific error types.

Suggested change
if err != nil {
switch err.(type) {
case distribution.ErrTagUnknown:
return false, nil
}
return false, err
}
if err != nil {
var tagErr distribution.ErrTagUnknown
if errors.As(err, &tagErr) {
return false, nil
}
return false, err
}

@aftersnow aftersnow left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(duplicate, please see the English review below)

@aftersnow aftersnow left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! The fix direction is correct. A few suggestions:

Required

  1. Missing digest validation on tag re-creation path: When manifest exists but tag doesn't, io.ReadAll(reader) reads content and pushes it, but the function returns early before validateDigest(). Please add validation or comment why it's safe to skip.

  2. No unit test coverage: The core new path (StatManifest=true, StatTag=false) has no tests. Please add coverage.

Suggestions

  1. The formatting changes in constants.go are unrelated to the fix — consider a separate commit.

  2. The second commit message feat: update dis is unclear — please reword it.

@aftersnow aftersnow left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review on the current head (6125024). I reproduced #483 on today's main (f241cec) with a local registry, so the fix is still needed:

pull 1  -> Successfully pulled, `ls` shows the tag
rm 1    -> Deleted: v1
pull 2  -> Successfully pulled, but `ls` shows 0 rows (tag not recreated)
rm 2    -> Error: failed to delete manifest v1: filesystem: Path not found: .../_manifests/tags/v1

The direction of the fix (re-create the tag when the manifest blob already exists) is right. What still blocks it:

1. Digest validation is skipped on the re-tag path (pkg/backend/pull.go). When StatManifest is true and StatTag is false, the code reads the remote manifest with io.ReadAll(reader) and stores it under the tag, then returns before validateDigest runs. A corrupted or substituted response from the registry is stored and tagged without a check. Please validate the digest of body against desc.Digest before PushManifest, the same as the non-existing path does.

2. A stale tag is not refreshed. StatTag only reports existence. If the local tag exists but points to an older manifest (the remote tag moved), the pull still skips and leaves the stale tag in place. repository.Tags(ctx).Get(ctx, tag) already returns the descriptor; comparing its digest with desc.Digest and re-tagging on mismatch fixes this with no extra storage call. That would also make StatTag return the digest instead of a bool.

3. No unit test for the new branch (StatManifest=true, StatTag=false). The storage mock already has StatTag, so a table test on pullIfNotExist is cheap.

4. Unrelated reformatting of pkg/modelfile/constants.go conflicts with main today and is not part of the fix. Please drop it (or send it separately).

5. DCO fails. Both commits need a Signed-off-by line (git commit -s).

The errors.As change for distribution.ErrTagUnknown that Gemini asked for is in place.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants