You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ngcp-api-tools/lib/NGCP/API/Client.pm

333 lines
7.8 KiB

package NGCP::API::Client;
use strict;
use warnings;
use feature qw(state);
use English qw(-no_match_vars);
use Config::Tiny;
use JSON::XS;
use IO::Socket::SSL;
use LWP::UserAgent;
use Readonly;
use URI;
sub _load_config_defaults {
my $cfg_file = '/etc/default/ngcp-api';
my $cfg;
$cfg = Config::Tiny->read($cfg_file)
or die "Cannot read $cfg_file: $ERRNO";
Readonly my $config => {
host => $cfg->{_}->{NGCP_API_IP},
port => $cfg->{_}->{NGCP_API_PORT},
iface => $cfg->{_}->{NGCP_API_IFACE},
sslverify => $cfg->{_}->{NGCP_API_SSLVERIFY} || 'yes',
sslverify_lb => $cfg->{_}->{NGCP_API_SSLVERIFY_LOOPBACK} || 'no',
read_timeout => $cfg->{_}->{NGCP_API_READ_TIMEOUT} || 180,
page_rows => $cfg->{_}->{NGCP_API_PAGE_ROWS} // 10,
auth_user => $cfg->{_}->{AUTH_SYSTEM_LOGIN},
auth_pass => $cfg->{_}->{AUTH_SYSTEM_PASSWORD},
verbose => 0,
};
return $config;
}
sub _get_config_defaults {
state $config = _load_config_defaults();
return $config;
}
sub _create_ua {
my $self = shift;
my $ua = LWP::UserAgent->new();
if ($self->{_opts}{sslverify} eq 'no' ||
($self->{_opts}{sslverify_lb} eq 'no' &&
$self->{_opts}{iface} =~ /^(lo|dummy)/)) {
$ua->ssl_opts(
verify_hostname => 0,
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
);
}
my $urlbase = URI->new();
$urlbase->scheme('https');
$urlbase->host($self->{_opts}{host});
$urlbase->port($self->{_opts}{port});
$ua->credentials($urlbase->host_port,
'api_admin_system', #'api_admin_http'
@{$self->{_opts}}{qw(auth_user auth_pass)});
if ($self->{_opts}{verbose}) {
$ua->show_progress(1);
$ua->add_handler("request_send", sub { shift->dump; return });
$ua->add_handler("response_done", sub { shift->dump; return });
}
$ua->timeout($self->{_opts}{read_timeout});
$self->{_ua} = $ua;
$self->{_urlbase} = $urlbase;
return ($ua,$urlbase);
}
sub _create_req {
my ($self, $method, $url) = @_;
my $req = HTTP::Request->new($method, $url->canonical);
if ($method eq "PATCH") {
$req->content_type("application/json-patch+json; charset='utf8'");
} else {
$req->content_type("application/json; charset='utf8'");
}
$req->header('Prefer' => 'return=representation');
$req->header('NGCP-UserAgent' => 'NGCP::API::Client'); #remove for 'api_admin_http'
return $req;
}
sub _get_url {
my ($self, $uri) = @_;
my $url = $self->{_urlbase}->clone();
$url->path($uri);
return $url;
}
sub new {
my $class = shift;
my $self = {
_opts => { },
};
bless $self, $class;
my $default_opts = _get_config_defaults();
foreach my $opt (keys %{$default_opts}) {
$self->{_opts}{$opt} //= $default_opts->{$opt};
}
$self->_create_ua();
return $self;
}
sub request {
my ($self, $method, $uri, $data) = @_;
my $req = $self->_create_req($method, $self->_get_url($uri));
if ($data) {
$req->content(encode_json($data));
}
my $res = $self->{_ua}->request($req);
return NGCP::API::Client::Result->new($res);
}
sub next_page {
my ( $self, $uri ) = @_;
my $collection_url;
if ($self->{_collection_url}) {
$collection_url = $self->{_collection_url};
} else {
$collection_url = $self->_get_url($uri);
my %params = $collection_url->query_form;
$params{page} //= 1;
$params{rows} //= $self->{_rows};
$collection_url->query_form(\%params);
$self->{_collection_url} = $collection_url;
}
my $req = $self->_create_req('GET', $collection_url);
my $res = NGCP::API::Client::Result->new($self->{_ua}->request($req));
my $data = $res->as_hash();
if ($data && ref($data) eq 'HASH') {
my $new_url = URI->new($data->{_links}->{next}->{href});
return $res unless $new_url;
my %params = $new_url->query_form;
return $res unless grep { $_ eq 'page' } keys %params;
%params = ( $collection_url->query_form, $new_url->query_form );
$new_url->query_form(\%params);
$self->{_collection_url} = $self->_get_url($new_url->canonical);
} else {
undef $self->{_collection_url};
}
return $res;
}
sub set_page_rows {
my ($self,$rows) = @_;
$self->{_rows} = $rows;
undef $self->{_collection_url};
return;
}
sub set_verbose {
my $self = shift;
$self->{_opts}{verbose} = shift || 0;
return;
}
package NGCP::API::Client::Result;
use warnings;
use strict;
use parent qw(HTTP::Response);
use JSON::XS;
sub new {
my ($class, $res_obj) = @_;
my $self = $class->SUPER::new($res_obj->code,
$res_obj->message,
$res_obj->headers,
$res_obj->content);
$self->{_cached} = undef;
return $self;
}
sub as_hash {
my $self = shift;
return $self->{_cached} if $self->{_cached};
$self->{_cached} = decode_json($self->content);
return $self->{_cached};
}
sub result {
my $self = shift;
my $location = $self->headers->header('Location') || '';
return $self->is_success
? sprintf "%s %s", $self->status_line, $location
: sprintf "%s %s", $self->status_line, $self->content;
}
1;
__END__
=pod
=head1 NAME
NGCP::API::Client - Client interface for the REST API
=head1 VERSION
See the package changelog
=head1 COMPATIBILITY
The version is compatible with NGCP platforms version >= mr4.3.x
=head1 SYNOPSIS
=head2
my $client = new NGCP::API::Client;
# GET (list)
my $uri = '/api/customers/';
my $res = $client->request("GET", $uri);
# POST (create)
my $uri = '/api/customers/';
my $data = { contact_id => 4,
status => "test",
billing_profile_id => 4,
type => "sipaccount" };
my $res = $client->request("POST", $uri, $data);
# PUT (update)
my $uri = '/api/customers/2';
my $data = { contact_id => 4,
status => "test",
billing_profile_id => 4,
type => "sipaccount" };
my $res = $client->request("POST", $uri, $data);
# PATCH (update fields)
my $uri = '/api/customers/2';
my $data = [ { op => "remove",
path => "/add_vat" },
{ op => "replace",
path => "/contact_id",
value => 5 } ];
my $res = $client->request("PATCH", $uri, $data);
# DELETE (remove)
my $uri = '/api/customers/2';
my $res = $client->request("DELETE", $uri);
# $res - response is an NGCP::API::Client::Result object
=head1 DESCRIPTION
The client is for internal REST API usage primarily by internal scripts and modules
=head2 new()
Return: NGCP::API::Client object
=head2 request($method, $uri)
Send a REST API request provided by a method (GET,POST,PUT,PATCH,DELETE)
Return: NGCP::API::Client::Result object which is an extended clone of HTTP:Respone
=head2 set_verbose(0|1)
Enable/disable tracing of the request/response.
Return: undef
=head2 NGCP::API::Client::Result->as_hash()
Return: result as a hash reference
=head2 NGCP::API::Client::Result->result()
Return: return a result string based on the request success
=head1 BUGS AND LIMITATIONS
L<https://bugtracker.sipwise.com>
=head1 AUTHOR
Kirill Solomko <ksolomko@sipwise.com>
=head1 LICENSE
This software is Copyright (c) 2016 by Sipwise GmbH, Austria.
All rights reserved. You may not copy, distribute
or modify without prior written permission from
Sipwise GmbH, Austria.
=cut
# vim: sw=4 ts=4 et