API-Docker

 view release on metacpan or  search on metacpan

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


  # One ArrayRef or a plain list, and nothing after either: this method reads
  # no options at all. The ArrayRef form used to be where the transport bounds
  # went, because a trailing `read_timeout => 2` in the plain list would be two
  # more settings as far as this method can tell -- they now go on the resource
  # class instead (karr k74), and what is left is a form, not a split.
  if (ref $settings[0] eq 'ARRAY') {
    my $list = shift @settings;
    croak __PACKAGE__ . '->configure takes nothing after the ArrayRef of '
      . 'settings; a transport bound goes on the resource class, as '
      . '$docker->plugins->using(read_timeout => 5)->configure(...)'
      if @settings;
    @settings = @$list;
  }

  croak __PACKAGE__ . '->configure requires at least one setting, as an '
    . 'ArrayRef or a list of "KEY=value" strings' unless @settings;

  croak __PACKAGE__ . '->configure settings must be plain strings'
    if grep { ref $_ } @settings;

  return $self->client->post("/plugins/$name/set", \@settings,
    %{ $self->_request_options },
  );
}



1;

__END__

=pod

=encoding UTF-8

=head1 NAME

API::Docker::API::Plugins - Docker Engine Plugins API

=head1 VERSION

version 0.004

=head1 SYNOPSIS

    my $docker = API::Docker->new;

    # List installed plugins
    my $plugins = $docker->plugins->list;

    # Install: look at what the plugin demands, then grant exactly that
    my $privileges = $docker->plugins->privileges('vieux/sshfs:latest');
    $docker->plugins->install('vieux/sshfs:latest',
        privileges => $privileges,
    );
    $docker->plugins->enable('vieux/sshfs:latest');

    # Inspect
    my $plugin = $docker->plugins->inspect('vieux/sshfs:latest');
    say $plugin->name, $plugin->enabled ? ' (enabled)' : ' (disabled)';

    # Configure, upgrade, disable, remove
    $docker->plugins->configure('vieux/sshfs:latest', ['DEBUG=1']);
    $docker->plugins->upgrade('vieux/sshfs:latest', privileges => $privileges);
    $docker->plugins->disable('vieux/sshfs:latest');
    $docker->plugins->remove('vieux/sshfs:latest');

=head1 DESCRIPTION

This module provides access to the Docker managed-plugin endpoints
(C</plugins>).

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

=head2 Installing is two calls, and the engine enforces it

C<< POST /plugins/pull >> takes the list of privileges the plugin demands
B<in its request body>, and the daemon compares that list against the one it
computes from the plugin's own config. They must match exactly -- same
length, same names, same values -- or the install fails with
C<incorrect privileges>. A plugin runs with the host access it asked for, so
the round trip exists to make somebody look at that access before granting
it.

L</privileges> is the first call, L</install> the second:

    my $privileges = $docker->plugins->privileges('vieux/sshfs:latest');
    # inspect $privileges here -- it is an ArrayRef of
    #   { Name => 'network', Description => '...', Value => ['host'] }
    $docker->plugins->install('vieux/sshfs:latest', privileges => $privileges);

C<install> B<requires> C<privileges> and croaks without it, which is stricter
than the engine: the daemon's own body parser treats a missing body as an
empty privilege list rather than an error, so a blind install of a plugin
that happens to demand nothing would quietly succeed and one that demands
C<network: host> would fail with an error naming neither. Passing
C<< accept_privileges => 1 >> makes C<install> perform the first call itself
and hand the answer straight back -- a blanket grant, spelled out at the call
site so it is greppable.

The same applies to L</upgrade>, which takes the same body.

=head2 Not available on Podman

