4 # implement persistent history ?
11 use HTTP
::Status
qw(:constants :is status_message);
16 use PVE
::RPCEnvironment
;
20 PVE
::INotify
::inotify_init
();
22 my $rpcenv = PVE
::RPCEnvironment-
>init('cli');
24 $rpcenv->set_language($ENV{LANG
});
25 $rpcenv->set_user('root@pam');
27 my $basedir = '/api2/json';
34 print STDERR
"ERROR: $msg\n" if $msg;
35 print STDERR
"USAGE: pvesh [verifyapi]\n";
36 print STDERR
" pvesh CMD [OPTIONS]\n";
40 my $disable_proxy = 0;
49 if ($cmd eq '--noproxy') {
53 } elsif ($cmd eq '--nooutput') {
54 # we use this when starting task in CLI (suppress printing upid)
55 # for example 'pvesh --nooutput create /nodes/localhost/stopall'
64 if ($cmd eq 'verifyapi') {
65 PVE
::RESTHandler
::validate_method_schemas
();
67 } elsif ($cmd eq 'ls' || $cmd eq 'get' || $cmd eq 'create' ||
68 $cmd eq 'set' || $cmd eq 'delete' ||$cmd eq 'help' ) {
69 pve_command
([ $cmd, @ARGV], $opt_nooutput);
72 print_usage
("unknown command '$cmd'");
77 if (scalar (@ARGV) != 0) {
82 print "entering PVE shell - type 'help' for help\n";
84 my $term = new Term
::ReadLine
('pvesh');
85 my $attribs = $term->Attribs;
90 my ($dir, undef, $rest) = $text =~ m
|^(.*/)?(([^/]*))?
$|;
91 my $path = abs_path
($cdir, $dir);
95 my $di = dir_info
($path);
96 if (my $children = $di->{children
}) {
97 foreach my $c (@$children) {
98 if ($c =~ /^\Q$rest/) {
99 my $new = $dir ?
"$dir$c" : $c;
105 if (scalar(@res) == 0) {
107 } elsif (scalar(@res) == 1) {
108 return ($res[0], $res[0], "$res[0]/");
111 # lcd : lowest common denominator
114 for (my $i = 1; $i <= length($tmp); $i++) {
116 foreach my $p (@res) {
117 if (substr($tmp, 0, $i) ne substr($p, 0, $i)) {
123 $lcd = substr($tmp, 0, $i);
132 # just to avoid an endless loop (called by attempted_completion_function)
133 $attribs->{completion_entry_function
} = sub {
134 my($text, $state) = @_;
138 $attribs->{attempted_completion_function
} = sub {
139 my ($text, $line, $start) = @_;
141 my $prefix = substr($line, 0, $start);
142 if ($prefix =~ /^\s*$/) { # first word (command completeion)
143 $attribs->{completion_word
} = [qw(help ls cd get set create delete quit)];
144 return $term->completion_matches($text, $attribs->{list_completion_function
});
147 if ($prefix =~ /^\s*\S+\s+$/) { # second word (path completion)
148 return complete_path
($text);
155 my ($current, $path) = @_;
159 return $current if !defined($path);
161 $ret = '' if $path =~ m
|^\
/|;
163 foreach my $d (split (/\/+/ , $path)) {
166 } elsif ($d eq '..') {
167 $ret = dirname
($ret);
168 $ret = '' if $ret eq '.';
181 my $read_password = sub {
182 my $attribs = $term->Attribs;
183 my $old = $attribs->{redisplay_function
};
184 $attribs->{redisplay_function
} = $attribs->{shadow_redisplay
};
185 my $input = $term->readline('password: ');
186 my $conf = $term->readline('Retype new password: ');
187 $attribs->{redisplay_function
} = $old;
188 die "Passwords do not match.\n" if ($input ne $conf);
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
};
232 my $node = $uri_param->{$pn};
233 die "proxy parameter '$pn' does not exists" if !$node;
235 if ($node ne 'localhost' && ($node ne PVE
::INotify
::nodename
())) {
236 die "proxy loop detected - aborting\n" if $disable_proxy;
237 my $remip = PVE
::Cluster
::remote_node_ip
($node);
238 return ($node, $remip);
246 my ($node, $remip, $dir, $cmd, $args) = @_;
248 my $remcmd = ['ssh', '-o', 'BatchMode=yes', "root\@$remip",
249 'pvesh', '--noproxy', $cmd, $dir, @$args];
251 system(@$remcmd) == 0 || die "proxy handler failed\n";
255 my ($dir, $cmd, $args, $nooutput) = @_;
257 my $method = map_cmd
($cmd);
260 my ($handler, $info) = PVE
::API2-
>find_handler($method, $dir, $uri_param);
261 if (!$handler || !$info) {
262 die "no '$cmd' handler for '$dir'\n";
265 my ($node, $remip) = check_proxyto
($info, $uri_param);
266 return proxy_handler
($node, $remip, $dir, $cmd, $args) if $node;
268 my $data = $handler->cli_handler("$cmd $dir", $info->{name
}, $args, [], $uri_param, $read_password);
272 warn "200 OK\n"; # always print OK status if successful
274 if ($info && $info->{returns
} && $info->{returns
}->{type
}) {
275 my $rtype = $info->{returns
}->{type
};
277 return if $rtype eq 'null';
279 if ($rtype eq 'string') {
280 print $data if $data;
285 print to_json
($data, {utf8
=> 1, allow_nonref
=> 1, canonical
=> 1, pretty
=> 1 });
290 sub find_resource_methods
{
291 my ($path, $ihash) = @_;
293 for my $method (qw(GET POST PUT DELETE)) {
295 my ($handler, $info, $pm) = PVE
::API2-
>find_handler($method, $path, $uri_param);
296 if ($handler && $info && !$ihash->{$info}) {
301 uri_param
=> $uri_param,
308 my ($path, $opts) = @_;
312 find_resource_methods
($path, $ihash);
314 if (!scalar(keys(%$ihash))) {
315 die "no such resource\n";
318 my $di = dir_info
($path);
319 if (my $children = $di->{children
}) {
320 foreach my $c (@$children) {
321 my $cp = abs_path
($path, $c);
322 find_resource_methods
($cp, $ihash);
326 foreach my $mi (sort { $a->{path
} cmp $b->{path
} } values %$ihash) {
327 my $method = $mi->{info
}->{method};
329 # we skip index methods for now.
330 next if ($method eq 'GET') && PVE
::JSONSchema
::method_get_child_link
($mi->{info
});
332 my $path = $mi->{path
};
333 $path =~ s
|/+$||; # remove trailing slash
335 my $cmd = reverse_map_cmd
($method);
337 print $mi->{handler
}->usage_str($mi->{info
}->{name
}, "$cmd $path", [], $mi->{uri_param
},
338 $opts->{verbose
} ?
'full' : 'short', 1);
339 print "\n\n" if $opts->{verbose
};
349 my ($handler, $info) = PVE
::API2-
>find_handler('GET', $path);
350 if (!($handler && $info)) {
353 if (PVE
::JSONSchema
::method_get_child_link
($info)) {
360 ($handler, $info) = PVE
::API2-
>find_handler('PUT', $path);
361 if (!($handler && $info)) {
367 ($handler, $info) = PVE
::API2-
>find_handler('POST', $path);
368 if (!($handler && $info)) {
374 ($handler, $info) = PVE
::API2-
>find_handler('DELETE', $path);
375 if (!($handler && $info)) {
384 sub extract_children
{
385 my ($lnk, $data) = @_;
389 return $res if !($lnk && $data);
391 my $href = $lnk->{href
};
392 if ($href =~ m/^\{(\S+)\}$/) {
395 foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
397 my $value = $elem->{$prop};
408 my $res = { path
=> $path };
410 my ($handler, $info, $pm) = PVE
::API2-
>find_handler('GET', $path, $uri_param);
411 if ($handler && $info) {
413 my $data = $handler->handle($info, $uri_param);
414 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
415 $res->{children
} = extract_children
($lnk, $data);
422 my ($dir, $args) = @_;
425 my ($handler, $info) = PVE
::API2-
>find_handler('GET', $dir, $uri_param);
426 if (!$handler || !$info) {
427 die "no such resource\n";
430 if (!PVE
::JSONSchema
::method_get_child_link
($info)) {
431 die "resource does not define child links\n";
434 my ($node, $remip) = check_proxyto
($info, $uri_param);
435 return proxy_handler
($node, $remip, $dir, 'ls', $args) if $node;
438 my $data = $handler->cli_handler("ls $dir", $info->{name
}, $args, [], $uri_param, $read_password);
439 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
440 my $children = extract_children
($lnk, $data);
442 foreach my $c (@$children) {
443 my $cap = resource_cap
(abs_path
($dir, $c));
450 my ($args, $nooutput) = @_;
452 PVE
::Cluster
::cfs_update
();
454 $rpcenv->init_request();
456 my $cmd = shift @$args;
460 my $path = shift @$args;
462 die "usage: cd [dir]\n" if scalar(@$args);
464 if (!defined($path)) {
468 my $new_dir = abs_path
($cdir, $path);
469 my ($handler, $info) = PVE
::API2-
>find_handler('GET', $new_dir);
470 die "no such resource\n" if !$handler;
474 } elsif ($cmd eq 'help') {
476 my $help_usage_error = sub {
477 die "usage: help [path] [--verbose]\n";
482 &$help_usage_error() if !Getopt
::Long
::GetOptionsFromArray
($args, $opts, 'verbose');
485 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
486 $path = shift @$args;
489 &$help_usage_error() if scalar(@$args);
491 print "help [path] [--verbose]\n";
493 print "ls [path]\n\n";
495 print_help
(abs_path
($cdir, $path), $opts);
497 } elsif ($cmd eq 'ls') {
499 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
500 $path = shift @$args;
503 list_dir
(abs_path
($cdir, $path), $args);
505 } elsif ($cmd eq 'get') {
508 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
509 $path = shift @$args;
512 call_method
(abs_path
($cdir, $path), $cmd, $args);
514 } elsif ($cmd eq 'create') {
517 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
518 $path = shift @$args;
521 call_method
(abs_path
($cdir, $path), $cmd, $args, $nooutput);
523 } elsif ($cmd eq 'delete') {
525 my $path = shift @$args;
527 die "usage: delete [path]\n" if scalar(@$args);
529 call_method
(abs_path
($cdir, $path), $cmd, $args);
531 } elsif ($cmd eq 'set') {
534 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
535 $path = shift @$args;
538 call_method
(abs_path
($cdir, $path), $cmd, $args);
541 die "unknown command '$cmd'\n";
547 while (defined ($input = $term->readline("pve:/$cdir> "))) {
550 next if $input =~ m/^\s*$/;
552 if ($input =~ m/^\s*q(uit)?\s*$/) {
556 $term->addhistory($input);
559 my $args = [ shellwords
($input) ];