API-Docker

 view release on metacpan or  search on metacpan

lib/API/Docker/API/Images.pm  view on Meta::CPAN

has client => (
  is       => 'ro',
  required => 1,
  weak_ref => 1,
);


# The class is the caller's argument rather than a constant of this module:
# `list` and `inspect` are two definitions in the swagger and therefore two
# generated classes. Both carry the same convenience methods, composed by
# API::Docker::Role::Entity::Image -- see "The two image shapes".
#
# from_data, not new: this is a daemon response, and the two entry points of
# 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 {

lib/API/Docker/API/Images.pm  view on Meta::CPAN

        context   => $tar_data,
        t         => 'myapp:v1',
        buildargs => { APP_VERSION => '1.0' },
        nocache   => 1,
    );

Build an image from a tar archive containing a Dockerfile and build context.

The C<context> parameter is required and must contain the raw bytes of a tar
archive (or a scalar reference to one).

Returns an ArrayRef of build events, one per object in the engine's
newline-delimited JSON stream, even when the stream carried a single object
(C<< q => 1 >> produces exactly one). A successful build returns; a failed one
croaks.

    my $events = $images->build(context => $tar, t => 'myapp:latest');
    my ($aux) = grep { $_->{aux} } @$events;
    my $image_id = $aux->{aux}{ID};

The engine answers a failed build with HTTP 200 and reports the failure as an
C<errorDetail> object inside the stream, so nothing about the response status
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

lib/API/Docker/API/Images.pm  view on Meta::CPAN

=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

=item * C<filters> - HashRef of filter name to ArrayRef of string values; the
engine accepts C<dangling>, C<until> and C<label> here. Shape-checked and
normalised by L<API::Docker::Role::Filters>

=back

=head2 get

    use Path::Tiny;
    my $tar = $images->get('alpine:3');
    path('alpine.tar')->spew_raw($tar);

Export one image, and the history behind it, as a tar archive -- the endpoint
behind C<docker image save>. Together with L</load> it is the only way in or
out of a daemon that does not go through a registry.

B<The return value is raw bytes, not a decoded structure.> The engine answers
with the tar stream itself, and the transport is told to hand it back
untouched (C<< raw => 1 >>), so what arrives is byte for byte what the daemon
wrote. Write it with a binary-safe file handle -- C<< path(...)->spew_raw >>,
or C<binmode> on a handle of your own. Treating it as text corrupts it, and
nothing about the value announces that it is binary.

The archive holds one tarball per layer, a config JSON per image,
C<manifest.json> and C<repositories>. Measured against Podman 5.4.2 (API
1.41): the response is chunked with
C<< Content-Type: application/octet; charset=us-ascii >>, where Docker sends
C<application/x-tar> -- the transport looks at neither, so the difference does
not reach the caller. Exporting C<alpine:3> through this method produced bytes
md5-identical to what C<curl --unix-socket> wrote for the same request, all
8705536 of them, so the chunked reader is binary-clean.

An unknown image croaks. On the same engine that is C<404 Not Found> with
C<< {"message":"failed to find image ...: image not known"} >>.

=head2 Exporting without buffering the archive

The whole archive is buffered in memory before it is returned, so exporting a
large image costs its full size in RAM. Pass C<on_chunk> and the bytes are
handed over as they arrive instead, and nothing is kept:

    use Path::Tiny;
    my $out = path('alpine.tar')->openw_raw;
    my $summary = $images->get('alpine:3',
        on_chunk => sub { print {$out} $_[0] });
    close $out;

    $summary;   # { delivered => 266, stopped => 0 }

The units are whatever the transport read, not a fixed size: a chunk boundary
carries no meaning in a tar stream, and the only guarantee is that
concatenating them in order gives the same bytes the buffered call returns.
Write them to a binary-safe handle, exactly as for the buffered value.

Measured against Podman 5.4.2 (API 1.41): exporting C<alpine:3> this way
delivered its 8705536 bytes in 266 pieces, md5-identical to what the buffered
call returns for the same request, with no more than one piece held at a time.

With a callback the return value is the summary HashRef
C<< { delivered => N, stopped => 0|1 } >>, not the archive: C<delivered> is
how many pieces went to the callback, C<stopped> is 1 when the callback ended
the transfer. Stopping leaves a B<truncated> archive behind -- the export is
one tar stream, not a sequence of independent records -- so stop only to
abandon it. See
L<API::Docker::Role::HTTP/"Streaming a response as it arrives">.

Options:

=over

=item * C<on_chunk> - CodeRef called with each piece of the archive as it
arrives, instead of the whole thing being returned

=back

