Dist-Zilla-Plugin-Docker-API

 view release on metacpan or  search on metacpan

.gitignore  view on Meta::CPAN

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

CLAUDE.md  view on Meta::CPAN


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 };
}



( run in 1.098 second using v1.01-cache-2.11-cpan-007c89162af )