api for actions (#1031)
* implementation of GET and DELETE for Job Queue API * implement POST jobs submission to queue via API * implement GET /queue/backends API endpoint to get backend names
This commit is contained in:
@@ -315,7 +315,7 @@ sub jq_userlog {
|
|||||||
return schema(vars->{'tenant'})->resultset('Admin')->search({
|
return schema(vars->{'tenant'})->resultset('Admin')->search({
|
||||||
username => $user,
|
username => $user,
|
||||||
log => { '-not_like' => 'duplicate of %' },
|
log => { '-not_like' => 'duplicate of %' },
|
||||||
finished => { '>' => \"(LOCALTIMESTAMP - interval '5 seconds')" },
|
finished => { '>' => \"(CURRENT_TIMESTAMP - interval '5 seconds')" },
|
||||||
})->with_times->all;
|
})->with_times->all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ use App::Netdisco::Web::Search;
|
|||||||
use App::Netdisco::Web::Device;
|
use App::Netdisco::Web::Device;
|
||||||
use App::Netdisco::Web::Report;
|
use App::Netdisco::Web::Report;
|
||||||
use App::Netdisco::Web::API::Objects;
|
use App::Netdisco::Web::API::Objects;
|
||||||
|
use App::Netdisco::Web::API::Queue;
|
||||||
use App::Netdisco::Web::AdminTask;
|
use App::Netdisco::Web::AdminTask;
|
||||||
use App::Netdisco::Web::TypeAhead;
|
use App::Netdisco::Web::TypeAhead;
|
||||||
use App::Netdisco::Web::PortControl;
|
use App::Netdisco::Web::PortControl;
|
||||||
@@ -405,6 +406,8 @@ $swagger_doc->{tags} = [
|
|||||||
description => 'Device, Port, and associated Node Data'},
|
description => 'Device, Port, and associated Node Data'},
|
||||||
{name => 'Reports',
|
{name => 'Reports',
|
||||||
description => 'Canned and Custom Reports'},
|
description => 'Canned and Custom Reports'},
|
||||||
|
{name => 'Queue',
|
||||||
|
description => 'Operations on the Job Queue'},
|
||||||
];
|
];
|
||||||
$swagger_doc->{securityDefinitions} = {
|
$swagger_doc->{securityDefinitions} = {
|
||||||
APIKeyHeader =>
|
APIKeyHeader =>
|
||||||
|
|||||||
@@ -46,6 +46,59 @@ foreach my $rel (qw/device_ips vlans ports modules port_vlans wireless_ports ssi
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
swagger_path {
|
||||||
|
tags => ['Objects'],
|
||||||
|
path => (setting('api_base') || '').'/object/device/{ip}/jobs',
|
||||||
|
description => 'Delete jobs and clear skiplist for a device, optionally filtered by fields',
|
||||||
|
parameters => [
|
||||||
|
ip => {
|
||||||
|
description => 'Canonical IP of the Device. Use Search methods to find this.',
|
||||||
|
required => 1,
|
||||||
|
in => 'path',
|
||||||
|
},
|
||||||
|
port => {
|
||||||
|
description => 'Port field of the Job',
|
||||||
|
},
|
||||||
|
action => {
|
||||||
|
description => 'Action field of the Job',
|
||||||
|
},
|
||||||
|
status => {
|
||||||
|
description => 'Status field of the Job',
|
||||||
|
},
|
||||||
|
username => {
|
||||||
|
description => 'Username of the Job submitter',
|
||||||
|
},
|
||||||
|
userip => {
|
||||||
|
description => 'IP address of the Job submitter',
|
||||||
|
},
|
||||||
|
backend => {
|
||||||
|
description => 'Backend instance assigned the Job',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses => { default => {} },
|
||||||
|
}, del '/api/v1/object/device/:ip/jobs' => require_role api_admin => sub {
|
||||||
|
my $device = try { schema(vars->{'tenant'})->resultset('Device')
|
||||||
|
->find( params->{ip} ) } or send_error('Bad Device', 404);
|
||||||
|
|
||||||
|
my $gone = schema(vars->{'tenant'})->resultset('Admin')->search({
|
||||||
|
device => param('ip'),
|
||||||
|
( param('port') ? ( port => param('port') ) : () ),
|
||||||
|
( param('action') ? ( action => param('action') ) : () ),
|
||||||
|
( param('status') ? ( status => param('status') ) : () ),
|
||||||
|
( param('username') ? ( username => param('username') ) : () ),
|
||||||
|
( param('userip') ? ( userip => param('userip') ) : () ),
|
||||||
|
( param('backend') ? ( backend => param('backend') ) : () ),
|
||||||
|
})->delete;
|
||||||
|
|
||||||
|
schema(vars->{'tenant'})->resultset('DeviceSkip')->search({
|
||||||
|
device => param('ip'),
|
||||||
|
( param('action') ? ( actionset => { '&&' => \[ 'ARRAY[?]', param('action') ] } ) : () ),
|
||||||
|
( param('backend') ? ( backend => param('backend') ) : () ),
|
||||||
|
})->delete;
|
||||||
|
|
||||||
|
return to_json { deleted => ($gone || 0)};
|
||||||
|
};
|
||||||
|
|
||||||
foreach my $rel (qw/nodes active_nodes nodes_with_age active_nodes_with_age vlans logs/) {
|
foreach my $rel (qw/nodes active_nodes nodes_with_age active_nodes_with_age vlans logs/) {
|
||||||
swagger_path {
|
swagger_path {
|
||||||
tags => ['Objects'],
|
tags => ['Objects'],
|
||||||
|
|||||||
179
lib/App/Netdisco/Web/API/Queue.pm
Normal file
179
lib/App/Netdisco/Web/API/Queue.pm
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
package App::Netdisco::Web::API::Queue;
|
||||||
|
|
||||||
|
use Dancer ':syntax';
|
||||||
|
use Dancer::Plugin::DBIC;
|
||||||
|
use Dancer::Plugin::Swagger;
|
||||||
|
use Dancer::Plugin::Auth::Extensible;
|
||||||
|
|
||||||
|
use App::Netdisco::JobQueue 'jq_insert';
|
||||||
|
use Try::Tiny;
|
||||||
|
|
||||||
|
swagger_path {
|
||||||
|
tags => ['Queue'],
|
||||||
|
path => (setting('api_base') || '').'/queue/backends',
|
||||||
|
description => 'Return list of currently active backend names (usually FQDN)',
|
||||||
|
responses => { default => {} },
|
||||||
|
}, get '/api/v1/queue/backends' => require_role api_admin => sub {
|
||||||
|
# from 1d988bbf7 this always returns an entry
|
||||||
|
my @names = schema(vars->{'tenant'})->resultset('DeviceSkip')
|
||||||
|
->get_distinct_col('backend');
|
||||||
|
|
||||||
|
return to_json \@names;
|
||||||
|
};
|
||||||
|
|
||||||
|
swagger_path {
|
||||||
|
tags => ['Queue'],
|
||||||
|
path => (setting('api_base') || '').'/queue/jobs',
|
||||||
|
description => 'Return jobs in the queue, optionally filtered by fields',
|
||||||
|
parameters => [
|
||||||
|
limit => {
|
||||||
|
description => 'Maximum number of Jobs to return',
|
||||||
|
type => 'integer',
|
||||||
|
default => (setting('jobs_qdepth') || 50),
|
||||||
|
},
|
||||||
|
device => {
|
||||||
|
description => 'IP address field of the Job',
|
||||||
|
},
|
||||||
|
port => {
|
||||||
|
description => 'Port field of the Job',
|
||||||
|
},
|
||||||
|
action => {
|
||||||
|
description => 'Action field of the Job',
|
||||||
|
},
|
||||||
|
status => {
|
||||||
|
description => 'Status field of the Job',
|
||||||
|
},
|
||||||
|
username => {
|
||||||
|
description => 'Username of the Job submitter',
|
||||||
|
},
|
||||||
|
userip => {
|
||||||
|
description => 'IP address of the Job submitter',
|
||||||
|
},
|
||||||
|
backend => {
|
||||||
|
description => 'Backend instance assigned the Job',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses => { default => {} },
|
||||||
|
}, get '/api/v1/queue/jobs' => require_role api_admin => sub {
|
||||||
|
my @set = schema(vars->{'tenant'})->resultset('Admin')->search({
|
||||||
|
( param('device') ? ( device => param('device') ) : () ),
|
||||||
|
( param('port') ? ( port => param('port') ) : () ),
|
||||||
|
( param('action') ? ( action => param('action') ) : () ),
|
||||||
|
( param('status') ? ( status => param('status') ) : () ),
|
||||||
|
( param('username') ? ( username => param('username') ) : () ),
|
||||||
|
( param('userip') ? ( userip => param('userip') ) : () ),
|
||||||
|
( param('backend') ? ( backend => param('backend') ) : () ),
|
||||||
|
-or => [
|
||||||
|
{ 'log' => undef },
|
||||||
|
{ 'log' => { '-not_like' => 'duplicate of %' } },
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
order_by => { -desc => [qw/entered device action/] },
|
||||||
|
rows => (param('limit') || setting('jobs_qdepth') || 50),
|
||||||
|
})->with_times->hri->all;
|
||||||
|
|
||||||
|
return to_json \@set;
|
||||||
|
};
|
||||||
|
|
||||||
|
swagger_path {
|
||||||
|
tags => ['Queue'],
|
||||||
|
path => (setting('api_base') || '').'/queue/jobs',
|
||||||
|
description => 'Delete jobs and skiplist entries, optionally filtered by fields',
|
||||||
|
parameters => [
|
||||||
|
device => {
|
||||||
|
description => 'IP address field of the Job',
|
||||||
|
},
|
||||||
|
port => {
|
||||||
|
description => 'Port field of the Job',
|
||||||
|
},
|
||||||
|
action => {
|
||||||
|
description => 'Action field of the Job',
|
||||||
|
},
|
||||||
|
status => {
|
||||||
|
description => 'Status field of the Job',
|
||||||
|
},
|
||||||
|
username => {
|
||||||
|
description => 'Username of the Job submitter',
|
||||||
|
},
|
||||||
|
userip => {
|
||||||
|
description => 'IP address of the Job submitter',
|
||||||
|
},
|
||||||
|
backend => {
|
||||||
|
description => 'Backend instance assigned the Job',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses => { default => {} },
|
||||||
|
}, del '/api/v1/queue/jobs' => require_role api_admin => sub {
|
||||||
|
my $gone = schema(vars->{'tenant'})->resultset('Admin')->search({
|
||||||
|
( param('device') ? ( device => param('device') ) : () ),
|
||||||
|
( param('port') ? ( port => param('port') ) : () ),
|
||||||
|
( param('action') ? ( action => param('action') ) : () ),
|
||||||
|
( param('status') ? ( status => param('status') ) : () ),
|
||||||
|
( param('username') ? ( username => param('username') ) : () ),
|
||||||
|
( param('userip') ? ( userip => param('userip') ) : () ),
|
||||||
|
( param('backend') ? ( backend => param('backend') ) : () ),
|
||||||
|
})->delete;
|
||||||
|
|
||||||
|
schema(vars->{'tenant'})->resultset('DeviceSkip')->search({
|
||||||
|
( param('device') ? ( device => param('device') ) : () ),
|
||||||
|
( param('action') ? ( actionset => { '&&' => \[ 'ARRAY[?]', param('action') ] } ) : () ),
|
||||||
|
( param('backend') ? ( backend => param('backend') ) : () ),
|
||||||
|
})->delete;
|
||||||
|
|
||||||
|
return to_json { deleted => ($gone || 0)};
|
||||||
|
};
|
||||||
|
|
||||||
|
swagger_path {
|
||||||
|
tags => ['Queue'],
|
||||||
|
path => (setting('api_base') || '').'/queue/jobs',
|
||||||
|
description => 'Submit jobs to the queue',
|
||||||
|
parameters => [
|
||||||
|
jobs => {
|
||||||
|
description => 'List of job specifications (action, device?, port?, extra?).',
|
||||||
|
default => '[]',
|
||||||
|
schema => {
|
||||||
|
type => 'array',
|
||||||
|
items => {
|
||||||
|
type => 'object',
|
||||||
|
properties => {
|
||||||
|
action => {
|
||||||
|
type => 'string',
|
||||||
|
required => 1,
|
||||||
|
},
|
||||||
|
device => {
|
||||||
|
type => 'string',
|
||||||
|
required => 0,
|
||||||
|
},
|
||||||
|
port => {
|
||||||
|
type => 'string',
|
||||||
|
required => 0,
|
||||||
|
},
|
||||||
|
extra => {
|
||||||
|
type => 'string',
|
||||||
|
required => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
in => 'body',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses => { default => {} },
|
||||||
|
}, post '/api/v1/queue/jobs' => require_role api_admin => sub {
|
||||||
|
my $data = request->body || '';
|
||||||
|
my $jobs = (length $data ? try { from_json($data) } : []);
|
||||||
|
|
||||||
|
(ref [] eq ref $jobs) or send_error('Malformed body', 400);
|
||||||
|
|
||||||
|
foreach my $job (@$jobs) {
|
||||||
|
ref {} eq ref $job or send_error('Malformed job', 400);
|
||||||
|
$job->{username} = session('logged_in_user');
|
||||||
|
$job->{userip} = request->remote_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $happy = jq_insert($jobs);
|
||||||
|
|
||||||
|
return to_json { success => $happy };
|
||||||
|
};
|
||||||
|
|
||||||
|
true;
|
||||||
@@ -202,8 +202,8 @@ get '/logout' => sub {
|
|||||||
redirect uri_for(setting('web_home'))->path;
|
redirect uri_for(setting('web_home'))->path;
|
||||||
};
|
};
|
||||||
|
|
||||||
# user redirected here (POST -> GET) when login fails
|
# user redirected here when require_role does not succeed
|
||||||
get qr{^/(?:login(?:/denied)?)?} => sub {
|
any qr{^/(?:login(?:/denied)?)?} => sub {
|
||||||
my $api = ((request->accept and request->accept =~ m/(?:json|javascript)/) ? true : false);
|
my $api = ((request->accept and request->accept =~ m/(?:json|javascript)/) ? true : false);
|
||||||
|
|
||||||
if ($api) {
|
if ($api) {
|
||||||
|
|||||||
Reference in New Issue
Block a user