chainguard-dev / chainguard-dev/apko
ImageLayoutToLayer leaves its partial output file behind on build failure
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 1.7k
- Forks
- 228
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 62
Description
ImageLayoutToLayer leaves its output file behind when the build fails. On main (pkg/build/build.go:218-231) the only cleanup on the error paths is a deferred Close:
defer outfile.Close()
lw := newLayerWriter(outfile)
if err := writeTar(ctx, lw.w, bc.fs); err != nil {
return "", nil, fmt.Errorf("generating tarball: %w", err)
}
l, err := lw.finalize()
if err != nil {
return "", nil, fmt.Errorf("finalizing layer: %w", err)
}
Nothing unlinks the partial file, and bc.o.TarballPath has already been pointed at it a few lines above.
Why this matters for library callers specifically
The codebase already states the principle, in pkg/build/erofs_layers.go:57-61:
Nothing here is usable half-built: a caller that gets an error gets no layers, so every writer opened along the way has to be released and its temp file removed. Library callers have no MkdirTemp/RemoveAll wrapper around this the way the CLI does, and each writer holds an unlinked spool fd besides.
That is exactly right, and it is why the erofs multi-layer path cleans up after itself. The CLI does wrap its work in os.MkdirTemp + defer os.RemoveAll (internal/cli/build.go:96-100, internal/cli/publish.go:102-106), so leftovers are invisible there. A library caller that supplies its own TempDirPath, or that lets Options.TempDir() mint an apko-temp-* directory nobody removes, keeps every partial from every failed attempt.
So the multi-layer erofs path follows the principle and the single-layer tar path does not.
Suggested fix
Remove the output file on the error returns, but only when apko created it. When the caller supplied a path via WithTarball, that file is the caller's, and deleting it would be a breaking ownership change. The distinction is already available at the point of creation (pkg/build/build.go:188-192 branches on whether TarballPath was set).
Note
This is pre-existing on main and not a regression. It is worth recording because #2479 adds the same cleanup to splitLayers, which will leave ImageLayoutToLayer as the one remaining layer-producing path that retains partials on failure. That PR also makes the leftover harder to spot in its opt-in mode: the abort sequence closes the tar writer and then the gzip writer, so the abandoned file is a well-formed gzip containing a truncated tar rather than something that obviously fails to parse.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in pkg/build/build.go:188-192 to trace whether the tarball path was supplied or created, then inspect ImageLayoutToLayer at lines 218-231 and compare its error cleanup with pkg/build/erofs_layers.go:57-61. Verify that failures from writeTar or finalize remove only apko-created output files, while caller-supplied WithTarball files remain; run the relevant build tests and confirm successful outputs are unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- Half a day
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100