8 use HTTP
::Status
qw(:constants :is status_message);
16 use PMG
::RESTEnvironment
;
24 PMG
::RESTEnvironment-
>setup_default_cli_env();
26 my $rpcenv = PMG
::RESTEnvironment-
>get();
28 my $ticket = PMG
::Ticket
::assemble_ticket
('root@pam');
30 my $logid = $ENV{PVE_LOG_ID
} || 'pmgsh';
33 my $basedir = '/api2/json';
40 print STDERR
"ERROR: $msg\n" if $msg;
41 print STDERR
"USAGE: pmgsh [verifyapi]\n";
42 print STDERR
" pmgsh CMD [OPTIONS]\n";
46 my $disable_proxy = 0;
55 if ($cmd eq '--noproxy') {
59 } elsif ($cmd eq '--nooutput') {
60 # we use this when starting task in CLI (suppress printing upid)
69 if ($cmd eq 'verifyapi') {
70 PVE
::RESTHandler
::validate_method_schemas
();
72 } elsif ($cmd eq 'ls' || $cmd eq 'get' || $cmd eq 'create' ||
73 $cmd eq 'set' || $cmd eq 'delete' ||$cmd eq 'help' ) {
74 pmg_command
([ $cmd, @ARGV], $opt_nooutput);
77 print_usage
("unknown command '$cmd'");
82 if (scalar (@ARGV) != 0) {
87 print "entering PMG shell - type 'help' for help\n";
89 my $term = new Term
::ReadLine
('pmgsh');
90 my $attribs = $term->Attribs;
95 my ($dir, undef, $rest) = $text =~ m
|^(.*/)?(([^/]*))?
$|;
96 my $path = abs_path
($cdir, $dir);
100 my $di = dir_info
($path);
101 if (my $children = $di->{children
}) {
102 foreach my $c (@$children) {
103 if ($c =~ /^\Q$rest/) {
104 my $new = $dir ?
"$dir$c" : $c;
110 if (scalar(@res) == 0) {
112 } elsif (scalar(@res) == 1) {
113 return ($res[0], $res[0], "$res[0]/");
116 # lcd : lowest common denominator
119 for (my $i = 1; $i <= length($tmp); $i++) {
121 foreach my $p (@res) {
122 if (substr($tmp, 0, $i) ne substr($p, 0, $i)) {
128 $lcd = substr($tmp, 0, $i);
137 # just to avoid an endless loop (called by attempted_completion_function)
138 $attribs->{completion_entry_function
} = sub {
139 my($text, $state) = @_;
143 $attribs->{attempted_completion_function
} = sub {
144 my ($text, $line, $start) = @_;
146 my $prefix = substr($line, 0, $start);
147 if ($prefix =~ /^\s*$/) { # first word (command completeion)
148 $attribs->{completion_word
} = [qw(help ls cd get set create delete quit)];
149 return $term->completion_matches($text, $attribs->{list_completion_function
});
152 if ($prefix =~ /^\s*\S+\s+$/) { # second word (path completion)
153 return complete_path
($text);
160 my ($current, $path) = @_;
164 return $current if !defined($path);
166 $ret = '' if $path =~ m
|^\
/|;
168 foreach my $d (split (/\/+/ , $path)) {
171 } elsif ($d eq '..') {
172 $ret = dirname
($ret);
173 $ret = '' if $ret eq '.';
186 my $param_mapping = sub {
189 return [ PVE
::CLIHandler
::get_standard_mapping
('pve-password') ];
192 sub reverse_map_cmd
{
202 my $cmd = $mmap->{$method};
204 die "got strange value for method ('$method') - internal error" if !$cmd;
220 my $method = $mmap->{$cmd};
222 die "unable to map method" if !$method;
228 my ($info, $uri_param) = @_;
230 if ($info->{proxyto
}) {
231 my $pn = $info->{proxyto
};
233 if ($pn eq 'master') {
234 $node = PMG
::Cluster
::get_master_node
();
236 $node = $uri_param->{$pn};
237 die "proxy parameter '$pn' does not exists" if !$node;
240 if ($node ne 'localhost' && ($node ne PVE
::INotify
::nodename
())) {
241 die "proxy loop detected - aborting\n" if $disable_proxy;
242 my $remip = PMG
::Cluster
::remote_node_ip
($node);
243 return ($node, $remip);
251 my ($node, $remip, $dir, $cmd, $args) = @_;
253 my $remcmd = ['ssh', '-o', 'BatchMode=yes', "root\@$remip",
254 'pmgsh', '--noproxy', $cmd, $dir, @$args];
256 system(@$remcmd) == 0 || die "proxy handler failed\n";
260 my ($dir, $cmd, $args, $nooutput) = @_;
262 my $method = map_cmd
($cmd);
265 my ($handler, $info) = PMG
::API2-
>find_handler($method, $dir, $uri_param);
266 if (!$handler || !$info) {
267 die "no '$cmd' handler for '$dir'\n";
270 my ($node, $remip) = check_proxyto
($info, $uri_param);
271 return proxy_handler
($node, $remip, $dir, $cmd, $args) if $node;
273 my $data = $handler->cli_handler("$cmd $dir", $info->{name
}, $args, [], $uri_param, $param_mapping);
277 warn "200 OK\n"; # always print OK status if successful
279 if ($info && $info->{returns
} && $info->{returns
}->{type
}) {
280 my $rtype = $info->{returns
}->{type
};
282 return if $rtype eq 'null';
284 if ($rtype eq 'string') {
285 print $data if $data;
290 print to_json
($data, {utf8
=> 1, allow_nonref
=> 1, canonical
=> 1, pretty
=> 1 });
295 sub find_resource_methods
{
296 my ($path, $ihash) = @_;
298 for my $method (qw(GET POST PUT DELETE)) {
301 my ($handler, $info) = PMG
::API2-
>find_handler($method, $path, $uri_param, \
$path_match);
302 if ($handler && $info && !$ihash->{$info}) {
307 uri_param
=> $uri_param,
314 my ($path, $opts) = @_;
318 find_resource_methods
($path, $ihash);
320 if (!scalar(keys(%$ihash))) {
321 die "no such resource\n";
324 my $di = dir_info
($path);
325 if (my $children = $di->{children
}) {
326 foreach my $c (@$children) {
327 my $cp = abs_path
($path, $c);
328 find_resource_methods
($cp, $ihash);
332 foreach my $mi (sort { $a->{path
} cmp $b->{path
} } values %$ihash) {
333 my $method = $mi->{info
}->{method};
335 # we skip index methods for now.
336 next if ($method eq 'GET') && PVE
::JSONSchema
::method_get_child_link
($mi->{info
});
338 my $path = $mi->{path
};
339 $path =~ s
|/+$||; # remove trailing slash
341 my $cmd = reverse_map_cmd
($method);
343 print $mi->{handler
}->usage_str($mi->{info
}->{name
}, "$cmd $path", [], $mi->{uri_param
},
344 $opts->{verbose
} ?
'full' : 'short', 1);
345 print "\n\n" if $opts->{verbose
};
355 my ($handler, $info) = PMG
::API2-
>find_handler('GET', $path);
356 if (!($handler && $info)) {
359 if (PVE
::JSONSchema
::method_get_child_link
($info)) {
366 ($handler, $info) = PMG
::API2-
>find_handler('PUT', $path);
367 if (!($handler && $info)) {
373 ($handler, $info) = PMG
::API2-
>find_handler('POST', $path);
374 if (!($handler && $info)) {
380 ($handler, $info) = PMG
::API2-
>find_handler('DELETE', $path);
381 if (!($handler && $info)) {
390 sub extract_children
{
391 my ($lnk, $data) = @_;
395 return $res if !($lnk && $data);
397 my $href = $lnk->{href
};
398 if ($href =~ m/^\{(\S+)\}$/) {
401 foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
403 my $value = $elem->{$prop};
414 my $res = { path
=> $path };
416 my ($handler, $info, $pm) = PMG
::API2-
>find_handler('GET', $path, $uri_param);
417 if ($handler && $info) {
419 my $data = $handler->handle($info, $uri_param);
420 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
421 $res->{children
} = extract_children
($lnk, $data);
428 my ($dir, $args) = @_;
431 my ($handler, $info) = PMG
::API2-
>find_handler('GET', $dir, $uri_param);
432 if (!$handler || !$info) {
433 die "no such resource\n";
436 if (!PVE
::JSONSchema
::method_get_child_link
($info)) {
437 die "resource does not define child links\n";
440 my ($node, $remip) = check_proxyto
($info, $uri_param);
441 return proxy_handler
($node, $remip, $dir, 'ls', $args) if $node;
444 my $data = $handler->cli_handler("ls $dir", $info->{name
}, $args, [], $uri_param, $param_mapping);
445 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
446 my $children = extract_children
($lnk, $data);
448 foreach my $c (@$children) {
449 my $cap = resource_cap
(abs_path
($dir, $c));
455 my ($args, $nooutput) = @_;
457 $rpcenv->init_request();
459 $rpcenv->set_ticket($ticket);
460 $rpcenv->set_user('root@pam');
461 $rpcenv->set_role('root');
463 my $cmd = shift @$args;
467 my $path = shift @$args;
469 die "usage: cd [dir]\n" if scalar(@$args);
471 if (!defined($path)) {
475 my $new_dir = abs_path
($cdir, $path);
476 my ($handler, $info) = PMG
::API2-
>find_handler('GET', $new_dir);
477 die "no such resource\n" if !$handler;
481 } elsif ($cmd eq 'help') {
483 my $help_usage_error = sub {
484 die "usage: help [path] [--verbose]\n";
489 &$help_usage_error() if !Getopt
::Long
::GetOptionsFromArray
($args, $opts, 'verbose');
492 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
493 $path = shift @$args;
496 &$help_usage_error() if scalar(@$args);
498 print "help [path] [--verbose]\n";
500 print "ls [path]\n\n";
502 print_help
(abs_path
($cdir, $path), $opts);
504 } elsif ($cmd eq 'ls') {
506 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
507 $path = shift @$args;
510 list_dir
(abs_path
($cdir, $path), $args);
512 } elsif ($cmd =~ m/^get|delete|set$/) {
515 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
516 $path = shift @$args;
519 call_method
(abs_path
($cdir, $path), $cmd, $args);
521 } elsif ($cmd eq 'create') {
524 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
525 $path = shift @$args;
528 call_method
(abs_path
($cdir, $path), $cmd, $args, $nooutput);
531 die "unknown command '$cmd'\n";
537 while (defined ($input = $term->readline("pmg:/$cdir> "))) {
540 next if $input =~ m/^\s*$/;
542 if ($input =~ m/^\s*q(uit)?\s*$/) {
546 # add input to history if it gets not
547 # automatically added
548 if (!$term->Features->{autohistory
}) {
549 $term->addhistory($input);
553 my $args = [ shellwords
($input) ];
563 pmgsh - shell interface to the Promox Mail Gateway API
567 pmgsh [get|set|create|delete|help] [REST API path] [--verbose]
571 pmgsh provides a shell-like interface to the Proxmox Mail Gateway API, in
578 when called without parameters, pmgsh starts an interactive client,
579 where you can navigate in the different parts of the API
583 when started with parameters pmgsh will send a command to the
584 corresponding REST url, and will return the JSON formatted output
590 get the list of nodes in my cluster