API-Docker

 view release on metacpan or  search on metacpan

.claude/CLAUDE.md  view on Meta::CPAN

# API-Docker

Perl client for the Docker Engine API.

## Docker Engine API

- **Unix Socket**: Default transport via `/var/run/docker.sock`
- **TCP**: Remote Docker daemons via `tcp://host:port`
- **TLS**: Optional TLS for secure remote connections
- **Auto-Negotiate**: Detects highest API version from daemon

## Build & Test

```bash
dzil build
dzil test
prove -lv t/

.claude/CLAUDE.md  view on Meta::CPAN


## Test Architecture

Unified mock/live tests controlled by environment variables:

```bash
# Mock mode (default):
prove -l t/

# Read tests against real Docker:
API_DOCKER_TEST_HOST=unix:///var/run/docker.sock prove -l t/

# Full live mode (read + write):
API_DOCKER_TEST_HOST=unix:///var/run/docker.sock API_DOCKER_TEST_WRITE=1 prove -l t/
```

| Env Var | Effect |
|---------|--------|
| (none) | All tests run with mocks |
| `API_DOCKER_TEST_HOST` | Read tests live, write tests skip |
| `API_DOCKER_TEST_HOST` + `API_DOCKER_TEST_WRITE=1` | All tests live |

Test helper: `t/lib/Test/API/Docker/Mock.pm` exports `test_docker`, `is_live`, `can_write`, `skip_unless_write`, `check_live_access`, `register_cleanup`, `load_fixture`.

## Structure

```
lib/API/
├── Docker.pm                  # Main entry point + auto-negotiate
└── Docker/
    ├── Role/
    │   └── HTTP.pm            # HTTP over Unix Socket / TCP
    ├── API/
    │   ├── Containers.pm      # Container management
    │   ├── Images.pm          # Image management
    │   ├── Networks.pm        # Network management
    │   ├── Volumes.pm         # Volume management
    │   ├── System.pm          # System info, version, ping
    │   └── Exec.pm            # Exec into containers
    ├── Container.pm           # Container entity
    ├── Image.pm               # Image entity
    ├── Network.pm             # Network entity
    └── Volume.pm              # Volume entity
