API-Docker

 view release on metacpan or  search on metacpan

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

has images => (
  is      => 'lazy',
  builder => sub { API::Docker::API::Images->new(client => $_[0]) },
);


has networks => (
  is      => 'lazy',
  builder => sub { API::Docker::API::Networks->new(client => $_[0]) },
);


has volumes => (
  is      => 'lazy',
  builder => sub { API::Docker::API::Volumes->new(client => $_[0]) },
);


has exec => (
  is      => 'lazy',
  builder => sub { API::Docker::API::Exec->new(client => $_[0]) },
);


has distribution => (
  is      => 'lazy',
  builder => sub { API::Docker::API::Distribution->new(client => $_[0]) },
);


has secrets => (
  is      => 'lazy',
  builder => sub { API::Docker::API::Secrets->new(client => $_[0]) },
);


has configs => (
  is      => 'lazy',
  builder => sub { API::Docker::API::Configs->new(client => $_[0]) },
);


has plugins => (
  is      => 'lazy',
  builder => sub { API::Docker::API::Plugins->new(client => $_[0]) },
);


sub negotiate_version {
  my ($self, %opts) = @_;
  return if $self->_version_negotiated;
  return if defined $self->api_version;

  $log->debug("Auto-negotiating API version");
  my $version_info = $self->_request('GET', '/version',
    exists $opts{read_timeout} ? ( read_timeout => $opts{read_timeout} ) : (),
    exists $opts{connect_timeout} ? ( connect_timeout => $opts{connect_timeout} ) : (),
  );

  # The ApiVersion is put straight into every later request path (/v1.44/...),
  # so it has to be a JSON object carrying one of the form N.N -- nothing else
  # can be trusted there. Three ways a body fails that, each measured against a
  # fake daemon: a non-object body reached strict refs ('garbage' died with
  # "Can't use string as a HASH ref", [1] with "Not a HASH reference"); an
  # object with no ApiVersion set _version_negotiated and then sent every
  # request unversioned; and an ApiVersion copied verbatim let 'v1.44/../x'
  # become "GET /vv1.44/../x/info". One croak, naming the endpoint and the
  # shape, covers all of them.
  my $got;
  if (!defined $version_info) {
    $got = 'nothing';
  }
  elsif (ref $version_info ne 'HASH') {
    $got = ref $version_info ? 'a ' . ref($version_info) . ' reference'
      : "the non-object body '" . $version_info . "'";
  }
  elsif (!defined $version_info->{ApiVersion}) {
    $got = 'an object with no ApiVersion field';
  }
  else {
    my $v = $version_info->{ApiVersion};
    $got = 'an ApiVersion of '
      . (ref $v ? 'a ' . ref($v) . ' reference' : "'" . $v . "'");
  }

  croak __PACKAGE__ . '->negotiate_version: GET /version must answer with a '
    . 'JSON object carrying an ApiVersion of the form N.N (e.g. "1.44"); got '
    . $got
    unless ref $version_info eq 'HASH'
      && defined $version_info->{ApiVersion}
      && !ref $version_info->{ApiVersion}
      && $version_info->{ApiVersion} =~ /^\d+\.\d+$/;

  $self->_set_api_version($version_info->{ApiVersion});
  $log->debugf("Negotiated API version: %s", $version_info->{ApiVersion});
  $self->_version_negotiated(1);
}


around _request => sub {
  my ($orig, $self, $method, $path, %opts) = @_;

  # Auto-negotiate before any versioned request, but not for /version itself.
  # The triggering request's own bounds are handed to it: the negotiation is a
  # pre-flight the caller never wrote, and a caller who asked for a bound and
  # then hung in GET /version has been told something untrue (karr k72).
  if ($path ne '/version' && !defined $self->api_version && !$self->_version_negotiated) {
    $self->negotiate_version(
      exists $opts{read_timeout} ? ( read_timeout => $opts{read_timeout} ) : (),
      exists $opts{connect_timeout} ? ( connect_timeout => $opts{connect_timeout} ) : (),
    );
  }

  return $self->$orig($method, $path, %opts);
};


1;

__END__

=pod

=encoding UTF-8

=head1 NAME

API::Docker - Perl client for the Docker Engine API

=head1 VERSION

version 0.004

=head1 SYNOPSIS

    use API::Docker;

    # Connect to local Docker daemon via Unix socket
    my $docker = API::Docker->new;

    # Or connect to remote Docker daemon
    my $docker = API::Docker->new(
        host => 'tcp://192.168.1.100:2375',
    );

    # System information
    my $info = $docker->system->info;

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

