Dist-Zilla-Plugin-Docker-API
view release on metacpan or search on metacpan
.claude/agents/dzil-docker-worker.md view on Meta::CPAN
---
You are the dzil-docker-worker for **Dist::Zilla::Plugin::Docker::API**.
Implement, refactor, debug and test this distribution. The conventions above are
non-negotiable â apply silently, do not restate.
**Where your lane ends.** You own the plugin: how phases hook into Dist::Zilla, how
dist.ini config becomes attributes, how tags are templated, and how `Client.pm` adapts
`API::Docker`. You do not own the Engine API. If the task turns on what the daemon
accepts or answers â an endpoint's parameters, an event stream's shape, registry auth,
version gating â that is `../p5-api-docker`'s, and the answer is a ticket on its board,
not a workaround here. Guessing at daemon behavior from this adapter is how a wrong
assumption gets cemented; `_split_image_ref` exists because exactly that went wrong once.
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
**Two consumers, both in this workspace.** `@Author::GETTY::Docker` in
.claude/skills/dzil-docker-core/SKILL.md view on Meta::CPAN
## The client seam
`client_class` (default `â¦::API::Client`) is loaded with a runtime
`eval "require"` and constructed with two coderefs, `logger` and
`logger_fatal`, that funnel into the plugin's `log`/`log_fatal`. **This seam is
the entire test strategy** â everything below it is fake-able and nothing in
`API.pm` may reach `API::Docker` directly.
Inside `Client.pm`:
- **`_split_image_ref` exists because the engine's tag endpoint takes repo and
tag separately** (`POST /images/{name}/tag?repo=&tag=`). Passing a full
reference as `repo` leaves `tag` empty, the engine appends its own default,
and podman rejects `example/app:1.0:latest` with a 500 â every version tag
quietly lost. A colon before the last slash is a registry port, not a tag.
- **A failed build's output rides on the exception, not the event loop.**
API::Docker croaks on the `errorDetail` event before the caller iterates, so
the preceding stream is drained from `$err->events` â otherwise a failed
build logs only its last line.
- **Concise mode is the default.** `_is_build_step_header` recognises the
legacy builder (`Step N/M :`), Podman's classic builder (`STEP N/M:`) and
BuildKit (`#N [N/M]`, `#N [name N/M]`). A fourth builder format needs a
fourth pattern here, plus a case in `t/60-build-progress.t`.
- Registry auth is read from `~/.docker/config.json` (or `$DOCKER_CONFIG`),
handling `identitytoken`, base64 `auth` and plain user/password, with the
Docker Hub key aliases spelled out.
## Cross-repo: `../p5-api-docker` owns the wire
`API::Docker` is a Getty distribution in the same workspace and `cpanfile` pins
it (`0.003`). A question about what the daemon accepts or answers â endpoint
shape, event stream, auth header, version gating â is **that** repo's, and gets
a ticket on its board rather than a workaround here. `_split_image_ref` living
here rather than there is a deliberate choice: `API::Docker` mirrors the
endpoint and should keep doing so.
The consumer in the other direction is `@Author::GETTY::Docker` in
`../p5-dist-zilla-pluginbundle-author-getty`, which constructs this plugin
programmatically. Any attribute rename or `init_arg` change is a cross-repo
change â check that bundle still builds.
## Tests â the Recorder fake, never a daemon
`prove -lr t/` must pass on a machine with no engine at all, and does.
both checks.
- A failed release push is fatal. API::Docker 0.004 croaks on a push
failure rather than returning an errorDetail event, and the client
catches it and surfaces it through the same log_fatal, carrying the
engine's errorDetail message when the error is a stream error and
the exception itself otherwise.
- A failed build logs the complete build output, not just the failing
step: the client drains the exception's event list through the same
progress filter used for a live stream before re-raising.
- Every tag after the first now applies. The image reference is split
into repository and tag before reaching the engine's tag endpoint,
so a registry host:port is no longer mistaken for the tag separator
and %V/%v no longer vanish -- only "latest" survived the default tag
list before. A tag the engine refuses is left out of the result
instead of being reported as applied.
- New DZIL_DOCKER_API_SKIP=1 skips the image build for one run -- no
engine contact, no image -- for dzil build/install/test while the
image cannot build yet. dzil release refuses to run with it set.
- Document the container engine requirement in POD and README: any
engine serving the Docker Engine HTTP API works, no docker binary
needed, and target reaches it unchanged for multi-stage builds.
dist_zilla_docker_api_plugin_plan.md view on Meta::CPAN
auth => $auth,
);
$client->inspect_image($image_ref);
$client->image_exists_locally($image_ref);
$client->remote_tag_exists($image_ref); # optional registry helper
```
### `...::Progress`
Docker build/push endpoints return streaming progress records. The plugin should parse these and forward:
```text
stream/status/progress -> log_debug or log
errorDetail/error -> log_fatal
aux.ID -> captured image ID
```
Important: do not treat HTTP 200 alone as success. Docker build/push can return a successful HTTP response while the stream contains an error event. The adapter must parse the stream completely.
### `...::Registry`
lib/Dist/Zilla/Plugin/Docker/API/Client.pm view on Meta::CPAN
my ($engine) = grep { ($_->{Name} // '') =~ /engine/i }
@{ $version->{Components} // [] };
return {
version => $version->{Version},
api_version => $version->{ApiVersion},
engine => $engine ? $engine->{Name} : undef,
};
}
# The engine's tag endpoint takes the repository and the tag as two separate
# parameters (POST /images/{name}/tag?repo=&tag=), so a full image reference
# has to be taken apart before it goes over the wire. Passing the whole thing
# as `repo` leaves `tag` empty and the engine fills in its own default:
# podman then rejects `example/app:1.0:latest` with a 500 and every version
# tag is quietly lost. Splitting happens here rather than in API::Docker,
# which mirrors the endpoint and should keep doing so.
sub _split_image_ref {
my ( $self, $ref ) = @_;
my $colon = rindex $ref, ':';
return ( $ref, undef ) if $colon < 0;
# A colon before the last slash belongs to a registry host:port, not to a
# tag: registry.example.com:5000/team/app carries no tag at all.
return ( $ref, undef ) if rindex($ref, '/') > $colon;
return ( substr($ref, 0, $colon), substr($ref, $colon + 1) );
t/45-image-ref-tagging.t view on Meta::CPAN
use strict;
use warnings;
use Test::More;
use Dist::Zilla::Plugin::Docker::API::Client;
# The engine's tag endpoint takes the repository and the tag as two separate
# parameters (POST /images/{name}/tag?repo=&tag=). Handing it a full image
# reference as `repo` and no `tag` makes the engine append its own default:
# podman turns `example/app:1.0` into `example/app:1.0:latest` and answers 500.
# The result was that every version tag silently failed while the build log
# still claimed the image had been tagged.
{
package FakeImages;
sub new {
my ($class, %arg) = @_;
( run in 1.332 second using v1.01-cache-2.11-cpan-cb04b646d8c )