=head2 get_all

    my $tar = $images->get_all('alpine:3', 'registry:2');
    my $tar = $images->get_all([ 'alpine:3', 'registry:2' ]);

Export several images into one tar archive. Takes the names as a list or as a
single ArrayRef; at least one is required.

B<The return value is raw bytes>, exactly as for L</get> -- see there for what
that means for writing it out.

C<manifest.json> inside the archive carries one entry per image, so a single
tar can be carried to another host and loaded in one L</load> call. Measured
against Podman 5.4.2 (API 1.41): asking for no names at all answers
C<400 Bad Request> with C<< {"message":"no images to download"} >>.

C<on_chunk> works here exactly as it does for L</get> -- several images make a
bigger archive, so this is where not buffering it matters most -- but it can
only be passed with the ArrayRef form:

    my $summary = $images->get_all([ 'alpine:3', 'registry:2' ],
        on_chunk => sub { print {$out} $_[0] });

The list form takes names and nothing else: a trailing option pair in it would
be indistinguishable from two more image names. Options after the ArrayRef
must come in pairs; an odd number croaks.

A transport bound is not one of those options: it goes on the resource class,
which works with either form -- C<< $docker->images->using(read_timeout => 5)
->get_all('alpine:3') >>, see L<API::Docker::Role::Using>.

Measured against Podman 5.4.2 (API 1.41): C<alpine:3> and C<registry:2>
together came to 34725888 bytes in 1060 pieces, none of which had to be held.

=head2 load

    use Path::Tiny;
    my $events = $images->load(path('alpine.tar')->slurp_raw);

    for my $event (@$events) {
        print $event->{stream} if defined $event->{stream};
    }

Import a tar archive produced by L</get> or L</get_all> -- the endpoint behind
C<docker image load>. The archive is the request body; pass it as raw bytes or
as a scalar reference to them, the way L</build> takes its context.

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.
The last of them names what was imported:

    my ($loaded) = grep { ($_->{stream} // '') =~ /^Loaded image/ } @$events;

Options:

=over

=item * C<quiet> - Suppress the per-layer progress detail in the response
stream

=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

C<quiet> changes how much the engine says, not what this method returns: the
body stays newline-delimited JSON and the return stays an ArrayRef either way.
B<Podman ignores it entirely> -- measured against 5.4.2 (API 1.41), C<quiet>
unset, C<0> and C<1> all produce the identical single
C<< {"stream":"Loaded image: ..."} >> object. Should an engine answer a quiet
load with a body of no bytes at all, the transport still returns C<[]>, not
C<undef> -- the C<ndjson> branch in L<API::Docker::Role::HTTP/_request> runs
before the empty-body check that would return C<undef>, so a caller that
iterates the result unconditionally needs no guard for this case.

A failed load croaks, but by which route depends on the engine, the same split
L</pull> and L</push> have. Docker reports it as an C<errorDetail> object
inside a 200 stream, which croaks with an L<API::Docker::Error::Stream>
carrying the events. Podman reports it in the status line instead: measured
against 5.4.2, a body that is not an image archive answers C<500 Internal
Server Error> with C<< {"message":"failed to load image: payload does not
match any of the supported image formats: ..."} >>, and 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 any stream is decoded.
Inspect C<$@> as a string rather than testing for the exception class.

The archive is sent as one buffered request body, so loading a large image
costs its full size in RAM.

=head2 commit

    my $result = $images->commit(
        container => $container_id,
        repo      => 'myapp',
        tag       => 'snapshot',
        comment   => 'after the migration ran',
    );
    my $image_id = $result->{Id};

    # With a config override and Dockerfile instructions
    $images->commit(
        container => $container_id,
        repo      => 'myapp',
        tag       => 'v2',
        config    => { Cmd => [ '/bin/sh' ], Labels => { built => 'here' } },
        changes   => [ 'EXPOSE 8080', 'LABEL stage=release' ],
    );

Create an image from a container's current filesystem. This is the one
image-producing path that does not go through a build context, and it is how a
caller snapshots a container it has been exec-ing into.

Returns the raw daemon response, a HashRef with an C<Id> key. Measured against
Podman 5.4.2 (API 1.41) the status is C<201 Created> and C<Id> is a bare hex
digest with no C<sha256:> prefix; Docker prefixes it. Do not compare it
literally against an id from C<inspect> without normalising.

Options:

=over

=item * C<container> - Container id or name to commit (required)

=item * C<repo> - Repository for the new image, e.g. C<myapp>

=item * C<tag> - Tag for the new image

=item * C<comment> - Commit message stored in the image history



( run in 1.144 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )