API-Docker
view release on metacpan or search on metacpan
lib/API/Docker/API/Images.pm view on Meta::CPAN
# API::Docker::Role::Type read it differently. from_data takes the swagger's
# wire names and nothing else, so a key it has not heard of keeps its own
# spelling instead of being read as the Perl name of one it has, and a value
# that disagrees with the swagger costs its own field rather than the whole
# response. `client` is ours rather than the engine's, so it goes beside the
# data instead of into it.
sub _wrap {
my ($self, $class, $data) = @_;
return $class->from_data($data, client => $self->client);
}
sub _wrap_list {
my ($self, $class, $list) = @_;
return [ map { $self->_wrap($class, $_) } @$list ];
}
sub list {
my ($self, %opts) = @_;
my %params;
$params{all} = $opts{all} ? 1 : 0 if defined $opts{all};
$params{digests} = $opts{digests} ? 1 : 0 if defined $opts{digests};
$params{filters} = $self->_normalise_filters($opts{filters})
if defined $opts{filters};
my $result = $self->client->get('/images/json',
params => \%params,
%{ $self->_request_options },
);
return $self->_wrap_list('API::Docker::Type::ImageSummary', $result // []);
}
sub build {
my ($self, %opts) = @_;
my $context = delete $opts{context};
croak "Build context required (tar archive as scalar ref or raw bytes)" unless defined $context;
my %params;
$params{dockerfile} = $opts{dockerfile} if defined $opts{dockerfile};
$params{t} = $opts{t} if defined $opts{t};
$params{q} = $opts{q} ? 1 : 0 if defined $opts{q};
$params{nocache} = $opts{nocache} ? 1 : 0 if defined $opts{nocache};
$params{pull} = $opts{pull} if defined $opts{pull};
$params{rm} = defined $opts{rm} ? ($opts{rm} ? 1 : 0) : 1;
$params{forcerm} = $opts{forcerm} ? 1 : 0 if defined $opts{forcerm};
$params{memory} = $opts{memory} if defined $opts{memory};
$params{memswap} = $opts{memswap} if defined $opts{memswap};
$params{cpushares} = $opts{cpushares} if defined $opts{cpushares};
$params{cpusetcpus} = $opts{cpusetcpus} if defined $opts{cpusetcpus};
$params{cpuperiod} = $opts{cpuperiod} if defined $opts{cpuperiod};
$params{cpuquota} = $opts{cpuquota} if defined $opts{cpuquota};
$params{shmsize} = $opts{shmsize} if defined $opts{shmsize};
$params{networkmode} = $opts{networkmode} if defined $opts{networkmode};
$params{platform} = $opts{platform} if defined $opts{platform};
$params{target} = $opts{target} if defined $opts{target};
$params{buildargs} = encode_json($opts{buildargs}) if $opts{buildargs};
$params{labels} = encode_json($opts{labels}) if $opts{labels};
my $raw = ref $context eq 'SCALAR' ? $$context : $context;
# A build's registry credentials ride in X-Registry-Config, not
# X-Registry-Auth: the map lets `FROM private.registry/...` authenticate,
# and a build may draw base images from several registries at once. Sent
# only when given -- an anonymous build needs no header.
my %headers;
$headers{'X-Registry-Config'} =
$self->_registry_config_header($opts{registry_config})
if defined $opts{registry_config};
# exists, not truth: an unset callback is a caller bug, and falling back to
# the buffered path for it would hand a long build back as silence.
return $self->client->_request('POST', '/build',
raw_body => $raw,
content_type => 'application/x-tar',
params => \%params,
%headers ? ( headers => \%headers ) : (),
%{ $self->_request_options },
exists $opts{on_event} ? ( on_event => $opts{on_event} ) : ( ndjson => 1 ),
);
}
# The tag query parameter is not a default to hand out unconditionally: the
# engine appends it to whatever reference `fromImage` already carries. Docker
# lets tag take precedence and silently rewrites `nginx:1.25` to `nginx:latest`
# (a wrong image, reported as success); Podman concatenates to
# `nginx:1.25:latest` and answers 500 `invalid reference format`. A digest
# reference breaks the same way on both. So `tag` is defaulted only when the
# reference carries neither -- a `:tag` in the segment after the last `/`, or an
# `@digest` anywhere. The colon in a registry `host:port/` is before that
# segment, so it is not mistaken for a tag.
sub _reference_has_tag_or_digest {
my ($self, $ref) = @_;
return 1 if $ref =~ /\@/;
my ($last_segment) = $ref =~ m{([^/]*)\z};
return $last_segment =~ /:/ ? 1 : 0;
}
# Only when credentials were given: an anonymous pull needs no header, and the
# engine reads X-Registry-Auth off /images/create only to reach a private
# registry. This is the plugins/distribution policy, not push's always-send --
# push must send even the anonymous {} because the engine rejects a push with
# no header at all.
sub _auth_headers {
my ($self, $opts) = @_;
return () unless defined $opts->{auth};
return (headers => { 'X-Registry-Auth' => $self->_registry_auth_header($opts->{auth}) });
}
sub pull {
my ($self, %opts) = @_;
croak "fromImage required" unless $opts{fromImage};
my %params;
$params{fromImage} = $opts{fromImage};
if (defined $opts{tag}) {
$params{tag} = $opts{tag};
}
elsif (!$self->_reference_has_tag_or_digest($opts{fromImage})) {
$params{tag} = 'latest';
}
return $self->client->post('/images/create', undef,
params => \%params,
$self->_auth_headers(\%opts),
%{ $self->_request_options },
exists $opts{on_event} ? ( on_event => $opts{on_event} ) : ( ndjson => 1 ),
);
}
sub inspect {
my ($self, $name) = @_;
croak "Image name required" unless $name;
my $result = $self->client->get("/images/$name/json",
%{ $self->_request_options },
);
return $self->_wrap('API::Docker::Type::ImageInspect', $result);
}
sub history {
my ($self, $name) = @_;
croak "Image name required" unless $name;
return $self->client->get("/images/$name/history",
%{ $self->_request_options },
);
}
sub push {
my ($self, $name, %opts) = @_;
croak "Image name required" unless $name;
my %params;
$params{tag} = $opts{tag} if defined $opts{tag};
my $auth_header = $self->_registry_auth_header($opts{auth});
return $self->client->post(
"/images/$name/push",
undef,
lib/API/Docker/API/Images.pm view on Meta::CPAN
says the build broke. This method used to return that stream like any other
and leave the scan to the caller, which meant a caller who did not know to
scan reported a broken build as a success. It now croaks with an
L<API::Docker::Error::Stream> instead:
my $events = eval { $images->build(context => $tar, t => 'myapp:latest') };
if (my $err = $@) {
warn "$err"; # the reason, with Carp's location suffix
for my $event (@{ $err->events }) { # the build output up to the failure
print $event->{stream} if defined $event->{stream};
}
}
The exception stringifies to what a plain C<croak> would have produced, so
existing C<eval>-and-inspect-C<$@> code needs no change.
Options:
=over
=item * C<context> - Tar archive bytes (required)
=item * C<dockerfile> - Path to Dockerfile within the archive (default: C<Dockerfile>)
=item * C<t> - Tag for the image (e.g. C<name:tag>)
=item * C<q> - Suppress verbose build output
=item * C<nocache> - Do not use cache when building
=item * C<pull> - Always pull base image
=item * C<rm> - Remove intermediate containers (default: true)
=item * C<forcerm> - Always remove intermediate containers
=item * C<buildargs> - HashRef of build-time variables
=item * C<labels> - HashRef of labels to set on the image
=item * C<memory> - Memory limit in bytes
=item * C<memswap> - Total memory (memory + swap), -1 to disable swap
=item * C<cpushares> - CPU shares (relative weight)
=item * C<cpusetcpus> - CPUs to use (e.g. C<0-3>, C<0,1>)
=item * C<cpuperiod> - CPU CFS period (microseconds)
=item * C<cpuquota> - CPU CFS quota (microseconds)
=item * C<shmsize> - Size of /dev/shm in bytes
=item * C<networkmode> - Network mode during build
=item * C<platform> - Platform (e.g. C<linux/amd64>)
=item * C<target> - Multi-stage build target
=item * C<registry_config> - Registry credentials for the base images the build
pulls, sent as C<X-Registry-Config>. A HashRef mapping each registry hostname
to its AuthConfig --
C<< { 'registry.example:5000' => { username => 'me', password => 'secret' } } >>
-- so a C<FROM private.registry/...> can authenticate, and a build drawing from
several registries can carry all of them at once. A pre-encoded base64 string
is also accepted. Sent only when given. This is B<not> C<auth>/C<X-Registry-Auth>,
which carries a single AuthConfig; C</build> uses the map form. See
L<API::Docker::Role::RegistryAuth>
=item * C<on_event> - CodeRef called with each build event as it arrives,
instead of the ArrayRef being collected and returned; see below
=back
=head2 Progress as it arrives
Without a callback the whole stream is read before anything is parsed, so a
build that takes two minutes is two minutes of silence followed by all of its
output at once. Pass C<on_event> and the events are handed over as the daemon
sends them:
my $summary = $images->build(
context => $tar,
t => 'myapp:latest',
on_event => sub {
my ($event, $stop) = @_;
print $event->{stream} if defined $event->{stream};
},
);
$summary; # { delivered => 41, stopped => 0 }
With a callback the return value is that summary HashRef, not the events:
C<delivered> is how many went to the callback, C<stopped> is 1 when the
callback ended the stream and 0 when the daemon did. Nothing is accumulated,
so a caller that wants the C<aux> event with the image id in it must keep that
event itself as it goes by. See
L<API::Docker::Role::HTTP/"Streaming a response as it arrives">.
The same section applies to L</pull>, L</push> and L</load>, which take
C<on_event> on the same terms.
=head3 A failed build still croaks, one event earlier
The C<errorDetail> check runs either way, so a failed build croaks with an
L<API::Docker::Error::Stream> on both paths. What differs is when, and what
the exception carries:
=over
=item * Buffered, the stream is scanned once it is complete, and
C<< $err->events >> is the B<whole> event list -- all the build output that
led up to the failure.
=item * Streamed, the check runs per event, so the croak happens at the event
that reports the failure rather than when the daemon eventually closes. The
exception then carries B<that one event> alone: a callback stream keeps no
history, having handed every earlier event to the callback already. The
failing event itself is not delivered.
lib/API/Docker/API/Images.pm view on Meta::CPAN
warn "$err"; # the reason, as before
# $err->events is the failing event; @output is what preceded it
}
=head2 pull
my $events = $images->pull(fromImage => 'nginx', tag => 'latest');
my $events = $images->pull(fromImage => 'nginx:1.25'); # tag rides in the name
my $events = $images->pull(fromImage => 'alpine@sha256:...'); # by digest
Pull an image from a registry.
C<tag> defaults to C<latest> B<only when C<fromImage> carries no tag or digest
of its own>. The engine appends C<tag> to the reference rather than treating it
as a fallback, so defaulting it onto an already-qualified name breaks the pull:
measured against Docker 29.7.2 (API 1.55) C<< pull(fromImage => 'nginx:1.25')
>> would silently fetch C<nginx:latest> and report success, and against Podman
5.8.4 (compat API 1.44) the same request answers C<500 invalid reference
format> for C<nginx:1.25:latest>. A digest reference breaks the same way on
both. So C<tag> is sent only if given explicitly, or defaulted to C<latest>
when the name carries neither a C<:tag> (in the segment after the last C</>)
nor an C<@digest>. A registry C<host:port/> prefix is not mistaken for a tag.
Returns an ArrayRef of progress events, one per object in the engine's
newline-delimited JSON stream, even when the stream carried a single object.
A failed pull croaks either way, but which way depends on the engine, so do
not write code that expects one of them:
=over
=item * Docker reports it in the stream. The response is HTTP 200 and the
failure is an C<errorDetail> object among the progress events; this method
croaks with an L<API::Docker::Error::Stream>, whose C<< ->events >> holds the
progress that preceded the failure.
=item * Podman reports it in the status line. Measured against the rootless
socket (5.4.2, API 1.41): pulling a repository that does not exist answers
C<403 Forbidden> with C<< {"message":"denied: requested access to the resource
is denied"} >>, and an existing repository with a missing tag answers
C<404 Not Found> with C<< {"message":"manifest unknown: manifest unknown"} >>.
Neither reaches the stream at all -- the transport's own status handling
croaks with an L<API::Docker::Error::HTTP> -- which is that same string to
anything inspecting C<$@> as text -- first.
=back
Catching L<API::Docker::Error::Stream> specifically is therefore not a
reliable way to catch a failed pull. C<eval> and inspect C<$@> as a string,
which both cases satisfy.
Options:
=over
=item * C<fromImage> - Image name to pull (required)
=item * C<tag> - Tag to pull. Defaulted to C<latest> only when C<fromImage>
carries no tag or digest of its own; see above
=item * C<auth> - Registry credentials for pulling from a private registry,
sent as C<X-Registry-Auth>. A HashRef of the usual keys (C<username>,
C<password>, C<serveraddress>, or C<identitytoken>) or a pre-encoded base64
string, exactly as L</push> takes it. Unlike C<push>, the header is sent
B<only> when C<auth> is given -- an anonymous pull carries none, which the
engine reads as the anonymous case. See L<API::Docker::Role::RegistryAuth>
=item * C<on_event> - CodeRef called with each progress event as it arrives,
instead of the ArrayRef being collected and returned. The return value is then
the summary HashRef and a stream failure croaks one event in, exactly as for
L</build>; see L</"Progress as it arrives">
=back
=head2 inspect
my $image = $images->inspect('nginx:latest');
Get detailed information about an image. Returns an
L<API::Docker::Type::ImageInspect>, which is B<not> the class L</list>
returns -- see L</"The two image shapes">.
=head2 history
my $history = $images->history('nginx:latest');
Get image history (layers). Returns ArrayRef of layer information.
=head2 push
my $events = $images->push('myrepo/nginx', tag => 'v1');
$images->push('myrepo/nginx', auth => {
username => 'me',
password => 'secret',
serveraddress => 'https://index.docker.io/v1/',
});
Push an image to a registry. Optionally specify C<tag>.
Returns an ArrayRef of progress events, one per object in the engine's
newline-delimited JSON stream, even when the stream carried a single object.
A failed push croaks, by one of two routes depending on the engine -- an
unauthorised push to a private registry is the common case, and it is exactly
the one that must not be reported as a success.
Docker reports it inside a 200 stream as an C<errorDetail> object, which
croaks with an L<API::Docker::Error::Stream> carrying the progress events.
Podman puts an C<errorDetail> body behind a real error status instead:
measured against the rootless socket (5.4.2, API 1.41), a push to an
unreachable registry answers C<500 Internal Server Error> with
C<< {"errorDetail":{"message":"... connection refused"},"error":"..."} >>, so
the transport's status handling croaks with an L<API::Docker::Error::HTTP> --
which is that same string to anything inspecting C<$@> as text -- before the
stream is ever decoded. That body carries no C<message> key, so the whole
JSON object ends up as the croak text.
Either way the failure is loud. Inspect C<$@> as a string rather than testing
for the exception class, which only the first route produces.
The Docker Engine requires an C<X-Registry-Auth> header on every push,
even for anonymous attempts; the header is always sent. Pass C<auth> as
a hashref of credentials (typical keys: C<username>, C<password>,
C<serveraddress>, or C<identitytoken>), or as a pre-encoded base64 string.
Without C<auth> the header carries an empty JSON object.
Options:
=over
=item * C<tag> - Tag to push
=item * C<auth> - Registry credentials, as above
=item * C<on_event> - CodeRef called with each progress event as it arrives --
layer by layer, rather than the whole upload in one silence -- instead of the
ArrayRef being collected and returned. The return value is then the summary
HashRef and a stream failure croaks one event in, exactly as for L</build>;
see L</"Progress as it arrives">
=back
=head2 tag
$images->tag('nginx:latest', repo => 'myrepo/nginx', tag => 'v1');
Tag an image with a new repository and/or tag name.
=head2 remove
$images->remove('nginx:latest', force => 1);
Remove an image.
Options:
=over
=item * C<force> - Force removal
=item * C<noprune> - Do not delete untagged parents
=back
=head2 search
my $results = $images->search('nginx', limit => 25);
Search Docker Hub for images. Returns ArrayRef of search results.
Options:
=over
=item * C<limit> - Maximum number of results
=item * C<filters> - HashRef of filter name to ArrayRef of string values; the
engine accepts C<is-official>, C<is-automated> and C<stars> here. The boolean
ones want the string, C<< { 'is-official' => ['true'] } >>, and C<stars> a
number written as one -- L<API::Docker::Role::Filters> takes care of both and
croaks on a shape the daemon would refuse
=back
=head2 prune
my $result = $images->prune(filters => { dangling => ['true'] });
Delete unused images. Returns hashref with C<ImagesDeleted> and C<SpaceReclaimed>.
Options:
=over
( run in 0.573 second using v1.01-cache-2.11-cpan-85d3896f969 )