1 package PVE
::APIClient
::Helpers
;
8 use File
::Path
qw(make_path);
10 use PVE
::APIClient
::Exception
qw(raise);
13 use HTTP
::Status
qw(:constants);
15 my $pve_api_definition;
17 my $pve_api_definition_fn = "/usr/share/pve-client/pve-api-definition.dat";
26 my $default_output_format = 'text';
27 my $client_output_format = $default_output_format;
29 sub set_output_format
{
32 if (!defined($format)) {
33 $client_output_format = $default_output_format;
35 $client_output_format = $format;
39 sub get_output_format
{
40 return $client_output_format;
44 my ($data, $result_schema) = @_;
46 my $format = get_output_format
();
48 return if $result_schema->{type
} eq 'null';
50 # TODO: implement different output formats ($format)
52 if ($format eq 'json') {
53 print to_json
($data, {utf8
=> 1, allow_nonref
=> 1, canonical
=> 1, pretty
=> 1 });
54 } elsif ($format eq 'text') {
55 my $type = $result_schema->{type
};
56 if ($type eq 'object') {
58 } elsif ($type eq 'array') {
59 my $item_type = $result_schema->{items
}->{type
};
60 if ($item_type eq 'object') {
62 } elsif ($item_type eq 'array') {
65 foreach my $el (@$data) {
73 die "internal error: unknown output format"; # should not happen
77 sub get_api_definition
{
79 if (!defined($pve_api_definition)) {
80 open(my $fh, '<', $pve_api_definition_fn) ||
81 die "unable to open '$pve_api_definition_fn' - $!\n";
82 $pve_api_definition = Storable
::fd_retrieve
($fh);
85 return $pve_api_definition;
88 my $map_path_to_info = sub {
89 my ($child_list, $stack, $uri_param) = @_;
91 while (defined(my $comp = shift @$stack)) {
92 foreach my $child (@$child_list) {
93 my $text = $child->{text
};
97 } elsif ($text =~ m/^\{(\S+)\}$/) {
99 $uri_param->{$1} = $comp;
101 next; # text next child
103 if ($child->{leaf
} || !scalar(@$stack)) {
106 $child_list = $child->{children
};
107 last; # test next path component
114 sub find_method_info
{
115 my ($path, $method, $uri_param, $noerr) = @_;
119 my $stack = [ grep { length($_) > 0 } split('\/+' , $path)]; # skip empty fragments
121 my $child = $map_path_to_info->(get_api_definition
(), $stack, $uri_param);
123 if (!($child && $child->{info
} && $child->{info
}->{$method})) {
124 return undef if $noerr;
125 die "unable to find API method '$method' for path '$path'\n";
128 return $child->{info
}->{$method};
131 sub complete_api_call_options
{
132 my ($cmd, $prop, $prev, $cur, $args) = @_;
134 my $print_result = sub {
136 print "$p\n" if $p =~ m/^$cur/;
140 my $print_parameter_completion = sub {
142 my $d = $prop->{$pname};
143 if ($d->{completion
}) {
144 my $vt = ref($d->{completion
});
146 my $res = $d->{completion
}->($cmd, $pname, $cur, $args);
147 &$print_result(@$res);
149 } elsif ($d->{type
} eq 'boolean') {
150 &$print_result('0', '1');
151 } elsif ($d->{enum
}) {
152 &$print_result(@{$d->{enum
}});
156 my @option_list = ();
157 foreach my $key (keys %$prop) {
158 push @option_list, "--$key";
162 &$print_result(@option_list);
166 if ($prev =~ m/^--?(.+)$/ && $prop->{$1}) {
168 &$print_parameter_completion($pname);
172 &$print_result(@option_list);
175 sub complete_api_path
{
178 get_api_definition
(); # make sure API data is loaded
182 my ($dir, $rest) = $text =~ m
|^(?
:(.*)/)?(?:([^/]*))?
$|;
185 if (!defined($dir)) {
187 $info = { children
=> $pve_api_definition };
189 my $stack = [ grep { length($_) > 0 } split('\/+' , $dir)]; # skip empty fragments
190 $info = $map_path_to_info->($pve_api_definition, $stack, {});
195 my $prefix = length($dir) ?
"/$dir/" : '/';
196 if (my $children = $info->{children
}) {
197 foreach my $c (@$children) {
198 my $ctext = $c->{text
};
199 if ($ctext =~ m/^\{(\S+)\}$/) {
200 push @$res, "$prefix$ctext";
201 push @$res, "$prefix$ctext/";
203 push @$res, "$prefix$rest";
204 push @$res, "$prefix$rest/";
206 } elsif ($ctext =~ m/^\Q$rest/) {
207 push @$res, "$prefix$ctext";
208 push @$res, "$prefix$ctext/" if $c->{children
};
217 # test for command lines with api calls (or similar bash completion calls):
218 # example1: pveclient api get remote1 /cluster
219 sub extract_path_info
{
220 my ($uri_param) = @_;
224 my $test_path_properties = sub {
227 return if scalar(@$args) < 5;
228 return if $args->[1] ne 'api';
230 my $path = $args->[4];
231 if (my $method = $method_map->{$args->[2]}) {
232 $info = find_method_info
($path, $method, $uri_param, 1);
236 if (defined(my $cmd = $ARGV[0])) {
238 $test_path_properties->([$0, @ARGV]);
239 } elsif ($cmd eq 'bashcomplete') {
240 my $cmdline = substr($ENV{COMP_LINE
}, 0, $ENV{COMP_POINT
});
241 my $args = PVE
::APIClient
::Tools
::split_args
($cmdline);
242 $test_path_properties->($args);
249 sub get_vmid_resource
{
250 my ($conn, $vmid) = @_;
252 my $resources = $conn->get('api2/json/cluster/resources', {type
=> 'vm'});
255 for my $tmp (@$resources) {
256 if ($tmp->{vmid
} eq $vmid) {
262 if (!defined($resource)) {
263 die "\"$vmid\" not found
";
270 my ($conn, $node, $upid) = @_;
272 my $path = "api2
/json/nodes
/$node/tasks/$upid/status";
276 $task_status = $conn->get($path, {});
278 if ($task_status->{status} eq "stopped
") {
285 return $task_status->{exitstatus};
288 sub configuration_directory {
290 my $home = $ENV{HOME} // '';
291 my $xdg = $ENV{XDG_CONFIG_HOME} // '';
293 my $subdir = "pveclient
";
295 return "$xdg/$subdir" if length($xdg);
297 return "$home/.config
/$subdir" if length($home);
299 die "neither XDG_CONFIG_HOME nor HOME environment variable set
\n";
302 my $ticket_cache_filename = "/.tickets
";
304 sub ticket_cache_lookup {
307 my $dir = configuration_directory();
308 my $filename = "$dir/$ticket_cache_filename";
311 eval { $data = from_json(PVE::APIClient::Tools::file_get_contents($filename)); };
314 my $ticket = $data->{$remote};
315 return undef if !defined($ticket);
318 my $max_age = 3600*2 - 60;
320 if ($ticket =~ m/:([a-fA-F0-9]{8})::/) {
323 my $age = $ctime - $ttime;
325 return $ticket if ($age > $min_age) && ($age < $max_age);
331 sub ticket_cache_update {
332 my ($remote, $ticket) = @_;
334 my $dir = configuration_directory();
335 my $filename = "$dir/$ticket_cache_filename";
341 my $raw = PVE::APIClient::Tools::file_get_contents($filename);
342 eval { $data = from_json($raw); };
345 $data->{$remote} = $ticket;
347 PVE::APIClient::Tools::file_set_contents($filename, to_json($data), 0600);
350 PVE::APIClient::Tools::lock_file($filename, undef, $code);