API-Docker

 view release on metacpan or  search on metacpan

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

    '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
must pass those keys through byte for byte. Getting this wrong silently
rewrites user input.

    Labels          arbitrary label names, often dotted: com.example.Some-Label
    Annotations     same

.claude/skills/getty-perl-core/SKILL.md  view on Meta::CPAN

- **Scripts, tests, plain modules:** `use strict; use warnings;` explicitly.
- **Classes:** `Moo`, `Moose`, `Catalyst` and anything derived from them enable both on import — do not repeat them there.

The rule is "always on", never "leave them out". Omitting them from a class is correct only because the object system already did it.

## Object system

- **One object system per distribution.** Pick Moo or Moose and use it everywhere; mixing is for boundaries a framework forces (e.g. RapidApp), not a choice.
- **`is => 'ro'` is the default.** `rw` is the exception and needs a reason.
- **`lazy_build => 1` + `sub _build_foo`** over `default => sub { ... }` for anything non-trivial.
- **`weak_ref => 1`** on attributes holding a reference back to a parent/owner — standard for nested object graphs, prevents circular refs.
- **`namespace::autoclean`** on every class file. Classes extending DBIx::Class (`MooseX::NonMoose`) use **`MooseX::MarkAsMethods autoclean => 1`** instead.
- **`no Moose;` + `__PACKAGE__->meta->make_immutable;`** at the bottom of every Moose class.
- **Types:** the tendency is to type what arrives from outside — Moose's own constraints where Moose is already there, `Types::Standard` where it is not. Not every distribution needs a type system, and none needs one for every field: `getty-perl-ty...

## Singletons

- **`->instance`** for `MooseX::Singleton` / `MooX::Singleton` classes. Never `->new` on a singleton.
- **`->new`** for everything else.

## Subroutines

.claude/skills/getty-perl-moo/SKILL.md  view on Meta::CPAN


## Pattern 9 – Lifecycle Hooks

```perl
around BUILDARGS => sub {
  my ($orig, $class, @args) = @_;
  return { source => $args[0] } if @args == 1 && !ref $args[0];  # normalize
  $class->$orig(@args);
};

sub FOREIGNBUILDARGS {          # maps args to non-Moo parent's constructor
  my ($class, $args) = @_;
  return ($args->{source});
}

sub BUILD {                     # runs AFTER all attributes are set; parent→child order
  my ($self, $args) = @_;
  die "invalid" unless length $args->{source};
}
# DEMOLISH: child→parent order. Never override DESTROY directly.
```

Do NOT call `SUPER::BUILD` manually – Moo handles the chain.

---

## Pattern 10 – Strict Constructor

