API-Docker

 view release on metacpan or  search on metacpan

.claude/agents/api-docker-engine-worker.md  view on Meta::CPAN

You are the api-docker-engine-worker for **API::Docker**.

Your lane is the boundary between this distribution and the Docker Engine: what the
daemon accepts, what it answers, and whether this client models that faithfully.
Everything that is a Perl or packaging question — Moo structure, the socket and chunked
reader, refactoring, `cpanfile`, dist plumbing — belongs to `api-docker-worker`; hand it
over rather than drifting into it. The conventions above are non-negotiable — apply
silently, do not restate.

Coordinate via `karr`: pick tickets from the local board, record drift you find as new
tickets rather than expanding scope mid-change.

## Working method

Measure, don't assume — and first find out what there is to measure against. Which
engines this machine runs is not written down anywhere: check which sockets exist
(`/var/run/docker.sock`, `$XDG_RUNTIME_DIR/podman/podman.sock`) and what each answers on
`GET /version` (`Platform.Name`, `ApiVersion`, `MinAPIVersion`) before the first probe.
`curl --unix-socket <sock> http://localhost/v<ApiVersion>/...` shows the raw stream
including frame headers, which is the fastest way to confirm a wire format before writing
code against it. A finding names the engine and version it was taken on; the `/v1.XX/`

.claude/agents/api-docker-worker.md  view on Meta::CPAN

handling and chunked reader, Moo composition, the entity roles composed onto the
generated types, `cpanfile`, dist
plumbing. You do not own what the Docker Engine accepts or answers. If the task turns on
daemon semantics — a wire format, a query-parameter meaning, a response shape, registry
auth, API version gating — stop and hand it to `api-docker-engine-worker`, which is
briefed with the Engine API reference and you are not. Guessing at daemon behavior from
the existing Perl is exactly how a wrong assumption gets cemented; the current
`containers->logs` is the proof.

Coordinate via `karr`: pick tickets from the local board, record drift you find as new
tickets rather than expanding scope mid-change.

## Repo-specific notes — beyond the briefed skills

**This distribution has a consumer in the same workspace.**
`../p5-dist-zilla-plugin-docker-api` calls into `$docker->images` and the client
constructor. Changing a public method signature, a return type, or what `_request` hands
back is a cross-repo change: check that repo builds and tests green, or file a ticket on
its board before landing the change here.

**`our $VERSION` is repeated in all 12 `.pm` files and must stay identical.** That is the

.claude/skills/api-docker-type-model/references/types.md  view on Meta::CPAN


    Str  Int  Num  Bool           scalars
    [Str]                         an array of scalars
    [[Str]]                       an array of arrays of scalars
    ['PortBinding']               an array of typed objects
    'HostConfig'                  a single typed object
    { Str, Str }                  a hash whose KEYS ARE CALLER DATA
    { Str, ['PortBinding'] }      same, values are typed

Docker's `definitions:` are flat — there are no groups and no prefix map. A
quoted short name is expanded under `API::Docker::Type::`, and an inline
object nests under its owner (`Mount::BindOptions`). `+Full::Class::Name`
escapes the expansion.

The swagger's `allOf` is not a type but inheritance: `docker_extends 'Resources'`
at the top of the class, which merges the parent's registry entries first so
serialisation keeps the swagger's own field order.

## Keys that are caller data — never translate these

The hash form `{ Str, ... }` marks a field whose keys the user chose. The DSL

Changes  view on Meta::CPAN

    mis-shaped filter is normalised to the engine's JSON map-of-arrays
    instead of silently matching nothing.
  - JSON request bodies send booleans as real `true`/`false`; a caller may
    pass `1`/`0` or a JSON boolean interchangeably.
  - `_uri_encode` UTF-8-encodes a decoded character string before
    percent-escaping, so a name or tag typed as characters (`ü`, `中`) goes
    out as valid UTF-8.
  - A request path outside the RFC 3986 origin-form character set is refused
    before it reaches the daemon, closing a request-line injection through a
    container name or image reference.
  - An ArrayRef query parameter expands into one repeated `k=v` pair per
    element (`names=a&names=b`), which some endpoints require.
  - A bare JSON scalar body (`null`, `true`, a number, a quoted string) is
    decoded rather than handed back as raw bytes; `raw` and `ndjson` return
    `''` and `[]` for a zero-byte body instead of `undef`.
  - Registry credentials reach `images->pull` (`auth`, sent as
    `X-Registry-Auth`) and `images->build` (`registry_config`, sent as
    `X-Registry-Config`), sent only when given. An already-base64 auth value
    in the standard alphabet is respelled URL-safe, which the engine
    requires.
  - `images->pull` no longer appends a default `tag` onto a reference that

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

  $CLASS_SUGAR{$target}{role_composed} = 1;
  Moo::Role->apply_roles_to_package($target, 'API::Docker::Role::Type');
  return;
}

