]>
Commit | Line | Data |
---|---|---|
c669f42d DM |
1 | package PVE::CLI::pvesm; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
9559a62a | 6 | use POSIX qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC); |
c669f42d DM |
7 | use Fcntl ':flock'; |
8 | use File::Path; | |
c56f7a71 | 9 | use MIME::Base64 qw(encode_base64); |
c669f42d | 10 | |
1ebd925d FG |
11 | use IO::Socket::IP; |
12 | use IO::Socket::UNIX; | |
13 | use Socket qw(SOCK_STREAM); | |
14 | ||
c669f42d DM |
15 | use PVE::SafeSyslog; |
16 | use PVE::Cluster; | |
17 | use PVE::INotify; | |
18 | use PVE::RPCEnvironment; | |
19 | use PVE::Storage; | |
25a95836 | 20 | use PVE::Tools qw(extract_param); |
c669f42d DM |
21 | use PVE::API2::Storage::Config; |
22 | use PVE::API2::Storage::Content; | |
25a95836 | 23 | use PVE::API2::Storage::PruneBackups; |
2892b673 | 24 | use PVE::API2::Storage::Scan; |
c669f42d | 25 | use PVE::API2::Storage::Status; |
c669f42d | 26 | use PVE::JSONSchema qw(get_standard_option); |
c26f3a71 | 27 | use PVE::PTY; |
c669f42d DM |
28 | |
29 | use PVE::CLIHandler; | |
30 | ||
31 | use base qw(PVE::CLIHandler); | |
32 | ||
a0e3e224 | 33 | my $KNOWN_EXPORT_FORMATS = ['raw+size', 'tar+size', 'qcow2+size', 'vmdk+size', 'zfs', 'btrfs']; |
47f37b53 | 34 | |
c669f42d DM |
35 | my $nodename = PVE::INotify::nodename(); |
36 | ||
42f2c57d DC |
37 | sub param_mapping { |
38 | my ($name) = @_; | |
39 | ||
40 | my $password_map = PVE::CLIHandler::get_standard_mapping('pve-password', { | |
41 | func => sub { | |
42 | my ($value) = @_; | |
43 | return $value if $value; | |
44 | return PVE::PTY::read_password("Enter Password: "); | |
45 | }, | |
46 | }); | |
baf77120 | 47 | |
0ca8eb4f WB |
48 | my $enc_key_map = { |
49 | name => 'encryption-key', | |
50 | desc => 'a file containing an encryption key, or the special value "autogen"', | |
51 | func => sub { | |
52 | my ($value) = @_; | |
53 | return $value if $value eq 'autogen'; | |
54 | return PVE::Tools::file_get_contents($value); | |
55 | } | |
56 | }; | |
57 | ||
c56f7a71 FG |
58 | my $master_key_map = { |
59 | name => 'master-pubkey', | |
60 | desc => 'a file containing a PEM-formatted master public key', | |
61 | func => sub { | |
62 | my ($value) = @_; | |
63 | return encode_base64(PVE::Tools::file_get_contents($value), ''); | |
64 | } | |
65 | }; | |
66 | ||
22b68016 AL |
67 | my $keyring_map = { |
68 | name => 'keyring', | |
69 | desc => 'file containing the keyring to authenticate in the Ceph cluster', | |
70 | func => sub { | |
71 | my ($value) = @_; | |
72 | return PVE::Tools::file_get_contents($value); | |
73 | }, | |
74 | }; | |
0ca8eb4f | 75 | |
42f2c57d DC |
76 | my $mapping = { |
77 | 'cifsscan' => [ $password_map ], | |
11a942a1 | 78 | 'cifs' => [ $password_map ], |
39fc0c77 | 79 | 'pbs' => [ $password_map ], |
22b68016 AL |
80 | 'create' => [ $password_map, $enc_key_map, $master_key_map, $keyring_map ], |
81 | 'update' => [ $password_map, $enc_key_map, $master_key_map, $keyring_map ], | |
42f2c57d DC |
82 | }; |
83 | return $mapping->{$name}; | |
c26f3a71 WL |
84 | } |
85 | ||
f984732e DM |
86 | sub setup_environment { |
87 | PVE::RPCEnvironment->setup_default_cli_env(); | |
88 | } | |
89 | ||
5f184292 FE |
90 | __PACKAGE__->register_method ({ |
91 | name => 'apiinfo', | |
92 | path => 'apiinfo', | |
93 | method => 'GET', | |
94 | description => "Returns APIVER and APIAGE.", | |
95 | parameters => { | |
96 | additionalProperties => 0, | |
97 | properties => {}, | |
98 | }, | |
99 | returns => { | |
100 | type => 'object', | |
101 | properties => { | |
102 | apiver => { type => 'integer' }, | |
103 | apiage => { type => 'integer' }, | |
104 | }, | |
105 | }, | |
106 | code => sub { | |
107 | return { | |
108 | apiver => PVE::Storage::APIVER, | |
109 | apiage => PVE::Storage::APIAGE, | |
110 | }; | |
111 | } | |
112 | }); | |
113 | ||
c669f42d DM |
114 | __PACKAGE__->register_method ({ |
115 | name => 'path', | |
116 | path => 'path', | |
117 | method => 'GET', | |
118 | description => "Get filesystem path for specified volume", | |
119 | parameters => { | |
120 | additionalProperties => 0, | |
121 | properties => { | |
122 | volume => { | |
123 | description => "Volume identifier", | |
124 | type => 'string', format => 'pve-volume-id', | |
f3bd890d | 125 | completion => \&PVE::Storage::complete_volume, |
c669f42d DM |
126 | }, |
127 | }, | |
128 | }, | |
129 | returns => { type => 'null' }, | |
130 | ||
131 | code => sub { | |
132 | my ($param) = @_; | |
133 | ||
134 | my $cfg = PVE::Storage::config(); | |
135 | ||
136 | my $path = PVE::Storage::path ($cfg, $param->{volume}); | |
137 | ||
138 | print "$path\n"; | |
139 | ||
140 | return undef; | |
141 | ||
142 | }}); | |
143 | ||
fa017b96 FG |
144 | __PACKAGE__->register_method ({ |
145 | name => 'extractconfig', | |
146 | path => 'extractconfig', | |
147 | method => 'GET', | |
148 | description => "Extract configuration from vzdump backup archive.", | |
149 | permissions => { | |
150 | description => "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.", | |
151 | user => 'all', | |
152 | }, | |
153 | protected => 1, | |
154 | parameters => { | |
155 | additionalProperties => 0, | |
156 | properties => { | |
157 | volume => { | |
158 | description => "Volume identifier", | |
159 | type => 'string', | |
160 | completion => \&PVE::Storage::complete_volume, | |
161 | }, | |
162 | }, | |
163 | }, | |
164 | returns => { type => 'null' }, | |
165 | code => sub { | |
166 | my ($param) = @_; | |
167 | my $volume = $param->{volume}; | |
168 | ||
169 | my $rpcenv = PVE::RPCEnvironment::get(); | |
170 | my $authuser = $rpcenv->get_user(); | |
171 | ||
172 | my $storage_cfg = PVE::Storage::config(); | |
1f37ad56 FE |
173 | PVE::Storage::check_volume_access( |
174 | $rpcenv, | |
175 | $authuser, | |
176 | $storage_cfg, | |
177 | undef, | |
178 | $volume, | |
179 | 'backup', | |
180 | ); | |
fa017b96 | 181 | |
f303dec6 FE |
182 | if (PVE::Storage::parse_volume_id($volume, 1)) { |
183 | my (undef, undef, $ownervm) = PVE::Storage::parse_volname($storage_cfg, $volume); | |
184 | $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']); | |
185 | } | |
186 | ||
fa017b96 FG |
187 | my $config_raw = PVE::Storage::extract_vzdump_config($storage_cfg, $volume); |
188 | ||
189 | print "$config_raw\n"; | |
190 | return; | |
191 | }}); | |
192 | ||
c669f42d DM |
193 | my $print_content = sub { |
194 | my ($list) = @_; | |
195 | ||
61c261e7 | 196 | my ($maxlenname, $maxsize) = (0, 0); |
c669f42d | 197 | foreach my $info (@$list) { |
c669f42d DM |
198 | my $volid = $info->{volid}; |
199 | my $sidlen = length ($volid); | |
200 | $maxlenname = $sidlen if $sidlen > $maxlenname; | |
61c261e7 | 201 | $maxsize = $info->{size} if ($info->{size} // 0) > $maxsize; |
c669f42d | 202 | } |
61c261e7 TL |
203 | my $sizemaxdigits = length($maxsize); |
204 | ||
205 | my $basefmt = "%-${maxlenname}s %-7s %-9s %${sizemaxdigits}s"; | |
206 | printf "$basefmt %s\n", "Volid", "Format", "Type", "Size", "VMID"; | |
c669f42d DM |
207 | |
208 | foreach my $info (@$list) { | |
209 | next if !$info->{vmid}; | |
210 | my $volid = $info->{volid}; | |
211 | ||
61c261e7 | 212 | printf "$basefmt %d\n", $volid, $info->{format}, $info->{content}, $info->{size}, $info->{vmid}; |
c669f42d DM |
213 | } |
214 | ||
215 | foreach my $info (sort { $a->{format} cmp $b->{format} } @$list) { | |
216 | next if $info->{vmid}; | |
217 | my $volid = $info->{volid}; | |
218 | ||
61c261e7 | 219 | printf "$basefmt\n", $volid, $info->{format}, $info->{content}, $info->{size}; |
c669f42d DM |
220 | } |
221 | }; | |
222 | ||
223 | my $print_status = sub { | |
224 | my $res = shift; | |
225 | ||
226 | my $maxlen = 0; | |
227 | foreach my $res (@$res) { | |
228 | my $storeid = $res->{storage}; | |
229 | $maxlen = length ($storeid) if length ($storeid) > $maxlen; | |
230 | } | |
231 | $maxlen+=1; | |
232 | ||
d40e27de TL |
233 | printf "%-${maxlen}s %10s %10s %15s %15s %15s %8s\n", 'Name', 'Type', |
234 | 'Status', 'Total', 'Used', 'Available', '%'; | |
235 | ||
c669f42d DM |
236 | foreach my $res (sort { $a->{storage} cmp $b->{storage} } @$res) { |
237 | my $storeid = $res->{storage}; | |
238 | ||
d40e27de TL |
239 | my $active = $res->{active} ? 'active' : 'inactive'; |
240 | my ($per, $per_fmt) = (0, '% 7.2f%%'); | |
241 | $per = ($res->{used}*100)/$res->{total} if $res->{total} > 0; | |
242 | ||
243 | if (!$res->{enabled}) { | |
04301013 | 244 | $per = 'N/A'; |
d40e27de TL |
245 | $per_fmt = '% 8s'; |
246 | $active = 'disabled'; | |
247 | } | |
c669f42d | 248 | |
d40e27de TL |
249 | printf "%-${maxlen}s %10s %10s %15d %15d %15d $per_fmt\n", $storeid, |
250 | $res->{type}, $active, $res->{total}/1024, $res->{used}/1024, | |
251 | $res->{avail}/1024, $per; | |
c669f42d DM |
252 | } |
253 | }; | |
254 | ||
47f37b53 WB |
255 | __PACKAGE__->register_method ({ |
256 | name => 'export', | |
257 | path => 'export', | |
258 | method => 'GET', | |
a43a796c | 259 | description => "Used internally to export a volume.", |
47f37b53 WB |
260 | protected => 1, |
261 | parameters => { | |
262 | additionalProperties => 0, | |
263 | properties => { | |
264 | volume => { | |
265 | description => "Volume identifier", | |
266 | type => 'string', | |
267 | completion => \&PVE::Storage::complete_volume, | |
268 | }, | |
269 | format => { | |
270 | description => "Export stream format", | |
271 | type => 'string', | |
272 | enum => $KNOWN_EXPORT_FORMATS, | |
273 | }, | |
274 | filename => { | |
275 | description => "Destination file name", | |
276 | type => 'string', | |
277 | }, | |
278 | base => { | |
279 | description => "Snapshot to start an incremental stream from", | |
280 | type => 'string', | |
bef7920d | 281 | pattern => qr/[a-z0-9_\-]{1,40}/i, |
47f37b53 WB |
282 | maxLength => 40, |
283 | optional => 1, | |
284 | }, | |
285 | snapshot => { | |
286 | description => "Snapshot to export", | |
287 | type => 'string', | |
bef7920d | 288 | pattern => qr/[a-z0-9_\-]{1,40}/i, |
47f37b53 WB |
289 | maxLength => 40, |
290 | optional => 1, | |
291 | }, | |
292 | 'with-snapshots' => { | |
293 | description => | |
294 | "Whether to include intermediate snapshots in the stream", | |
295 | type => 'boolean', | |
296 | optional => 1, | |
297 | default => 0, | |
298 | }, | |
3cc29a04 WB |
299 | 'snapshot-list' => { |
300 | description => "Ordered list of snapshots to transfer", | |
301 | type => 'string', | |
302 | format => 'string-list', | |
303 | optional => 1, | |
304 | }, | |
47f37b53 WB |
305 | }, |
306 | }, | |
307 | returns => { type => 'null' }, | |
308 | code => sub { | |
309 | my ($param) = @_; | |
310 | ||
3cc29a04 WB |
311 | my $with_snapshots = $param->{'with-snapshots'}; |
312 | if (defined(my $list = $param->{'snapshot-list'})) { | |
313 | $with_snapshots = PVE::Tools::split_list($list); | |
314 | } | |
315 | ||
47f37b53 WB |
316 | my $filename = $param->{filename}; |
317 | ||
318 | my $outfh; | |
319 | if ($filename eq '-') { | |
320 | $outfh = \*STDOUT; | |
321 | } else { | |
9559a62a | 322 | sysopen($outfh, $filename, O_CREAT|O_WRONLY|O_TRUNC) |
47f37b53 WB |
323 | or die "open($filename): $!\n"; |
324 | } | |
325 | ||
326 | eval { | |
327 | my $cfg = PVE::Storage::config(); | |
328 | PVE::Storage::volume_export($cfg, $outfh, $param->{volume}, $param->{format}, | |
3cc29a04 | 329 | $param->{snapshot}, $param->{base}, $with_snapshots); |
47f37b53 WB |
330 | }; |
331 | my $err = $@; | |
332 | if ($filename ne '-') { | |
333 | close($outfh); | |
334 | unlink($filename) if $err; | |
335 | } | |
336 | die $err if $err; | |
337 | return; | |
338 | } | |
339 | }); | |
340 | ||
341 | __PACKAGE__->register_method ({ | |
342 | name => 'import', | |
343 | path => 'import', | |
344 | method => 'PUT', | |
a43a796c | 345 | description => "Used internally to import a volume.", |
47f37b53 WB |
346 | protected => 1, |
347 | parameters => { | |
348 | additionalProperties => 0, | |
349 | properties => { | |
350 | volume => { | |
351 | description => "Volume identifier", | |
352 | type => 'string', | |
353 | completion => \&PVE::Storage::complete_volume, | |
354 | }, | |
355 | format => { | |
356 | description => "Import stream format", | |
357 | type => 'string', | |
358 | enum => $KNOWN_EXPORT_FORMATS, | |
359 | }, | |
360 | filename => { | |
228e5be9 | 361 | description => "Source file name. For '-' stdin is used, the " . |
1ebd925d FG |
362 | "tcp://<IP-or-CIDR> format allows to use a TCP connection, " . |
363 | "the unix://PATH-TO-SOCKET format a UNIX socket as input." . | |
228e5be9 | 364 | "Else, the file is treated as common file.", |
47f37b53 WB |
365 | type => 'string', |
366 | }, | |
367 | base => { | |
368 | description => "Base snapshot of an incremental stream", | |
369 | type => 'string', | |
bef7920d | 370 | pattern => qr/[a-z0-9_\-]{1,40}/i, |
47f37b53 WB |
371 | maxLength => 40, |
372 | optional => 1, | |
373 | }, | |
374 | 'with-snapshots' => { | |
375 | description => | |
376 | "Whether the stream includes intermediate snapshots", | |
377 | type => 'boolean', | |
378 | optional => 1, | |
379 | default => 0, | |
380 | }, | |
52595938 WB |
381 | 'delete-snapshot' => { |
382 | description => "A snapshot to delete on success", | |
383 | type => 'string', | |
bef7920d | 384 | pattern => qr/[a-z0-9_\-]{1,80}/i, |
52595938 WB |
385 | maxLength => 80, |
386 | optional => 1, | |
387 | }, | |
a97d3ee4 FE |
388 | 'allow-rename' => { |
389 | description => "Choose a new volume ID if the requested " . | |
390 | "volume ID already exists, instead of throwing an error.", | |
391 | type => 'boolean', | |
392 | optional => 1, | |
393 | default => 0, | |
394 | }, | |
3cc29a04 WB |
395 | snapshot => { |
396 | description => "The current-state snapshot if the stream contains snapshots", | |
397 | type => 'string', | |
398 | pattern => qr/[a-z0-9_\-]{1,40}/i, | |
399 | maxLength => 40, | |
400 | optional => 1, | |
401 | }, | |
47f37b53 WB |
402 | }, |
403 | }, | |
a97d3ee4 | 404 | returns => { type => 'string' }, |
47f37b53 WB |
405 | code => sub { |
406 | my ($param) = @_; | |
407 | ||
408 | my $filename = $param->{filename}; | |
409 | ||
410 | my $infh; | |
411 | if ($filename eq '-') { | |
412 | $infh = \*STDIN; | |
228e5be9 TL |
413 | } elsif ($filename =~ m!^tcp://(([^/]+)(/\d+)?)$!) { |
414 | my ($cidr, $ip, $subnet) = ($1, $2, $3); | |
415 | if ($subnet) { # got real CIDR notation, not just IP | |
a2aae38c | 416 | my $ips = PVE::Network::get_local_ip_from_cidr($cidr); |
ed2df8e3 TL |
417 | die "Unable to get any local IP address in network '$cidr'\n" |
418 | if scalar(@$ips) < 1; | |
419 | die "Got multiple local IP address in network '$cidr'\n" | |
420 | if scalar(@$ips) > 1; | |
421 | ||
422 | $ip = $ips->[0]; | |
228e5be9 TL |
423 | } |
424 | my $family = PVE::Tools::get_host_address_family($ip); | |
425 | my $port = PVE::Tools::next_migrate_port($family, $ip); | |
426 | ||
427 | my $sock_params = { | |
428 | Listen => 1, | |
429 | ReuseAddr => 1, | |
430 | Proto => &Socket::IPPROTO_TCP, | |
431 | GetAddrInfoFlags => 0, | |
432 | LocalAddr => $ip, | |
433 | LocalPort => $port, | |
434 | }; | |
435 | my $socket = IO::Socket::IP->new(%$sock_params) | |
436 | or die "failed to open socket: $!\n"; | |
437 | ||
438 | print "$ip\n$port\n"; # tell remote where to connect | |
439 | *STDOUT->flush(); | |
440 | ||
441 | my $prev_alarm = alarm 0; | |
442 | local $SIG{ALRM} = sub { die "timed out waiting for client\n" }; | |
443 | alarm 30; | |
444 | my $client = $socket->accept; # Wait for a client | |
445 | alarm $prev_alarm; | |
1ebd925d FG |
446 | close($socket); |
447 | ||
448 | $infh = \*$client; | |
449 | } elsif ($filename =~ m!^unix://(.*)$!) { | |
450 | my $socket_path = $1; | |
451 | my $socket = IO::Socket::UNIX->new( | |
452 | Type => SOCK_STREAM(), | |
453 | Local => $socket_path, | |
454 | Listen => 1, | |
455 | ) or die "failed to open socket: $!\n"; | |
456 | ||
457 | print "ready\n"; | |
458 | *STDOUT->flush(); | |
459 | ||
460 | my $prev_alarm = alarm 0; | |
461 | local $SIG{ALRM} = sub { die "timed out waiting for client\n" }; | |
462 | alarm 30; | |
463 | my $client = $socket->accept; # Wait for a client | |
464 | alarm $prev_alarm; | |
228e5be9 TL |
465 | close($socket); |
466 | ||
467 | $infh = \*$client; | |
47f37b53 | 468 | } else { |
9559a62a | 469 | sysopen($infh, $filename, O_RDONLY) |
47f37b53 WB |
470 | or die "open($filename): $!\n"; |
471 | } | |
472 | ||
473 | my $cfg = PVE::Storage::config(); | |
52595938 WB |
474 | my $volume = $param->{volume}; |
475 | my $delete = $param->{'delete-snapshot'}; | |
a97d3ee4 | 476 | my $imported_volid = PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format}, |
3cc29a04 WB |
477 | $param->{snapshot}, $param->{base}, $param->{'with-snapshots'}, |
478 | $param->{'allow-rename'}); | |
a97d3ee4 | 479 | PVE::Storage::volume_snapshot_delete($cfg, $imported_volid, $delete) |
52595938 | 480 | if defined($delete); |
a97d3ee4 | 481 | return $imported_volid; |
47f37b53 WB |
482 | } |
483 | }); | |
484 | ||
25a95836 FE |
485 | __PACKAGE__->register_method ({ |
486 | name => 'prunebackups', | |
487 | path => 'prunebackups', | |
488 | method => 'GET', | |
8ca00a63 FE |
489 | description => "Prune backups. Only those using the standard naming scheme are considered. " . |
490 | "If no keep options are specified, those from the storage configuration are used.", | |
25a95836 FE |
491 | protected => 1, |
492 | proxyto => 'node', | |
493 | parameters => { | |
494 | additionalProperties => 0, | |
495 | properties => { | |
496 | 'dry-run' => { | |
497 | description => "Only show what would be pruned, don't delete anything.", | |
498 | type => 'boolean', | |
499 | optional => 1, | |
500 | }, | |
501 | node => get_standard_option('pve-node'), | |
502 | storage => get_standard_option('pve-storage-id', { | |
503 | completion => \&PVE::Storage::complete_storage_enabled, | |
504 | }), | |
a0933d7e | 505 | %{$PVE::Storage::Plugin::prune_backups_format}, |
25a95836 FE |
506 | type => { |
507 | description => "Either 'qemu' or 'lxc'. Only consider backups for guests of this type.", | |
508 | type => 'string', | |
509 | optional => 1, | |
510 | enum => ['qemu', 'lxc'], | |
511 | }, | |
512 | vmid => get_standard_option('pve-vmid', { | |
513 | description => "Only consider backups for this guest.", | |
514 | optional => 1, | |
515 | completion => \&PVE::Cluster::complete_vmid, | |
516 | }), | |
517 | }, | |
518 | }, | |
519 | returns => { | |
520 | type => 'object', | |
521 | properties => { | |
522 | dryrun => { | |
523 | description => 'If it was a dry run or not. The list will only be defined in that case.', | |
524 | type => 'boolean', | |
525 | }, | |
526 | list => { | |
527 | type => 'array', | |
528 | items => { | |
529 | type => 'object', | |
530 | properties => { | |
531 | volid => { | |
532 | description => "Backup volume ID.", | |
533 | type => 'string', | |
534 | }, | |
535 | 'ctime' => { | |
536 | description => "Creation time of the backup (seconds since the UNIX epoch).", | |
537 | type => 'integer', | |
538 | }, | |
539 | 'mark' => { | |
540 | description => "Whether the backup would be kept or removed. For backups that don't " . | |
541 | "use the standard naming scheme, it's 'protected'.", | |
542 | type => 'string', | |
543 | }, | |
544 | type => { | |
545 | description => "One of 'qemu', 'lxc', 'openvz' or 'unknown'.", | |
546 | type => 'string', | |
547 | }, | |
548 | 'vmid' => { | |
549 | description => "The VM the backup belongs to.", | |
550 | type => 'integer', | |
551 | optional => 1, | |
552 | }, | |
553 | }, | |
554 | }, | |
555 | }, | |
556 | }, | |
557 | }, | |
558 | code => sub { | |
559 | my ($param) = @_; | |
560 | ||
561 | my $dryrun = extract_param($param, 'dry-run') ? 1 : 0; | |
562 | ||
a0933d7e FE |
563 | my $keep_opts; |
564 | foreach my $keep (keys %{$PVE::Storage::Plugin::prune_backups_format}) { | |
565 | $keep_opts->{$keep} = extract_param($param, $keep) if defined($param->{$keep}); | |
566 | } | |
567 | $param->{'prune-backups'} = PVE::JSONSchema::print_property_string( | |
568 | $keep_opts, $PVE::Storage::Plugin::prune_backups_format) if $keep_opts; | |
569 | ||
25a95836 FE |
570 | my $list = []; |
571 | if ($dryrun) { | |
572 | $list = PVE::API2::Storage::PruneBackups->dryrun($param); | |
573 | } else { | |
574 | PVE::API2::Storage::PruneBackups->delete($param); | |
575 | } | |
576 | ||
577 | return { | |
578 | dryrun => $dryrun, | |
579 | list => $list, | |
580 | }; | |
581 | }}); | |
582 | ||
39fc0c77 TL |
583 | my $print_api_result = sub { |
584 | my ($data, $schema, $options) = @_; | |
585 | PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); | |
586 | }; | |
587 | ||
c669f42d DM |
588 | our $cmddef = { |
589 | add => [ "PVE::API2::Storage::Config", 'create', ['type', 'storage'] ], | |
590 | set => [ "PVE::API2::Storage::Config", 'update', ['storage'] ], | |
591 | remove => [ "PVE::API2::Storage::Config", 'delete', ['storage'] ], | |
592 | status => [ "PVE::API2::Storage::Status", 'index', [], | |
593 | { node => $nodename }, $print_status ], | |
594 | list => [ "PVE::API2::Storage::Content", 'index', ['storage'], | |
595 | { node => $nodename }, $print_content ], | |
596 | alloc => [ "PVE::API2::Storage::Content", 'create', ['storage', 'vmid', 'filename', 'size'], | |
597 | { node => $nodename }, sub { | |
598 | my $volid = shift; | |
e967e0ef | 599 | print "successfully created '$volid'\n"; |
c669f42d DM |
600 | }], |
601 | free => [ "PVE::API2::Storage::Content", 'delete', ['volume'], | |
602 | { node => $nodename } ], | |
957321a8 | 603 | scan => { |
2892b673 | 604 | nfs => [ "PVE::API2::Storage::Scan", 'nfsscan', ['server'], { node => $nodename }, sub { |
957321a8 TL |
605 | my $res = shift; |
606 | ||
607 | my $maxlen = 0; | |
608 | foreach my $rec (@$res) { | |
609 | my $len = length ($rec->{path}); | |
610 | $maxlen = $len if $len > $maxlen; | |
611 | } | |
612 | foreach my $rec (@$res) { | |
613 | printf "%-${maxlen}s %s\n", $rec->{path}, $rec->{options}; | |
614 | } | |
615 | }], | |
2892b673 | 616 | cifs => [ "PVE::API2::Storage::Scan", 'cifsscan', ['server'], { node => $nodename }, sub { |
957321a8 TL |
617 | my $res = shift; |
618 | ||
619 | my $maxlen = 0; | |
620 | foreach my $rec (@$res) { | |
621 | my $len = length ($rec->{share}); | |
622 | $maxlen = $len if $len > $maxlen; | |
623 | } | |
624 | foreach my $rec (@$res) { | |
625 | printf "%-${maxlen}s %s\n", $rec->{share}, $rec->{description}; | |
626 | } | |
627 | }], | |
2892b673 | 628 | glusterfs => [ "PVE::API2::Storage::Scan", 'glusterfsscan', ['server'], { node => $nodename }, sub { |
957321a8 TL |
629 | my $res = shift; |
630 | ||
631 | foreach my $rec (@$res) { | |
632 | printf "%s\n", $rec->{volname}; | |
633 | } | |
634 | }], | |
2892b673 | 635 | iscsi => [ "PVE::API2::Storage::Scan", 'iscsiscan', ['portal'], { node => $nodename }, sub { |
957321a8 TL |
636 | my $res = shift; |
637 | ||
638 | my $maxlen = 0; | |
639 | foreach my $rec (@$res) { | |
640 | my $len = length ($rec->{target}); | |
641 | $maxlen = $len if $len > $maxlen; | |
642 | } | |
643 | foreach my $rec (@$res) { | |
644 | printf "%-${maxlen}s %s\n", $rec->{target}, $rec->{portal}; | |
645 | } | |
646 | }], | |
2892b673 | 647 | lvm => [ "PVE::API2::Storage::Scan", 'lvmscan', [], { node => $nodename }, sub { |
957321a8 TL |
648 | my $res = shift; |
649 | foreach my $rec (@$res) { | |
650 | printf "$rec->{vg}\n"; | |
651 | } | |
652 | }], | |
2892b673 | 653 | lvmthin => [ "PVE::API2::Storage::Scan", 'lvmthinscan', ['vg'], { node => $nodename }, sub { |
957321a8 TL |
654 | my $res = shift; |
655 | foreach my $rec (@$res) { | |
656 | printf "$rec->{lv}\n"; | |
657 | } | |
658 | }], | |
39fc0c77 TL |
659 | pbs => [ |
660 | "PVE::API2::Storage::Scan", | |
661 | 'pbsscan', | |
662 | ['server', 'username'], | |
663 | { node => $nodename }, | |
664 | $print_api_result, | |
665 | $PVE::RESTHandler::standard_output_options, | |
666 | ], | |
2892b673 | 667 | zfs => [ "PVE::API2::Storage::Scan", 'zfsscan', [], { node => $nodename }, sub { |
957321a8 TL |
668 | my $res = shift; |
669 | ||
670 | foreach my $rec (@$res) { | |
671 | printf "$rec->{pool}\n"; | |
672 | } | |
673 | }], | |
674 | }, | |
675 | nfsscan => { alias => 'scan nfs' }, | |
676 | cifsscan => { alias => 'scan cifs' }, | |
677 | glusterfsscan => { alias => 'scan glusterfs' }, | |
678 | iscsiscan => { alias => 'scan iscsi' }, | |
679 | lvmscan => { alias => 'scan lvm' }, | |
680 | lvmthinscan => { alias => 'scan lvmthin' }, | |
681 | zfsscan => { alias => 'scan zfs' }, | |
c669f42d | 682 | path => [ __PACKAGE__, 'path', ['volume']], |
fa017b96 | 683 | extractconfig => [__PACKAGE__, 'extractconfig', ['volume']], |
47f37b53 | 684 | export => [ __PACKAGE__, 'export', ['volume', 'format', 'filename']], |
a97d3ee4 FE |
685 | import => [ __PACKAGE__, 'import', ['volume', 'format', 'filename'], {}, sub { |
686 | my $volid = shift; | |
687 | print PVE::Storage::volume_imported_message($volid); | |
688 | }], | |
5f184292 FE |
689 | apiinfo => [ __PACKAGE__, 'apiinfo', [], {}, sub { |
690 | my $res = shift; | |
691 | ||
692 | print "APIVER $res->{apiver}\n"; | |
693 | print "APIAGE $res->{apiage}\n"; | |
694 | }], | |
25a95836 FE |
695 | 'prune-backups' => [ __PACKAGE__, 'prunebackups', ['storage'], { node => $nodename }, sub { |
696 | my $res = shift; | |
697 | ||
698 | my ($dryrun, $list) = ($res->{dryrun}, $res->{list}); | |
699 | ||
700 | return if !$dryrun; | |
701 | ||
c3e87d0f FE |
702 | if (!scalar(@{$list})) { |
703 | print "No backups found\n"; | |
704 | return; | |
705 | } | |
706 | ||
7b73d327 FE |
707 | print "NOTE: this is only a preview and might not be what a subsequent\n" . |
708 | "prune call does if backups are removed/added in the meantime.\n\n"; | |
25a95836 FE |
709 | |
710 | my @sorted = sort { | |
711 | my $vmcmp = PVE::Tools::safe_compare($a->{vmid}, $b->{vmid}, sub { $_[0] <=> $_[1] }); | |
712 | return $vmcmp if $vmcmp ne 0; | |
713 | return $a->{ctime} <=> $b->{ctime}; | |
714 | } @{$list}; | |
715 | ||
716 | my $maxlen = 0; | |
717 | foreach my $backup (@sorted) { | |
718 | my $volid = $backup->{volid}; | |
719 | $maxlen = length($volid) if length($volid) > $maxlen; | |
720 | } | |
721 | $maxlen+=1; | |
722 | ||
723 | printf("%-${maxlen}s %15s %10s\n", 'Backup', 'Backup-ID', 'Prune-Mark'); | |
724 | foreach my $backup (@sorted) { | |
725 | my $type = $backup->{type}; | |
726 | my $vmid = $backup->{vmid}; | |
727 | my $backup_id = defined($vmid) ? "$type/$vmid" : "$type"; | |
728 | printf("%-${maxlen}s %15s %10s\n", $backup->{volid}, $backup_id, $backup->{mark}); | |
729 | } | |
730 | }], | |
c669f42d DM |
731 | }; |
732 | ||
733 | 1; |