]>
git.proxmox.com Git - pve-manager.git/blob - bin/pvesh
7 use HTTP
::Status
qw(:constants :is status_message);
8 use String
::ShellQuote
;
9 use PVE
::JSONSchema
qw(get_standard_option);
13 use PVE
::RPCEnvironment
;
15 use PVE
::CLIFormatter
;
21 use base
qw(PVE::CLIHandler);
23 my $disable_proxy = 0;
31 if ($ARGV[0] eq '--noproxy') {
35 } elsif ($ARGV[0] eq '--nooutput') {
36 # we use this when starting task in CLI (suppress printing upid)
37 # for example 'pvesh --nooutput create /nodes/localhost/stopall'
45 sub setup_environment
{
46 PVE
::RPCEnvironment-
>setup_default_cli_env();
49 sub complete_api_path
{
52 my ($dir, undef, $rest) = $text =~ m
|^(.*/)?(([^/]*))?
$|;
54 my $path = $dir // ''; # copy
62 my $di = dir_info
($path);
63 if (my $children = $di->{children
}) {
64 foreach my $c (@$children) {
65 if ($c =~ /^\Q$rest/) {
66 my $new = $dir ?
"$dir$c" : $c;
72 if (scalar(@$res) == 1) {
73 return [$res->[0], "$res->[0]/"];
87 my ($info, $uri_param) = @_;
89 my $rpcenv = PVE
::RPCEnvironment-
>get();
91 if ($info->{proxyto
} || $info->{proxyto_callback
}) {
92 my $node = PVE
::API2Tools
::resolve_proxyto
(
93 $rpcenv, $info->{proxyto_callback
}, $info->{proxyto
}, $uri_param);
95 if ($node ne 'localhost' && ($node ne PVE
::INotify
::nodename
())) {
96 die "proxy loop detected - aborting\n" if $disable_proxy;
97 my $remip = PVE
::Cluster
::remote_node_ip
($node);
98 return ($node, $remip);
106 my ($node, $remip, $path, $cmd, $param, $noout) = @_;
109 foreach my $key (keys %$param) {
110 push @$args, "--$key", $param->{$key};
113 push @$args, '--quiet' if $noout;
115 my $remcmd = ['ssh', '-o', 'BatchMode=yes', "root\@$remip",
116 'pvesh', '--noproxy', $cmd, $path,
119 if (scalar(@$args)) {
120 my $cmdargs = [String
::ShellQuote
::shell_quote
(@$args)];
121 push @$remcmd, @$cmdargs;
125 PVE
::Tools
::run_command
($remcmd, errmsg
=> "proxy handler failed",
126 outfunc
=> sub { $json .= shift });
128 return decode_json
($json);
131 sub extract_children
{
132 my ($lnk, $data) = @_;
136 return $res if !($lnk && $data);
138 my $href = $lnk->{href
};
139 if ($href =~ m/^\{(\S+)\}$/) {
142 foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
144 my $value = $elem->{$prop};
155 my $res = { path
=> $path };
157 my ($handler, $info, $pm) = PVE
::API2-
>find_handler('GET', $path, $uri_param);
158 if ($handler && $info) {
160 my $data = $handler->handle($info, $uri_param);
161 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
162 $res->{children
} = extract_children
($lnk, $data);
169 # dynamically update schema definition
170 # like: pvesh <get|set|create|delete|help> <path>
172 sub extract_path_info
{
173 my ($uri_param) = @_;
177 my $test_path_properties = sub {
178 my ($method, $path) = @_;
179 (undef, $info) = PVE
::API2-
>find_handler($method, $path, $uri_param);
182 if (defined(my $cmd = $ARGV[0])) {
183 if (my $method = $method_map->{$cmd}) {
184 if (my $path = $ARGV[1]) {
185 $test_path_properties->($method, $path);
187 } elsif ($cmd eq 'bashcomplete') {
188 my $cmdline = substr($ENV{COMP_LINE
}, 0, $ENV{COMP_POINT
});
189 my $args = PVE
::Tools
::split_args
($cmdline);
190 if (defined(my $cmd = $args->[1])) {
191 if (my $method = $method_map->{$cmd}) {
192 if (my $path = $args->[2]) {
193 $test_path_properties->($method, $path);
204 my $path_properties = {};
205 my $path_returns = { type
=> 'null' };
207 my $api_path_property = {
208 description
=> "API path.",
211 my ($cmd, $pname, $cur, $args) = @_;
212 return complete_api_path
($cur);
217 if (my $info = extract_path_info
($uri_param)) {
218 foreach my $key (keys %{$info->{parameters
}->{properties
}}) {
219 next if defined($uri_param->{$key});
220 $path_properties->{$key} = $info->{parameters
}->{properties
}->{$key};
222 $path_returns = $info->{returns
};
225 $path_properties->{format
} = get_standard_option
('pve-output-format');
226 $path_properties->{api_path
} = $api_path_property;
227 $path_properties->{noproxy
} = {
228 description
=> "Disable automatic proxying.",
232 $path_properties->{quiet
} = {
233 description
=> "Suppress printing results.",
238 my $format_result = sub {
239 my ($data, $result_schema, $options) = @_;
241 return if $opt_nooutput || ($options->{format
}//'') eq 'none';
243 PVE
::CLIFormatter
::print_api_result
($data, $path_returns, undef, $options);
246 sub call_api_method
{
247 my ($cmd, $param) = @_;
249 my $method = $method_map->{$cmd} || die "unable to map command '$cmd'";
251 my $path = PVE
::Tools
::extract_param
($param, 'api_path');
252 die "missing API path\n" if !defined($path);
255 my ($handler, $info) = PVE
::API2-
>find_handler($method, $path, $uri_param);
256 if (!$handler || !$info) {
257 die "no '$cmd' handler for '$path'\n";
260 my ($node, $remip) = check_proxyto
($info, $uri_param);
261 return proxy_handler
($node, $remip, $path, $cmd, $param, $opt_nooutput) if $node;
263 foreach my $p (keys %$uri_param) {
264 $param->{$p} = $uri_param->{$p};
267 my $data = $handler->handle($info, $param);
272 __PACKAGE__-
>register_method ({
276 description
=> "Call API GET on <api_path>.",
278 additionalProperties
=> 0,
279 properties
=> $path_properties,
281 returns
=> $path_returns,
285 return call_api_method
('get', $param);
288 __PACKAGE__-
>register_method ({
292 description
=> "Call API PUT on <api_path>.",
294 additionalProperties
=> 0,
295 properties
=> $path_properties,
297 returns
=> $path_returns,
301 return call_api_method
('set', $param);
304 __PACKAGE__-
>register_method ({
308 description
=> "Call API POST on <api_path>.",
310 additionalProperties
=> 0,
311 properties
=> $path_properties,
313 returns
=> $path_returns,
317 return call_api_method
('create', $param);
320 __PACKAGE__-
>register_method ({
324 description
=> "Call API DELETE on <api_path>.",
326 additionalProperties
=> 0,
327 properties
=> $path_properties,
329 returns
=> $path_returns,
333 return call_api_method
('delete', $param);
336 __PACKAGE__-
>register_method ({
340 description
=> "print API usage information for <api_path>.",
342 additionalProperties
=> 0,
344 api_path
=> $api_path_property,
346 description
=> "Verbose output format.",
351 description
=> "Including schema for returned data.",
356 description
=> "API command.",
358 enum
=> [ keys %$method_map ],
363 returns
=> { type
=> 'null' },
367 $opt_nooutput = 1; # we print directly
369 my $path = $param->{api_path
};
372 foreach my $cmd (qw(get set create delete)) {
373 next if $param->{command
} && $cmd ne $param->{command
};
374 my $method = $method_map->{$cmd};
375 my ($handler, $info) = PVE
::API2-
>find_handler($method, $path);
379 if ($param->{verbose
}) {
380 print $handler->usage_str(
381 $info->{name
}, "pvesh $cmd $path", undef, {}, 'full');
384 print "USAGE: " . $handler->usage_str(
385 $info->{name
}, "pvesh $cmd $path", undef, {}, 'short');
387 if ($param-> {returns
}) {
388 my $schema = to_json
($info->{returns
}, {utf8
=> 1, canonical
=> 1, pretty
=> 1 });
389 print "RETURNS: $schema\n";
394 if ($param->{command
}) {
395 die "no '$param->{command}' handler for '$path'\n";
397 die "no such resource '$path'\n"
405 usage
=> [ __PACKAGE__
, 'usage', ['api_path'], {}, $format_result ],
406 get
=> [ __PACKAGE__
, 'get', ['api_path'], {}, $format_result ],
407 set
=> [ __PACKAGE__
, 'set', ['api_path'], {}, $format_result ],
408 create
=> [ __PACKAGE__
, 'create', ['api_path'], {}, $format_result ],
409 delete => [ __PACKAGE__
, 'delete', ['api_path'], {}, $format_result ],
414 __PACKAGE__-
>run_cli_handler();
421 pvesh - shell interface to the Promox VE API
425 pvesh [get|set|create|delete|usage] [REST API path] [--verbose]
429 pvesh provides a command line interface to the Proxmox VE REST API.
433 get the list of nodes in my cluster
437 get a list of available options for the datacenter
439 pvesh usage cluster/options -v
441 set the HTMl5 NoVNC console as the default console for the datacenter
443 pvesh set cluster/options -console html5