4 # implement persistent history ?
10 use HTTP
::Status
qw(:constants :is status_message);
15 use PVE
::RPCEnvironment
;
19 use Data
::Dumper
; # fixme: remove
21 PVE
::INotify
::inotify_init
();
23 my $rpcenv = PVE
::RPCEnvironment-
>init('cli');
25 $rpcenv->set_language($ENV{LANG
});
26 $rpcenv->set_user('root@pam');
28 my $basedir = '/api2/json';
34 if ($cmd && $cmd eq 'verifyapi') {
35 PVE
::RESTHandler
::validate_method_schemas
();
42 print STDERR
"ERROR: $msg\n" if $msg;
43 print STDERR
"USAGE: pvesh [verifyapi]\n";
47 if (scalar (@ARGV) != 0) {
52 print "entering PVE shell - type 'help' for help\n";
54 my $term = new Term
::ReadLine
('pvesh');
55 my $attribs = $term->Attribs;
60 my ($dir, undef, $rest) = $text =~ m
|^(.*/)?(([^/]*))?
$|;
61 my $path = abs_path
($cdir, $dir);
65 my $di = dir_info
($path);
66 if (my $children = $di->{children
}) {
67 foreach my $c (@$children) {
68 if ($c =~ /^\Q$rest/) {
69 my $new = $dir ?
"$dir$c" : $c;
75 if (scalar(@res) == 0) {
77 } elsif (scalar(@res) == 1) {
78 return ($res[0], $res[0], "$res[0]/");
81 # lcd : lowest common denominator
84 for (my $i = 1; $i <= length($tmp); $i++) {
86 foreach my $p (@res) {
87 if (substr($tmp, 0, $i) ne substr($p, 0, $i)) {
93 $lcd = substr($tmp, 0, $i);
102 # just to avoid an endless loop (called by attempted_completion_function)
103 $attribs->{completion_entry_function
} = sub {
104 my($text, $state) = @_;
108 $attribs->{attempted_completion_function
} = sub {
109 my ($text, $line, $start) = @_;
111 my $prefix = substr($line, 0, $start);
112 if ($prefix =~ /^\s*$/) { # first word (command completeion)
113 $attribs->{completion_word
} = [qw(help ls cd get set create delete quit)];
114 return $term->completion_matches($text, $attribs->{list_completion_function
});
117 if ($prefix =~ /^\s*\S+\s+$/) { # second word (path completion)
118 return complete_path
($text);
125 my ($current, $path) = @_;
129 return $current if !defined($path);
131 $ret = '' if $path =~ m
|^\
/|;
133 foreach my $d (split (/\/+/ , $path)) {
136 } elsif ($d eq '..') {
137 $ret = dirname
($ret);
138 $ret = '' if $ret eq '.';
151 my $read_password = sub {
152 my $attribs = $term->Attribs;
153 my $old = $attribs->{redisplay_function
};
154 $attribs->{redisplay_function
} = $attribs->{shadow_redisplay
};
155 my $input = $term->readline('password: ');
156 my $conf = $term->readline('Retype new password: ');
157 $attribs->{redisplay_function
} = $old;
158 die "Passwords do not match.\n" if ($input ne $conf);
162 sub reverse_map_cmd
{
172 my $cmd = $mmap->{$method};
174 die "got strange value for method ('$method') - internal error" if !$cmd;
190 my $method = $mmap->{$cmd};
192 die "unable to map method" if !$method;
198 my ($info, $uri_param) = @_;
200 if ($info->{proxyto
}) {
201 my $pn = $info->{proxyto
};
202 my $node = $uri_param->{$pn};
204 if ($node ne 'localhost' && ($node ne PVE
::INotify
::nodename
())) {
205 die "can't proxy to remote node - not implemented";
211 my ($dir, $cmd, $args) = @_;
213 my $method = map_cmd
($cmd);
216 my ($handler, $info) = PVE
::API2-
>find_handler($method, $dir, $uri_param);
217 if (!$handler || !$info) {
218 die "no '$cmd' handler for '$dir'\n";
221 check_proxyto
($info, $uri_param);
223 my $data = $handler->cli_handler("$cmd $dir", $info->{name
}, $args, [], $uri_param, $read_password);
225 warn "200 OK\n"; # always print OK status if successful
227 return if $info && $info->{returns
} &&
228 $info->{returns
}->{type
} && $info->{returns
}->{type
} eq 'null';
230 print to_json
($data, {allow_nonref
=> 1, canonical
=> 1, pretty
=> 1 });
235 sub find_resource_methods
{
236 my ($path, $ihash) = @_;
238 for my $method (qw(GET POST PUT DELETE)) {
240 my ($handler, $info, $pm) = PVE
::API2-
>find_handler($method, $path, $uri_param);
241 if ($handler && $info && !$ihash->{$info}) {
246 uri_param
=> $uri_param,
253 my ($path, $opts) = @_;
257 find_resource_methods
($path, $ihash);
259 if (!scalar(keys(%$ihash))) {
260 die "no such resource\n";
263 my $di = dir_info
($path);
264 if (my $children = $di->{children
}) {
265 foreach my $c (@$children) {
266 my $cp = abs_path
($path, $c);
267 find_resource_methods
($cp, $ihash);
271 foreach my $mi (sort { $a->{path
} cmp $b->{path
} } values %$ihash) {
272 my $method = $mi->{info
}->{method};
274 # we skip index methods for now.
275 next if ($method eq 'GET') && PVE
::JSONSchema
::method_get_child_link
($mi->{info
});
277 my $path = $mi->{path
};
278 $path =~ s
|/+$||; # remove trailing slash
280 my $cmd = reverse_map_cmd
($method);
282 print $mi->{handler
}->usage_str($mi->{info
}->{name
}, "$cmd $path", [], $mi->{uri_param
},
283 $opts->{verbose
} ?
'full' : 'short', 1);
284 print "\n\n" if $opts->{verbose
};
294 my ($handler, $info) = PVE
::API2-
>find_handler('GET', $path);
295 if (!($handler && $info)) {
298 if (PVE
::JSONSchema
::method_get_child_link
($info)) {
305 ($handler, $info) = PVE
::API2-
>find_handler('PUT', $path);
306 if (!($handler && $info)) {
312 ($handler, $info) = PVE
::API2-
>find_handler('POST', $path);
313 if (!($handler && $info)) {
319 ($handler, $info) = PVE
::API2-
>find_handler('DELETE', $path);
320 if (!($handler && $info)) {
329 sub extract_children
{
330 my ($lnk, $data) = @_;
334 return $res if !($lnk && $data);
336 my $href = $lnk->{href
};
337 if ($href =~ m/^\{(\S+)\}$/) {
340 foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
342 my $value = $elem->{$prop};
353 my $res = { path
=> $path };
355 my ($handler, $info, $pm) = PVE
::API2-
>find_handler('GET', $path, $uri_param);
356 if ($handler && $info) {
358 my $data = $handler->handle($info, $uri_param);
359 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
360 $res->{children
} = extract_children
($lnk, $data);
367 my ($dir, $args) = @_;
370 my ($handler, $info) = PVE
::API2-
>find_handler('GET', $dir, $uri_param);
371 if (!$handler || !$info) {
372 die "no such resource\n";
375 check_proxyto
($info, $uri_param);
377 if (!PVE
::JSONSchema
::method_get_child_link
($info)) {
378 die "resource does not define child links\n";
381 my $data = $handler->cli_handler("ls $dir", $info->{name
}, $args, [], $uri_param, $read_password);
382 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
383 my $children = extract_children
($lnk, $data);
385 foreach my $c (@$children) {
386 my $cap = resource_cap
(abs_path
($dir, $c));
394 PVE
::Cluster
::cfs_update
();
396 $rpcenv->init_request();
398 my $args = [ shellwords
($input) ];
400 my $cmd = shift @$args;
404 my $path = shift @$args;
406 die "usage: cd [dir]\n" if scalar(@$args);
408 if (!defined($path)) {
412 my $new_dir = abs_path
($cdir, $path);
413 my ($handler, $info) = PVE
::API2-
>find_handler('GET', $new_dir);
414 die "no such resource\n" if !$handler;
418 } elsif ($cmd eq 'help') {
420 my $help_usage_error = sub {
421 die "usage: help [path] [--verbose]\n";
426 &$help_usage_error() if !Getopt
::Long
::GetOptionsFromArray
($args, $opts, 'verbose');
429 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
430 $path = shift @$args;
433 &$help_usage_error() if scalar(@$args);
435 print "help [path] [--verbose]\n";
437 print "ls [path]\n\n";
439 print_help
(abs_path
($cdir, $path), $opts);
441 } elsif ($cmd eq 'ls') {
443 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
444 $path = shift @$args;
447 list_dir
(abs_path
($cdir, $path), $args);
449 } elsif ($cmd eq 'get') {
452 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
453 $path = shift @$args;
456 call_method
(abs_path
($cdir, $path), $cmd, $args);
458 } elsif ($cmd eq 'create') {
461 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
462 $path = shift @$args;
465 call_method
(abs_path
($cdir, $path), $cmd, $args);
467 } elsif ($cmd eq 'delete') {
469 my $path = shift @$args;
471 die "usage: delete [path]\n" if scalar(@$args);
473 call_method
(abs_path
($cdir, $path), $cmd, $args);
475 } elsif ($cmd eq 'set') {
478 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
479 $path = shift @$args;
482 call_method
(abs_path
($cdir, $path), $cmd, $args);
485 die "unknown command '$cmd'\n";
491 while (defined ($input = $term->readline("pve:/$cdir> "))) {
494 next if $input =~ m/^\s*$/;
496 if ($input =~ m/^\s*q(uit)?\s*$/) {
500 $term->addhistory($input);
503 pve_command
($input);