]>
Commit | Line | Data |
---|---|---|
29505e2c DM |
1 | package PVE::APIClient::Helpers; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
ab79ce78 | 6 | use Storable; |
29505e2c DM |
7 | use JSON; |
8 | use PVE::APIClient::Exception qw(raise); | |
29505e2c DM |
9 | use Encode::Locale; |
10 | use Encode; | |
11 | use HTTP::Status qw(:constants); | |
12 | ||
13 | my $pve_api_definition; | |
14 | my $pve_api_path_hash; | |
15 | ||
ab79ce78 | 16 | my $pve_api_definition_fn = "/usr/share/pve-client/pve-api-definition.dat"; |
29505e2c | 17 | |
b133a905 DM |
18 | my $method_map = { |
19 | create => 'POST', | |
20 | set => 'PUT', | |
21 | get => 'GET', | |
22 | delete => 'DELETE', | |
23 | }; | |
24 | ||
29505e2c DM |
25 | my $build_pve_api_path_hash; |
26 | $build_pve_api_path_hash = sub { | |
27 | my ($tree) = @_; | |
28 | ||
29 | my $class = ref($tree); | |
30 | return $tree if !$class; | |
31 | ||
32 | if ($class eq 'ARRAY') { | |
33 | foreach my $el (@$tree) { | |
34 | $build_pve_api_path_hash->($el); | |
35 | } | |
36 | } elsif ($class eq 'HASH') { | |
37 | if (defined($tree->{leaf}) && defined(my $path = $tree->{path})) { | |
38 | $pve_api_path_hash->{$path} = $tree; | |
39 | } | |
40 | foreach my $k (keys %$tree) { | |
41 | $build_pve_api_path_hash->($tree->{$k}); | |
42 | } | |
43 | } | |
44 | }; | |
45 | ||
b5aeedb0 | 46 | my $default_output_format = 'text'; |
61ad3df5 DM |
47 | my $client_output_format = $default_output_format; |
48 | ||
49 | sub set_output_format { | |
50 | my ($format) = @_; | |
51 | ||
52 | if (!defined($format)) { | |
53 | $client_output_format = $default_output_format; | |
54 | } else { | |
55 | $client_output_format = $format; | |
56 | } | |
57 | } | |
58 | ||
59 | sub get_output_format { | |
60 | return $client_output_format; | |
61 | } | |
62 | ||
4db9ff67 DM |
63 | sub print_result { |
64 | my ($data, $result_schema) = @_; | |
65 | ||
66 | my $format = get_output_format(); | |
67 | ||
68 | return if $result_schema->{type} eq 'null'; | |
69 | ||
70 | # TODO: implement different output formats ($format) | |
71 | ||
72 | if ($format eq 'json') { | |
73 | print to_json($data, {utf8 => 1, allow_nonref => 1, canonical => 1, pretty => 1 }); | |
b5aeedb0 | 74 | } elsif ($format eq 'text') { |
4db9ff67 DM |
75 | my $type = $result_schema->{type}; |
76 | if ($type eq 'object') { | |
77 | die "implement me"; | |
78 | } elsif ($type eq 'array') { | |
79 | my $item_type = $result_schema->{items}->{type}; | |
80 | if ($item_type eq 'object') { | |
81 | die "implement me"; | |
82 | } elsif ($item_type eq 'array') { | |
83 | die "implement me"; | |
84 | } else { | |
85 | foreach my $el (@$data) { | |
86 | print "$el\n" | |
87 | } | |
88 | } | |
89 | } else { | |
90 | print "$data\n"; | |
91 | } | |
92 | } else { | |
93 | die "internal error: unknown output format"; # should not happen | |
94 | } | |
95 | } | |
96 | ||
29505e2c DM |
97 | sub get_api_definition { |
98 | ||
99 | if (!defined($pve_api_definition)) { | |
29505e2c DM |
100 | open(my $fh, '<', $pve_api_definition_fn) || |
101 | die "unable to open '$pve_api_definition_fn' - $!\n"; | |
ab79ce78 | 102 | $pve_api_definition = Storable::fd_retrieve($fh); |
29505e2c DM |
103 | $build_pve_api_path_hash->($pve_api_definition); |
104 | } | |
105 | ||
29505e2c DM |
106 | return $pve_api_definition; |
107 | } | |
108 | ||
109 | sub lookup_api_method { | |
635c0511 | 110 | my ($path, $method, $noerr) = @_; |
29505e2c DM |
111 | |
112 | get_api_definition(); # make sure API data is loaded | |
113 | ||
635c0511 DM |
114 | my $info = $pve_api_path_hash->{$path}; |
115 | ||
116 | if (!$info) { | |
117 | return undef if $noerr; | |
29505e2c | 118 | die "unable to find API info for path '$path'\n"; |
635c0511 | 119 | } |
29505e2c | 120 | |
635c0511 DM |
121 | my $data = $info->{info}->{$method}; |
122 | ||
123 | if (!$data) { | |
124 | return undef if $noerr; | |
29505e2c | 125 | die "unable to find API method '$method' for path '$path'\n"; |
635c0511 | 126 | } |
29505e2c DM |
127 | |
128 | return $data; | |
129 | } | |
130 | ||
635c0511 DM |
131 | sub complete_api_call_options { |
132 | my ($cmd, $prop, $prev, $cur, $args) = @_; | |
133 | ||
134 | my $print_result = sub { | |
135 | foreach my $p (@_) { | |
136 | print "$p\n" if $p =~ m/^$cur/; | |
137 | } | |
138 | }; | |
139 | ||
140 | my $print_parameter_completion = sub { | |
141 | my ($pname) = @_; | |
142 | my $d = $prop->{$pname}; | |
143 | if ($d->{completion}) { | |
144 | my $vt = ref($d->{completion}); | |
145 | if ($vt eq 'CODE') { | |
146 | my $res = $d->{completion}->($cmd, $pname, $cur, $args); | |
147 | &$print_result(@$res); | |
148 | } | |
149 | } elsif ($d->{type} eq 'boolean') { | |
150 | &$print_result('0', '1'); | |
151 | } elsif ($d->{enum}) { | |
152 | &$print_result(@{$d->{enum}}); | |
153 | } | |
154 | }; | |
155 | ||
156 | my @option_list = (); | |
157 | foreach my $key (keys %$prop) { | |
158 | push @option_list, "--$key"; | |
159 | } | |
160 | ||
161 | if ($cur =~ m/^-/) { | |
162 | &$print_result(@option_list); | |
163 | return; | |
164 | } | |
165 | ||
166 | if ($prev =~ m/^--?(.+)$/ && $prop->{$1}) { | |
167 | my $pname = $1; | |
168 | &$print_parameter_completion($pname); | |
169 | return; | |
170 | } | |
171 | ||
172 | &$print_result(@option_list); | |
173 | } | |
174 | ||
175 | sub complete_api_path { | |
176 | my ($text) = @_; | |
177 | ||
178 | get_api_definition(); # make sure API data is loaded | |
179 | ||
180 | $text =~ s!^/!!; | |
181 | ||
182 | my ($dir, $rest) = $text =~ m|^(?:(.*)/)?(?:([^/]*))?$|; | |
183 | ||
184 | my $info; | |
185 | if (!defined($dir)) { | |
186 | $dir = ''; | |
187 | $info = { children => $pve_api_definition }; | |
188 | } else { | |
189 | $info = $pve_api_path_hash->{"/$dir"}; | |
190 | } | |
191 | ||
b133a905 | 192 | my $res = []; |
635c0511 DM |
193 | if ($info) { |
194 | if (my $children = $info->{children}) { | |
195 | foreach my $c (@$children) { | |
196 | if ($c->{path} =~ m!\Q$dir/$rest!) { | |
b133a905 DM |
197 | push @$res, $c->{path}; |
198 | push @$res, "$c->{path}/" if $c->{children}; | |
635c0511 DM |
199 | } |
200 | } | |
201 | } | |
202 | } | |
b133a905 DM |
203 | return $res; |
204 | } | |
205 | ||
206 | # test for command lines with api calls (or similar bash completion calls): | |
207 | # example1: pveclient api get remote1 /cluster | |
208 | sub extract_path_info { | |
209 | ||
210 | my $info; | |
211 | ||
212 | my $test_path_properties = sub { | |
213 | my ($args) = @_; | |
214 | ||
215 | return if scalar(@$args) < 5; | |
216 | return if $args->[1] ne 'api'; | |
217 | ||
218 | my $path = $args->[4]; | |
219 | if (my $method = $method_map->{$args->[2]}) { | |
220 | $info = lookup_api_method($path, $method, 1); | |
221 | } | |
222 | }; | |
223 | ||
224 | if (defined(my $cmd = $ARGV[0])) { | |
225 | if ($cmd eq 'api') { | |
226 | $test_path_properties->([$0, @ARGV]); | |
227 | } elsif ($cmd eq 'bashcomplete') { | |
228 | my $cmdline = substr($ENV{COMP_LINE}, 0, $ENV{COMP_POINT}); | |
ca3269f4 | 229 | my $args = PVE::APIClient::Tools::split_args($cmdline); |
b133a905 DM |
230 | $test_path_properties->($args); |
231 | } | |
232 | } | |
233 | ||
234 | return $info; | |
635c0511 DM |
235 | } |
236 | ||
2f964a75 RJ |
237 | sub get_vmid_resource { |
238 | my ($conn, $vmid) = @_; | |
239 | ||
240 | my $resources = $conn->get('api2/json/cluster/resources', {type => 'vm'}); | |
241 | ||
242 | my $resource; | |
243 | for my $tmp (@$resources) { | |
244 | if ($tmp->{vmid} eq $vmid) { | |
245 | $resource = $tmp; | |
246 | last; | |
247 | } | |
248 | } | |
249 | ||
250 | if (!defined($resource)) { | |
251 | die "\"$vmid\" not found"; | |
252 | } | |
253 | ||
254 | return $resource; | |
255 | } | |
256 | ||
257 | sub poll_task { | |
258 | my ($conn, $node, $upid) = @_; | |
259 | ||
260 | my $path = "api2/json/nodes/$node/tasks/$upid/status"; | |
261 | ||
262 | my $task_status; | |
263 | while(1) { | |
264 | $task_status = $conn->get($path, {}); | |
265 | ||
266 | if ($task_status->{status} eq "stopped") { | |
267 | last; | |
268 | } | |
269 | ||
270 | sleep(10); | |
271 | } | |
272 | ||
273 | return $task_status->{exitstatus}; | |
274 | } | |
275 | ||
29505e2c | 276 | 1; |