chainguard-dev / chainguard-dev/apko

ImageLayoutToLayer leaves its partial output file behind on build failure

Open Beginner friendly
#2,483 0 comments 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.