]> git.proxmox.com Git - pve-storage.git/blame - PVE/CLI/pvesm.pm
prune_backups CLI: print different message when there's no backups at all
[pve-storage.git] / PVE / CLI / pvesm.pm
CommitLineData
c669f42d
DM
1package PVE::CLI::pvesm;
2
3use strict;
4use warnings;
5
9559a62a 6use POSIX qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC);
c669f42d
DM
7use Fcntl ':flock';
8use File::Path;
9
10use PVE::SafeSyslog;
11use PVE::Cluster;
12use PVE::INotify;
13use PVE::RPCEnvironment;
14use PVE::Storage;
25a95836 15use PVE::Tools qw(extract_param);
c669f42d
DM
16use PVE::API2::Storage::Config;
17use PVE::API2::Storage::Content;
25a95836 18use PVE::API2::Storage::PruneBackups;
c669f42d 19use PVE::API2::Storage::Status;
c669f42d 20use PVE::JSONSchema qw(get_standard_option);
c26f3a71 21use PVE::PTY;
c669f42d
DM
22
23use PVE::CLIHandler;
24
25use base qw(PVE::CLIHandler);
26
9559a62a 27my $KNOWN_EXPORT_FORMATS = ['raw+size', 'tar+size', 'qcow2+size', 'vmdk+size', 'zfs'];
47f37b53 28
c669f42d
DM
29my $nodename = PVE::INotify::nodename();
30
42f2c57d
DC
31sub param_mapping {
32 my ($name) = @_;
33
34 my $password_map = PVE::CLIHandler::get_standard_mapping('pve-password', {
35 func => sub {
36 my ($value) = @_;
37 return $value if $value;
38 return PVE::PTY::read_password("Enter Password: ");
39 },
40 });
baf77120 41
0ca8eb4f
WB
42 my $enc_key_map = {
43 name => 'encryption-key',
44 desc => 'a file containing an encryption key, or the special value "autogen"',
45 func => sub {
46 my ($value) = @_;
47 return $value if $value eq 'autogen';
48 return PVE::Tools::file_get_contents($value);
49 }
50 };
51
52
42f2c57d
DC
53 my $mapping = {
54 'cifsscan' => [ $password_map ],
0ca8eb4f
WB
55 'create' => [ $password_map, $enc_key_map ],
56 'update' => [ $password_map, $enc_key_map ],
42f2c57d
DC
57 };
58 return $mapping->{$name};
c26f3a71
WL
59}
60
f984732e
DM
61sub setup_environment {
62 PVE::RPCEnvironment->setup_default_cli_env();
63}
64
5f184292
FE
65__PACKAGE__->register_method ({
66 name => 'apiinfo',
67 path => 'apiinfo',
68 method => 'GET',
69 description => "Returns APIVER and APIAGE.",
70 parameters => {
71 additionalProperties => 0,
72 properties => {},
73 },
74 returns => {
75 type => 'object',
76 properties => {
77 apiver => { type => 'integer' },
78 apiage => { type => 'integer' },
79 },
80 },
81 code => sub {
82 return {
83 apiver => PVE::Storage::APIVER,
84 apiage => PVE::Storage::APIAGE,
85 };
86 }
87});
88
c669f42d
DM
89__PACKAGE__->register_method ({
90 name => 'path',
91 path => 'path',
92 method => 'GET',
93 description => "Get filesystem path for specified volume",
94 parameters => {
95 additionalProperties => 0,
96 properties => {
97 volume => {
98 description => "Volume identifier",
99 type => 'string', format => 'pve-volume-id',
f3bd890d 100 completion => \&PVE::Storage::complete_volume,
c669f42d
DM
101 },
102 },
103 },
104 returns => { type => 'null' },
105
106 code => sub {
107 my ($param) = @_;
108
109 my $cfg = PVE::Storage::config();
110
111 my $path = PVE::Storage::path ($cfg, $param->{volume});
112
113 print "$path\n";
114
115 return undef;
116
117 }});
118
fa017b96
FG
119__PACKAGE__->register_method ({
120 name => 'extractconfig',
121 path => 'extractconfig',
122 method => 'GET',
123 description => "Extract configuration from vzdump backup archive.",
124 permissions => {
125 description => "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.",
126 user => 'all',
127 },
128 protected => 1,
129 parameters => {
130 additionalProperties => 0,
131 properties => {
132 volume => {
133 description => "Volume identifier",
134 type => 'string',
135 completion => \&PVE::Storage::complete_volume,
136 },
137 },
138 },
139 returns => { type => 'null' },
140 code => sub {
141 my ($param) = @_;
142 my $volume = $param->{volume};
143
144 my $rpcenv = PVE::RPCEnvironment::get();
145 my $authuser = $rpcenv->get_user();
146
147 my $storage_cfg = PVE::Storage::config();
04a13668 148 PVE::Storage::check_volume_access($rpcenv, $authuser, $storage_cfg, undef, $volume);
fa017b96
FG
149
150 my $config_raw = PVE::Storage::extract_vzdump_config($storage_cfg, $volume);
151
152 print "$config_raw\n";
153 return;
154 }});
155
c669f42d
DM
156my $print_content = sub {
157 my ($list) = @_;
158
61c261e7 159 my ($maxlenname, $maxsize) = (0, 0);
c669f42d 160 foreach my $info (@$list) {
c669f42d
DM
161 my $volid = $info->{volid};
162 my $sidlen = length ($volid);
163 $maxlenname = $sidlen if $sidlen > $maxlenname;
61c261e7 164 $maxsize = $info->{size} if ($info->{size} // 0) > $maxsize;
c669f42d 165 }
61c261e7
TL
166 my $sizemaxdigits = length($maxsize);
167
168 my $basefmt = "%-${maxlenname}s %-7s %-9s %${sizemaxdigits}s";
169 printf "$basefmt %s\n", "Volid", "Format", "Type", "Size", "VMID";
c669f42d
DM
170
171 foreach my $info (@$list) {
172 next if !$info->{vmid};
173 my $volid = $info->{volid};
174
61c261e7 175 printf "$basefmt %d\n", $volid, $info->{format}, $info->{content}, $info->{size}, $info->{vmid};
c669f42d
DM
176 }
177
178 foreach my $info (sort { $a->{format} cmp $b->{format} } @$list) {
179 next if $info->{vmid};
180 my $volid = $info->{volid};
181
61c261e7 182 printf "$basefmt\n", $volid, $info->{format}, $info->{content}, $info->{size};
c669f42d
DM
183 }
184};
185
186my $print_status = sub {
187 my $res = shift;
188
189 my $maxlen = 0;
190 foreach my $res (@$res) {
191 my $storeid = $res->{storage};
192 $maxlen = length ($storeid) if length ($storeid) > $maxlen;
193 }
194 $maxlen+=1;
195
d40e27de
TL
196 printf "%-${maxlen}s %10s %10s %15s %15s %15s %8s\n", 'Name', 'Type',
197 'Status', 'Total', 'Used', 'Available', '%';
198
c669f42d
DM
199 foreach my $res (sort { $a->{storage} cmp $b->{storage} } @$res) {
200 my $storeid = $res->{storage};
201
d40e27de
TL
202 my $active = $res->{active} ? 'active' : 'inactive';
203 my ($per, $per_fmt) = (0, '% 7.2f%%');
204 $per = ($res->{used}*100)/$res->{total} if $res->{total} > 0;
205
206 if (!$res->{enabled}) {
04301013 207 $per = 'N/A';
d40e27de
TL
208 $per_fmt = '% 8s';
209 $active = 'disabled';
210 }
c669f42d 211
d40e27de
TL
212 printf "%-${maxlen}s %10s %10s %15d %15d %15d $per_fmt\n", $storeid,
213 $res->{type}, $active, $res->{total}/1024, $res->{used}/1024,
214 $res->{avail}/1024, $per;
c669f42d
DM
215 }
216};
217
47f37b53
WB
218__PACKAGE__->register_method ({
219 name => 'export',
220 path => 'export',
221 method => 'GET',
a43a796c 222 description => "Used internally to export a volume.",
47f37b53
WB
223 protected => 1,
224 parameters => {
225 additionalProperties => 0,
226 properties => {
227 volume => {
228 description => "Volume identifier",
229 type => 'string',
230 completion => \&PVE::Storage::complete_volume,
231 },
232 format => {
233 description => "Export stream format",
234 type => 'string',
235 enum => $KNOWN_EXPORT_FORMATS,
236 },
237 filename => {
238 description => "Destination file name",
239 type => 'string',
240 },
241 base => {
242 description => "Snapshot to start an incremental stream from",
243 type => 'string',
244 pattern => qr/[a-z0-9_\-]{1,40}/,
245 maxLength => 40,
246 optional => 1,
247 },
248 snapshot => {
249 description => "Snapshot to export",
250 type => 'string',
251 pattern => qr/[a-z0-9_\-]{1,40}/,
252 maxLength => 40,
253 optional => 1,
254 },
255 'with-snapshots' => {
256 description =>
257 "Whether to include intermediate snapshots in the stream",
258 type => 'boolean',
259 optional => 1,
260 default => 0,
261 },
262 },
263 },
264 returns => { type => 'null' },
265 code => sub {
266 my ($param) = @_;
267
268 my $filename = $param->{filename};
269
270 my $outfh;
271 if ($filename eq '-') {
272 $outfh = \*STDOUT;
273 } else {
9559a62a 274 sysopen($outfh, $filename, O_CREAT|O_WRONLY|O_TRUNC)
47f37b53
WB
275 or die "open($filename): $!\n";
276 }
277
278 eval {
279 my $cfg = PVE::Storage::config();
280 PVE::Storage::volume_export($cfg, $outfh, $param->{volume}, $param->{format},
281 $param->{snapshot}, $param->{base}, $param->{'with-snapshots'});
282 };
283 my $err = $@;
284 if ($filename ne '-') {
285 close($outfh);
286 unlink($filename) if $err;
287 }
288 die $err if $err;
289 return;
290 }
291});
292
293__PACKAGE__->register_method ({
294 name => 'import',
295 path => 'import',
296 method => 'PUT',
a43a796c 297 description => "Used internally to import a volume.",
47f37b53
WB
298 protected => 1,
299 parameters => {
300 additionalProperties => 0,
301 properties => {
302 volume => {
303 description => "Volume identifier",
304 type => 'string',
305 completion => \&PVE::Storage::complete_volume,
306 },
307 format => {
308 description => "Import stream format",
309 type => 'string',
310 enum => $KNOWN_EXPORT_FORMATS,
311 },
312 filename => {
228e5be9
TL
313 description => "Source file name. For '-' stdin is used, the " .
314 "tcp://<IP-or-CIDR> format allows to use a TCP connection as input. " .
315 "Else, the file is treated as common file.",
47f37b53
WB
316 type => 'string',
317 },
318 base => {
319 description => "Base snapshot of an incremental stream",
320 type => 'string',
321 pattern => qr/[a-z0-9_\-]{1,40}/,
322 maxLength => 40,
323 optional => 1,
324 },
325 'with-snapshots' => {
326 description =>
327 "Whether the stream includes intermediate snapshots",
328 type => 'boolean',
329 optional => 1,
330 default => 0,
331 },
52595938
WB
332 'delete-snapshot' => {
333 description => "A snapshot to delete on success",
334 type => 'string',
335 pattern => qr/[a-z0-9_\-]{1,80}/,
336 maxLength => 80,
337 optional => 1,
338 },
a97d3ee4
FE
339 'allow-rename' => {
340 description => "Choose a new volume ID if the requested " .
341 "volume ID already exists, instead of throwing an error.",
342 type => 'boolean',
343 optional => 1,
344 default => 0,
345 },
47f37b53
WB
346 },
347 },
a97d3ee4 348 returns => { type => 'string' },
47f37b53
WB
349 code => sub {
350 my ($param) = @_;
351
352 my $filename = $param->{filename};
353
354 my $infh;
355 if ($filename eq '-') {
356 $infh = \*STDIN;
228e5be9
TL
357 } elsif ($filename =~ m!^tcp://(([^/]+)(/\d+)?)$!) {
358 my ($cidr, $ip, $subnet) = ($1, $2, $3);
359 if ($subnet) { # got real CIDR notation, not just IP
a2aae38c 360 my $ips = PVE::Network::get_local_ip_from_cidr($cidr);
ed2df8e3
TL
361 die "Unable to get any local IP address in network '$cidr'\n"
362 if scalar(@$ips) < 1;
363 die "Got multiple local IP address in network '$cidr'\n"
364 if scalar(@$ips) > 1;
365
366 $ip = $ips->[0];
228e5be9
TL
367 }
368 my $family = PVE::Tools::get_host_address_family($ip);
369 my $port = PVE::Tools::next_migrate_port($family, $ip);
370
371 my $sock_params = {
372 Listen => 1,
373 ReuseAddr => 1,
374 Proto => &Socket::IPPROTO_TCP,
375 GetAddrInfoFlags => 0,
376 LocalAddr => $ip,
377 LocalPort => $port,
378 };
379 my $socket = IO::Socket::IP->new(%$sock_params)
380 or die "failed to open socket: $!\n";
381
382 print "$ip\n$port\n"; # tell remote where to connect
383 *STDOUT->flush();
384
385 my $prev_alarm = alarm 0;
386 local $SIG{ALRM} = sub { die "timed out waiting for client\n" };
387 alarm 30;
388 my $client = $socket->accept; # Wait for a client
389 alarm $prev_alarm;
390 close($socket);
391
392 $infh = \*$client;
47f37b53 393 } else {
9559a62a 394 sysopen($infh, $filename, O_RDONLY)
47f37b53
WB
395 or die "open($filename): $!\n";
396 }
397
398 my $cfg = PVE::Storage::config();
52595938
WB
399 my $volume = $param->{volume};
400 my $delete = $param->{'delete-snapshot'};
a97d3ee4
FE
401 my $imported_volid = PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format},
402 $param->{base}, $param->{'with-snapshots'}, $param->{'allow-rename'});
403 PVE::Storage::volume_snapshot_delete($cfg, $imported_volid, $delete)
52595938 404 if defined($delete);
a97d3ee4 405 return $imported_volid;
47f37b53
WB
406 }
407});
408
7963ba74
DC
409__PACKAGE__->register_method ({
410 name => 'nfsscan',
411 path => 'nfs',
412 method => 'GET',
413 description => "Scan remote NFS server.",
414 protected => 1,
415 proxyto => "node",
416 permissions => {
417 check => ['perm', '/storage', ['Datastore.Allocate']],
418 },
419 parameters => {
420 additionalProperties => 0,
421 properties => {
422 node => get_standard_option('pve-node'),
423 server => {
424 description => "The server address (name or IP).",
425 type => 'string', format => 'pve-storage-server',
426 },
427 },
428 },
429 returns => {
430 type => 'array',
431 items => {
432 type => "object",
433 properties => {
434 path => {
435 description => "The exported path.",
436 type => 'string',
437 },
438 options => {
439 description => "NFS export options.",
440 type => 'string',
441 },
442 },
443 },
444 },
445 code => sub {
446 my ($param) = @_;
447
448 my $server = $param->{server};
449 my $res = PVE::Storage::scan_nfs($server);
450
451 my $data = [];
0b49e5cc 452 foreach my $k (sort keys %$res) {
7963ba74
DC
453 push @$data, { path => $k, options => $res->{$k} };
454 }
455 return $data;
456 }});
457
458__PACKAGE__->register_method ({
459 name => 'cifsscan',
460 path => 'cifs',
461 method => 'GET',
462 description => "Scan remote CIFS server.",
463 protected => 1,
464 proxyto => "node",
465 permissions => {
466 check => ['perm', '/storage', ['Datastore.Allocate']],
467 },
468 parameters => {
469 additionalProperties => 0,
470 properties => {
471 node => get_standard_option('pve-node'),
472 server => {
473 description => "The server address (name or IP).",
474 type => 'string', format => 'pve-storage-server',
475 },
476 username => {
477 description => "User name.",
478 type => 'string',
479 optional => 1,
480 },
481 password => {
482 description => "User password.",
483 type => 'string',
484 optional => 1,
485 },
486 domain => {
487 description => "SMB domain (Workgroup).",
488 type => 'string',
489 optional => 1,
490 },
491 },
492 },
493 returns => {
494 type => 'array',
495 items => {
496 type => "object",
497 properties => {
498 share => {
499 description => "The cifs share name.",
500 type => 'string',
501 },
502 description => {
503 description => "Descriptive text from server.",
504 type => 'string',
505 },
506 },
507 },
508 },
509 code => sub {
510 my ($param) = @_;
511
512 my $server = $param->{server};
513
514 my $username = $param->{username};
515 my $password = $param->{password};
516 my $domain = $param->{domain};
517
518 my $res = PVE::Storage::scan_cifs($server, $username, $password, $domain);
519
520 my $data = [];
0b49e5cc 521 foreach my $k (sort keys %$res) {
7963ba74
DC
522 push @$data, { share => $k, description => $res->{$k} };
523 }
524
525 return $data;
526 }});
527
528# Note: GlusterFS currently does not have an equivalent of showmount.
529# As workaround, we simply use nfs showmount.
530# see http://www.gluster.org/category/volumes/
531
532__PACKAGE__->register_method ({
533 name => 'glusterfsscan',
534 path => 'glusterfs',
535 method => 'GET',
536 description => "Scan remote GlusterFS server.",
537 protected => 1,
538 proxyto => "node",
539 permissions => {
540 check => ['perm', '/storage', ['Datastore.Allocate']],
541 },
542 parameters => {
543 additionalProperties => 0,
544 properties => {
545 node => get_standard_option('pve-node'),
546 server => {
547 description => "The server address (name or IP).",
548 type => 'string', format => 'pve-storage-server',
549 },
550 },
551 },
552 returns => {
553 type => 'array',
554 items => {
555 type => "object",
2e2e11db 556 properties => {
7963ba74
DC
557 volname => {
558 description => "The volume name.",
559 type => 'string',
560 },
561 },
562 },
563 },
564 code => sub {
565 my ($param) = @_;
566
567 my $server = $param->{server};
568 my $res = PVE::Storage::scan_nfs($server);
569
570 my $data = [];
0b49e5cc 571 foreach my $path (sort keys %$res) {
7963ba74
DC
572 if ($path =~ m!^/([^\s/]+)$!) {
573 push @$data, { volname => $1 };
574 }
575 }
576 return $data;
577 }});
578
579__PACKAGE__->register_method ({
580 name => 'iscsiscan',
581 path => 'iscsi',
582 method => 'GET',
583 description => "Scan remote iSCSI server.",
584 protected => 1,
585 proxyto => "node",
586 permissions => {
587 check => ['perm', '/storage', ['Datastore.Allocate']],
588 },
589 parameters => {
590 additionalProperties => 0,
591 properties => {
592 node => get_standard_option('pve-node'),
593 portal => {
594 description => "The iSCSI portal (IP or DNS name with optional port).",
595 type => 'string', format => 'pve-storage-portal-dns',
596 },
597 },
598 },
599 returns => {
600 type => 'array',
601 items => {
602 type => "object",
603 properties => {
604 target => {
605 description => "The iSCSI target name.",
606 type => 'string',
607 },
608 portal => {
609 description => "The iSCSI portal name.",
610 type => 'string',
611 },
612 },
613 },
614 },
615 code => sub {
616 my ($param) = @_;
617
618 my $res = PVE::Storage::scan_iscsi($param->{portal});
619
620 my $data = [];
0b49e5cc 621 foreach my $k (sort keys %$res) {
7963ba74
DC
622 push @$data, { target => $k, portal => join(',', @{$res->{$k}}) };
623 }
624
625 return $data;
626 }});
627
628__PACKAGE__->register_method ({
629 name => 'lvmscan',
630 path => 'lvm',
631 method => 'GET',
632 description => "List local LVM volume groups.",
633 protected => 1,
634 proxyto => "node",
635 permissions => {
636 check => ['perm', '/storage', ['Datastore.Allocate']],
637 },
638 parameters => {
639 additionalProperties => 0,
640 properties => {
641 node => get_standard_option('pve-node'),
642 },
643 },
644 returns => {
645 type => 'array',
646 items => {
647 type => "object",
648 properties => {
649 vg => {
650 description => "The LVM logical volume group name.",
651 type => 'string',
652 },
653 },
654 },
655 },
656 code => sub {
657 my ($param) = @_;
658
659 my $res = PVE::Storage::LVMPlugin::lvm_vgs();
660 return PVE::RESTHandler::hash_to_array($res, 'vg');
661 }});
662
663__PACKAGE__->register_method ({
664 name => 'lvmthinscan',
665 path => 'lvmthin',
666 method => 'GET',
667 description => "List local LVM Thin Pools.",
668 protected => 1,
669 proxyto => "node",
670 permissions => {
671 check => ['perm', '/storage', ['Datastore.Allocate']],
672 },
673 parameters => {
674 additionalProperties => 0,
675 properties => {
676 node => get_standard_option('pve-node'),
677 vg => {
678 type => 'string',
679 pattern => '[a-zA-Z0-9\.\+\_][a-zA-Z0-9\.\+\_\-]+', # see lvm(8) manpage
680 maxLength => 100,
681 },
682 },
683 },
684 returns => {
685 type => 'array',
686 items => {
687 type => "object",
688 properties => {
689 lv => {
690 description => "The LVM Thin Pool name (LVM logical volume).",
691 type => 'string',
692 },
693 },
694 },
695 },
696 code => sub {
697 my ($param) = @_;
698
699 return PVE::Storage::LvmThinPlugin::list_thinpools($param->{vg});
700 }});
701
702__PACKAGE__->register_method ({
703 name => 'zfsscan',
704 path => 'zfs',
705 method => 'GET',
706 description => "Scan zfs pool list on local node.",
707 protected => 1,
708 proxyto => "node",
709 permissions => {
710 check => ['perm', '/storage', ['Datastore.Allocate']],
711 },
712 parameters => {
713 additionalProperties => 0,
714 properties => {
715 node => get_standard_option('pve-node'),
716 },
717 },
718 returns => {
719 type => 'array',
720 items => {
721 type => "object",
722 properties => {
723 pool => {
724 description => "ZFS pool name.",
725 type => 'string',
726 },
727 },
728 },
729 },
730 code => sub {
731 my ($param) = @_;
732
733 return PVE::Storage::scan_zfs();
734 }});
735
25a95836
FE
736__PACKAGE__->register_method ({
737 name => 'prunebackups',
738 path => 'prunebackups',
739 method => 'GET',
740 description => "Prune backups. This is only a wrapper for the proper API endpoints.",
741 protected => 1,
742 proxyto => 'node',
743 parameters => {
744 additionalProperties => 0,
745 properties => {
746 'dry-run' => {
747 description => "Only show what would be pruned, don't delete anything.",
748 type => 'boolean',
749 optional => 1,
750 },
751 node => get_standard_option('pve-node'),
752 storage => get_standard_option('pve-storage-id', {
753 completion => \&PVE::Storage::complete_storage_enabled,
754 }),
755 'prune-backups' => get_standard_option('prune-backups', {
756 description => "Use these retention options instead of those from the storage configuration.",
757 optional => 1,
758 }),
759 type => {
760 description => "Either 'qemu' or 'lxc'. Only consider backups for guests of this type.",
761 type => 'string',
762 optional => 1,
763 enum => ['qemu', 'lxc'],
764 },
765 vmid => get_standard_option('pve-vmid', {
766 description => "Only consider backups for this guest.",
767 optional => 1,
768 completion => \&PVE::Cluster::complete_vmid,
769 }),
770 },
771 },
772 returns => {
773 type => 'object',
774 properties => {
775 dryrun => {
776 description => 'If it was a dry run or not. The list will only be defined in that case.',
777 type => 'boolean',
778 },
779 list => {
780 type => 'array',
781 items => {
782 type => 'object',
783 properties => {
784 volid => {
785 description => "Backup volume ID.",
786 type => 'string',
787 },
788 'ctime' => {
789 description => "Creation time of the backup (seconds since the UNIX epoch).",
790 type => 'integer',
791 },
792 'mark' => {
793 description => "Whether the backup would be kept or removed. For backups that don't " .
794 "use the standard naming scheme, it's 'protected'.",
795 type => 'string',
796 },
797 type => {
798 description => "One of 'qemu', 'lxc', 'openvz' or 'unknown'.",
799 type => 'string',
800 },
801 'vmid' => {
802 description => "The VM the backup belongs to.",
803 type => 'integer',
804 optional => 1,
805 },
806 },
807 },
808 },
809 },
810 },
811 code => sub {
812 my ($param) = @_;
813
814 my $dryrun = extract_param($param, 'dry-run') ? 1 : 0;
815
816 my $list = [];
817 if ($dryrun) {
818 $list = PVE::API2::Storage::PruneBackups->dryrun($param);
819 } else {
820 PVE::API2::Storage::PruneBackups->delete($param);
821 }
822
823 return {
824 dryrun => $dryrun,
825 list => $list,
826 };
827 }});
828
c669f42d
DM
829our $cmddef = {
830 add => [ "PVE::API2::Storage::Config", 'create', ['type', 'storage'] ],
831 set => [ "PVE::API2::Storage::Config", 'update', ['storage'] ],
832 remove => [ "PVE::API2::Storage::Config", 'delete', ['storage'] ],
833 status => [ "PVE::API2::Storage::Status", 'index', [],
834 { node => $nodename }, $print_status ],
835 list => [ "PVE::API2::Storage::Content", 'index', ['storage'],
836 { node => $nodename }, $print_content ],
837 alloc => [ "PVE::API2::Storage::Content", 'create', ['storage', 'vmid', 'filename', 'size'],
838 { node => $nodename }, sub {
839 my $volid = shift;
e967e0ef 840 print "successfully created '$volid'\n";
c669f42d
DM
841 }],
842 free => [ "PVE::API2::Storage::Content", 'delete', ['volume'],
843 { node => $nodename } ],
957321a8
TL
844 scan => {
845 nfs => [ __PACKAGE__, 'nfsscan', ['server'], { node => $nodename }, sub {
846 my $res = shift;
847
848 my $maxlen = 0;
849 foreach my $rec (@$res) {
850 my $len = length ($rec->{path});
851 $maxlen = $len if $len > $maxlen;
852 }
853 foreach my $rec (@$res) {
854 printf "%-${maxlen}s %s\n", $rec->{path}, $rec->{options};
855 }
856 }],
857 cifs => [ __PACKAGE__, 'cifsscan', ['server'], { node => $nodename }, sub {
858 my $res = shift;
859
860 my $maxlen = 0;
861 foreach my $rec (@$res) {
862 my $len = length ($rec->{share});
863 $maxlen = $len if $len > $maxlen;
864 }
865 foreach my $rec (@$res) {
866 printf "%-${maxlen}s %s\n", $rec->{share}, $rec->{description};
867 }
868 }],
869 glusterfs => [ __PACKAGE__, 'glusterfsscan', ['server'], { node => $nodename }, sub {
870 my $res = shift;
871
872 foreach my $rec (@$res) {
873 printf "%s\n", $rec->{volname};
874 }
875 }],
876 iscsi => [ __PACKAGE__, 'iscsiscan', ['portal'], { node => $nodename }, sub {
877 my $res = shift;
878
879 my $maxlen = 0;
880 foreach my $rec (@$res) {
881 my $len = length ($rec->{target});
882 $maxlen = $len if $len > $maxlen;
883 }
884 foreach my $rec (@$res) {
885 printf "%-${maxlen}s %s\n", $rec->{target}, $rec->{portal};
886 }
887 }],
888 lvm => [ __PACKAGE__, 'lvmscan', [], { node => $nodename }, sub {
889 my $res = shift;
890 foreach my $rec (@$res) {
891 printf "$rec->{vg}\n";
892 }
893 }],
894 lvmthin => [ __PACKAGE__, 'lvmthinscan', ['vg'], { node => $nodename }, sub {
895 my $res = shift;
896 foreach my $rec (@$res) {
897 printf "$rec->{lv}\n";
898 }
899 }],
900 zfs => [ __PACKAGE__, 'zfsscan', [], { node => $nodename }, sub {
901 my $res = shift;
902
903 foreach my $rec (@$res) {
904 printf "$rec->{pool}\n";
905 }
906 }],
907 },
908 nfsscan => { alias => 'scan nfs' },
909 cifsscan => { alias => 'scan cifs' },
910 glusterfsscan => { alias => 'scan glusterfs' },
911 iscsiscan => { alias => 'scan iscsi' },
912 lvmscan => { alias => 'scan lvm' },
913 lvmthinscan => { alias => 'scan lvmthin' },
914 zfsscan => { alias => 'scan zfs' },
c669f42d 915 path => [ __PACKAGE__, 'path', ['volume']],
fa017b96 916 extractconfig => [__PACKAGE__, 'extractconfig', ['volume']],
47f37b53 917 export => [ __PACKAGE__, 'export', ['volume', 'format', 'filename']],
a97d3ee4
FE
918 import => [ __PACKAGE__, 'import', ['volume', 'format', 'filename'], {}, sub {
919 my $volid = shift;
920 print PVE::Storage::volume_imported_message($volid);
921 }],
5f184292
FE
922 apiinfo => [ __PACKAGE__, 'apiinfo', [], {}, sub {
923 my $res = shift;
924
925 print "APIVER $res->{apiver}\n";
926 print "APIAGE $res->{apiage}\n";
927 }],
25a95836
FE
928 'prune-backups' => [ __PACKAGE__, 'prunebackups', ['storage'], { node => $nodename }, sub {
929 my $res = shift;
930
931 my ($dryrun, $list) = ($res->{dryrun}, $res->{list});
932
933 return if !$dryrun;
934
c3e87d0f
FE
935 if (!scalar(@{$list})) {
936 print "No backups found\n";
937 return;
938 }
939
7b73d327
FE
940 print "NOTE: this is only a preview and might not be what a subsequent\n" .
941 "prune call does if backups are removed/added in the meantime.\n\n";
25a95836
FE
942
943 my @sorted = sort {
944 my $vmcmp = PVE::Tools::safe_compare($a->{vmid}, $b->{vmid}, sub { $_[0] <=> $_[1] });
945 return $vmcmp if $vmcmp ne 0;
946 return $a->{ctime} <=> $b->{ctime};
947 } @{$list};
948
949 my $maxlen = 0;
950 foreach my $backup (@sorted) {
951 my $volid = $backup->{volid};
952 $maxlen = length($volid) if length($volid) > $maxlen;
953 }
954 $maxlen+=1;
955
956 printf("%-${maxlen}s %15s %10s\n", 'Backup', 'Backup-ID', 'Prune-Mark');
957 foreach my $backup (@sorted) {
958 my $type = $backup->{type};
959 my $vmid = $backup->{vmid};
960 my $backup_id = defined($vmid) ? "$type/$vmid" : "$type";
961 printf("%-${maxlen}s %15s %10s\n", $backup->{volid}, $backup_id, $backup->{mark});
962 }
963 }],
c669f42d
DM
964};
965
9661;