```

## Tech

- **Moo** for OOP
- **IO::Socket::UNIX** for Unix socket transport (no LWP dependency)
- **JSON::MaybeXS** for JSON handling
- **Log::Any** for logging
- **Dist::Zilla** with `[@Author::GETTY]`

CLAUDE.md  view on Meta::CPAN

    currently on CPAN is the previous tag. `dzil release` bumps the
    version automatically — never bump it by hand before a release.

12. **`{{$NEXT}}` in `Changes` is the placeholder for the upcoming
    release.** Add entries under it as you change behavior; `dzil
    release` replaces it with the version + timestamp.

## What this distribution is

A pure-Perl client for the Docker Engine API. No LWP, no shell-outs —
HTTP/1.1 (incl. chunked) is spoken directly over the daemon's Unix
socket (default) or a TCP endpoint.

The synchronous `_request` core lives in
`API::Docker::Role::HTTP`; resource-specific API methods live in
`API::Docker::API::*`. Entity wrappers (`API::Docker::Container`,
`API::Docker::Image`, ...) hang off the resource APIs.

## Layout

```
lib/API/Docker.pm                       # main client, version negotiation
lib/API/Docker/Role/HTTP.pm             # HTTP/1.1 transport (unix:// + tcp://)
lib/API/Docker/API/System.pm            # /version, /info, /_ping
lib/API/Docker/API/Containers.pm        # container endpoints
lib/API/Docker/API/Images.pm            # image endpoints (build, pull, push, ...)
lib/API/Docker/API/Networks.pm          # network endpoints
lib/API/Docker/API/Volumes.pm           # volume endpoints
lib/API/Docker/API/Exec.pm              # exec endpoints
lib/API/Docker/{Container,Image,Network,Volume}.pm  # entity classes
t/                                      # tests (prove -l t/)
t/lib/Test/API/Docker/Mock.pm           # fixture-driven mock helper
t/fixtures/*.json                       # captured daemon responses

CLAUDE.md  view on Meta::CPAN

## Build and test

```bash
dzil build              # build the dist
dzil test               # full test suite
prove -lv t/images.t    # single test
cpanm --installdeps .   # install deps from cpanfile
```

By default tests are fixture-driven (no Docker daemon needed). Set
`API_DOCKER_TEST_HOST=unix:///var/run/docker.sock` to also exercise the
read-only live paths; add `API_DOCKER_TEST_WRITE=1` to enable mutating
tests (create/remove containers, etc.).

## API conventions

- **Resource accessors live under the client:** `$docker->images`,
  `$docker->containers`, etc. Each returns a `*::API::*` instance.
- **List/inspect endpoints return entity objects** (e.g.
  `$docker->images->list` returns `[API::Docker::Image, ...]`); raw
  endpoints (e.g. `tag`, `push`) return the raw daemon response.

Changes  view on Meta::CPAN

    Docker Engine refuses pushes without it (`HTTP 400: missing
    X-Registry-Auth: invalid X-Registry-Auth header: EOF`). A new `auth`
    option accepts a hashref of credentials (`username`, `password`,
    `serveraddress`, or `identitytoken`) which is JSON-encoded and
    base64url-wrapped per the Docker Engine spec. Without `auth` the
    header carries an empty JSON object so unauthenticated/public
    pushes succeed where they previously failed at the HTTP layer.

0.001     2026-04-29 00:40:43Z
    - Initial release as API::Docker
    - Docker Engine API client with Unix socket and TCP support
    - Auto-negotiate API version from daemon
    - Container, Image, Network, Volume, System, and Exec APIs
    - Pure Perl implementation with minimal dependencies (no LWP)
    - HTTP/1.1 transport with chunked transfer encoding support

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

use API::Docker::API::System;
use API::Docker::API::Containers;
use API::Docker::API::Images;
use API::Docker::API::Networks;
use API::Docker::API::Volumes;
use API::Docker::API::Exec;


has host => (
  is      => 'ro',
  default => sub { $ENV{DOCKER_HOST} // 'unix:///var/run/docker.sock' },
);


has api_version => (
  is      => 'rwp',
  default => undef,
);


has tls => (

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

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

=head1 VERSION

version 0.002

=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;
    my $version = $docker->system->version;

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

API::Docker is a Perl client for the Docker Engine API. It provides a clean
object-oriented interface to manage Docker containers, images, networks, and
volumes.

Key features:

=over

=item * Pure Perl implementation with minimal dependencies

=item * Unix socket and TCP transport support

=item * Automatic API version negotiation

=item * Object-oriented entity classes (Container, Image, Network, Volume)

=item * Comprehensive logging via L<Log::Any>

=back

=head2 Architecture

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


=back

=item * B<HTTP Role> - L<API::Docker::Role::HTTP> - HTTP transport layer

=back

=head2 host

Docker daemon connection URL. Defaults to C<$ENV{DOCKER_HOST}> or
C<unix:///var/run/docker.sock>.

Supported formats:

=over

=item * C<unix:///path/to/socket> - Unix socket (default)

=item * C<tcp://host:port> - TCP connection

=back

=head2 api_version

Docker API version to use (e.g., C<1.41>). If not set, the client will
automatically negotiate the highest API version supported by the daemon.

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

(e.g., C<1.41>).

=head1 ENVIRONMENT VARIABLES

=over

=item C<DOCKER_HOST>

Docker daemon connection URL. Used as default for L</host> if not explicitly set.

Examples: C<unix:///var/run/docker.sock>, C<tcp://localhost:2375>

=item C<DOCKER_CERT_PATH>

Path to TLS certificates directory. Used as default for L</cert_path>.

=back

=head1 SEE ALSO

=over

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

=head2 Names

ArrayRef of container names (from C<list>).

=head2 Image

Image name used to create the container.

=head2 Created

Container creation timestamp (Unix epoch).

=head2 State

Container state. From C<list>: string like C<running>, C<exited>. From
C<inspect>: hashref with C<Running>, C<Paused>, C<ExitCode>, etc.

=head2 Status

Human-readable status string (e.g., "Up 2 hours").

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


has _socket => (
  is      => 'lazy',
  clearer => '_clear_socket',
);

sub _build__socket {
  my ($self) = @_;
  my $host = $self->host;

  if ($host =~ m{^unix://(.+)$}) {
    my $path = $1;
    $log->debugf("Connecting to Unix socket: %s", $path);
    my $sock = IO::Socket::UNIX->new(
      Peer => $path,
      Type => SOCK_STREAM,
    );
    croak "Cannot connect to Unix socket $path: $!" unless $sock;
    return $sock;
  }
  elsif ($host =~ m{^tcp://([^:]+):(\d+)$}) {
    my ($addr, $port) = ($1, $2);
    $log->debugf("Connecting to TCP %s:%s", $addr, $port);
    my $sock = IO::Socket::INET->new(
      PeerAddr => $addr,
      PeerPort => $port,
      Proto    => 'tcp',
    );
    croak "Cannot connect to $addr:$port: $!" unless $sock;
    return $sock;
  }
  else {
    croak "Unsupported host format: $host (expected unix:// or tcp://)";
  }
}

sub _reconnect {
  my ($self) = @_;
  $self->_clear_socket;
  return $self->_socket;
}

sub _request {

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

    has api_version => (is => 'ro');

    with 'API::Docker::Role::HTTP';

    # Now use get, post, put, delete_request methods
    my $data = $self->get('/containers/json');

=head1 DESCRIPTION

This role provides HTTP transport for the Docker Engine API. It implements
HTTP/1.1 communication over Unix sockets and TCP sockets without depending on
heavy HTTP client libraries like LWP.

Features:

=over

=item * Unix socket transport (C<unix://...>)

=item * TCP socket transport (C<tcp://host:port>)

=item * HTTP/1.1 chunked transfer encoding

=item * Automatic JSON encoding/decoding

=item * Request/response logging via L<Log::Any>

=item * Automatic connection management

t/basic.t  view on Meta::CPAN

use_ok('API::Docker::API::Volumes');
use_ok('API::Docker::API::Exec');
use_ok('API::Docker::Container');
use_ok('API::Docker::Image');
use_ok('API::Docker::Network');
use_ok('API::Docker::Volume');

# Test default construction
my $docker = API::Docker->new(api_version => '1.47');
isa_ok($docker, 'API::Docker');
is($docker->host, 'unix:///var/run/docker.sock', 'default host');
is($docker->api_version, '1.47', 'api_version set');
is($docker->tls, 0, 'tls off by default');

# Test custom host
my $docker_tcp = API::Docker->new(
  host        => 'tcp://remote:2375',
  api_version => '1.47',
);
is($docker_tcp->host, 'tcp://remote:2375', 'custom host');

t/images_push_auth.t  view on Meta::CPAN


subtest 'pre-encoded base64-like string passes through' => sub {
    my $pre = 'eyJ1IjoibWUifQ';
    is API::Docker::API::Images::_build_registry_auth_header($pre), $pre,
        'string passed through unchanged';
};

subtest 'push() sends X-Registry-Auth via _request' => sub {
    require API::Docker;
    my $docker = API::Docker->new(
        host        => 'unix:///dev/null',
        api_version => '1.47',
    );

    my $captured;
    my $mock = sub {
        my ($self, $method, $path, %opts) = @_;
        $captured = { method => $method, path => $path, %opts };
        return [];
    };

t/lib/Test/API/Docker/Mock.pm  view on Meta::CPAN

sub skip_unless_write {
  if (is_live() && !can_write()) {
    plan skip_all => 'Write tests skipped (set API_DOCKER_TEST_WRITE=1 to enable)';
  }
}

sub check_live_access {
  return unless is_live();

  my $host = $ENV{API_DOCKER_TEST_HOST};
  if ($host =~ m{^unix://(.+)$}) {
    unless (-S $1) {
      plan skip_all => "Docker socket $1 not available";
    }
  }

  eval {
    require API::Docker;
    my $docker = API::Docker->new(host => $host);
    my $result = $docker->system->ping;
    die "ping failed" unless $result eq 'OK';

t/lib/Test/API/Docker/Mock.pm  view on Meta::CPAN

sub _mock_docker {
  my (%routes) = @_;

  unless (grep { /version/ } keys %routes) {
    $routes{'GET /version'} = load_fixture('system_version');
  }

  require API::Docker;

  my $docker = API::Docker->new(
    host        => 'unix:///var/run/docker.sock',
    api_version => '1.47',
  );

  my $mock_request = sub {
    my ($self, $method, $path, %opts) = @_;

    my $clean_path = $path;
    $clean_path =~ s{^/v[\d.]+}{};

    my $key = "$method $clean_path";



( run in 2.086 seconds using v1.01-cache-2.11-cpan-64ef6c95b5d )