8 use HTTP
::Status
qw(:constants :is status_message);
15 use PMG
::RESTEnvironment
;
23 PMG
::RESTEnvironment-
>setup_default_cli_env();
25 my $rpcenv = PMG
::RESTEnvironment-
>get();
27 my $ticket = PMG
::Ticket
::assemble_ticket
('root@pam');
29 my $logid = $ENV{PVE_LOG_ID
} || 'pmgsh';
32 my $basedir = '/api2/json';
39 print STDERR
"ERROR: $msg\n" if $msg;
40 print STDERR
"USAGE: pmgsh [verifyapi]\n";
41 print STDERR
" pmgsh CMD [OPTIONS]\n";
45 my $disable_proxy = 0;
54 if ($cmd eq '--noproxy') {
58 } elsif ($cmd eq '--nooutput') {
59 # we use this when starting task in CLI (suppress printing upid)
68 if ($cmd eq 'verifyapi') {
69 PVE
::RESTHandler
::validate_method_schemas
();
71 } elsif ($cmd eq 'ls' || $cmd eq 'get' || $cmd eq 'create' ||
72 $cmd eq 'set' || $cmd eq 'delete' ||$cmd eq 'help' ) {
73 pmg_command
([ $cmd, @ARGV], $opt_nooutput);
76 print_usage
("unknown command '$cmd'");
81 if (scalar (@ARGV) != 0) {
86 print "entering PMG shell - type 'help' for help\n";
88 my $term = new Term
::ReadLine
('pmgsh');
89 my $attribs = $term->Attribs;
94 my ($dir, undef, $rest) = $text =~ m
|^(.*/)?(([^/]*))?
$|;
95 my $path = abs_path
($cdir, $dir);
99 my $di = dir_info
($path);
100 if (my $children = $di->{children
}) {
101 foreach my $c (@$children) {
102 if ($c =~ /^\Q$rest/) {
103 my $new = $dir ?
"$dir$c" : $c;
109 if (scalar(@res) == 0) {
111 } elsif (scalar(@res) == 1) {
112 return ($res[0], $res[0], "$res[0]/");
115 # lcd : lowest common denominator
118 for (my $i = 1; $i <= length($tmp); $i++) {
120 foreach my $p (@res) {
121 if (substr($tmp, 0, $i) ne substr($p, 0, $i)) {
127 $lcd = substr($tmp, 0, $i);
136 # just to avoid an endless loop (called by attempted_completion_function)
137 $attribs->{completion_entry_function
} = sub {
138 my($text, $state) = @_;
142 $attribs->{attempted_completion_function
} = sub {
143 my ($text, $line, $start) = @_;
145 my $prefix = substr($line, 0, $start);
146 if ($prefix =~ /^\s*$/) { # first word (command completeion)
147 $attribs->{completion_word
} = [qw(help ls cd get set create delete quit)];
148 return $term->completion_matches($text, $attribs->{list_completion_function
});
151 if ($prefix =~ /^\s*\S+\s+$/) { # second word (path completion)
152 return complete_path
($text);
159 my ($current, $path) = @_;
163 return $current if !defined($path);
165 $ret = '' if $path =~ m
|^\
/|;
167 foreach my $d (split (/\/+/ , $path)) {
170 } elsif ($d eq '..') {
171 $ret = dirname
($ret);
172 $ret = '' if $ret eq '.';
185 my $read_password = sub {
186 my $attribs = $term->Attribs;
187 my $old = $attribs->{redisplay_function
};
188 $attribs->{redisplay_function
} = $attribs->{shadow_redisplay
};
189 my $input = $term->readline('password: ');
190 my $conf = $term->readline('Retype new password: ');
191 $attribs->{redisplay_function
} = $old;
193 # remove password from history
194 if ($term->Features->{autohistory
}) {
195 my $historyPosition = $term->where_history();
196 $term->remove_history($historyPosition);
197 $term->remove_history($historyPosition - 1);
200 die "Passwords do not match.\n" if ($input ne $conf);
204 sub reverse_map_cmd
{
214 my $cmd = $mmap->{$method};
216 die "got strange value for method ('$method') - internal error" if !$cmd;
232 my $method = $mmap->{$cmd};
234 die "unable to map method" if !$method;
240 my ($info, $uri_param) = @_;
242 if ($info->{proxyto
}) {
243 my $pn = $info->{proxyto
};
245 if ($pn eq 'master') {
246 $node = PMG
::Cluster
::get_master_node
();
248 $node = $uri_param->{$pn};
249 die "proxy parameter '$pn' does not exists" if !$node;
252 if ($node ne 'localhost' && ($node ne PVE
::INotify
::nodename
())) {
253 die "proxy loop detected - aborting\n" if $disable_proxy;
254 my $remip = PMG
::Cluster
::remote_node_ip
($node);
255 return ($node, $remip);
263 my ($node, $remip, $dir, $cmd, $args) = @_;
265 my $remcmd = ['ssh', '-o', 'BatchMode=yes', "root\@$remip",
266 'pmgsh', '--noproxy', $cmd, $dir, @$args];
268 system(@$remcmd) == 0 || die "proxy handler failed\n";
272 my ($dir, $cmd, $args, $nooutput) = @_;
274 my $method = map_cmd
($cmd);
277 my ($handler, $info) = PMG
::API2-
>find_handler($method, $dir, $uri_param);
278 if (!$handler || !$info) {
279 die "no '$cmd' handler for '$dir'\n";
282 my ($node, $remip) = check_proxyto
($info, $uri_param);
283 return proxy_handler
($node, $remip, $dir, $cmd, $args) if $node;
285 my $data = $handler->cli_handler("$cmd $dir", $info->{name
}, $args, [], $uri_param, $read_password);
289 warn "200 OK\n"; # always print OK status if successful
291 if ($info && $info->{returns
} && $info->{returns
}->{type
}) {
292 my $rtype = $info->{returns
}->{type
};
294 return if $rtype eq 'null';
296 if ($rtype eq 'string') {
297 print $data if $data;
302 print to_json
($data, {utf8
=> 1, allow_nonref
=> 1, canonical
=> 1, pretty
=> 1 });
307 sub find_resource_methods
{
308 my ($path, $ihash) = @_;
310 for my $method (qw(GET POST PUT DELETE)) {
313 my ($handler, $info) = PMG
::API2-
>find_handler($method, $path, $uri_param, \
$path_match);
314 if ($handler && $info && !$ihash->{$info}) {
319 uri_param
=> $uri_param,
326 my ($path, $opts) = @_;
330 find_resource_methods
($path, $ihash);
332 if (!scalar(keys(%$ihash))) {
333 die "no such resource\n";
336 my $di = dir_info
($path);
337 if (my $children = $di->{children
}) {
338 foreach my $c (@$children) {
339 my $cp = abs_path
($path, $c);
340 find_resource_methods
($cp, $ihash);
344 foreach my $mi (sort { $a->{path
} cmp $b->{path
} } values %$ihash) {
345 my $method = $mi->{info
}->{method};
347 # we skip index methods for now.
348 next if ($method eq 'GET') && PVE
::JSONSchema
::method_get_child_link
($mi->{info
});
350 my $path = $mi->{path
};
351 $path =~ s
|/+$||; # remove trailing slash
353 my $cmd = reverse_map_cmd
($method);
355 print $mi->{handler
}->usage_str($mi->{info
}->{name
}, "$cmd $path", [], $mi->{uri_param
},
356 $opts->{verbose
} ?
'full' : 'short', 1);
357 print "\n\n" if $opts->{verbose
};
367 my ($handler, $info) = PMG
::API2-
>find_handler('GET', $path);
368 if (!($handler && $info)) {
371 if (PVE
::JSONSchema
::method_get_child_link
($info)) {
378 ($handler, $info) = PMG
::API2-
>find_handler('PUT', $path);
379 if (!($handler && $info)) {
385 ($handler, $info) = PMG
::API2-
>find_handler('POST', $path);
386 if (!($handler && $info)) {
392 ($handler, $info) = PMG
::API2-
>find_handler('DELETE', $path);
393 if (!($handler && $info)) {
402 sub extract_children
{
403 my ($lnk, $data) = @_;
407 return $res if !($lnk && $data);
409 my $href = $lnk->{href
};
410 if ($href =~ m/^\{(\S+)\}$/) {
413 foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
415 my $value = $elem->{$prop};
426 my $res = { path
=> $path };
428 my ($handler, $info, $pm) = PMG
::API2-
>find_handler('GET', $path, $uri_param);
429 if ($handler && $info) {
431 my $data = $handler->handle($info, $uri_param);
432 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
433 $res->{children
} = extract_children
($lnk, $data);
440 my ($dir, $args) = @_;
443 my ($handler, $info) = PMG
::API2-
>find_handler('GET', $dir, $uri_param);
444 if (!$handler || !$info) {
445 die "no such resource\n";
448 if (!PVE
::JSONSchema
::method_get_child_link
($info)) {
449 die "resource does not define child links\n";
452 my ($node, $remip) = check_proxyto
($info, $uri_param);
453 return proxy_handler
($node, $remip, $dir, 'ls', $args) if $node;
456 my $data = $handler->cli_handler("ls $dir", $info->{name
}, $args, [], $uri_param, $read_password);
457 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
458 my $children = extract_children
($lnk, $data);
460 foreach my $c (@$children) {
461 my $cap = resource_cap
(abs_path
($dir, $c));
467 my ($args, $nooutput) = @_;
469 $rpcenv->init_request();
471 $rpcenv->set_ticket($ticket);
472 $rpcenv->set_user('root@pam');
473 $rpcenv->set_role('root');
475 my $cmd = shift @$args;
479 my $path = shift @$args;
481 die "usage: cd [dir]\n" if scalar(@$args);
483 if (!defined($path)) {
487 my $new_dir = abs_path
($cdir, $path);
488 my ($handler, $info) = PMG
::API2-
>find_handler('GET', $new_dir);
489 die "no such resource\n" if !$handler;
493 } elsif ($cmd eq 'help') {
495 my $help_usage_error = sub {
496 die "usage: help [path] [--verbose]\n";
501 &$help_usage_error() if !Getopt
::Long
::GetOptionsFromArray
($args, $opts, 'verbose');
504 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
505 $path = shift @$args;
508 &$help_usage_error() if scalar(@$args);
510 print "help [path] [--verbose]\n";
512 print "ls [path]\n\n";
514 print_help
(abs_path
($cdir, $path), $opts);
516 } elsif ($cmd eq 'ls') {
518 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
519 $path = shift @$args;
522 list_dir
(abs_path
($cdir, $path), $args);
524 } elsif ($cmd =~ m/^get|delete|set$/) {
527 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
528 $path = shift @$args;
531 call_method
(abs_path
($cdir, $path), $cmd, $args);
533 } elsif ($cmd eq 'create') {
536 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
537 $path = shift @$args;
540 call_method
(abs_path
($cdir, $path), $cmd, $args, $nooutput);
543 die "unknown command '$cmd'\n";
549 while (defined ($input = $term->readline("pmg:/$cdir> "))) {
552 next if $input =~ m/^\s*$/;
554 if ($input =~ m/^\s*q(uit)?\s*$/) {
558 # add input to history if it gets not
559 # automatically added
560 if (!$term->Features->{autohistory
}) {
561 $term->addhistory($input);
565 my $args = [ shellwords
($input) ];
575 pmgsh - shell interface to the Promox Mail Gateway API
579 pmgsh [get|set|create|delete|help] [REST API path] [--verbose]
583 pmgsh provides a shell-like interface to the Proxmox Mail Gateway API, in
590 when called without parameters, pmgsh starts an interactive client,
591 where you can navigate in the different parts of the API
595 when started with parameters pmgsh will send a command to the
596 corresponding REST url, and will return the JSON formatted output
602 get the list of nodes in my cluster