API-Docker
view release on metacpan or search on metacpan
t/stream_error.t view on Meta::CPAN
register_cleanup(sub {
my $after = eval { $docker->containers->list(all => 1) } || [];
eval { $docker->containers->remove($_->id, force => 1) }
for grep { !$seen_before{ $_->id } } @$after;
});
}
my $dockerfile = "FROM alpine:3\nRUN exit 7\n";
my $tar = _tar_context($dockerfile);
my $events = eval {
$docker->images->build(context => $tar, t => $tag, forcerm => 1)
};
my $err = $@;
ok !defined $events, 'no return value from the failed build';
isa_ok $err, 'API::Docker::Error::Stream';
ok scalar @{ $err->events } > 1, 'the build output came with it';
my ($error_event) = grep { ref $_ eq 'HASH' && $_->{errorDetail} } @{ $err->events };
ok $error_event, 'one of the carried events is the one that triggered the croak';
# The claim is that the RUN step's exit code reaches the caller -- not
# any particular sentence about it, which karr k50 already ruled out as
# a stable interface. Measured 2026-08-27: Docker 29.7.2/API 1.55 puts it
# in errorDetail structurally --
# {"code":7,"message":"...returned a non-zero code: 7"} -- while Podman
# 5.4.2/API 1.41 sends only {"message":"...exit status 7\n"}, no `code`
# key at all. So the exit code is genuinely only prose on Podman; assert
# the strongest form each engine actually offers rather than a wording
# that happens to appear on both (there isn't one).
if (live_engine() eq 'docker') {
is $error_event->{errorDetail}{code}, 7,
'Docker carries the RUN exit status structurally, in errorDetail.code';
}
else {
like "$err", qr/exit status 7/,
'Podman carries the RUN exit status only in the message text';
}
my $tagged = eval { $docker->images->inspect($tag) };
ok !$tagged, 'the failed build left no tagged image behind';
if (live_engine() eq 'docker') {
my %seen_before = map { $_ => 1 } @before;
my @leaked = grep { !$seen_before{$_} }
map { $_->id } @{ $docker->containers->list(all => 1) };
is_deeply [ sort @leaked ], [],
'forcerm=1 left no intermediate container behind (karr k63)';
}
};
}
# A minimal ustar archive holding one file, so the live build needs no
# Archive::Tar.
sub _tar_context {
my ($content) = @_;
my $name = 'Dockerfile';
my $header = pack 'a100 a8 a8 a8 a12 a12 A8 a1 a100 a255',
$name, '0000644', '0000000', '0000000',
sprintf('%011o', length $content), sprintf('%011o', time),
'', '0', '', '';
my $checksum = 0;
$checksum += $_ for unpack 'C*', $header;
substr($header, 148, 8) = sprintf('%06o', $checksum) . "\0 ";
my $body = $content . "\0" x ((512 - length($content) % 512) % 512);
return $header . $body . "\0" x 1024;
}
done_testing;
( run in 1.264 second using v1.01-cache-2.11-cpan-54e63673c56 )