+++ /dev/null
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-use Term::ReadLine;
-use File::Basename;
-use Getopt::Long;
-use HTTP::Status qw(:constants :is status_message);
-use Text::ParseWords;
-
-use PVE::JSONSchema;
-use PVE::SafeSyslog;
-use PVE::INotify;
-use PVE::CLIHandler;
-
-use PMG::RESTEnvironment;
-
-use PMG::Ticket;
-use PMG::Cluster;
-use PMG::API2;
-
-use JSON;
-
-PMG::RESTEnvironment->setup_default_cli_env();
-
-my $rpcenv = PMG::RESTEnvironment->get();
-
-my $ticket = PMG::Ticket::assemble_ticket('root@pam');
-
-my $logid = $ENV{PVE_LOG_ID} || 'pmgsh';
-initlog($logid);
-
-my $basedir = '/api2/json';
-
-my $cdir = '';
-
-sub print_usage {
- my $msg = shift;
-
- print STDERR "ERROR: $msg\n" if $msg;
- print STDERR "USAGE: pmgsh [verifyapi]\n";
- print STDERR " pmgsh CMD [OPTIONS]\n";
-
-}
-
-my $disable_proxy = 0;
-my $opt_nooutput = 0;
-
-my $cmd = shift;
-
-my $optmatch;
-do {
- $optmatch = 0;
- if ($cmd) {
- if ($cmd eq '--noproxy') {
- $cmd = shift;
- $disable_proxy = 1;
- $optmatch = 1;
- } elsif ($cmd eq '--nooutput') {
- # we use this when starting task in CLI (suppress printing upid)
- $cmd = shift;
- $opt_nooutput = 1;
- $optmatch = 1;
- }
- }
-} while ($optmatch);
-
-if ($cmd) {
- if ($cmd eq 'verifyapi') {
- PVE::RESTHandler::validate_method_schemas();
- exit 0;
- } elsif ($cmd eq 'ls' || $cmd eq 'get' || $cmd eq 'create' ||
- $cmd eq 'set' || $cmd eq 'delete' ||$cmd eq 'help' ) {
- pmg_command([ $cmd, @ARGV], $opt_nooutput);
- exit(0);
- } else {
- print_usage ("unknown command '$cmd'");
- exit (-1);
- }
-}
-
-if (scalar (@ARGV) != 0) {
- print_usage();
- exit(-1);
-}
-
-print "entering PMG shell - type 'help' for help\n";
-
-my $term = new Term::ReadLine('pmgsh');
-my $attribs = $term->Attribs;
-
-sub complete_path {
- my($text) = @_;
-
- my ($dir, undef, $rest) = $text =~ m|^(.*/)?(([^/]*))?$|;
- my $path = abs_path($cdir, $dir);
-
- my @res = ();
-
- my $di = dir_info($path);
- if (my $children = $di->{children}) {
- foreach my $c (@$children) {
- if ($c =~ /^\Q$rest/) {
- my $new = $dir ? "$dir$c" : $c;
- push @res, $new;
- }
- }
- }
-
- if (scalar(@res) == 0) {
- return undef;
- } elsif (scalar(@res) == 1) {
- return ($res[0], $res[0], "$res[0]/");
- }
-
- # lcd : lowest common denominator
- my $lcd = '';
- my $tmp = $res[0];
- for (my $i = 1; $i <= length($tmp); $i++) {
- my $found = 1;
- foreach my $p (@res) {
- if (substr($tmp, 0, $i) ne substr($p, 0, $i)) {
- $found = 0;
- last;
- }
- }
- if ($found) {
- $lcd = substr($tmp, 0, $i);
- } else {
- last;
- }
- }
-
- return ($lcd, @res);
-};
-
-# just to avoid an endless loop (called by attempted_completion_function)
-$attribs->{completion_entry_function} = sub {
- my($text, $state) = @_;
- return undef;
-};
-
-$attribs->{attempted_completion_function} = sub {
- my ($text, $line, $start) = @_;
-
- my $prefix = substr($line, 0, $start);
- if ($prefix =~ /^\s*$/) { # first word (command completeion)
- $attribs->{completion_word} = [qw(help ls cd get set create delete quit)];
- return $term->completion_matches($text, $attribs->{list_completion_function});
- }
-
- if ($prefix =~ /^\s*\S+\s+$/) { # second word (path completion)
- return complete_path($text);
- }
-
- return ();
-};
-
-sub abs_path {
- my ($current, $path) = @_;
-
- my $ret = $current;
-
- return $current if !defined($path);
-
- $ret = '' if $path =~ m|^\/|;
-
- foreach my $d (split (/\/+/ , $path)) {
- if ($d eq '.') {
- next;
- } elsif ($d eq '..') {
- $ret = dirname($ret);
- $ret = '' if $ret eq '.';
- } else {
- $ret = "$ret/$d";
- }
- }
-
- $ret =~ s|\/+|\/|g;
- $ret =~ s|^\/||;
- $ret =~ s|\/$||;
-
- return $ret;
-}
-
-my $param_mapping = sub {
- my ($name) = @_;
-
- return [ PVE::CLIHandler::get_standard_mapping('pve-password') ];
-};
-
-sub reverse_map_cmd {
- my $method = shift;
-
- my $mmap = {
- GET => 'get',
- PUT => 'set',
- POST => 'create',
- DELETE => 'delete',
- };
-
- my $cmd = $mmap->{$method};
-
- die "got strange value for method ('$method') - internal error" if !$cmd;
-
- return $cmd;
-}
-
-sub map_cmd {
- my $cmd = shift;
-
- my $mmap = {
- create => 'POST',
- set => 'PUT',
- get => 'GET',
- ls => 'GET',
- delete => 'DELETE',
- };
-
- my $method = $mmap->{$cmd};
-
- die "unable to map method" if !$method;
-
- return $method;
-}
-
-sub check_proxyto {
- my ($info, $uri_param) = @_;
-
- if ($info->{proxyto}) {
- my $pn = $info->{proxyto};
- my $node;
- if ($pn eq 'master') {
- $node = PMG::Cluster::get_master_node();
- } else {
- $node = $uri_param->{$pn};
- die "proxy parameter '$pn' does not exists" if !$node;
- }
-
- if ($node ne 'localhost' && ($node ne PVE::INotify::nodename())) {
- die "proxy loop detected - aborting\n" if $disable_proxy;
- my $remip = PMG::Cluster::remote_node_ip($node);
- return ($node, $remip);
- }
- }
-
- return undef;
-}
-
-sub proxy_handler {
- my ($node, $remip, $dir, $cmd, $args) = @_;
-
- my $remcmd = ['ssh', '-o', 'BatchMode=yes', "root\@$remip",
- 'pmgsh', '--noproxy', $cmd, $dir, @$args];
-
- system(@$remcmd) == 0 || die "proxy handler failed\n";
-}
-
-sub call_method {
- my ($dir, $cmd, $args, $nooutput) = @_;
-
- my $method = map_cmd($cmd);
-
- my $uri_param = {};
- my ($handler, $info) = PMG::API2->find_handler($method, $dir, $uri_param);
- if (!$handler || !$info) {
- die "no '$cmd' handler for '$dir'\n";
- }
-
- my ($node, $remip) = check_proxyto($info, $uri_param);
- return proxy_handler($node, $remip, $dir, $cmd, $args) if $node;
-
- my $data = $handler->cli_handler("$cmd $dir", $info->{name}, $args, [], $uri_param, $param_mapping);
-
- return if $nooutput;
-
- warn "200 OK\n"; # always print OK status if successful
-
- if ($info && $info->{returns} && $info->{returns}->{type}) {
- my $rtype = $info->{returns}->{type};
-
- return if $rtype eq 'null';
-
- if ($rtype eq 'string') {
- print $data if $data;
- return;
- }
- }
-
- print to_json($data, {utf8 => 1, allow_nonref => 1, canonical => 1, pretty => 1 });
-
- return;
-}
-
-sub find_resource_methods {
- my ($path, $ihash) = @_;
-
- for my $method (qw(GET POST PUT DELETE)) {
- my $uri_param = {};
- my $path_match;
- my ($handler, $info) = PMG::API2->find_handler($method, $path, $uri_param, \$path_match);
- if ($handler && $info && !$ihash->{$info}) {
- $ihash->{$info} = {
- path => $path_match,
- handler => $handler,
- info => $info,
- uri_param => $uri_param,
- };
- }
- }
-}
-
-sub print_help {
- my ($path, $opts) = @_;
-
- my $ihash = {};
-
- find_resource_methods($path, $ihash);
-
- if (!scalar(keys(%$ihash))) {
- die "no such resource\n";
- }
-
- my $di = dir_info($path);
- if (my $children = $di->{children}) {
- foreach my $c (@$children) {
- my $cp = abs_path($path, $c);
- find_resource_methods($cp, $ihash);
- }
- }
-
- foreach my $mi (sort { $a->{path} cmp $b->{path} } values %$ihash) {
- my $method = $mi->{info}->{method};
-
- # we skip index methods for now.
- next if ($method eq 'GET') && PVE::JSONSchema::method_get_child_link($mi->{info});
-
- my $path = $mi->{path};
- $path =~ s|/+$||; # remove trailing slash
-
- my $cmd = reverse_map_cmd($method);
-
- print $mi->{handler}->usage_str($mi->{info}->{name}, "$cmd $path", [], $mi->{uri_param},
- $opts->{verbose} ? 'full' : 'short', 1);
- print "\n\n" if $opts->{verbose};
- }
-
-};
-
-sub resource_cap {
- my ($path) = @_;
-
- my $res = '';
-
- my ($handler, $info) = PMG::API2->find_handler('GET', $path);
- if (!($handler && $info)) {
- $res .= '--';
- } else {
- if (PVE::JSONSchema::method_get_child_link($info)) {
- $res .= 'Dr';
- } else {
- $res .= '-r';
- }
- }
-
- ($handler, $info) = PMG::API2->find_handler('PUT', $path);
- if (!($handler && $info)) {
- $res .= '-';
- } else {
- $res .= 'w';
- }
-
- ($handler, $info) = PMG::API2->find_handler('POST', $path);
- if (!($handler && $info)) {
- $res .= '-';
- } else {
- $res .= 'c';
- }
-
- ($handler, $info) = PMG::API2->find_handler('DELETE', $path);
- if (!($handler && $info)) {
- $res .= '-';
- } else {
- $res .= 'd';
- }
-
- return $res;
-}
-
-sub extract_children {
- my ($lnk, $data) = @_;
-
- my $res = [];
-
- return $res if !($lnk && $data);
-
- my $href = $lnk->{href};
- if ($href =~ m/^\{(\S+)\}$/) {
- my $prop = $1;
-
- foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
- next if !ref($elem);
- my $value = $elem->{$prop};
- push @$res, $value;
- }
- }
-
- return $res;
-}
-
-sub dir_info {
- my ($path) = @_;
-
- my $res = { path => $path };
- my $uri_param = {};
- my ($handler, $info, $pm) = PMG::API2->find_handler('GET', $path, $uri_param);
- if ($handler && $info) {
- eval {
- my $data = $handler->handle($info, $uri_param);
- my $lnk = PVE::JSONSchema::method_get_child_link($info);
- $res->{children} = extract_children($lnk, $data);
- }; # ignore errors ?
- }
- return $res;
-}
-
-sub list_dir {
- my ($dir, $args) = @_;
-
- my $uri_param = {};
- my ($handler, $info) = PMG::API2->find_handler('GET', $dir, $uri_param);
- if (!$handler || !$info) {
- die "no such resource\n";
- }
-
- if (!PVE::JSONSchema::method_get_child_link($info)) {
- die "resource does not define child links\n";
- }
-
- my ($node, $remip) = check_proxyto($info, $uri_param);
- return proxy_handler($node, $remip, $dir, 'ls', $args) if $node;
-
-
- my $data = $handler->cli_handler("ls $dir", $info->{name}, $args, [], $uri_param, $param_mapping);
- my $lnk = PVE::JSONSchema::method_get_child_link($info);
- my $children = extract_children($lnk, $data);
-
- foreach my $c (@$children) {
- my $cap = resource_cap(abs_path($dir, $c));
- print "$cap $c\n";
- }
-}
-
-sub pmg_command {
- my ($args, $nooutput) = @_;
-
- $rpcenv->init_request();
-
- $rpcenv->set_ticket($ticket);
- $rpcenv->set_user('root@pam');
- $rpcenv->set_role('root');
-
- my $cmd = shift @$args;
-
- if ($cmd eq 'cd') {
-
- my $path = shift @$args;
-
- die "usage: cd [dir]\n" if scalar(@$args);
-
- if (!defined($path)) {
- $cdir = '';
- return;
- } else {
- my $new_dir = abs_path($cdir, $path);
- my ($handler, $info) = PMG::API2->find_handler('GET', $new_dir);
- die "no such resource\n" if !$handler;
- $cdir = $new_dir;
- }
-
- } elsif ($cmd eq 'help') {
-
- my $help_usage_error = sub {
- die "usage: help [path] [--verbose]\n";
- };
-
- my $opts = {};
-
- &$help_usage_error() if !Getopt::Long::GetOptionsFromArray($args, $opts, 'verbose');
-
- my $path;
- if (scalar(@$args) && $args->[0] !~ m/^\-/) {
- $path = shift @$args;
- }
-
- &$help_usage_error() if scalar(@$args);
-
- print "help [path] [--verbose]\n";
- print "cd [path]\n";
- print "ls [path]\n\n";
-
- print_help(abs_path($cdir, $path), $opts);
-
- } elsif ($cmd eq 'ls') {
- my $path;
- if (scalar(@$args) && $args->[0] !~ m/^\-/) {
- $path = shift @$args;
- }
-
- list_dir(abs_path($cdir, $path), $args);
-
- } elsif ($cmd =~ m/^get|delete|set$/) {
-
- my $path;
- if (scalar(@$args) && $args->[0] !~ m/^\-/) {
- $path = shift @$args;
- }
-
- call_method(abs_path($cdir, $path), $cmd, $args);
-
- } elsif ($cmd eq 'create') {
-
- my $path;
- if (scalar(@$args) && $args->[0] !~ m/^\-/) {
- $path = shift @$args;
- }
-
- call_method(abs_path($cdir, $path), $cmd, $args, $nooutput);
-
- } else {
- die "unknown command '$cmd'\n";
- }
-
-}
-
-my $input;
-while (defined ($input = $term->readline("pmg:/$cdir> "))) {
- chomp $input;
-
- next if $input =~ m/^\s*$/;
-
- if ($input =~ m/^\s*q(uit)?\s*$/) {
- exit (0);
- }
-
- # add input to history if it gets not
- # automatically added
- if (!$term->Features->{autohistory}) {
- $term->addhistory($input);
- }
-
- eval {
- my $args = [ shellwords($input) ];
- pmg_command($args);
- };
- warn $@ if $@;
-}
-
-__END__
-
-=head1 NAME
-
-pmgsh - shell interface to the Promox Mail Gateway API
-
-=head1 SYNOPSIS
-
-pmgsh [get|set|create|delete|help] [REST API path] [--verbose]
-
-=head1 DESCRIPTION
-
-pmgsh provides a shell-like interface to the Proxmox Mail Gateway API, in
-two different modes:
-
-=over
-
-=item interactive
-
-when called without parameters, pmgsh starts an interactive client,
-where you can navigate in the different parts of the API
-
-=item command line
-
-when started with parameters pmgsh will send a command to the
-corresponding REST url, and will return the JSON formatted output
-
-=back
-
-=head1 EXAMPLES
-
-get the list of nodes in my cluster
-
-pmgsh get /nodes
-