```perl
package StrictThing;

.claude/skills/getty-perl-moo/SKILL.md  view on Meta::CPAN

| Shared attributes/methods, stable "is-a" | `extends` |
| Optional/horizontal feature | `Moo::Role` + `with` |
| Same pattern, different config | `MooX::Role::Parameterized` |
| Delegate method set to sub-object | `handles` |
| Array/Hash operations on attribute | `Sub::HandlesVia` |
| Logging/validation/caching wrapper | `before`/`around`/`after` |
| Catch constructor typos | `MooX::StrictConstructor` |
| Cross-project boilerplate | `Import::Into` house-style module |
| Named types | `Type::Tiny` / `Types::Standard` |
| Multiple roles define same method | Sequential `with` or refactor |
| Legacy non-Moo parent | `FOREIGNBUILDARGS` |
| Multiple inheritance | Last resort; use `mro 'c3'` |

---

## Common Pitfalls

- `default => []` → **shared state bug**. Always `default => sub { [] }`.
- `extends 'A'; extends 'B'` → replaces, does NOT add B to A. Use `extends 'A', 'B'`.
- Imports after `use Moo::Role` are **composed into consumers** as methods.
- `namespace::autoclean` < 0.16 inflates Moo classes to Moose unexpectedly.

.claude/skills/kanban-issues-karr-cli/SKILL.md  view on Meta::CPAN


### Delete task

```bash
karr delete ID                               # asks first
karr delete ID --yes                         # skip confirmation
karr delete ID,ID,ID --yes                   # a batch
```

Before an id goes, `delete` names on STDERR every card on this board that
points at it -- a `depends_on` entry or a `parent` -- and every cross-board
link the card itself carries (`escalated-from:`, `needs:`), offering
`karr archive` as the way to keep the card readable instead. The delete then
proceeds: karr warns about dependencies, it does not block on them. `--json`
carries the same sentences as `dependent_warnings` and `cross_board_warnings`
in the result object.

The question itself goes to STDERR on every path, not only under `--json`:
STDOUT belongs to the result, so `karr delete ID --json` decodes as a whole
even when the answer is typed rather than passed as `--yes`. A task with a live
claim is not deleted at all -- release it or wait for `claim_timeout`.

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

    # and what the path was, without a second request
    my %stat;
    my $tar = $containers->get_archive($id, path => '/var/log', stat => \%stat);
    say $stat{name};

Read a path out of a container as a tar archive -- the outbound half of
C<docker cp>. Returns the raw archive bytes, never decoded and never modified.

A file comes back as a one-member archive named after its basename; a
directory comes back as the directory and everything under it, with paths
relative to its parent. The whole archive is buffered in memory.

Options:

=over

=item * C<path> - Path inside the container to read. Required

=item * C<stat> - HashRef the C<X-Docker-Container-Path-Stat> header is
decoded into. The engine sends it on this response as well as on the HEAD
one, so asking for it here saves the extra round trip L</stat_archive> would

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

    $containers->put_archive($id, path('payload.tar')->slurp_raw,
        path => '/opt/app');

Write a tar archive into a path inside the container -- the inbound half of
C<docker cp>. The archive is the request body; pass it as raw bytes or as a
scalar reference to them, the way L<API::Docker::API::Images/load> takes its
archive. Returns nothing: the engine answers a success with an empty body.

C<path> must name a B<directory that already exists> in the container; the
archive's members are unpacked into it. Writing a single file means putting
that file in a one-member archive and naming its parent directory as C<path> --
there is no "write these bytes to this filename" form of this endpoint.

The archive is sent as one buffered request body, so this costs its full size
in RAM.

Options:

=over

=item * C<path> - Directory inside the container to unpack into. Required

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

worth knowing before reading a value off the wrong one:

=over

=item * C<< ->created >> is an integer Unix epoch on a summary and an
RFC 3339 string on an inspect. Same field name, two types -- C<Int> and
C<Str> in the model, which is the swagger's own answer, not a normalisation
this client applies. The same split a container has, see
L<API::Docker::API::Containers/"The two container shapes">.

=item * The parent layer is C<< ->parent_id >> on a summary and
C<< ->parent >> on an inspect. Both are empty for an image pulled from a
registry rather than built locally, and the swagger marks the inspect one
deprecated.

=item * C<< ->labels >> is top-level on a summary only. An inspect carries
the labels under C<< ->config->labels >>, where C<< ->config >> is the
L<API::Docker::Type::ImageConfig> the image runs containers with --
C<< ->cmd >>, C<< ->env >>, C<< ->entrypoint >>, C<< ->exposed_ports >> and
the rest.

=item * C<< ->containers >> (how many containers use the image) and

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

    $images->remove('nginx:latest', force => 1);

Remove an image.

Options:

=over

=item * C<force> - Force removal

=item * C<noprune> - Do not delete untagged parents

=back

=head2 search

    my $results = $images->search('nginx', limit => 25);

Search Docker Hub for images. Returns ArrayRef of search results.

Options:

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

      croak __PACKAGE__ . ": $class has '$name' as an entity attribute and as "
        . 'a daemon field; one of the two has to be renamed'
        if $reg->{$name} || defined $wire->{$name};
    }
    \%mine;
  };
}

# --- merged views over @ISA ------------------------------------------------
#
# A generated class that resolves an `allOf` inherits its parent's fields
# (see API::Docker::Type), so every lookup below is the class's own registry
# entry merged with its ancestors'. Nearest declaration wins.

sub _docker_attr_registry {
  my $class = ref($_[0]) || $_[0];
  return $ATTR_CACHE{$class} //= _merge_registry($class);
}

sub _merge_registry {
  my ($class) = @_;
  my %info = %{ $API::Docker::Type::REGISTRY{$class} // {} };
  no strict 'refs';
  for my $parent (@{"${class}::ISA"}) {
    my $up = _merge_registry($parent);
    $info{$_} //= $up->{$_} for keys %$up;
  }
  return \%info;
}

sub _docker_attr_order {
  my $class = ref($_[0]) || $_[0];
  return $ORDER_CACHE{$class} //= _merge_order($class);
}

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


sub _docker_wire_index {
  my $class = ref($_[0]) || $_[0];
  return $WIRE_CACHE{$class} //= do {
    my $reg = _docker_attr_registry($class);
    +{ map { ($reg->{$_}{wire} => $_) } keys %$reg };
  };
}

# Called by API::Docker::Type after every registration: a merged view
# computed before a parent gained an attribute must not survive.
sub _invalidate_docker_cache {
  my ($class) = @_;
  my %sweep;
  @sweep{ keys %ATTR_CACHE, keys %ORDER_CACHE, keys %WIRE_CACHE,
          keys %ENTITY_CACHE } = ();
  for my $cached (keys %sweep) {
    next unless $cached eq $class || $cached->isa($class);
    delete $ATTR_CACHE{$cached};
    delete $ORDER_CACHE{$cached};
    delete $WIRE_CACHE{$cached};

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

  my $info = $REGISTRY{$target}{$name};
  $has->($name,
    is => 'rw',
    ($info->{isa}    ? (isa    => $info->{isa})    : ()),
    ($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


The C<$ref> becomes a superclass and only the inline schema's properties are
declared in the child:

    package API::Docker::Type::HostConfig;
    use API::Docker::Type;

    docker_extends 'Resources';

C<allOf> in swagger means composition, and Perl inheritance says exactly
that. Nothing is duplicated: the parent's fields, their POD and the inline
classes declared inside the parent all stay in one place, and the merged
registry in L<API::Docker::Role::Type> presents C<HostConfig> with all ~70
fields. The alternative -- copying the parent's declarations into the child
-- would duplicate 31 attributes and their C<=attr> blocks and force the
inline classes underneath them to be named twice.

An C<allOf> holding a single C<$ref> and nothing else is not composition at
all; it is swagger's way of hanging a description on a C<$ref>
(C<Mount.Type> and C<MountPoint.Type> both do it). Such a field takes the
type of what it references, which for C<MountType> is C<Str>.

=head2 Inline objects become classes

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

package API::Docker::Type::BuildCache;
# ABSTRACT: Information about a build cache record
our $VERSION = '0.004';
use API::Docker::Type;
use namespace::clean;


docker id => Str, wire => 'ID';


docker parents => [Str], since => '1.44';


docker type => Str,
  enum => [qw(
    internal frontend source.local source.git.checkout exec.cachemount regular
  )];


docker description => Str;

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


=head1 DESCRIPTION

Generated from the C<BuildCache> definition of C<spec/v1.51.yaml>.

=head2 id

Unique ID of the build cache record. Serialised as C<ID> -- spelled out,
because deriving it from the Perl name would produce C<Id>.

=head2 parents

List of parent build cache record IDs.

=head2 type

Cache record type. The swagger enumerates C<internal>, C<frontend>,
C<source.local>, C<source.git.checkout>, C<exec.cachemount> and C<regular>.

=head2 description

Description of the build-step that produced the build cache.

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

Generated from the C<HealthConfig> definition of C<spec/v1.51.yaml>.

Healthcheck commands should be side-effect free.

=head2 test

The test to perform. Possible values are:

=over 4

=item * C<[]> inherit healthcheck from image or parent image

=item * C<["NONE"]> disable healthcheck

=item * C<["CMD", args...]> exec arguments directly

=item * C<["CMD-SHELL", command]> run command with system's default shell

=back

A non-zero exit code indicates a failed healthcheck:

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


docker manifests => [ 'ImageManifestSummary' ], since => '1.51';


docker repo_tags => [Str];


docker repo_digests => [Str];


docker parent => Str;


docker comment => Str;


docker created => Str;


docker docker_version => Str;

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

=head2 repo_digests

List of content-addressable digests of locally available image manifests
that the image is referenced from. Multiple manifests can refer to the same
image.

These digests are usually only available if the image was either pulled from
a registry, or if the image was pushed to a registry, which is when the
manifest is generated and its digest calculated.

=head2 parent

ID of the parent image.

Depending on how the image was created, this field may be empty and is only
set for images that were built/created locally. This field is empty if the
image was pulled from an image registry.

> B<Deprecated>: This field is only set when using the deprecated > legacy
builder. It is included in API responses for informational > purposes, but
should not be depended on as it will be omitted > once the legacy builder is
removed.

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

our $VERSION = '0.004';
use API::Docker::Type;
use API::Docker::Type::ImageManifestSummary;
use API::Docker::Type::OCIDescriptor;
use namespace::clean;


docker id => Str, required => 1;


docker parent_id => Str, required => 1;


docker repo_tags => [Str], required => 1;


docker repo_digests => [Str], required => 1;


docker created => Int, required => 1;

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

ID is the content-addressable ID of an image.

This identifier is a content-addressable digest calculated from the image's
configuration (which includes the digests of layers used by the image).

Note that this digest differs from the C<RepoDigests> below, which holds
digests of image manifests that reference the image. The swagger lists this
field as required; nothing here enforces that, see
L<API::Docker::Type/C<since> is documentation>.

=head2 parent_id

ID of the parent image.

Depending on how the image was created, this field may be empty and is only
set for images that were built/created locally. This field is empty if the
image was pulled from an image registry. The swagger lists this field as
required; nothing here enforces that, see L<API::Docker::Type/C<since> is
documentation>.

=head2 repo_tags

List of image names/tags in the local image cache that reference this image.

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

use API::Docker::Type::ThrottleDevice;
use namespace::clean;


docker cpu_shares => Int;


docker memory => Int;


docker cgroup_parent => Str;


docker blkio_weight => Int;


docker blkio_weight_device => [ 'Resources::BlkioWeightDevice' ];


docker blkio_device_read_bps => [ 'ThrottleDevice' ];

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


=head2 cpu_shares

An integer value representing this container's relative CPU weight versus
other containers.

=head2 memory

Memory limit in bytes. The daemon defaults it to 0.

=head2 cgroup_parent

Path to C<cgroups> under which the container's C<cgroup> is created. If the
path is not absolute, the path is considered to be relative to the
C<cgroups> path of the init process. Cgroups are created if they do not
already exist.

=head2 blkio_weight

Block IO weight (relative weight).

t/dist_source.t  view on Meta::CPAN

# %INC, though: `require`/Module::Runtime still leave the key set, just with
# an undef value, as a guard against re-attempting a load that is known to
# fail -- and Cwd::abs_path(undef) resolves to the current directory rather
# than returning undef, which reads as a false "loaded from the checkout
# root" pass if that key is not screened out first. So a defined %INC value
# is what "actually loaded" means below; an undef one is treated as no entry
# at all, on purpose, and never checked. The question here is not "does
# everything compile" -- that is each module's own t/*.t -- only "whatever
# DID load, did it come from here".

my $dist_root = path(__FILE__)->parent->parent->realpath;
my $lib_dir   = $dist_root->child('lib');

my @acceptable_roots = ($lib_dir->stringify);

# `dzil test` builds and runs from a fresh temporary directory under
# <checkout>/.build/<random> -- created new for every run, so it can never
# hold a leftover -- and its TestRunner loads modules from that tree's
# blib/lib, not its lib/. `dzil release`'s [@Filter/TestRelease] instead
# builds the tarball and EXTRACTS it, so this test then runs one level
# deeper still: <checkout>/.build/<random>/<Dist-Version>/, with the
# extracted distribution's own directory as an extra ancestor between
# $dist_root and .build. Checking only the immediate parent (as this used
# to) catches the first shape and misses the second, so every release-time
# run of this test failed even though blib/lib held exactly the right
# modules. Recognise both shapes by walking $dist_root's ancestors for one
# named .build, and only that signal, as what makes blib/lib a second
# acceptable root -- so a blib/ sitting directly in a real, persistent
# checkout (the actual "leftover blib" failure mode, which has no .build
# ancestor at all) still fails this test rather than being waved through.
my $in_build_tree = 0;
my $ancestor       = $dist_root;
while ($ancestor->parent->stringify ne $ancestor->stringify) {
  $ancestor = $ancestor->parent;
  if ($ancestor->basename eq '.build') {
    $in_build_tree = 1;
    last;
  }
}
push @acceptable_roots, $dist_root->child('blib', 'lib')->stringify
  if $in_build_tree;

my %rel_paths;   # e.g. "API/Docker/Image.pm" => 1

t/lib/Test/API/Docker/Mock.pm  view on Meta::CPAN

  load_fixture_raw
  mock_response
  is_live
  can_write
  skip_unless_write
  check_live_access
  register_cleanup
  live_engine
);

my $FIXTURES_DIR = path(__FILE__)->parent->parent->parent->parent->parent->child('fixtures');

my @_cleanups;

sub load_fixture {
  my ($name) = @_;
  my $file = $FIXTURES_DIR->child("$name.json");
  croak "Fixture not found: $file" unless $file->exists;
  return decode_json($file->slurp_utf8);
}

t/tls_read.t  view on Meta::CPAN

  subject         => { CN => 'localhost' },
  purpose         => 'server',
  subjectAltNames => [ [ DNS => 'localhost' ], [ IP => '127.0.0.1' ] ],
);
IO::Socket::SSL::Utils::PEM_cert2file($ca, $dir->child('ca.pem') . '');
IO::Socket::SSL::Utils::PEM_cert2file($server_cert, $dir->child('server.pem') . '');
IO::Socket::SSL::Utils::PEM_key2file($server_key, $dir->child('server-key.pem') . '');

# ---------------------------------------------------------------------------
# A TLS server that speaks whatever $respond->($conn) tells it to, once per
# connection. Forked so the parent's read is a real client of a real socket
# rather than a mock -- the whole point of this file.
# ---------------------------------------------------------------------------

my @CHILDREN;

sub start_server {
  my ($respond) = @_;

  my $listen = IO::Socket::INET->new(
    LocalAddr => '127.0.0.1', LocalPort => 0, Listen => 1, ReuseAddr => 1,

t/truncated_response.t  view on Meta::CPAN

    'stringifies to the reason, not to a reference address';
  like "$err", qr{\QGET /v1.41/x\E}, 'the request is named in the string';
  like "$err", qr/ at \S+ line \d+/, "with Carp's own location suffix";
  ok !!$err, 'and it is true as an exception';
  is $err->as_string, "$err", 'as_string is what the overload returns';
};

subtest 'a reader driven with no context still names the phase' => sub {
  # t/role_http.t drives the readers directly with no request behind them, so
  # there is no endpoint to name. The message leaves it out rather than
  # interpolating an empty pair of parentheses.
  my ($err) = over_pair(
    "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nhello wor",
    sub { $client->_read_response($_[0], 'GET', {}) });

  isa_ok $err, 'API::Docker::Error::Truncated';
  is $err && $err->endpoint, '', 'no endpoint';
  like $err && "$err", qr/\ADocker API response truncated: /,
    'and the message goes straight to the reason';
};

t/type.t  view on Meta::CPAN

    'HostConfig is a Resources, because the swagger says allOf [ $ref Resources, ... ]');
  my $order = API::Docker::Type::HostConfig->docker_attribute_order;
  is(scalar @$order, 70, 'HostConfig carries 31 inherited plus 39 of its own');
  is($order->[0], 'cpu_shares', 'the inherited fields come first, as the allOf lists them');
  is($order->[31], 'binds', 'and the class own fields follow in spec order');
  my $hc = API::Docker::Type::HostConfig->from_data({ CpuShares => 512, Binds => ['/a:/b'] });
  is($hc->cpu_shares, 512, 'an inherited field inflates on the child');
  is_deeply($hc->TO_JSON, { CpuShares => 512, Binds => ['/a:/b'] },
    'and serialises flat, the way it sits on the wire');
  is(scalar @{ API::Docker::Type::Resources->docker_attribute_order }, 31,
    'the parent is unaffected by the child');
};

# ---------------------------------------------------------------------------
# Both spellings, and what the DSL refuses
# ---------------------------------------------------------------------------

subtest 'constructor accepts both spellings' => sub {
  my $a = API::Docker::Type::Port->new(private_port => 80, type => 'tcp');
  my $b = API::Docker::Type::Port->from_data({ PrivatePort => 80, Type => 'tcp' });
  is($json->encode($a->TO_JSON), $json->encode($b->TO_JSON),



( run in 3.107 seconds using v1.01-cache-2.11-cpan-80ec619307d )