view release on metacpan or search on metacpan
Build
Build.bat
pm_to_blib
# XS build artifacts
*.o
*.bs
# Testing
cover_db/
pod2htmd.tmp
# Claude Code â commit: skills/, agents/, hooks/, settings.json
# Ignore: local overrides, credentials, session data
.claude/*.local.*
.claude/local/
.claude/.credentials.json
.claude/statsig/
.claude/todos/
.claude/projects/
# karr materialized task view -- never commit
tasks/
config.yml
2. **Use `mcp__firecrawl__firecrawl_scrape`** over `WebFetch` for fetching
page content.
3. **Use `context7` for library docs** (CPAN, npm, etc.) â *except* this
distribution itself. For `Dist::Zilla::Plugin::Docker::API` always read
the local source under `lib/`, never context7.
4. **Untracked files that are not in `.gitignore` belong in the commit.**
`.gitignore` is the source of truth. Only obvious secrets
(`.env`, credentials) are excluded â and even then warn, don't silently
drop them.
5. **Auto-Memory is for personal/user preferences only.** Project
conventions belong in this `CLAUDE.md` or in a skill, never in
auto-memory.
6. **Perl edits go to a `dzil-docker-*` agent**, which gets
`getty-perl-core` and the object-system skills force-loaded via
`briefing.skills`. If you are editing Perl without that briefing, load
`getty-perl-core` first. See Delegation below.
dist_zilla_docker_api_plugin_plan.md view on Meta::CPAN
Support three layers:
### 1. Let Docker daemon use existing auth
Default for v0.001.
```ini
push = 1
```
No explicit credentials. The Docker daemon/client auth config handles registry credentials.
### 2. Dist::Zilla stash
Later:
```ini
registry_auth = %DockerHub
```
or:
lib/Dist/Zilla/Plugin/Docker/API.pm view on Meta::CPAN
# A rejected or expired registry credential otherwise surfaces only when the
# push fails -- after Dist::Zilla gathered and wrote the distribution and
# after_build built and tagged the image, i.e. after the damage. This runs
# before any of it.
#
# The phase hook that runs early enough is before_build, and it can tell a
# release from a plain build: Dist::Zilla::Dist::Builder::release sets
# DZIL_RELEASING before it calls build_archive, so the variable is already
# there when this hook runs (measured against Dist::Zilla 6.037). A plain
# dzil build never sets it and so never needs registry credentials.
#
# Only the registry host of `image` decides which credential applies, and
# that part carries no template variables, so no expansion is needed here.
sub _precheck_registry_auth {
my ($self) = @_;
my $image_ref = $self->image;
my $status = eval { $self->client->verify_auth_for_image_ref($image_ref) };
my $error = $@;
lib/Dist/Zilla/Plugin/Docker/API.pm view on Meta::CPAN
# own text, which does, is carried through verbatim.
if ($error) {
$self->log_fatal('the registry credential check for '.$image_ref
.' failed: '.$self->_flatten_error($error)
.' (set DZIL_DOCKER_API_SKIP_PRECHECK=1 to skip this check)');
}
# No credential found for that registry. An anonymous push is a legal
# thing to attempt, so this is a note, not a failure.
unless (defined $status) {
$self->log('Docker::API: no registry credentials found for '
.$image_ref.' - the release push will be anonymous');
return;
}
$self->log('Docker::API registry credentials accepted for '.$image_ref);
}
sub after_build {
my ($self, $arg) = @_;
return if $ENV{DZIL_DOCKER_API_SKIP};
$self->log("Docker::API building image");
my $build_root = $arg->{build_root};
lib/Dist/Zilla/Plugin/Docker/API.pm view on Meta::CPAN
Set C<DZIL_DOCKER_API_SKIP_PRECHECK=1> to skip the check and get the previous
behaviour back, where an unreachable engine only surfaces once the build
reaches the image. With several C<Docker::API> plugins in one F<dist.ini>,
each runs its own precheck.
When C<before_build> runs as part of C<dzil release> (Dist::Zilla sets
C<DZIL_RELEASING> before it calls C<build_archive>, which is early enough to
tell a release from a plain build) and both C<release_enabled> and
C<release_push> are true, the same hook also pre-flights the registry
credential the eventual push would use -- resolved as described in
L</"Registry credentials"> below -- and hands it to the engine's
C<POST /auth> (C<< system->auth >>) before anything is built. A plain
C<dzil build> never triggers this and needs no registry credentials at all.
No credential resolved for the registry is not a failure -- an anonymous push
is a legal thing to attempt, so nothing is checked and nothing fails. A
failed check is fatal, before the build starts, and its message says only
that the check failed, not that the credential was rejected: Podman answers
a rejected credential and an unreachable registry with the same C<500>, so
the two cannot be told apart from the status alone, and the engine's own
text is included instead.
C<DZIL_DOCKER_API_SKIP_PRECHECK=1> skips this credential check along with the
engine version probe above.
Set C<DZIL_DOCKER_API_SKIP=1> to skip the image build entirely for one run --
no engine contact, no image, one loud log line per plugin. This is for local
C<dzil build> / C<dzil install> / C<dzil test> while the image cannot build
yet, for example while a dependency pinned in the F<Dockerfile>'s C<cpanm>
run is not released. C<dzil release> refuses to run with the variable set:
a skipped build phase means there is no image to tag and push.
=head2 Registry credentials
The release push, the C<fail_if_tag_exists> lookup and the registry
credential precheck above all resolve a credential for an image reference
the same way, through C<auth_for_image_ref>: the C<auths> block of
F<config.json> in the directory named by C<DOCKER_CONFIG>, or
F<~/.docker/config.json> when that is unset. Nothing else is read --
C<REGISTRY_AUTH_FILE> and Podman's own
F<$XDG_RUNTIME_DIR/containers/auth.json> are not consulted, regardless of
which engine is at the other end of C<DOCKER_HOST>.
lib/Dist/Zilla/Plugin/Docker/API.pm view on Meta::CPAN
Podman's classic builder C<STEP N/M: ...> and BuildKit's C<#N [N/M] ...> â
instead of the full per-command output.
Set to true to see every line the daemon streams back. Errors are always
surfaced regardless of this flag.
=item C<fail_if_tag_exists> - Abort the release if any tag already exists on
the remote registry (default: false). The check runs before anything is
tagged or pushed, and only when C<release_push> is also true. It asks the
I<registry>, not the local daemon, through C<API::Docker>'s
C<< distribution->exists >> (C<GET /distribution/{name}/json>), using the
credential resolved for C<image> (see L</"Registry credentials">), or an
anonymous request when none applies.
An engine that has no C</distribution> route -- rootless Podman among them --
cannot answer the question at all, and that is treated as a release-stopping
failure rather than as "the tag is free": the release aborts with the
engine's own error and a reminder that C<fail_if_tag_exists = 0> releases
without the check.
=item C<skip_latest_on_trial> - Skip C<latest> tag for trial releases
lib/Dist/Zilla/Plugin/Docker/API/Client.pm view on Meta::CPAN
my $auth = $self->auth_for_image_ref($image_ref);
return $self->docker->distribution->exists(
$image_ref,
defined $auth ? ( auth => $auth ) : (),
) ? 1 : 0;
}
# Pre-flight for the release push: hand the engine, through POST /auth, the
# very credentials push_image would use for this reference and let the
# registry judge them. A rejected credential croaks; the caller is meant to
# make that fatal before anything is built.
#
# Returns undef when no credential could be resolved for the reference. That
# is not a failure -- an anonymous push to a public registry is a legal thing
# to do, and system->auth croaks on an empty AuthConfig, so there would be
# nothing to ask about.
sub verify_auth_for_image_ref {
my ($self, $image_ref) = @_;
t/54-release-auth-precheck.t view on Meta::CPAN
is($calls->[0]{image_ref}, 'ghcr.io/example/my-app',
'it was asked about the configured image');
is(scalar @{ $rec->calls_of('engine_info') }, 1,
'the engine precheck still ran too');
is(scalar @{ $rec->calls_of('build_image') }, 1,
'and the build still happened');
};
# A plain `dzil build` never sets DZIL_RELEASING. No release is happening,
# so no registry credentials should be needed at all.
subtest 'does not fire on a plain dzil build' => sub {
local $ENV{DZIL_DOCKER_API_SKIP_PRECHECK};
delete $ENV{DZIL_DOCKER_API_SKIP_PRECHECK};
local $ENV{DZIL_RELEASING};
delete $ENV{DZIL_RELEASING};
my $tzil = build_dist('');
$tzil->build;
my $rec = docker_plugin($tzil)->client;
is(scalar @{ $rec->calls_of('verify_auth_for_image_ref') }, 0,
'the auth precheck was never asked');
is(scalar @{ $rec->calls_of('build_image') }, 1,
'the build still happened');
};
# A release that never pushes needs no registry credentials either.
subtest 'does not fire when release_push = 0' => sub {
local $ENV{DZIL_DOCKER_API_SKIP_PRECHECK};
delete $ENV{DZIL_DOCKER_API_SKIP_PRECHECK};
local $ENV{DZIL_RELEASING} = 1;
my $tzil = build_dist("release_push = 0");
$tzil->build;
my $rec = docker_plugin($tzil)->client;
is(scalar @{ $rec->calls_of('verify_auth_for_image_ref') }, 0,
t/54-release-auth-precheck.t view on Meta::CPAN
my $ok = eval { $tzil->build; 1 };
ok($ok, 'the build did not die') or diag("build died: $@");
is(scalar @{ $rec->calls_of('verify_auth_for_image_ref') }, 1,
'the precheck was still asked');
is(scalar @{ $rec->calls_of('build_image') }, 1,
'and the build proceeded');
my $logged = join "\n", @{ $tzil->log_messages };
like($logged, qr/no registry credentials found/i,
'the anonymous case is noted in the log');
};
# DZIL_DOCKER_API_SKIP_PRECHECK=1 skips *both* prechecks. Prove it against a
# credential that would otherwise be fatal, so a no-op skip couldn't pass by
# accident.
subtest 'DZIL_DOCKER_API_SKIP_PRECHECK=1 skips the engine and the auth check' => sub {
local $ENV{DZIL_DOCKER_API_SKIP_PRECHECK} = 1;
local $ENV{DZIL_RELEASING} = 1;
t/56-client-registry-internals.t view on Meta::CPAN
'no auth key at all -- an explicit auth => undef is not the same request';
};
subtest 'remote_tag_exists passes the resolved auth when one exists' => sub {
my ($client, $distribution) = client_for(exists_result => 1);
my $result = $client->remote_tag_exists('ghcr.io/getty/foo:v1');
is $result, 1, 'truthy engine answer normalized to 1';
my $call = $distribution->{calls}[0];
ok exists $call->{auth}, 'auth key present for a registry with credentials';
is $call->{auth}{username}, 'getty', 'resolved username carried through';
is $call->{auth}{password}, 'ghpat', 'resolved password carried through';
};
subtest 'verify_auth_for_image_ref never calls system->auth without a credential' => sub {
my ($client, undef, $system) = client_for();
my $result = $client->verify_auth_for_image_ref('unknown.example.com/foo:tag');
is $result, undef, 'no credential -> undef, not a croak';
t/lib/Dist/Zilla/Plugin/Docker/API/Client/Recorder.pm view on Meta::CPAN
);
# What verify_auth_for_image_ref returns. undef (the default) means no
# credential could be resolved for the reference, which is not an error.
has verify_auth_result => (
is => 'rw',
default => sub { undef },
);
# Set to a message to make verify_auth_for_image_ref croak -- the registry
# rejecting the credentials.
has verify_auth_error => (
is => 'rw',
default => sub { undef },
);
sub _record {
my ($self, $name, %args) = @_;
push @{ $self->calls }, { method => $name, %args };
}