4 # implement persistent history ?
11 use HTTP
::Status
qw(:constants :is status_message);
13 use String
::ShellQuote
;
18 use PVE
::RPCEnvironment
;
23 PVE
::INotify
::inotify_init
();
25 my $rpcenv = PVE
::RPCEnvironment-
>init('cli');
27 $rpcenv->set_language($ENV{LANG
});
28 $rpcenv->set_user('root@pam');
30 my $logid = $ENV{PVE_LOG_ID
} || 'pvesh';
33 my $basedir = '/api2/json';
40 print STDERR
"ERROR: $msg\n" if $msg;
41 print STDERR
"USAGE: pvesh [verifyapi]\n";
42 print STDERR
" pvesh 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)
61 # for example 'pvesh --nooutput create /nodes/localhost/stopall'
70 if ($cmd eq 'verifyapi') {
71 PVE
::RESTHandler
::validate_method_schemas
();
73 } elsif ($cmd eq 'ls' || $cmd eq 'get' || $cmd eq 'create' ||
74 $cmd eq 'set' || $cmd eq 'delete' ||$cmd eq 'help' ) {
75 pve_command
([ $cmd, @ARGV], $opt_nooutput);
78 print_usage
("unknown command '$cmd'");
83 if (scalar (@ARGV) != 0) {
88 print "entering PVE shell - type 'help' for help\n";
90 my $term = new Term
::ReadLine
('pvesh');
91 my $attribs = $term->Attribs;
96 my ($dir, undef, $rest) = $text =~ m
|^(.*/)?(([^/]*))?
$|;
97 my $path = abs_path
($cdir, $dir);
101 my $di = dir_info
($path);
102 if (my $children = $di->{children
}) {
103 foreach my $c (@$children) {
104 if ($c =~ /^\Q$rest/) {
105 my $new = $dir ?
"$dir$c" : $c;
111 if (scalar(@res) == 0) {
113 } elsif (scalar(@res) == 1) {
114 return ($res[0], $res[0], "$res[0]/");
117 # lcd : lowest common denominator
120 for (my $i = 1; $i <= length($tmp); $i++) {
122 foreach my $p (@res) {
123 if (substr($tmp, 0, $i) ne substr($p, 0, $i)) {
129 $lcd = substr($tmp, 0, $i);
138 # just to avoid an endless loop (called by attempted_completion_function)
139 $attribs->{completion_entry_function
} = sub {
140 my($text, $state) = @_;
144 $attribs->{attempted_completion_function
} = sub {
145 my ($text, $line, $start) = @_;
147 my $prefix = substr($line, 0, $start);
148 if ($prefix =~ /^\s*$/) { # first word (command completeion)
149 $attribs->{completion_word
} = [qw(help ls cd get set create delete quit)];
150 return $term->completion_matches($text, $attribs->{list_completion_function
});
153 if ($prefix =~ /^\s*\S+\s+$/) { # second word (path completion)
154 return complete_path
($text);
161 my ($current, $path) = @_;
165 return $current if !defined($path);
167 $ret = '' if $path =~ m
|^\
/|;
169 foreach my $d (split (/\/+/ , $path)) {
172 } elsif ($d eq '..') {
173 $ret = dirname
($ret);
174 $ret = '' if $ret eq '.';
187 my $read_password = sub {
188 my $attribs = $term->Attribs;
189 my $old = $attribs->{redisplay_function
};
190 $attribs->{redisplay_function
} = $attribs->{shadow_redisplay
};
191 my $input = $term->readline('password: ');
192 my $conf = $term->readline('Retype new password: ');
193 $attribs->{redisplay_function
} = $old;
195 # remove password from history
196 if ($term->Features->{autohistory
}) {
197 my $historyPosition = $term->where_history();
198 $term->remove_history($historyPosition);
199 $term->remove_history($historyPosition - 1);
202 die "Passwords do not match.\n" if ($input ne $conf);
206 sub reverse_map_cmd
{
216 my $cmd = $mmap->{$method};
218 die "got strange value for method ('$method') - internal error" if !$cmd;
234 my $method = $mmap->{$cmd};
236 die "unable to map method" if !$method;
242 my ($info, $uri_param) = @_;
244 if ($info->{proxyto
} || $info->{proxyto_callback
}) {
245 my $node = PVE
::API2Tools
::resolve_proxyto
(
246 $rpcenv, $info->{proxyto_callback
}, $info->{proxyto
}, $uri_param);
248 if ($node ne 'localhost' && ($node ne PVE
::INotify
::nodename
())) {
249 die "proxy loop detected - aborting\n" if $disable_proxy;
250 my $remip = PVE
::Cluster
::remote_node_ip
($node);
251 return ($node, $remip);
259 my ($node, $remip, $dir, $cmd, $args) = @_;
261 my $cmdargs = [String
::ShellQuote
::shell_quote
(@$args)];
262 my $remcmd = ['ssh', '-o', 'BatchMode=yes', "root\@$remip",
263 'pvesh', '--noproxy', $cmd, $dir, @$cmdargs];
265 system(@$remcmd) == 0 || die "proxy handler failed\n";
269 my ($dir, $cmd, $args, $nooutput) = @_;
271 my $method = map_cmd
($cmd);
274 my ($handler, $info) = PVE
::API2-
>find_handler($method, $dir, $uri_param);
275 if (!$handler || !$info) {
276 die "no '$cmd' handler for '$dir'\n";
279 my ($node, $remip) = check_proxyto
($info, $uri_param);
280 return proxy_handler
($node, $remip, $dir, $cmd, $args) if $node;
282 my $data = $handler->cli_handler("$cmd $dir", $info->{name
}, $args, [], $uri_param, $read_password);
286 warn "200 OK\n"; # always print OK status if successful
288 if ($info && $info->{returns
} && $info->{returns
}->{type
}) {
289 my $rtype = $info->{returns
}->{type
};
291 return if $rtype eq 'null';
293 if ($rtype eq 'string') {
294 print $data if $data;
299 print to_json
($data, {utf8
=> 1, allow_nonref
=> 1, canonical
=> 1, pretty
=> 1 });
304 sub find_resource_methods
{
305 my ($path, $ihash) = @_;
307 for my $method (qw(GET POST PUT DELETE)) {
310 my ($handler, $info) = PVE
::API2-
>find_handler($method, $path, $uri_param, \
$path_match);
311 if ($handler && $info && !$ihash->{$info}) {
316 uri_param
=> $uri_param,
323 my ($path, $opts) = @_;
327 find_resource_methods
($path, $ihash);
329 if (!scalar(keys(%$ihash))) {
330 die "no such resource\n";
333 my $di = dir_info
($path);
334 if (my $children = $di->{children
}) {
335 foreach my $c (@$children) {
336 my $cp = abs_path
($path, $c);
337 find_resource_methods
($cp, $ihash);
341 foreach my $mi (sort { $a->{path
} cmp $b->{path
} } values %$ihash) {
342 my $method = $mi->{info
}->{method};
344 # we skip index methods for now.
345 next if ($method eq 'GET') && PVE
::JSONSchema
::method_get_child_link
($mi->{info
});
347 my $path = $mi->{path
};
348 $path =~ s
|/+$||; # remove trailing slash
350 my $cmd = reverse_map_cmd
($method);
352 print $mi->{handler
}->usage_str($mi->{info
}->{name
}, "$cmd $path", [], $mi->{uri_param
},
353 $opts->{verbose
} ?
'full' : 'short', 1);
354 print "\n\n" if $opts->{verbose
};
364 my ($handler, $info) = PVE
::API2-
>find_handler('GET', $path);
365 if (!($handler && $info)) {
368 if (PVE
::JSONSchema
::method_get_child_link
($info)) {
375 ($handler, $info) = PVE
::API2-
>find_handler('PUT', $path);
376 if (!($handler && $info)) {
382 ($handler, $info) = PVE
::API2-
>find_handler('POST', $path);
383 if (!($handler && $info)) {
389 ($handler, $info) = PVE
::API2-
>find_handler('DELETE', $path);
390 if (!($handler && $info)) {
399 sub extract_children
{
400 my ($lnk, $data) = @_;
404 return $res if !($lnk && $data);
406 my $href = $lnk->{href
};
407 if ($href =~ m/^\{(\S+)\}$/) {
410 foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
412 my $value = $elem->{$prop};
423 my $res = { path
=> $path };
425 my ($handler, $info, $pm) = PVE
::API2-
>find_handler('GET', $path, $uri_param);
426 if ($handler && $info) {
428 my $data = $handler->handle($info, $uri_param);
429 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
430 $res->{children
} = extract_children
($lnk, $data);
437 my ($dir, $args) = @_;
440 my ($handler, $info) = PVE
::API2-
>find_handler('GET', $dir, $uri_param);
441 if (!$handler || !$info) {
442 die "no such resource\n";
445 if (!PVE
::JSONSchema
::method_get_child_link
($info)) {
446 die "resource does not define child links\n";
449 my ($node, $remip) = check_proxyto
($info, $uri_param);
450 return proxy_handler
($node, $remip, $dir, 'ls', $args) if $node;
453 my $data = $handler->cli_handler("ls $dir", $info->{name
}, $args, [], $uri_param, $read_password);
454 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
455 my $children = extract_children
($lnk, $data);
457 foreach my $c (@$children) {
458 my $cap = resource_cap
(abs_path
($dir, $c));
465 my ($args, $nooutput) = @_;
467 PVE
::Cluster
::cfs_update
();
469 $rpcenv->init_request();
471 my $cmd = shift @$args;
475 my $path = shift @$args;
477 die "usage: cd [dir]\n" if scalar(@$args);
479 if (!defined($path)) {
483 my $new_dir = abs_path
($cdir, $path);
484 my ($handler, $info) = PVE
::API2-
>find_handler('GET', $new_dir);
485 die "no such resource\n" if !$handler;
489 } elsif ($cmd eq 'help') {
491 my $help_usage_error = sub {
492 die "usage: help [path] [--verbose]\n";
497 &$help_usage_error() if !Getopt
::Long
::GetOptionsFromArray
($args, $opts, 'verbose');
500 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
501 $path = shift @$args;
504 &$help_usage_error() if scalar(@$args);
506 print "help [path] [--verbose]\n";
508 print "ls [path]\n\n";
510 print_help
(abs_path
($cdir, $path), $opts);
512 } elsif ($cmd eq 'ls') {
514 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
515 $path = shift @$args;
518 list_dir
(abs_path
($cdir, $path), $args);
520 } elsif ($cmd =~ m/^get|delete|set$/) {
523 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
524 $path = shift @$args;
527 call_method
(abs_path
($cdir, $path), $cmd, $args);
529 } elsif ($cmd eq 'create') {
532 if (scalar(@$args) && $args->[0] !~ m/^\-/) {
533 $path = shift @$args;
536 call_method
(abs_path
($cdir, $path), $cmd, $args, $nooutput);
539 die "unknown command '$cmd'\n";
545 while (defined ($input = $term->readline("pve:/$cdir> "))) {
548 next if $input =~ m/^\s*$/;
550 if ($input =~ m/^\s*q(uit)?\s*$/) {
554 # add input to history if it gets not
555 # automatically added
556 if (!$term->Features->{autohistory
}) {
557 $term->addhistory($input);
561 my $args = [ shellwords
($input) ];
571 pvesh - shell interface to the Promox VE API
575 pvesh [get|set|create|delete|help] [REST API path] [--verbose]
579 pvesh provides a shell-like interface to the Proxmox VE REST API, in two different modes:
585 when called without parameters, pvesh starts an interactive client, where you can navigate
586 in the different parts of the API
590 when started with parameters pvesh will send a command to the corresponding REST url, and will
591 return the JSON formatted output
597 get the list of nodes in my cluster
601 get a list of available options for the datacenter
603 pvesh help cluster/options -v
605 set the HTMl5 NoVNC console as the default console for the datacenter
607 pvesh set cluster/options -console html5