]>
Commit | Line | Data |
---|---|---|
b6cf0a66 DM |
1 | package PVE::API2::Storage::Status; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
7814e05f DM |
6 | use File::Path; |
7 | use File::Basename; | |
8 | use PVE::Tools; | |
9 | use PVE::INotify; | |
83d7192f | 10 | use PVE::Cluster; |
0ce8cadd | 11 | use PVE::RRD; |
b6cf0a66 DM |
12 | use PVE::Storage; |
13 | use PVE::API2::Storage::Content; | |
25a95836 | 14 | use PVE::API2::Storage::PruneBackups; |
f1a3ce3b | 15 | use PVE::API2::Storage::FileRestore; |
b6cf0a66 DM |
16 | use PVE::RESTHandler; |
17 | use PVE::RPCEnvironment; | |
18 | use PVE::JSONSchema qw(get_standard_option); | |
19 | use PVE::Exception qw(raise_param_exc); | |
20 | ||
21 | use base qw(PVE::RESTHandler); | |
22 | ||
25a95836 FE |
23 | __PACKAGE__->register_method ({ |
24 | subclass => "PVE::API2::Storage::PruneBackups", | |
25 | path => '{storage}/prunebackups', | |
26 | }); | |
27 | ||
b6cf0a66 | 28 | __PACKAGE__->register_method ({ |
7dd31e68 | 29 | subclass => "PVE::API2::Storage::Content", |
b6cf0a66 | 30 | # set fragment delimiter (no subdirs) - we need that, because volume |
7dd31e68 FE |
31 | # IDs may contain a slash '/' |
32 | fragmentDelimiter => '', | |
b6cf0a66 DM |
33 | path => '{storage}/content', |
34 | }); | |
35 | ||
f1a3ce3b SR |
36 | __PACKAGE__->register_method ({ |
37 | subclass => "PVE::API2::Storage::FileRestore", | |
38 | path => '{storage}/file-restore', | |
39 | }); | |
40 | ||
b6cf0a66 | 41 | __PACKAGE__->register_method ({ |
7dd31e68 | 42 | name => 'index', |
b6cf0a66 DM |
43 | path => '', |
44 | method => 'GET', | |
45 | description => "Get status for all datastores.", | |
7dd31e68 | 46 | permissions => { |
5f642f73 DM |
47 | description => "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'", |
48 | user => 'all', | |
49 | }, | |
b6cf0a66 DM |
50 | protected => 1, |
51 | proxyto => 'node', | |
52 | parameters => { | |
53 | additionalProperties => 0, | |
54 | properties => { | |
55 | node => get_standard_option('pve-node'), | |
f3bd890d DM |
56 | storage => get_standard_option('pve-storage-id', { |
57 | description => "Only list status for specified storage", | |
58 | optional => 1, | |
59 | completion => \&PVE::Storage::complete_storage_enabled, | |
60 | }), | |
7dd31e68 | 61 | content => { |
b6cf0a66 | 62 | description => "Only list stores which support this content type.", |
583c2802 | 63 | type => 'string', format => 'pve-storage-content-list', |
b6cf0a66 | 64 | optional => 1, |
98437f4c | 65 | completion => \&PVE::Storage::complete_content_type, |
b6cf0a66 | 66 | }, |
283608f3 DM |
67 | enabled => { |
68 | description => "Only list stores which are enabled (not disabled in config).", | |
69 | type => 'boolean', | |
12c2fe32 DM |
70 | optional => 1, |
71 | default => 0, | |
283608f3 DM |
72 | }, |
73 | target => get_standard_option('pve-node', { | |
74 | description => "If target is different to 'node', we only lists shared storages which " . | |
75 | "content is accessible on this 'node' and the specified 'target' node.", | |
76 | optional => 1, | |
f3bd890d | 77 | completion => \&PVE::Cluster::get_nodelist, |
283608f3 | 78 | }), |
d347322a | 79 | 'format' => { |
856c54bd DC |
80 | description => "Include information about formats", |
81 | type => 'boolean', | |
82 | optional => 1, | |
83 | default => 0, | |
84 | }, | |
b6cf0a66 DM |
85 | }, |
86 | }, | |
87 | returns => { | |
88 | type => 'array', | |
89 | items => { | |
90 | type => "object", | |
d347322a DM |
91 | properties => { |
92 | storage => get_standard_option('pve-storage-id'), | |
93 | type => { | |
94 | description => "Storage type.", | |
95 | type => 'string', | |
96 | }, | |
97 | content => { | |
98 | description => "Allowed storage content types.", | |
99 | type => 'string', format => 'pve-storage-content-list', | |
100 | }, | |
101 | enabled => { | |
102 | description => "Set when storage is enabled (not disabled).", | |
103 | type => 'boolean', | |
104 | optional => 1, | |
105 | }, | |
106 | active => { | |
107 | description => "Set when storage is accessible.", | |
108 | type => 'boolean', | |
109 | optional => 1, | |
110 | }, | |
111 | shared => { | |
112 | description => "Shared flag from storage configuration.", | |
113 | type => 'boolean', | |
114 | optional => 1, | |
115 | }, | |
116 | total => { | |
117 | description => "Total storage space in bytes.", | |
118 | type => 'integer', | |
119 | renderer => 'bytes', | |
120 | optional => 1, | |
121 | }, | |
122 | used => { | |
123 | description => "Used storage space in bytes.", | |
124 | type => 'integer', | |
125 | renderer => 'bytes', | |
126 | optional => 1, | |
127 | }, | |
128 | avail => { | |
129 | description => "Available storage space in bytes.", | |
130 | type => 'integer', | |
131 | renderer => 'bytes', | |
132 | optional => 1, | |
133 | }, | |
134 | used_fraction => { | |
135 | description => "Used fraction (used/total).", | |
136 | type => 'number', | |
137 | renderer => 'fraction_as_percentage', | |
138 | optional => 1, | |
139 | }, | |
140 | }, | |
b6cf0a66 DM |
141 | }, |
142 | links => [ { rel => 'child', href => "{storage}" } ], | |
143 | }, | |
144 | code => sub { | |
145 | my ($param) = @_; | |
146 | ||
5f642f73 DM |
147 | my $rpcenv = PVE::RPCEnvironment::get(); |
148 | my $authuser = $rpcenv->get_user(); | |
149 | ||
283608f3 DM |
150 | my $localnode = PVE::INotify::nodename(); |
151 | ||
152 | my $target = $param->{target}; | |
153 | ||
154 | undef $target if $target && ($target eq $localnode || $target eq 'localhost'); | |
f6c03d36 | 155 | |
83d7192f | 156 | my $cfg = PVE::Storage::config(); |
b6cf0a66 | 157 | |
856c54bd | 158 | my $info = PVE::Storage::storage_info($cfg, $param->{content}, $param->{format}); |
b6cf0a66 | 159 | |
5f642f73 DM |
160 | raise_param_exc({ storage => "No such storage." }) |
161 | if $param->{storage} && !defined($info->{$param->{storage}}); | |
f6c03d36 | 162 | |
5f642f73 DM |
163 | my $res = {}; |
164 | my @sids = PVE::Storage::storage_ids($cfg); | |
165 | foreach my $storeid (@sids) { | |
d347322a DM |
166 | my $data = $info->{$storeid}; |
167 | next if !$data; | |
5f642f73 DM |
168 | my $privs = [ 'Datastore.Audit', 'Datastore.AllocateSpace' ]; |
169 | next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1); | |
170 | next if $param->{storage} && $param->{storage} ne $storeid; | |
283608f3 DM |
171 | |
172 | my $scfg = PVE::Storage::storage_config($cfg, $storeid); | |
173 | ||
174 | next if $param->{enabled} && $scfg->{disable}; | |
f6c03d36 | 175 | |
283608f3 DM |
176 | if ($target) { |
177 | # check if storage content is accessible on local node and specified target node | |
178 | # we use this on the Clone GUI | |
179 | ||
180 | next if !$scfg->{shared}; | |
181 | next if !PVE::Storage::storage_check_node($cfg, $storeid, undef, 1); | |
182 | next if !PVE::Storage::storage_check_node($cfg, $storeid, $target, 1); | |
183 | } | |
184 | ||
d347322a DM |
185 | if ($data->{total}) { |
186 | $data->{used_fraction} = ($data->{used} // 0) / $data->{total}; | |
187 | } | |
188 | ||
189 | $res->{$storeid} = $data; | |
b6cf0a66 | 190 | } |
5f642f73 DM |
191 | |
192 | return PVE::RESTHandler::hash_to_array($res, 'storage'); | |
b6cf0a66 DM |
193 | }}); |
194 | ||
195 | __PACKAGE__->register_method ({ | |
196 | name => 'diridx', | |
7dd31e68 | 197 | path => '{storage}', |
b6cf0a66 DM |
198 | method => 'GET', |
199 | description => "", | |
7dd31e68 | 200 | permissions => { |
5f642f73 DM |
201 | check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], |
202 | }, | |
b6cf0a66 DM |
203 | parameters => { |
204 | additionalProperties => 0, | |
205 | properties => { | |
206 | node => get_standard_option('pve-node'), | |
207 | storage => get_standard_option('pve-storage-id'), | |
208 | }, | |
209 | }, | |
210 | returns => { | |
211 | type => 'array', | |
212 | items => { | |
213 | type => "object", | |
214 | properties => { | |
215 | subdir => { type => 'string' }, | |
216 | }, | |
217 | }, | |
218 | links => [ { rel => 'child', href => "{subdir}" } ], | |
219 | }, | |
220 | code => sub { | |
221 | my ($param) = @_; | |
222 | ||
223 | my $res = [ | |
b6cf0a66 | 224 | { subdir => 'content' }, |
837b1942 | 225 | { subdir => 'download-url' }, |
ead6be93 TL |
226 | { subdir => 'file-restore' }, |
227 | { subdir => 'prunebackups' }, | |
b6cf0a66 DM |
228 | { subdir => 'rrd' }, |
229 | { subdir => 'rrddata' }, | |
ead6be93 TL |
230 | { subdir => 'status' }, |
231 | { subdir => 'upload' }, | |
7dd31e68 FE |
232 | ]; |
233 | ||
b6cf0a66 DM |
234 | return $res; |
235 | }}); | |
236 | ||
237 | __PACKAGE__->register_method ({ | |
238 | name => 'read_status', | |
7dd31e68 | 239 | path => '{storage}/status', |
b6cf0a66 DM |
240 | method => 'GET', |
241 | description => "Read storage status.", | |
7dd31e68 | 242 | permissions => { |
5f642f73 DM |
243 | check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], |
244 | }, | |
b6cf0a66 DM |
245 | protected => 1, |
246 | proxyto => 'node', | |
247 | parameters => { | |
248 | additionalProperties => 0, | |
249 | properties => { | |
250 | node => get_standard_option('pve-node'), | |
251 | storage => get_standard_option('pve-storage-id'), | |
252 | }, | |
253 | }, | |
254 | returns => { | |
255 | type => "object", | |
256 | properties => {}, | |
257 | }, | |
258 | code => sub { | |
259 | my ($param) = @_; | |
260 | ||
83d7192f | 261 | my $cfg = PVE::Storage::config(); |
b6cf0a66 DM |
262 | |
263 | my $info = PVE::Storage::storage_info($cfg, $param->{content}); | |
264 | ||
265 | my $data = $info->{$param->{storage}}; | |
266 | ||
267 | raise_param_exc({ storage => "No such storage." }) | |
268 | if !defined($data); | |
7dd31e68 | 269 | |
b6cf0a66 DM |
270 | return $data; |
271 | }}); | |
272 | ||
273 | __PACKAGE__->register_method ({ | |
274 | name => 'rrd', | |
7dd31e68 | 275 | path => '{storage}/rrd', |
b6cf0a66 DM |
276 | method => 'GET', |
277 | description => "Read storage RRD statistics (returns PNG).", | |
7dd31e68 | 278 | permissions => { |
5f642f73 DM |
279 | check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], |
280 | }, | |
b6cf0a66 DM |
281 | protected => 1, |
282 | proxyto => 'node', | |
283 | parameters => { | |
284 | additionalProperties => 0, | |
285 | properties => { | |
286 | node => get_standard_option('pve-node'), | |
287 | storage => get_standard_option('pve-storage-id'), | |
288 | timeframe => { | |
289 | description => "Specify the time frame you are interested in.", | |
290 | type => 'string', | |
291 | enum => [ 'hour', 'day', 'week', 'month', 'year' ], | |
292 | }, | |
293 | ds => { | |
294 | description => "The list of datasources you want to display.", | |
295 | type => 'string', format => 'pve-configid-list', | |
296 | }, | |
297 | cf => { | |
298 | description => "The RRD consolidation function", | |
299 | type => 'string', | |
300 | enum => [ 'AVERAGE', 'MAX' ], | |
301 | optional => 1, | |
302 | }, | |
303 | }, | |
304 | }, | |
305 | returns => { | |
306 | type => "object", | |
307 | properties => { | |
308 | filename => { type => 'string' }, | |
309 | }, | |
310 | }, | |
311 | code => sub { | |
312 | my ($param) = @_; | |
313 | ||
0ce8cadd | 314 | return PVE::RRD::create_rrd_graph( |
7dd31e68 | 315 | "pve2-storage/$param->{node}/$param->{storage}", |
b6cf0a66 | 316 | $param->{timeframe}, $param->{ds}, $param->{cf}); |
b6cf0a66 DM |
317 | }}); |
318 | ||
319 | __PACKAGE__->register_method ({ | |
320 | name => 'rrddata', | |
7dd31e68 | 321 | path => '{storage}/rrddata', |
b6cf0a66 DM |
322 | method => 'GET', |
323 | description => "Read storage RRD statistics.", | |
7dd31e68 | 324 | permissions => { |
5f642f73 DM |
325 | check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], |
326 | }, | |
b6cf0a66 DM |
327 | protected => 1, |
328 | proxyto => 'node', | |
329 | parameters => { | |
330 | additionalProperties => 0, | |
331 | properties => { | |
332 | node => get_standard_option('pve-node'), | |
333 | storage => get_standard_option('pve-storage-id'), | |
334 | timeframe => { | |
335 | description => "Specify the time frame you are interested in.", | |
336 | type => 'string', | |
337 | enum => [ 'hour', 'day', 'week', 'month', 'year' ], | |
338 | }, | |
339 | cf => { | |
340 | description => "The RRD consolidation function", | |
341 | type => 'string', | |
342 | enum => [ 'AVERAGE', 'MAX' ], | |
343 | optional => 1, | |
344 | }, | |
345 | }, | |
346 | }, | |
347 | returns => { | |
348 | type => "array", | |
349 | items => { | |
350 | type => "object", | |
351 | properties => {}, | |
352 | }, | |
353 | }, | |
354 | code => sub { | |
355 | my ($param) = @_; | |
356 | ||
0ce8cadd | 357 | return PVE::RRD::create_rrd_data( |
7dd31e68 FE |
358 | "pve2-storage/$param->{node}/$param->{storage}", |
359 | $param->{timeframe}, $param->{cf}); | |
b6cf0a66 | 360 | }}); |
7814e05f | 361 | |
7dd31e68 | 362 | # makes no sense for big images and backup files (because it |
1f6610f3 | 363 | # create a copy of the file). |
7814e05f DM |
364 | __PACKAGE__->register_method ({ |
365 | name => 'upload', | |
7dd31e68 | 366 | path => '{storage}/upload', |
7814e05f | 367 | method => 'POST', |
1f6610f3 | 368 | description => "Upload templates and ISO images.", |
7dd31e68 | 369 | permissions => { |
1f6610f3 | 370 | check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']], |
5f642f73 | 371 | }, |
7814e05f DM |
372 | protected => 1, |
373 | parameters => { | |
374 | additionalProperties => 0, | |
375 | properties => { | |
376 | node => get_standard_option('pve-node'), | |
377 | storage => get_standard_option('pve-storage-id'), | |
7dd31e68 | 378 | content => { |
7814e05f DM |
379 | description => "Content type.", |
380 | type => 'string', format => 'pve-storage-content', | |
381 | }, | |
7dd31e68 | 382 | filename => { |
7814e05f DM |
383 | description => "The name of the file to create.", |
384 | type => 'string', | |
385 | }, | |
7dd31e68 | 386 | tmpfilename => { |
5c0720d6 | 387 | description => "The source file name. This parameter is usually set by the REST handler. You can only overwrite it when connecting to the trusted port on localhost.", |
7814e05f DM |
388 | type => 'string', |
389 | optional => 1, | |
390 | }, | |
391 | }, | |
392 | }, | |
393 | returns => { type => "string" }, | |
394 | code => sub { | |
395 | my ($param) = @_; | |
396 | ||
397 | my $rpcenv = PVE::RPCEnvironment::get(); | |
398 | ||
399 | my $user = $rpcenv->get_user(); | |
400 | ||
83d7192f | 401 | my $cfg = PVE::Storage::config(); |
7814e05f DM |
402 | |
403 | my $node = $param->{node}; | |
404 | my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node); | |
405 | ||
d68c7bca | 406 | die "can't upload to storage type '$scfg->{type}'\n" |
ee8c176d | 407 | if !defined($scfg->{path}); |
7814e05f DM |
408 | |
409 | my $content = $param->{content}; | |
410 | ||
411 | my $tmpfilename = $param->{tmpfilename}; | |
412 | die "missing temporary file name\n" if !$tmpfilename; | |
413 | ||
414 | my $size = -s $tmpfilename; | |
481f6177 | 415 | die "temporary file '$tmpfilename' does not exist\n" if !defined($size); |
7814e05f | 416 | |
edda43ed | 417 | my $filename = PVE::Storage::normalize_content_filename($param->{filename}); |
7814e05f DM |
418 | |
419 | my $path; | |
420 | ||
421 | if ($content eq 'iso') { | |
4c693491 | 422 | if ($filename !~ m![^/]+$PVE::Storage::iso_extension_re$!) { |
bba10cf4 | 423 | raise_param_exc({ filename => "wrong file extension" }); |
7814e05f DM |
424 | } |
425 | $path = PVE::Storage::get_iso_dir($cfg, $param->{storage}); | |
426 | } elsif ($content eq 'vztmpl') { | |
bba10cf4 LS |
427 | if ($filename !~ m![^/]+$PVE::Storage::vztmpl_extension_re$!) { |
428 | raise_param_exc({ filename => "wrong file extension" }); | |
7814e05f | 429 | } |
4ea5bca4 | 430 | $path = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage}); |
7814e05f | 431 | } else { |
1f6610f3 | 432 | raise_param_exc({ content => "upload content type '$content' not allowed" }); |
7814e05f DM |
433 | } |
434 | ||
d68c7bca | 435 | die "storage '$param->{storage}' does not support '$content' content\n" |
7814e05f DM |
436 | if !$scfg->{content}->{$content}; |
437 | ||
438 | my $dest = "$path/$filename"; | |
439 | my $dirname = dirname($dest); | |
440 | ||
5c0720d6 SR |
441 | # best effort to match apl_download behaviour |
442 | chmod 0644, $tmpfilename; | |
443 | ||
243f413a | 444 | # we simply overwrite the destination file if it already exists |
7814e05f DM |
445 | |
446 | my $cmd; | |
447 | if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) { | |
448 | my $remip = PVE::Cluster::remote_node_ip($node); | |
449 | ||
45c2ee35 | 450 | my @ssh_options = ('-o', 'BatchMode=yes'); |
7814e05f | 451 | |
53ec90e2 | 452 | my @remcmd = ('/usr/bin/ssh', @ssh_options, $remip, '--'); |
7814e05f | 453 | |
7dd31e68 | 454 | eval { |
7814e05f | 455 | # activate remote storage |
7dd31e68 FE |
456 | PVE::Tools::run_command([@remcmd, '/usr/sbin/pvesm', 'status', |
457 | '--storage', $param->{storage}]); | |
7814e05f | 458 | }; |
4af77132 | 459 | die "can't activate storage '$param->{storage}' on node '$node': $@\n" if $@; |
7814e05f | 460 | |
53ec90e2 | 461 | PVE::Tools::run_command([@remcmd, '/bin/mkdir', '-p', '--', PVE::Tools::shell_quote($dirname)], |
7814e05f DM |
462 | errmsg => "mkdir failed"); |
463 | ||
5c0720d6 | 464 | $cmd = ['/usr/bin/scp', @ssh_options, '-p', '--', $tmpfilename, "[$remip]:" . PVE::Tools::shell_quote($dest)]; |
7814e05f DM |
465 | } else { |
466 | PVE::Storage::activate_storage($cfg, $param->{storage}); | |
4ea5bca4 | 467 | File::Path::make_path($dirname); |
53ec90e2 | 468 | $cmd = ['cp', '--', $tmpfilename, $dest]; |
7814e05f DM |
469 | } |
470 | ||
7dd31e68 | 471 | my $worker = sub { |
7814e05f | 472 | my $upid = shift; |
7dd31e68 | 473 | |
7814e05f DM |
474 | print "starting file import from: $tmpfilename\n"; |
475 | print "target node: $node\n"; | |
476 | print "target file: $dest\n"; | |
477 | print "file size is: $size\n"; | |
478 | print "command: " . join(' ', @$cmd) . "\n"; | |
479 | ||
480 | eval { PVE::Tools::run_command($cmd, errmsg => 'import failed'); }; | |
481 | if (my $err = $@) { | |
482 | unlink $dest; | |
483 | die $err; | |
484 | } | |
485 | print "finished file import successfully\n"; | |
486 | }; | |
487 | ||
d6c9dc34 DM |
488 | my $upid = $rpcenv->fork_worker('imgcopy', undef, $user, $worker); |
489 | ||
490 | # apache removes the temporary file on return, so we need | |
491 | # to wait here to make sure the worker process starts and | |
492 | # opens the file before it gets removed. | |
493 | sleep(1); | |
494 | ||
495 | return $upid; | |
7814e05f | 496 | }}); |
7dd31e68 | 497 | |
837b1942 LS |
498 | __PACKAGE__->register_method({ |
499 | name => 'download_url', | |
500 | path => '{storage}/download-url', | |
501 | method => 'POST', | |
502 | description => "Download templates and ISO images by using an URL.", | |
503 | proxyto => 'node', | |
504 | permissions => { | |
505 | check => [ 'and', | |
506 | ['perm', '/storage/{storage}', [ 'Datastore.AllocateTemplate' ]], | |
507 | ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]], | |
508 | ], | |
509 | }, | |
510 | protected => 1, | |
511 | parameters => { | |
512 | additionalProperties => 0, | |
513 | properties => { | |
514 | node => get_standard_option('pve-node'), | |
515 | storage => get_standard_option('pve-storage-id'), | |
516 | url => { | |
517 | description => "The URL to download the file from.", | |
518 | type => 'string', | |
519 | pattern => 'https?://.*', | |
520 | }, | |
521 | content => { | |
d0a3db1b | 522 | description => "Content type.", # TODO: could be optional & detected in most cases |
837b1942 LS |
523 | type => 'string', format => 'pve-storage-content', |
524 | enum => ['iso', 'vztmpl'], | |
525 | }, | |
526 | filename => { | |
527 | description => "The name of the file to create. Caution: This will be normalized!", | |
528 | type => 'string', | |
529 | }, | |
530 | checksum => { | |
531 | description => "The expected checksum of the file.", | |
532 | type => 'string', | |
533 | requires => 'checksum-algorithm', | |
534 | optional => 1, | |
535 | }, | |
536 | 'checksum-algorithm' => { | |
537 | description => "The algorithm to calculate the checksum of the file.", | |
538 | type => 'string', | |
539 | enum => ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'], | |
540 | requires => 'checksum', | |
541 | optional => 1, | |
542 | }, | |
543 | 'verify-certificates' => { | |
544 | description => "If false, no SSL/TLS certificates will be verified.", | |
545 | type => 'boolean', | |
546 | optional => 1, | |
547 | default => 1, | |
d0a3db1b | 548 | }, |
837b1942 LS |
549 | }, |
550 | }, | |
551 | returns => { | |
552 | type => "string" | |
553 | }, | |
554 | code => sub { | |
555 | my ($param) = @_; | |
556 | ||
557 | my $rpcenv = PVE::RPCEnvironment::get(); | |
558 | my $user = $rpcenv->get_user(); | |
559 | ||
560 | my $cfg = PVE::Storage::config(); | |
561 | ||
562 | my ($node, $storage) = $param->@{'node', 'storage'}; | |
563 | my $scfg = PVE::Storage::storage_check_enabled($cfg, $storage, $node); | |
564 | ||
565 | die "can't upload to storage type '$scfg->{type}', not a file based storage!\n" | |
566 | if !defined($scfg->{path}); | |
567 | ||
568 | my ($content, $url) = $param->@{'content', 'url'}; | |
569 | ||
570 | die "storage '$storage' is not configured for content-type '$content'\n" | |
571 | if !$scfg->{content}->{$content}; | |
572 | ||
573 | my $filename = PVE::Storage::normalize_content_filename($param->{filename}); | |
574 | ||
575 | my $path; | |
576 | if ($content eq 'iso') { | |
577 | if ($filename !~ m![^/]+$PVE::Storage::iso_extension_re$!) { | |
578 | raise_param_exc({ filename => "wrong file extension" }); | |
579 | } | |
580 | $path = PVE::Storage::get_iso_dir($cfg, $storage); | |
581 | } elsif ($content eq 'vztmpl') { | |
582 | if ($filename !~ m![^/]+$PVE::Storage::vztmpl_extension_re$!) { | |
583 | raise_param_exc({ filename => "wrong file extension" }); | |
584 | } | |
585 | $path = PVE::Storage::get_vztmpl_dir($cfg, $storage); | |
586 | } else { | |
587 | raise_param_exc({ content => "upload content-type '$content' is not allowed" }); | |
588 | } | |
589 | ||
590 | PVE::Storage::activate_storage($cfg, $storage); | |
591 | File::Path::make_path($path); | |
592 | ||
593 | my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); | |
594 | my $opts = { | |
595 | hash_required => 0, | |
596 | verify_certificates => $param->{'verify-certificates'} // 1, | |
597 | http_proxy => $dccfg->{http_proxy}, | |
598 | }; | |
599 | ||
600 | my ($checksum, $checksum_algorithm) = $param->@{'checksum', 'checksum-algorithm'}; | |
601 | if ($checksum) { | |
602 | $opts->{"${checksum_algorithm}sum"} = $checksum; | |
603 | $opts->{hash_required} = 1; | |
604 | } | |
605 | ||
606 | my $worker = sub { | |
607 | PVE::Tools::download_file_from_url("$path/$filename", $url, $opts); | |
608 | }; | |
609 | ||
d0a3db1b | 610 | return $rpcenv->fork_worker('download', $filename, $user, $worker); |
837b1942 LS |
611 | }}); |
612 | ||
b6cf0a66 | 613 | 1; |