Returns L<API::Docker::API::System> instance for system operations like
C<info>, C<version>, C<ping>, and C<events>.

=head2 containers

Returns L<API::Docker::API::Containers> instance for container operations like
C<list>, C<create>, C<start>, C<stop>, and C<remove>.

=head2 images

Returns L<API::Docker::API::Images> instance for image operations like
C<list>, C<pull>, C<push>, and C<remove>.

=head2 networks

Returns L<API::Docker::API::Networks> instance for network operations like
C<list>, C<create>, C<connect>, and C<disconnect>.

=head2 volumes

Returns L<API::Docker::API::Volumes> instance for volume operations like
C<list>, C<create>, and C<remove>.

=head2 exec

Returns L<API::Docker::API::Exec> instance for executing commands in containers.

=head2 distribution

Returns L<API::Docker::API::Distribution> instance for registry manifest
lookups: C<inspect> and C<exists>.

=head2 secrets

Returns L<API::Docker::API::Secrets> instance for secret operations: C<list>,
C<create>, C<inspect>, C<update> and C<remove>.

=head2 configs

Returns L<API::Docker::API::Configs> instance for config operations: C<list>,
C<create>, C<inspect>, C<update> and C<remove>.

=head2 plugins

Returns L<API::Docker::API::Plugins> instance for managed-plugin operations:
C<list>, C<privileges>, C<install>, C<inspect>, C<remove>, C<enable>,
C<disable>, C<upgrade>, C<push> and C<configure>.

=head2 negotiate_version

    $docker->negotiate_version;
    $docker->negotiate_version(read_timeout => 5, connect_timeout => 2);

Automatically negotiate the highest API version supported by the Docker daemon.
This is called automatically before the first API request if L</api_version>
is not set.

After negotiation, L</api_version> will contain the negotiated version
(e.g., C<1.41>).

C<GET /version> must answer with a JSON object carrying an C<ApiVersion> of
the form C<N.N> -- the value is placed directly into the path of every later
request (C</v1.44/...>). A body that is not such an object croaks, naming the
endpoint and the shape expected: a non-object body, an object with no
C<ApiVersion>, or an C<ApiVersion> that is not two dot-separated numbers. This
replaces three earlier failures on the same path -- a non-object body dying in
C<strict refs>, an object with no C<ApiVersion> silently leaving the client
sending every request unversioned, and a malformed C<ApiVersion> being copied
verbatim into the request path.

Options:

=over

=item * C<read_timeout> - Seconds of silence after which the request gives up
and croaks with an L<API::Docker::Error::Timeout>. Off by default; see
L<API::Docker::Role::HTTP/"Bounding a request that never ends">

=item * C<connect_timeout> - Seconds after which opening the connection gives
up and croaks with an L<API::Docker::Error::Timeout> whose C<< ->phase >> is
C<'connect'>. Off by default; see
L<API::Docker::Role::HTTP/"Bounding the connection itself">

=back

Called on its own, with no options, the negotiation is bounded by the
L<API::Docker::Role::HTTP/read_timeout> and
L<API::Docker::Role::HTTP/connect_timeout> attributes of the client, like any
other request. Reached the way it normally is -- automatically, from the first
request -- it inherits that request's own bounds instead; see
L</"What a timeout covers">.

=head1 TIMEOUTS

=head2 What a timeout covers

Two bounds, covering different halves of a request.
L<API::Docker::Role::HTTP/connect_timeout> bounds opening the connection;
L<API::Docker::Role::HTTP/read_timeout> bounds reading the answer. Both are
attributes of the client, and they are set in two places -- which are two
levels, not two spellings of one thing:

    # the rule, for this client
    my $docker = API::Docker->new(connect_timeout => 2, read_timeout => 30);

    # the exception, for this run of calls
    $docker->containers->using(read_timeout => 5)->list;
    $docker->system->using(read_timeout => 0)->events;

L<API::Docker::Role::Using/using> returns a clone of the resource class
carrying the bounds, and every request made through that clone is given them.
There is deliberately no third way: the individual methods take no timeout
options, so their arguments are the request and nothing else.

Both are off by default, which is the behaviour this distribution has always
had. C<0> means the same as unset -- no bound -- and is how a client-wide
default is turned off for a run of calls: what C<using> carries is read with
C<exists> rather than for truth, so a C<0> reaches the transport instead of
vanishing into "no opinion".

Three things they do not do:



( run in 1.909 second using v1.01-cache-2.11-cpan-364913b4093 )