]>
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 | ||
61ad3df5 DM |
46 | my $default_output_format = 'table'; |
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 | ||
29505e2c DM |
63 | sub get_api_definition { |
64 | ||
65 | if (!defined($pve_api_definition)) { | |
29505e2c DM |
66 | open(my $fh, '<', $pve_api_definition_fn) || |
67 | die "unable to open '$pve_api_definition_fn' - $!\n"; | |
ab79ce78 | 68 | $pve_api_definition = Storable::fd_retrieve($fh); |
29505e2c DM |
69 | $build_pve_api_path_hash->($pve_api_definition); |
70 | } | |
71 | ||
29505e2c DM |
72 | return $pve_api_definition; |
73 | } | |
74 | ||
75 | sub lookup_api_method { | |
635c0511 | 76 | my ($path, $method, $noerr) = @_; |
29505e2c DM |
77 | |
78 | get_api_definition(); # make sure API data is loaded | |
79 | ||
635c0511 DM |
80 | my $info = $pve_api_path_hash->{$path}; |
81 | ||
82 | if (!$info) { | |
83 | return undef if $noerr; | |
29505e2c | 84 | die "unable to find API info for path '$path'\n"; |
635c0511 | 85 | } |
29505e2c | 86 | |
635c0511 DM |
87 | my $data = $info->{info}->{$method}; |
88 | ||
89 | if (!$data) { | |
90 | return undef if $noerr; | |
29505e2c | 91 | die "unable to find API method '$method' for path '$path'\n"; |
635c0511 | 92 | } |
29505e2c DM |
93 | |
94 | return $data; | |
95 | } | |
96 | ||
635c0511 DM |
97 | sub complete_api_call_options { |
98 | my ($cmd, $prop, $prev, $cur, $args) = @_; | |
99 | ||
100 | my $print_result = sub { | |
101 | foreach my $p (@_) { | |
102 | print "$p\n" if $p =~ m/^$cur/; | |
103 | } | |
104 | }; | |
105 | ||
106 | my $print_parameter_completion = sub { | |
107 | my ($pname) = @_; | |
108 | my $d = $prop->{$pname}; | |
109 | if ($d->{completion}) { | |
110 | my $vt = ref($d->{completion}); | |
111 | if ($vt eq 'CODE') { | |
112 | my $res = $d->{completion}->($cmd, $pname, $cur, $args); | |
113 | &$print_result(@$res); | |
114 | } | |
115 | } elsif ($d->{type} eq 'boolean') { | |
116 | &$print_result('0', '1'); | |
117 | } elsif ($d->{enum}) { | |
118 | &$print_result(@{$d->{enum}}); | |
119 | } | |
120 | }; | |
121 | ||
122 | my @option_list = (); | |
123 | foreach my $key (keys %$prop) { | |
124 | push @option_list, "--$key"; | |
125 | } | |
126 | ||
127 | if ($cur =~ m/^-/) { | |
128 | &$print_result(@option_list); | |
129 | return; | |
130 | } | |
131 | ||
132 | if ($prev =~ m/^--?(.+)$/ && $prop->{$1}) { | |
133 | my $pname = $1; | |
134 | &$print_parameter_completion($pname); | |
135 | return; | |
136 | } | |
137 | ||
138 | &$print_result(@option_list); | |
139 | } | |
140 | ||
141 | sub complete_api_path { | |
142 | my ($text) = @_; | |
143 | ||
144 | get_api_definition(); # make sure API data is loaded | |
145 | ||
146 | $text =~ s!^/!!; | |
147 | ||
148 | my ($dir, $rest) = $text =~ m|^(?:(.*)/)?(?:([^/]*))?$|; | |
149 | ||
150 | my $info; | |
151 | if (!defined($dir)) { | |
152 | $dir = ''; | |
153 | $info = { children => $pve_api_definition }; | |
154 | } else { | |
155 | $info = $pve_api_path_hash->{"/$dir"}; | |
156 | } | |
157 | ||
b133a905 | 158 | my $res = []; |
635c0511 DM |
159 | if ($info) { |
160 | if (my $children = $info->{children}) { | |
161 | foreach my $c (@$children) { | |
162 | if ($c->{path} =~ m!\Q$dir/$rest!) { | |
b133a905 DM |
163 | push @$res, $c->{path}; |
164 | push @$res, "$c->{path}/" if $c->{children}; | |
635c0511 DM |
165 | } |
166 | } | |
167 | } | |
168 | } | |
b133a905 DM |
169 | return $res; |
170 | } | |
171 | ||
172 | # test for command lines with api calls (or similar bash completion calls): | |
173 | # example1: pveclient api get remote1 /cluster | |
174 | sub extract_path_info { | |
175 | ||
176 | my $info; | |
177 | ||
178 | my $test_path_properties = sub { | |
179 | my ($args) = @_; | |
180 | ||
181 | return if scalar(@$args) < 5; | |
182 | return if $args->[1] ne 'api'; | |
183 | ||
184 | my $path = $args->[4]; | |
185 | if (my $method = $method_map->{$args->[2]}) { | |
186 | $info = lookup_api_method($path, $method, 1); | |
187 | } | |
188 | }; | |
189 | ||
190 | if (defined(my $cmd = $ARGV[0])) { | |
191 | if ($cmd eq 'api') { | |
192 | $test_path_properties->([$0, @ARGV]); | |
193 | } elsif ($cmd eq 'bashcomplete') { | |
194 | my $cmdline = substr($ENV{COMP_LINE}, 0, $ENV{COMP_POINT}); | |
ca3269f4 | 195 | my $args = PVE::APIClient::Tools::split_args($cmdline); |
b133a905 DM |
196 | $test_path_properties->($args); |
197 | } | |
198 | } | |
199 | ||
200 | return $info; | |
635c0511 DM |
201 | } |
202 | ||
2f964a75 RJ |
203 | sub get_vmid_resource { |
204 | my ($conn, $vmid) = @_; | |
205 | ||
206 | my $resources = $conn->get('api2/json/cluster/resources', {type => 'vm'}); | |
207 | ||
208 | my $resource; | |
209 | for my $tmp (@$resources) { | |
210 | if ($tmp->{vmid} eq $vmid) { | |
211 | $resource = $tmp; | |
212 | last; | |
213 | } | |
214 | } | |
215 | ||
216 | if (!defined($resource)) { | |
217 | die "\"$vmid\" not found"; | |
218 | } | |
219 | ||
220 | return $resource; | |
221 | } | |
222 | ||
223 | sub poll_task { | |
224 | my ($conn, $node, $upid) = @_; | |
225 | ||
226 | my $path = "api2/json/nodes/$node/tasks/$upid/status"; | |
227 | ||
228 | my $task_status; | |
229 | while(1) { | |
230 | $task_status = $conn->get($path, {}); | |
231 | ||
232 | if ($task_status->{status} eq "stopped") { | |
233 | last; | |
234 | } | |
235 | ||
236 | sleep(10); | |
237 | } | |
238 | ||
239 | return $task_status->{exitstatus}; | |
240 | } | |
241 | ||
29505e2c | 242 | 1; |