#!/usr/bin/perl

# Copyright (C) 2014-2016 SUSE LLC
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.

=head1 SYNOPSIS

client [OPTIONS] PATH

=head1 OPTIONS

=over 4

=item B<--host> HOST

connect to specified host, defaults to localhost

=item B<--params> FILE

load get/post parameters from a json file. For example

{
   "FLAVOR" : "DVD",
   "BUILD" : "42",
   "ARCH" : "i586",
   "DISTRI" : "opensuse",
   "VERSION" : "26",
}

=item B<--apibase>

Set API base URL component, default: '/api/v1'

=item B<--json-output>

Output JSON instead of perl structures.

=item B<--verbose, -v>

Be verbose in output.

=item B<--apikey> KEY, B<--apisecret> SECRET

Specify api key and secret to use, overrides use of config file ~/.config/openqa/client.conf

=item B<--json-data>

Send JSON data (which is expected by some routes)

For example:
jobs/639172 put --json-data '{"group_id": 1}'

=item B<--help, -h>

print help

=back

=head1 SYNOPSIS

Interact with the openQA API by specified route entry points and optionally
operations, defaults to the 'get' operation, i.e. just reading out the data
without changing it. See the help on the openQA instance you want to access
for available API routes.

Common top level entry points: jobs, workers, isos.

=item client --host openqa.example.com jobs

List all jobs. Caution: this will take a very long time or even timeout on big
productive instances.

=item client --host openqa.example.com jobs/1

Show details of job nr. B<1>.

=item client --host openqa.example.com jobs/1 delete

Delete job nr. B<1> (permissions read from config file).

=item client --host openqa.example.com isos post iso=bar.iso tests=blah

Trigger jobs on iso B<bar.iso> matching test suite B<blah>.

=cut

# Intentionally commented out - so far useful only for openQA debugging
# =item B<--form>
#
# Use HTTP forms instead of appending supplied parameters as URL query
#
# To create nested forms use dotted syntax. For example:
#
# client jobs/1/artefact post file.file=bar file.filename=bar.log

use strict;
use FindBin;
BEGIN { unshift @INC, "$FindBin::RealBin/../lib" }

use JSON;
use Data::Dump;
use Mojo::URL;
use OpenQA::Client;
use Getopt::Long;
Getopt::Long::Configure("no_ignore_case");

my %options;

sub usage($) {
    my $r = shift;
    eval "use Pod::Usage; pod2usage(1);";
    if ($@) {
        die "cannot display help, install perl(Pod::Usage)\n";
    }
}

GetOptions(
    \%options,     'host=s',   'apibase=s', 'json-output', 'verbose|v', 'apikey:s',
    'apisecret:s', 'params=s', 'form',      'json-data:s', 'help|h|?'
) or usage(1);

usage(1) unless @ARGV;

if ($options{form} && $options{'json-data'}) {
    print STDERR "ERROR: The options --form and --json-data can not be combined.\n";
    exit(2);
}

$options{host}    ||= 'localhost';
$options{apibase} ||= '/api/v1';

my $path = shift @ARGV;
# Relative paths are routed to v1
if ($path !~ m/^\//) {
    $path = join('/', $options{apibase}, $path);
}

my $method = 'get';
my %params;

if ($options{params}) {
    local $/;
    open(my $fh, '<', $options{params});
    my $info = JSON->new->relaxed->decode(<$fh>);
    close $fh;
    %params = %{$info};
}

for my $arg (@ARGV) {
    if ($arg =~ /^(?:get|post|delete|put)$/i) {
        $method = lc $arg;
    }
    elsif ($arg =~ /^([[:alnum:]_\[\]\.]+)=(.+)/) {
        $params{$1} = $2;
    }
}

my $url;
if ($options{host} !~ '/') {
    $url = Mojo::URL->new();
    $url->host($options{host});
    $url->scheme('http');
}
else {
    $url = Mojo::URL->new($options{host});
}

$url->path($path);

if (!$options{form}) {
    $url->query([%params]) if %params;
}
else {
    my %form;
    for (keys %params) {
        if (/(\S+)\.(\S+)/) {
            $form{$1}{$2} = $params{$_};
        }
        else {
            $form{$_} = $params{$_};
        }
    }
    %params = %form;
}

my $client = OpenQA::Client->new(apikey => $options{apikey}, apisecret => $options{apisecret}, api => $url->host);

my $res;
if ($options{form}) {
    $res = $client->$method($url, form => \%params)->res;
}
elsif ($options{'json-data'}) {
    $res = $client->$method($url, {'Content-Type' => 'application/json'} => $options{'json-data'})->res;
}
else {
    $res = $client->$method($url)->res;
}
if ($res->code == 200) {
    if ($options{'json-output'}) {
        print JSON->new->pretty->encode($res->json);
    }
    else {
        dd($res->json || $res->body);
    }
}
else {
    printf STDERR "ERROR: %s - %s\n", $res->code, $res->{error}->{message};
    if ($res->body) {
        if ($options{json}) {
            print JSON->new->pretty->encode($res->json);
        }
        else {
            dd($res->json || $res->body);
        }
    }
    exit(1);
}

1;
# vim: set sw=4 sts=4 et:
