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 $logid = $ENV{PVE_LOG_ID
} || 'pmgsh';
31 my $basedir = '/api2/json';
38 print STDERR
"ERROR: $msg\n" if $msg;
39 print STDERR
"USAGE: pmgsh [verifyapi]\n";
40 print STDERR
" pmgsh CMD [OPTIONS]\n";
44 my $disable_proxy = 0;
53 if ($cmd eq '--noproxy') {
57 } elsif ($cmd eq '--nooutput') {
58 # we use this when starting task in CLI (suppress printing upid)
67 if ($cmd eq 'verifyapi') {
68 PVE
::RESTHandler
::validate_method_schemas
();
70 } elsif ($cmd eq 'ls' || $cmd eq 'get' || $cmd eq 'create' ||
71 $cmd eq 'set' || $cmd eq 'delete' ||$cmd eq 'help' ) {
72 pmg_command
([ $cmd, @ARGV], $opt_nooutput);
75 print_usage
("unknown command '$cmd'");
80 if (scalar (@ARGV) != 0) {
85 print "entering PMG shell - type 'help' for help\n";
87 my $term = new Term
::ReadLine
('pmgsh');
88 my $attribs = $term->Attribs;
93 my ($dir, undef, $rest) = $text =~ m
|^(.*/)?(([^/]*))?
$|;
94 my $path = abs_path
($cdir, $dir);
98 my $di = dir_info
($path);
99 if (my $children = $di->{children
}) {
100 foreach my $c (@$children) {
101 if ($c =~ /^\Q$rest/) {
102 my $new = $dir ?
"$dir$c" : $c;
108 if (scalar(@res) == 0) {
110 } elsif (scalar(@res) == 1) {
111 return ($res[0], $res[0], "$res[0]/");
114 # lcd : lowest common denominator
117 for (my $i = 1; $i <= length($tmp); $i++) {
119 foreach my $p (@res) {
120 if (substr($tmp, 0, $i) ne substr($p, 0, $i)) {
126 $lcd = substr($tmp, 0, $i);
135 # just to avoid an endless loop (called by attempted_completion_function)
136 $attribs->{completion_entry_function
} = sub {
137 my($text, $state) = @_;
141 $attribs->{attempted_completion_function
} = sub {
142 my ($text, $line, $start) = @_;
144 my $prefix = substr($line, 0, $start);
145 if ($prefix =~ /^\s*$/) { # first word (command completeion)
146 $attribs->{completion_word
} = [qw(help ls cd get set create delete quit)];
147 return $term->completion_matches($text, $attribs->{list_completion_function
});
150 if ($prefix =~ /^\s*\S+\s+$/) { # second word (path completion)
151 return complete_path
($text);
158 my ($current, $path) = @_;
162 return $current if !defined($path);
164 $ret = '' if $path =~ m
|^\
/|;
166 foreach my $d (split (/\/+/ , $path)) {
169 } elsif ($d eq '..') {
170 $ret = dirname
($ret);
171 $ret = '' if $ret eq '.';
184 my $param_mapping = sub {
187 return [ PVE
::CLIHandler
::get_standard_mapping
('pve-password') ];
190 sub reverse_map_cmd
{
200 my $cmd = $mmap->{$method};
202 die "got strange value for method ('$method') - internal error" if !$cmd;
218 my $method = $mmap->{$cmd};
220 die "unable to map method" if !$method;
226 my ($info, $uri_param) = @_;
228 if ($info->{proxyto
}) {
229 my $pn = $info->{proxyto
};
231 if ($pn eq 'master') {
232 $node = PMG
::Cluster
::get_master_node
();
234 $node = $uri_param->{$pn};
235 die "proxy parameter '$pn' does not exists" if !$node;
238 if ($node ne 'localhost' && ($node ne PVE
::INotify
::nodename
())) {
239 die "proxy loop detected - aborting\n" if $disable_proxy;
240 my $remip = PMG
::Cluster
::remote_node_ip
($node);
241 return ($node, $remip);
249 my ($node, $remip, $dir, $cmd, $args) = @_;
251 my $remcmd = ['ssh', '-o', 'BatchMode=yes', "root\@$remip",
252 'pmgsh', '--noproxy', $cmd, $dir, @$args];
254 system(@$remcmd) == 0 || die "proxy handler failed\n";
258 my ($dir, $cmd, $args, $nooutput) = @_;
260 my $method = map_cmd
($cmd);
263 my ($handler, $info) = PMG
::API2-
>find_handler($method, $dir, $uri_param);
264 if (!$handler || !$info) {
265 die "no '$cmd' handler for '$dir'\n";
268 my ($node, $remip) = check_proxyto
($info, $uri_param);
269 return proxy_handler
($node, $remip, $dir, $cmd, $args) if $node;
271 my $data = $handler->cli_handler("$cmd $dir", $info->{name
}, $args, [], $uri_param, $param_mapping);
275 warn "200 OK\n"; # always print OK status if successful
277 if ($info && $info->{returns
} && $info->{returns
}->{type
}) {
278 my $rtype = $info->{returns
}->{type
};
280 return if $rtype eq 'null';
282 if ($rtype eq 'string') {
283 print $data if $data;
288 print to_json
($data, {utf8
=> 1, allow_nonref
=> 1, canonical
=> 1, pretty
=> 1 });
293 sub find_resource_methods
{
294 my ($path, $ihash) = @_;
296 for my $method (qw(GET POST PUT DELETE)) {
299 my ($handler, $info) = PMG
::API2-
>find_handler($method, $path, $uri_param, \
$path_match);
300 if ($handler && $info && !$ihash->{$info}) {
305 uri_param
=> $uri_param,
312 my ($path, $opts) = @_;
316 find_resource_methods
($path, $ihash);
318 if (!scalar(keys(%$ihash))) {
319 die "no such resource\n";
322 my $di = dir_info
($path);
323 if (my $children = $di->{children
}) {
324 foreach my $c (@$children) {
325 my $cp = abs_path
($path, $c);
326 find_resource_methods
($cp, $ihash);
330 foreach my $mi (sort { $a->{path
} cmp $b->{path
} } values %$ihash) {
331 my $method = $mi->{info
}->{method};
333 # we skip index methods for now.
334 next if ($method eq 'GET') && PVE
::JSONSchema
::method_get_child_link
($mi->{info
});
336 my $path = $mi->{path
};
337 $path =~ s
|/+$||; # remove trailing slash
339 my $cmd = reverse_map_cmd
($method);
341 print $mi->{handler
}->usage_str($mi->{info
}->{name
}, "$cmd $path", [], $mi->{uri_param
},
342 $opts->{verbose
} ?
'full' : 'short', 1);
343 print "\n\n" if $opts->{verbose
};
353 my ($handler, $info) = PMG
::API2-
>find_handler('GET', $path);
354 if (!($handler && $info)) {
357 if (PVE
::JSONSchema
::method_get_child_link
($info)) {
364 ($handler, $info) = PMG
::API2-
>find_handler('PUT', $path);
365 if (!($handler && $info)) {
371 ($handler, $info) = PMG
::API2-
>find_handler('POST', $path);
372 if (!($handler && $info)) {
378 ($handler, $info) = PMG
::API2-
>find_handler('DELETE', $path);
379 if (!($handler && $info)) {
388 sub extract_children
{
389 my ($lnk, $data) = @_;
393 return $res if !($lnk && $data);
395 my $href = $lnk->{href
};
396 if ($href =~ m/^\{(\S+)\}$/) {
399 foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
401 my $value = $elem->{$prop};
412 my $res = { path
=> $path };
414 my ($handler, $info, $pm) = PMG
::API2-
>find_handler('GET', $path, $uri_param);
415 if ($handler && $info) {
417 my $data = $handler->handle($info, $uri_param);
418 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
419 $res->{children
} = extract_children
($lnk, $data);
426 my ($dir, $args) = @_;
429 my ($handler, $info) = PMG
::API2-
>find_handler('GET', $dir, $uri_param);
430 if (!$handler || !$info) {
431 die "no such resource\n";
434 if (!PVE
::JSONSchema
::method_get_child_link
($info)) {
435 die "resource does not define child links\n";
438 my ($node, $remip) = check_proxyto
($info, $uri_param);
439 return proxy_handler
($node, $remip, $dir, 'ls', $args) if $node;
442 my $data = $handler->cli_handler("ls $dir", $info->{name
}, $args, [], $uri_param, $param_mapping);
443 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
444 my $children = extract_children
($lnk, $data);
446 foreach my $c (@$children) {
447 my $cap = resource_cap
(abs_path
($dir, $c));
453 my ($args, $nooutput) = @_;
455 $rpcenv->init_request();
457 my $ticket = PMG
::Ticket
::assemble_ticket
('root@pam');
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