# The one place a short class name becomes a full one. 'Mount' is
# API::Docker::Type::Mount; a name that already starts with the prefix is
# left alone; a leading + means "this is the full name, take it as it is".
# Docker's definitions are flat -- there are no groups to map, which is why
# there is no prefix table here and only this one rule.
sub _expand_class {
  my ($short) = @_;
  return substr($short, 1) if $short =~ /\A\+/;
  return $short if $short =~ /\AAPI::Docker::Type::/;
  return 'API::Docker::Type::' . $short;
}

# PortBindings <- port_bindings. One direction only; see the POD above.
sub _wire_from_perl {
  my ($name) = @_;
  return join '', map { ucfirst } split /_/, $name;

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

  }
  if (ref $spec eq 'HASH') {
    my @keys = keys %$spec;
    croak __PACKAGE__ . ": $where is a hash type; write it as { Str, \$value_type }"
      unless @keys == 1 && $keys[0] eq 'Str';
    return { kind => 'hash', inner => _parse_type($spec->{Str}, $where) };
  }
  if (!ref $spec) {
    return { kind => 'any' } if $spec eq 'Any';
    return { kind => 'scalar', scalar => $spec } if $SCALAR_TYPE{$spec};
    return { kind => 'object', class => _expand_class($spec) };
  }
  croak __PACKAGE__ . ": $where has an unreadable type spec (" . ref($spec) . ')';
}


sub describe_type {
  my ($d) = @_;
  my $kind = $d->{kind};
  return lc $d->{scalar} if $kind eq 'scalar';
  return 'object<' . $d->{class} . '>' if $kind eq 'object';

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

    ($info->{coerce} ? (coerce => $info->{coerce}) : ()),
  );
  return;
}

sub _docker_extends {
  my ($class, $target, @parents) = @_;
  _ensure_role($target);
  croak __PACKAGE__ . ": docker_extends in $target needs at least one class"
    unless @parents;
  my @full = map { use_module(_expand_class($_)) } @parents;
  my $extends = $CLASS_SUGAR{$target}{extends} // $target->can('extends');
  $extends->(@full);
  API::Docker::Role::Type::_invalidate_docker_cache($target);
  return;
}

1;

__END__

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

    [[Str]]                   an array of arrays of scalars
    ['PortBinding']           an array of typed objects
    'PortBinding'             a single typed object
    '+Some::Other::Class'     the same, without the namespace prefix
    { Str, Str }              a hash whose KEYS ARE CALLER DATA
    { Str, ['PortBinding'] }  the same, with typed values

A bare class name is short: C<'PortBinding'> is
C<API::Docker::Type::PortBinding>, C<'Mount::BindOptions'> is
C<API::Docker::Type::Mount::BindOptions>. The expansion happens in
C<_expand_class> and nowhere else; a leading C<+> escapes it.

=head2 describe_type

    API::Docker::Type::describe_type($info->{type});   # 'hash<array<object>>'

A descriptor as one string, for the drift checker's report. Objects render
as C<< object<Class> >>.

=head1 SUPPORT

t/transport_shape.t  view on Meta::CPAN


subtest 'raw is still bytes, whatever they spell' => sub {
  is fake_client('null')->get('/x', raw => 1), 'null',
    'raw => 1 hands back the four characters, decoding nothing';
};

# ===========================================================================
# karr k33 -- an ArrayRef param is the same parameter given more than once
# ===========================================================================

subtest 'an ArrayRef param expands to repeated pairs' => sub {
  my $c = fake_client('');
  $c->get('/images/get', params => { names => ['alpine:3', 'registry:2'] });

  is $c->request_line,
    'GET /v1.41/images/get?names=alpine:3&names=registry:2 HTTP/1.1',
    'one names= pair per element, and the reference colon is left raw';
};

subtest 'element order is the caller\'s, key order is sorted' => sub {
  my $c = fake_client('');



( run in 1.319 second using v1.01-cache-2.11-cpan-302cb4679cc )