mirror of
https://github.com/apple/container.git
synced 2026-08-28 19:36:31 +00:00
Route container image save reference list to stderr in stdout mode (#1804)
- Fixes #1801. - When `container image save` runs without `--output`, stdout carries the OCI tar archive. The command writes the archive bytes to stdout and then `print(reference)`s each saved image reference to stdout afterward, appending non-archive text after the tar EOF marker, which will cause strict tar/OCI consumers to fail. - This routes the saved-reference list to stderr in the no-`--output` branch, so stdout contains only archive bytes. When saving to a file via `--output`, stdout is free, so the references continue to print to stdout exactly as before. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
27acdbe2f8
commit
aa7fef3d1d
@@ -140,7 +140,15 @@ extension Application {
|
||||
|
||||
progress.finish()
|
||||
for reference in references {
|
||||
print(reference)
|
||||
if output == nil {
|
||||
// stdout is carrying the OCI archive in this branch, so the
|
||||
// saved-reference list goes to stderr via the logger. Printing
|
||||
// it to stdout appends non-archive bytes after the tar EOF and
|
||||
// corrupts the stream for redirection and pipelines (#1801).
|
||||
log.info("\(reference)")
|
||||
} else {
|
||||
print(reference)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +266,44 @@ class TestCLIImagesCommand: CLITest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test func testImageSaveToStdoutProducesCleanArchive() throws {
|
||||
do {
|
||||
// 1. pull and tag an image to save
|
||||
try doPull(imageName: alpine)
|
||||
let alpineRef: Reference = try Reference.parse(alpine)
|
||||
let alpineTagged = "\(alpineRef.name):testImageSaveToStdout"
|
||||
try doImageTag(image: alpine, newName: alpineTagged)
|
||||
defer {
|
||||
try? doRemoveImages(images: [alpineTagged])
|
||||
}
|
||||
|
||||
// 2. save to stdout (no --output): stdout is the archive stream
|
||||
let saveArgs = [
|
||||
"image",
|
||||
"save",
|
||||
alpineTagged,
|
||||
]
|
||||
let (outputData, _, error, status) = try run(arguments: saveArgs)
|
||||
if status != 0 {
|
||||
throw CLIError.executionFailed("save to stdout failed: \(error)")
|
||||
}
|
||||
|
||||
// 3. The archive on stdout must end at the tar EOF marker (two
|
||||
// 512-byte zero blocks). With the bug, the saved-reference list
|
||||
// is printed to stdout after the archive, so the trailing bytes
|
||||
// are reference text rather than the tar EOF zeros (#1801).
|
||||
#expect(outputData.count >= 1024, "stdout archive is too small to contain a tar EOF marker")
|
||||
let trailer = outputData.suffix(1024)
|
||||
#expect(trailer.allSatisfy { $0 == 0 }, "stdout archive has trailing non-archive bytes after the tar EOF marker")
|
||||
|
||||
// 4. The saved-reference list is still surfaced, on stderr.
|
||||
#expect(error.contains(alpineTagged), "expected the saved image reference on stderr")
|
||||
} catch {
|
||||
Issue.record("failed to save image to stdout \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@Test func testImageSaveMissingPlatform() throws {
|
||||
do {
|
||||
// 1. pull image
|
||||
|
||||
Reference in New Issue
Block a user