API-Docker

 view release on metacpan or  search on metacpan

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

    # List configs
    my $configs = $docker->configs->list;

    # Create a config -- Data is RAW BYTES, this class base64-encodes it
    my $created = $docker->configs->create(
        Name   => 'my-config',
        Data   => "listen 8080;\n",
        Labels => { app => 'web' },
    );

    # Inspect a config -- an API::Docker::Type::Config; spec->data stays base64
    my $config = $docker->configs->inspect($created->{ID});
    my $text   = $config->decoded_data;

    # Update: the version comes from the inspect above, and is mandatory
    my %spec = %{ $config->spec->TO_JSON };
    delete $spec{Data};                       # already base64 -- see below
    $spec{Labels} = { app => 'web', tier => 'edge' };
    $config->update(%spec);

    # Remove
    $docker->configs->remove($created->{ID});

=head1 DESCRIPTION

This module provides methods for managing Docker configs (C</configs>):
listing, creation, inspection, update and removal.

Accessed via C<< $docker->configs >>, or through
L<API::Docker::Role::Using/using> for a run of calls that needs its own
transport bound: C<< $docker->configs->using(read_timeout => 5) >>.

L</list> and L</inspect> return L<API::Docker::Type::Config> objects carrying
the convenience methods of L<API::Docker::Role::Entity::Config>. It is B<one>
class for both, where containers and images have two: the swagger answers
C<GET /configs> with an array of the C<Config> definition and
C<GET /configs/{id}> with that same definition. Field names are the
swagger's own spelling in snake_case -- C<ID> is C<< ->id >>, C<CreatedAt> is
C<< ->created_at >> -- and the nested ones are generated classes rather than
the raw HashRefs the old entity kept: C<< $config->spec >> is an
L<API::Docker::Type::ConfigSpec> and C<< $config->version >> an
L<API::Docker::Type::ObjectVersion>, whose C<< ->index >> is what
C<version_index> reaches.

Configs are L<API::Docker::API::Secrets> without the secrecy: the same five
endpoints, the same spec shape, the same mandatory C<version> on update. The
one behavioural difference is that a config's value can be read back --
L</inspect> returns it in C<< $config->spec->data >>, and
L<API::Docker::Role::Entity::Config/decoded_data> decodes it, where a secret
returns no payload at all. Which is the whole point of the split: put configuration in
a config, and anything you would mind seeing in a C<docker config inspect> in
a secret.

=head2 Data is raw bytes on the way out, base64 on the way back

The wire field C<Data> carries base64. B<L</create> and L</update> encode it
for you> -- pass them raw bytes, and do not pre-encode, or the daemon stores
your base64 text as the config's content.

Doing it here is not a convenience, it is a guard. The daemon does not
validate what it decodes: measured against Podman 5.4.2's C</secrets>, which
takes the identical field, a C<Data> of the plain text C<"hello there!"> was
accepted with B<HTTP 200> and stored three bytes of garbage -- Go's decoder
took the leading C<"hell">, stopped at the space, and said nothing. A caller
left to encode their own payload can corrupt the value and be told it worked.

The alphabet is B<standard> base64 with padding (C<+> and C</>), unwrapped,
and not the URL-safe one. The Engine API reference calls the field
"base64-url-safe-encoded" and that is measurably not what the engine takes:
four bytes sent as C<-v_--w==> were rejected B<500>, the same four as
C<+v/++w==> stored correctly.

C<Data> must be a byte string. Characters above C<U+00FF> croak here rather
than reaching L<MIME::Base64> and dying with a bare
C<Wide character in subroutine entry>; encode first, e.g. with
C<Encode::encode_utf8>.

B<The reverse trip is not symmetric, deliberately.> L</inspect> and L</list>
hand back what the daemon sent with nothing rewritten, so
C<< $config->spec->data >> is still base64. Decoding is a separate,
explicit call on the entity:

    my $text = $config->decoded_data;

The asymmetry follows one rule: this class encodes where getting it wrong is
silent, and rewrites nothing where getting it wrong is visible. An unencoded
C<Data> going out is stored as garbage with a 200; a base64 string coming back
is obvious the moment you look at it. So the decode is offered where it costs
nothing -- L<API::Docker::Role::Entity::Config/decoded_data> derives the bytes
on demand and leaves the spec verbatim -- rather than by replacing a field of a daemon
response, which nothing in this distribution does.

To send an already-encoded value verbatim, bypass this class:

    $docker->post('/configs/create', { Name => 'my-config', Data => $b64 });

=head2 update takes the current version, and it is mandatory

C<POST /configs/{id}/update> carries a C<version> query parameter and the
daemon rejects the request without it. The value is the C<Version.Index> of
the config as it stands right now, which is what L</inspect> returns:

    my $config = $docker->configs->inspect($id);
    $docker->configs->update($id, $config->version_index, %spec);

It is an optimistic-concurrency token, not a serial number to invent. If
anything else changed the config since that C<inspect>, the index has moved on
and the daemon refuses the write rather than silently overwriting that change.
Read it immediately before the update, and again before a retry.

This class makes it the second positional argument and croaks when it is
missing or not numeric, so the mistake surfaces here instead of one round trip
later. L<API::Docker::Role::Entity::Config/update> supplies it from the
entity's own C<< ->version->index >> instead, which is the same value read at
the same moment.

The Engine API reference states that only C<Labels> may actually change: the
rest of the spec must go back unchanged from what C<inspect> returned. Hence
C<< %spec = %{ $config->spec->TO_JSON } >> in the SYNOPSIS -- C<TO_JSON>
renders the spec object back into the daemon's own spelling, and the whole
spec goes back with the one key edited. Note that a spec from C<inspect>



( run in 1.069 second using v1.01-cache-2.11-cpan-a5162978ef8 )