Compute the saved image's sha256 in one pass, over the uncompressed stream #39

Merged
geusebio merged 2 commits from fix/save-sha256-single-pass into main 2026-08-25 07:45:56 +00:00
Owner

Summary

  • dockercli.Save now computes the sha256 of the saved image in one pass, teeing
    docker save's stdout through a hasher while it gzips to disk, and returns the hex digest
    directly.
  • Drops forward.go's separate sha256File helper, which re-opened and re-read the whole
    .tar.gz off disk just to hash it after Save had already written it.
  • The digest is now over the uncompressed tar stream, not the gzip bytes on disk — that's
    what the platform's bundle manifest is keyed on, and gzip output varies with the
    compressor's settings even for byte-identical input.

Why

One read instead of two, and a digest that means what the manifest actually needs it to mean.

Test plan

  • go build ./...
  • go test ./internal/dockercli/... — green, updated to assert the returned sum matches
    the uncompressed payload's own sha256
  • go test ./internal/cli/... — one pre-existing, unrelated failure
    (TestAppsDrawsApplicationsFromEveryPageNotJustTheFirst), confirmed present on main
    before this change too; not touched here
## Summary - `dockercli.Save` now computes the sha256 of the saved image in one pass, teeing `docker save`'s stdout through a hasher while it gzips to disk, and returns the hex digest directly. - Drops `forward.go`'s separate `sha256File` helper, which re-opened and re-read the whole `.tar.gz` off disk just to hash it after `Save` had already written it. - The digest is now over the *uncompressed* tar stream, not the gzip bytes on disk — that's what the platform's bundle manifest is keyed on, and gzip output varies with the compressor's settings even for byte-identical input. ## Why One read instead of two, and a digest that means what the manifest actually needs it to mean. ## Test plan - [x] `go build ./...` - [x] `go test ./internal/dockercli/...` — green, updated to assert the returned sum matches the uncompressed payload's own sha256 - [x] `go test ./internal/cli/...` — one pre-existing, unrelated failure (`TestAppsDrawsApplicationsFromEveryPageNotJustTheFirst`), confirmed present on `main` before this change too; not touched here
Compute the saved image's sha256 in one pass, over the uncompressed stream
Some checks failed
CI / build (pull_request) Failing after 27s
0fd158debe
Save() now tees docker save's stdout through a sha256 hasher while gzipping
it to disk, and returns the hex digest directly. Drops the separate
sha256File() helper in forward.go, which re-read the whole tar.gz back off
disk just to hash it — one read instead of two, and the digest is over the
uncompressed stream (what the platform's bundle manifest is keyed on), not
the gzip bytes, which vary with the compressor's settings for identical
content.
Merge Save()'s copy/close error handling, and cover it
All checks were successful
CI / build (pull_request) Successful in 2m41s
5124d7d406
The coverage ratchet caught internal/dockercli falling from 91.7% to 90.0%:
the previous commit's explicit closeErr branch has no realistic way to
happen independently of copyErr (gzip.Close() only flushes bytes io.Copy
already handed it), so merge the two into one errors.Join'd check instead
of carrying an effectively-untestable branch.

Add TestSaveFailsWhenTheDestinationCannotBeWritten, which forces the merged
path via a tiny RLIMIT_FSIZE rather than anything filesystem- or
timing-dependent.
Sign in to join this conversation.
No reviewers
No labels
in-progress
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Grey.ooo/someones.computer_agent!39
No description provided.