Measured against the rootless Podman socket (5.4.2, API 1.41): B<none> of the
C</plugins> endpoints exist there. C<< GET /v1.41/plugins >> answers
C<404 Not Found> with
C<< {"cause":"","message":"Path /v1.41/plugins is not supported","response":0} >>
(the C<1.41> there is this client's negotiated API version, echoed back from
the request path -- it moves with negotiation, not a fixed string in the
daemon's error text),
and every other path in this family -- C</plugins/privileges>,
C</plugins/pull>, C</plugins/{name}/json>, C</plugins/{name}/enable> and the
rest -- answers a bare C<404 Not Found> as C<text/plain>, meaning the compat
layer has no route registered for them at all. Managed plugins are a Docker
feature; Podman's own plugin model is not served here. Everything in this
class therefore needs a real Docker daemon.

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

call. A blanket grant: use it where the call site is allowed to trust the
plugin, and know that it reads as consent to whatever the plugin demands

=item * C<name> - Local name for the installed plugin, if it should differ
from C<remote>. A digest is not allowed here

=item * C<auth> - Registry credentials, as for L</privileges>

=item * C<on_event> - CodeRef called with each progress event as it arrives,
instead of the ArrayRef being collected and returned; see below

=back

Returns an ArrayRef of progress events, one per object in the engine's
newline-delimited JSON stream, C<[]> when the engine sent no progress
at all.

=head2 Progress as it arrives

Without a callback the whole stream is read before anything is parsed, so
pulling a plugin is silence until it is done. Pass C<on_event> and the events
are handed over as the daemon sends them:

    my $summary = $plugins->install('vieux/sshfs:latest',
        privileges => $privileges,
        on_event   => sub {
            my ($event, $stop) = @_;
            print $event->{status}, "\n" if defined $event->{status};
        },
    );

    $summary;   # { delivered => 18, stopped => 0 }

With a callback the return value is that summary HashRef, not the events:
C<delivered> is how many went to the callback, C<stopped> is 1 when the
callback ended the stream and 0 when the daemon did. Nothing is accumulated.
See L<API::Docker::Role::HTTP/"Streaming a response as it arrives">.

The C<errorDetail> check runs on this path too, per event rather than over the
finished list, so a failure inside the 200 stream still croaks with an
L<API::Docker::Error::Stream> -- at the event that reports it, and carrying
that one event alone rather than the whole stream. It is the difference
L<API::Docker::API::Images/"A failed build still croaks, one event earlier">
describes, and it applies here identically. A caller that wants the progress
that preceded a failure must collect it in the callback.

L</upgrade> and L</push> take C<on_event> on the same terms.

A failed install croaks by one of two routes, exactly as
L<API::Docker::API::Images/pull> does, because the daemon commits to HTTP 200
the moment it flushes the first progress object. A failure before that point
arrives as a real error status -- C<incorrect privileges> is reported this
way, since it is decided before anything is pulled -- and one after it
arrives as an C<errorDetail> object inside the 200 stream, which croaks with
an L<API::Docker::Error::Stream>. C<eval> and inspect C<$@> as a string
rather than testing for the exception class.

=head2 inspect

    my $plugin = $plugins->inspect('vieux/sshfs:latest');
    say $plugin->enabled;
    say join ', ', @{ $plugin->settings->env };

Get detailed information about an installed plugin. Returns an
L<API::Docker::Type::Plugin> -- the same class L</list> returns; see
L</"What this class returns">.

The name may carry a registry host, a repository path and a tag
(C<docker.io/vieux/sshfs:latest>) and is interpolated into the request path
as given: the daemon routes this endpoint as C<< /plugins/{name:.*}/json >>,
so the slashes and the colon must survive unescaped, and they do.

=head2 remove

    $plugins->remove('vieux/sshfs:latest');
    $plugins->remove('vieux/sshfs:latest', force => 1);

Remove an installed plugin. A plugin that is still enabled is refused unless
C<force> is set.

Options:

=over

=item * C<force> - Disable the plugin before removing it. Removing a plugin
that containers are still using will break them

=back

Returns C<undef>. The Engine API reference documents a C<Plugin> object as
the 200 response body here; the daemon writes no body at all.

=head2 enable

    $plugins->enable('vieux/sshfs:latest');
    $plugins->enable('vieux/sshfs:latest', timeout => 30);

Enable an installed plugin. Returns C<undef>.

Options:

=over

=item * C<timeout> - Seconds to wait for the plugin to come up, C<0> for no
timeout (the default)

=back

C<timeout> is B<always> sent, whether or not the caller passes it. The Engine
API reference gives it a default of C<0>, but the daemon has none: it reads
the raw query value and parses it with Go's C<strconv.Atoi>, so an absent
parameter is parsed as the empty string and the request fails with
C<strconv.Atoi: parsing "": invalid syntax> as an invalid-parameter error.
This is the one endpoint in the family where omitting an optional parameter
is fatal.

=head2 disable

    $plugins->disable('vieux/sshfs:latest');
    $plugins->disable('vieux/sshfs:latest', force => 1);

Disable an enabled plugin. Returns C<undef>.



( run in 0.702 second using v1.01-cache-2.11-cpan-a49fcb8fa48 )