API-Docker
view release on metacpan or search on metacpan
lib/API/Docker/API/Containers.pm view on Meta::CPAN
# does that by itself here: the check runs on $self, which is the clone
# ->using returned when there was one (karr k74).
$self->_assert_container_running($id) if $require_running;
my %params;
$params{stream} = $opts{stream} ? 1 : 0;
$params{logs} = defined $opts{logs} ? ($opts{logs} ? 1 : 0) : 1;
$params{stdout} = defined $opts{stdout} ? ($opts{stdout} ? 1 : 0) : 1;
$params{stderr} = defined $opts{stderr} ? ($opts{stderr} ? 1 : 0) : 1;
$params{stdin} = $opts{stdin} ? 1 : 0 if defined $opts{stdin};
return $self->client->stream_frames('POST', "/containers/$id/attach",
params => \%params,
defined $opts{tty} ? ( tty => $opts{tty} ) : (),
%{ $self->_request_options },
exists $opts{on_frame} ? ( on_frame => $opts{on_frame} ) : (),
);
}
sub top {
my ($self, $id, %opts) = @_;
croak "Container ID required" unless $id;
my %params;
$params{ps_args} = $opts{ps_args} if defined $opts{ps_args};
return $self->client->get("/containers/$id/top",
params => \%params,
%{ $self->_request_options },
);
}
# The Podman compatibility path, and deliberately only that. Podman answers
# GET /containers/{id}/stats for a container that is not running with an error
# object inside a response it has already committed to 200 --
# {"cause":"container is stopped","message":"container is stopped",
# "response":500}, chunked, for the one-shot call and for stream => 1 alike
# (measured on Podman 5.4.2, API 1.41). Neither guard in the transport sees
# it: the >= 400 croak reads the status line, which says 200, and the stream
# check triggers on errorDetail, which this object does not carry. Docker
# 29.7.2 (API 1.55) answers the same call with a real, zero-filled reading and
# never produces this shape at all -- so this belongs here, next to the one
# endpoint and the one engine it was measured on, and not in Role::HTTP, where
# it would be a heuristic on daemon prose sitting under all twelve modules.
#
# All four clauses have to hold. The narrowness is the point, not an accident:
#
# 1. the status was 2xx. True by construction on both paths this guards:
# _request croaks before returning for >= 400, and
# _read_streaming_response reads such a body whole rather than handing it
# to a callback, so nothing that failed the status line reaches here
# 2. the decoded value is a HashRef
# 3. it carries all three of cause, message and response, exactly
# lower-cased. Never case-insensitively, and this is the counter-example
# that fixes it: POST /containers/{id}/wait answers its SUCCESS case with
# a top-level `Error` key -- Podman sends "Error":null on every wait --
# so a rule matching /error/i would turn every successful wait into a
# failure. Measured over fifteen read endpoints per engine and every
# fixture in t/fixtures: no 2xx body on either engine carries even one of
# these three lower-cased at the top level
# 4. `response` is a non-ref scalar reading as an integer >= 400. That is
# what makes the rule self-evidencing rather than a guess about prose:
# the object is an error because Podman says so inside it. Known miss:
# Podman's GET /plugins answers {"cause":"","message":"Path ... is not
# supported","response":0}, which clause 4 rejects -- but it arrives with
# 404 on the status line and the transport croaks it long before this
# runs, so the miss goes in the conservative direction and costs nothing
#
# A bare {message => ...} deliberately does not trigger: that is the ordinary
# Docker error body, and treating one inside a 2xx as a failure would be a
# guess about prose rather than a reading of what the engine said.
sub _podman_error_object {
my ($self, $value) = @_;
return unless ref $value eq 'HASH';
return unless exists $value->{cause}
&& exists $value->{message}
&& exists $value->{response};
my $response = $value->{response};
return if ref $response;
return unless defined $response && $response =~ /\A[0-9]+\z/;
return unless $response >= 400;
return $value;
}
# API::Docker::Error::HTTP rather than ::Stream: the one-shot call is not a
# stream at all, so "Docker API stream error" would be the wrong sentence for
# it and ->events would be a fabricated list. What the caller wants instead is
# exactly what this class carries -- ->status for the code Podman named, and
# ->data for the object, whose `cause` key that attribute's own POD already
# points at. Two of its attributes are left at their defaults on this path, on
# purpose: ->reason, because the status line's reason phrase was "OK" and
# putting that on a 500 would mislead, and ->body, because the bytes were
# decoded by the transport before this check ever saw them.
sub _assert_no_podman_error {
my ($self, $endpoint, $value) = @_;
my $error = $self->_podman_error_object($value) or return $value;
my $reason = $error->{message};
$reason = $error->{cause} unless defined $reason && length $reason;
$reason = 'no message given' unless defined $reason && length $reason;
# Carp appends no location to a message that already ends in a newline.
$reason =~ s/\s+\z//;
# The object goes into a variable first: `croak CLASS->new(...)` is indirect
# object syntax and parses as CLASS->croak(new(...)). Carp hands a reference
# straight back rather than decorating it, so the location is captured by
# hand, naming the frame a croak of a plain string would have named.
my $err = API::Docker::Error::HTTP->new(
message => 'Docker API error (' . $error->{response} . '): ' . $reason
. ' -- reported inside a 200 response to ' . $endpoint,
location => shortmess(''),
status => $error->{response},
data => $error,
);
croak $err;
}
sub stats {
my ($self, $id, %opts) = @_;
croak "Container ID required" unless $id;
my $stream = $opts{stream} ? 1 : 0;
my %params = ( stream => $stream );
# one-shot asks the engine not to wait for a second sampling cycle, which
# only means anything to a single reading. It is sent for the one-shot call
# alone, the way it always was, and never beside stream => 1.
$params{'one-shot'} = 1 unless $stream;
( run in 1.327 second using v1.01-cache-2.11-cpan-54e63673c56 )