Cavil-CLI
view release on metacpan or search on metacpan
lib/Cavil/CLI/Client.pm view on Meta::CPAN
# SPDX-FileCopyrightText: SUSE LLC
# SPDX-License-Identifier: GPL-2.0-or-later
package Cavil::CLI::Client;
use Mojo::Base -base, -signatures;
use Mojo::URL;
use Mojo::UserAgent;
has 'on_wait'; # optional coderef, called ~10x/sec while a request is in flight (drives the spinner)
has token => sub { die "A Cavil API token is required (run 'cavil-cli config', or set CAVIL_API_KEY)\n" };
has ua => sub { Mojo::UserAgent->new->connect_timeout(30)->inactivity_timeout(120) };
has url => sub { die "A Cavil URL is required (run 'cavil-cli config', or set CAVIL_URL)\n" };
# The identity the API token belongs to: a quick way to confirm the url and token are set up right.
sub whoami ($self) {
return $self->_request('GET', '/api/v1/whoami')->json;
}
# Submit a source archive for a standard legal review. Returns {saved => {...}, duplicate => bool}; the same
# archive under the same name is idempotent (duplicate true). A 403 (key may not submit), 400 (checksum
# mismatch) and 413 (archive over the server limit) are the user's to fix, so they surface as friendly messages.
sub upload ($self, $tarball, $meta) {
# ephemeral asks for a one-off report with no lasting side effects (no open review left in the legal backlog),
# which is all this client ever wants. Servers without the planned ad-hoc mode ignore it, so it is always sent.
my $form = {
name => $meta->{name},
priority => $meta->{priority},
checksum => $meta->{checksum},
ephemeral => 1,
tarball => {file => $tarball},
(defined $meta->{external_link} ? (external_link => $meta->{external_link}) : ())
};
my $res = $self->_request('POST', '/api/v1/packages/upload', {form => $form, ok_codes => [400, 403, 413]});
die "This Cavil key may not submit packages (needs a read-write key with admin access)\n" if $res->code == 403;
die "The archive is too large for this Cavil instance; trim it with a .cavilignore file or --exclude-path\n"
if $res->code == 413;
die "Upload rejected: @{[$res->json->{error} // 'bad request']}\n" if $res->code == 400;
return $res->json;
}
# Fetch a report. Returns {ready => 1, data => ...} when it exists, or {ready => 0, stage => ...} while it is
# still being built (the endpoint answers 408 with the pipeline stage until the package is analyzed).
sub report ($self, $id, $format = 'json') {
my $res = $self->_request('GET', "/api/v1/report/$id.$format", {ok_codes => [408]});
return {ready => 1, data => ($format eq 'json' ? $res->json : $res->text)} if $res->code == 200;
return {ready => 0, stage => eval { $res->json->{stage} }};
}
# Fetch a generated document (spdx or notice), or undef while it is still being generated (408). Bytes are
# returned ready to write; the user agent transparently decompresses the server's gzip.
sub document ($self, $id, $key) {
my $res = $self->_request('GET', "/api/v1/documents/$id/$key", {ok_codes => [408]});
return undef if $res->code == 408;
return $res->body;
}
sub _headers ($self) {
return {Authorization => 'Bearer ' . $self->token};
}
sub _request ($self, $method, $path, $options = {}) {
my $ua = $self->ua;
my @body = $options->{json} ? (json => $options->{json}) : $options->{form} ? (form => $options->{form}) : ();
my $tx = $ua->build_tx($method => $self->_url($path) => $self->_headers, @body);
# A recurring timer on the UA's own loop fires during the blocking request (that loop is what start() runs),
# so the caller's spinner keeps moving while we wait on the server.
my $spin = $self->on_wait;
my $tid = $spin ? $ua->ioloop->recurring(0.1 => $spin) : undef;
$tx = $ua->start($tx);
$ua->ioloop->remove($tid) if defined $tid;
return $tx->result unless my $err = $tx->error;
# No status code means the request never reached the server: always fatal. An expected status (ok_codes, e.g.
# 408 while a report builds) is handed back for the caller to act on; anything else is a friendly die with no
# Carp file/line suffix leaking to the user.
die "Connection error from Cavil ($method $path): $err->{message}\n" unless $err->{code};
return $tx->result if grep { $_ == $err->{code} } @{$options->{ok_codes} // []};
die "$err->{code} response from Cavil ($method $path): $err->{message}\n";
}
sub _url ($self, $path) {
return Mojo::URL->new($self->url . $path);
}
1;
( run in 0.705 second using v1.01-cache-2.11-cpan-a49fcb8fa48 )