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]`
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
## 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.
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
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";