]> git.proxmox.com Git - pve-storage.git/blobdiff - PVE/CLI/pvesm.pm
pvesm: also map the password param for new style cifs scan command
[pve-storage.git] / PVE / CLI / pvesm.pm
index 63b212ae43081b8c3a8772b08a705cfde5b526d2..8f7740e9ed145ce675d747cd5f1616ced102727e 100755 (executable)
@@ -12,8 +12,11 @@ use PVE::Cluster;
 use PVE::INotify;
 use PVE::RPCEnvironment;
 use PVE::Storage;
+use PVE::Tools qw(extract_param);
 use PVE::API2::Storage::Config;
 use PVE::API2::Storage::Content;
+use PVE::API2::Storage::PruneBackups;
+use PVE::API2::Storage::Scan;
 use PVE::API2::Storage::Status;
 use PVE::JSONSchema qw(get_standard_option);
 use PVE::PTY;
@@ -36,9 +39,23 @@ sub param_mapping {
            return PVE::PTY::read_password("Enter Password: ");
        },
     });
+
+    my $enc_key_map = {
+       name => 'encryption-key',
+       desc => 'a file containing an encryption key, or the special value "autogen"',
+       func => sub {
+           my ($value) = @_;
+           return $value if $value eq 'autogen';
+           return PVE::Tools::file_get_contents($value);
+       }
+    };
+
+
     my $mapping = {
        'cifsscan' => [ $password_map ],
-       'create' => [ $password_map ],
+       'cifs' => [ $password_map ],
+       'create' => [ $password_map, $enc_key_map ],
+       'update' => [ $password_map, $enc_key_map ],
     };
     return $mapping->{$name};
 }
@@ -47,6 +64,30 @@ sub setup_environment {
     PVE::RPCEnvironment->setup_default_cli_env();
 }
 
+__PACKAGE__->register_method ({
+    name => 'apiinfo',
+    path => 'apiinfo',
+    method => 'GET',
+    description => "Returns APIVER and APIAGE.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => {
+       type => 'object',
+       properties => {
+           apiver => { type => 'integer' },
+           apiage => { type => 'integer' },
+       },
+    },
+    code => sub {
+       return {
+           apiver => PVE::Storage::APIVER,
+           apiage => PVE::Storage::APIAGE,
+       };
+    }
+});
+
 __PACKAGE__->register_method ({
     name => 'path',
     path => 'path',
@@ -117,30 +158,30 @@ __PACKAGE__->register_method ({
 my $print_content = sub {
     my ($list) = @_;
 
-    my $maxlenname = 0;
+    my ($maxlenname, $maxsize) = (0, 0);
     foreach my $info (@$list) {
-
        my $volid = $info->{volid};
        my $sidlen =  length ($volid);
        $maxlenname = $sidlen if $sidlen > $maxlenname;
+       $maxsize = $info->{size} if ($info->{size} // 0) > $maxsize;
     }
-    printf "%-${maxlenname}s %-6s %-9s %-10s %s\n", "Volid",
-       "Format", "Type", "Size", "VMID";
+    my $sizemaxdigits = length($maxsize);
+
+    my $basefmt = "%-${maxlenname}s %-7s %-9s %${sizemaxdigits}s";
+    printf "$basefmt %s\n", "Volid", "Format", "Type", "Size", "VMID";
 
     foreach my $info (@$list) {
        next if !$info->{vmid};
        my $volid = $info->{volid};
 
-       printf "%-${maxlenname}s %-6s %-9s %-10d %d\n", $volid,
-       $info->{format}, $info->{content}, $info->{size}, $info->{vmid};
+       printf "$basefmt %d\n", $volid, $info->{format}, $info->{content}, $info->{size}, $info->{vmid};
     }
 
     foreach my $info (sort { $a->{format} cmp $b->{format} } @$list) {
        next if $info->{vmid};
        my $volid = $info->{volid};
 
-       printf "%-${maxlenname}s %-6s %-9s %-10d\n", $volid,
-       $info->{format}, $info->{content}, $info->{size};
+       printf "$basefmt\n", $volid, $info->{format}, $info->{content}, $info->{size};
     }
 };
 
@@ -180,7 +221,7 @@ __PACKAGE__->register_method ({
     name => 'export',
     path => 'export',
     method => 'GET',
-    description => "Export a volume.",
+    description => "Used internally to export a volume.",
     protected => 1,
     parameters => {
        additionalProperties => 0,
@@ -255,7 +296,7 @@ __PACKAGE__->register_method ({
     name => 'import',
     path => 'import',
     method => 'PUT',
-    description => "Import a volume.",
+    description => "Used internally to import a volume.",
     protected => 1,
     parameters => {
        additionalProperties => 0,
@@ -297,9 +338,16 @@ __PACKAGE__->register_method ({
                maxLength => 80,
                optional => 1,
            },
+           'allow-rename' => {
+               description => "Choose a new volume ID if the requested " .
+                 "volume ID already exists, instead of throwing an error.",
+               type => 'boolean',
+               optional => 1,
+               default => 0,
+           },
        },
     },
-    returns => { type => 'null' },
+    returns => { type => 'string' },
     code => sub {
        my ($param) = @_;
 
@@ -352,166 +400,83 @@ __PACKAGE__->register_method ({
        my $cfg = PVE::Storage::config();
        my $volume = $param->{volume};
        my $delete = $param->{'delete-snapshot'};
-       PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format},
-           $param->{base}, $param->{'with-snapshots'});
-       PVE::Storage::volume_snapshot_delete($cfg, $volume, $delete)
+       my $imported_volid = PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format},
+           $param->{base}, $param->{'with-snapshots'}, $param->{'allow-rename'});
+       PVE::Storage::volume_snapshot_delete($cfg, $imported_volid, $delete)
            if defined($delete);
-       return;
+       return $imported_volid;
     }
 });
 
 __PACKAGE__->register_method ({
-    name => 'nfsscan',
-    path => 'nfs',
-    method => 'GET',
-    description => "Scan remote NFS server.",
-    protected => 1,
-    proxyto => "node",
-    permissions => {
-       check => ['perm', '/storage', ['Datastore.Allocate']],
-    },
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-           server => {
-               description => "The server address (name or IP).",
-               type => 'string', format => 'pve-storage-server',
-           },
-       },
-    },
-    returns => {
-       type => 'array',
-       items => {
-           type => "object",
-           properties => {
-               path => {
-                   description => "The exported path.",
-                   type => 'string',
-               },
-               options => {
-                   description => "NFS export options.",
-                   type => 'string',
-               },
-           },
-       },
-    },
-    code => sub {
-       my ($param) = @_;
-
-       my $server = $param->{server};
-       my $res = PVE::Storage::scan_nfs($server);
-
-       my $data = [];
-       foreach my $k (keys %$res) {
-           push @$data, { path => $k, options => $res->{$k} };
-       }
-       return $data;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'cifsscan',
-    path => 'cifs',
+    name => 'prunebackups',
+    path => 'prunebackups',
     method => 'GET',
-    description => "Scan remote CIFS server.",
+    description => "Prune backups. Only those using the standard naming scheme are considered. " .
+                  "If no keep options are specified, those from the storage configuration are used.",
     protected => 1,
-    proxyto => "node",
-    permissions => {
-       check => ['perm', '/storage', ['Datastore.Allocate']],
-    },
+    proxyto => 'node',
     parameters => {
        additionalProperties => 0,
        properties => {
-           node => get_standard_option('pve-node'),
-           server => {
-               description => "The server address (name or IP).",
-               type => 'string', format => 'pve-storage-server',
-           },
-           username => {
-               description => "User name.",
-               type => 'string',
+           'dry-run' => {
+               description => "Only show what would be pruned, don't delete anything.",
+               type => 'boolean',
                optional => 1,
            },
-           password => {
-               description => "User password.",
+           node => get_standard_option('pve-node'),
+           storage => get_standard_option('pve-storage-id', {
+               completion => \&PVE::Storage::complete_storage_enabled,
+            }),
+           %{$PVE::Storage::Plugin::prune_backups_format},
+           type => {
+               description => "Either 'qemu' or 'lxc'. Only consider backups for guests of this type.",
                type => 'string',
                optional => 1,
+               enum => ['qemu', 'lxc'],
            },
-           domain => {
-               description => "SMB domain (Workgroup).",
-               type => 'string',
+           vmid => get_standard_option('pve-vmid', {
+               description => "Only consider backups for this guest.",
                optional => 1,
-           },
+               completion => \&PVE::Cluster::complete_vmid,
+           }),
        },
     },
     returns => {
-       type => 'array',
-       items => {
-           type => "object",
-           properties => {
-               share => {
-                   description => "The cifs share name.",
-                   type => 'string',
-               },
-               description => {
-                   description => "Descriptive text from server.",
-                   type => 'string',
-               },
-           },
-       },
-    },
-    code => sub {
-       my ($param) = @_;
-
-       my $server = $param->{server};
-
-       my $username = $param->{username};
-       my $password = $param->{password};
-       my $domain = $param->{domain};
-
-       my $res = PVE::Storage::scan_cifs($server, $username, $password, $domain);
-
-       my $data = [];
-       foreach my $k (keys %$res) {
-           next if $k =~ m/NT_STATUS_/;
-           push @$data, { share => $k, description => $res->{$k} };
-       }
-
-       return $data;
-    }});
-
-# Note: GlusterFS currently does not have an equivalent of showmount.
-# As workaround, we simply use nfs showmount.
-# see http://www.gluster.org/category/volumes/
-
-__PACKAGE__->register_method ({
-    name => 'glusterfsscan',
-    path => 'glusterfs',
-    method => 'GET',
-    description => "Scan remote GlusterFS server.",
-    protected => 1,
-    proxyto => "node",
-    permissions => {
-       check => ['perm', '/storage', ['Datastore.Allocate']],
-    },
-    parameters => {
-       additionalProperties => 0,
+       type => 'object',
        properties => {
-           node => get_standard_option('pve-node'),
-           server => {
-               description => "The server address (name or IP).",
-               type => 'string', format => 'pve-storage-server',
+           dryrun => {
+               description => 'If it was a dry run or not. The list will only be defined in that case.',
+               type => 'boolean',
            },
-       },
-    },
-    returns => {
-       type => 'array',
-       items => {
-           type => "object",
-           properties => {
-               volname => {
-                   description => "The volume name.",
-                   type => 'string',
+           list => {
+               type => 'array',
+               items => {
+                   type => 'object',
+                   properties => {
+                       volid => {
+                           description => "Backup volume ID.",
+                           type => 'string',
+                       },
+                       'ctime' => {
+                           description => "Creation time of the backup (seconds since the UNIX epoch).",
+                           type => 'integer',
+                       },
+                       'mark' => {
+                           description => "Whether the backup would be kept or removed. For backups that don't " .
+                                          "use the standard naming scheme, it's 'protected'.",
+                           type => 'string',
+                       },
+                       type => {
+                           description => "One of 'qemu', 'lxc', 'openvz' or 'unknown'.",
+                           type => 'string',
+                       },
+                       'vmid' => {
+                           description => "The VM the backup belongs to.",
+                           type => 'integer',
+                           optional => 1,
+                       },
+                   },
                },
            },
        },
@@ -519,173 +484,26 @@ __PACKAGE__->register_method ({
     code => sub {
        my ($param) = @_;
 
-       my $server = $param->{server};
-       my $res = PVE::Storage::scan_nfs($server);
+       my $dryrun = extract_param($param, 'dry-run') ? 1 : 0;
 
-       my $data = [];
-       foreach my $path (keys %$res) {
-           if ($path =~ m!^/([^\s/]+)$!) {
-               push @$data, { volname => $1 };
-           }
+       my $keep_opts;
+       foreach my $keep (keys %{$PVE::Storage::Plugin::prune_backups_format}) {
+           $keep_opts->{$keep} = extract_param($param, $keep) if defined($param->{$keep});
        }
-       return $data;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'iscsiscan',
-    path => 'iscsi',
-    method => 'GET',
-    description => "Scan remote iSCSI server.",
-    protected => 1,
-    proxyto => "node",
-    permissions => {
-       check => ['perm', '/storage', ['Datastore.Allocate']],
-    },
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-           portal => {
-               description => "The iSCSI portal (IP or DNS name with optional port).",
-               type => 'string', format => 'pve-storage-portal-dns',
-           },
-       },
-    },
-    returns => {
-       type => 'array',
-       items => {
-           type => "object",
-           properties => {
-               target => {
-                   description => "The iSCSI target name.",
-                   type => 'string',
-               },
-               portal => {
-                   description => "The iSCSI portal name.",
-                   type => 'string',
-               },
-           },
-       },
-    },
-    code => sub {
-       my ($param) = @_;
-
-       my $res = PVE::Storage::scan_iscsi($param->{portal});
+       $param->{'prune-backups'} = PVE::JSONSchema::print_property_string(
+           $keep_opts, $PVE::Storage::Plugin::prune_backups_format) if $keep_opts;
 
-       my $data = [];
-       foreach my $k (keys %$res) {
-           push @$data, { target => $k, portal => join(',', @{$res->{$k}}) };
+       my $list = [];
+       if ($dryrun) {
+           $list = PVE::API2::Storage::PruneBackups->dryrun($param);
+       } else {
+           PVE::API2::Storage::PruneBackups->delete($param);
        }
 
-       return $data;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'lvmscan',
-    path => 'lvm',
-    method => 'GET',
-    description => "List local LVM volume groups.",
-    protected => 1,
-    proxyto => "node",
-    permissions => {
-       check => ['perm', '/storage', ['Datastore.Allocate']],
-    },
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-       },
-    },
-    returns => {
-       type => 'array',
-       items => {
-           type => "object",
-           properties => {
-               vg => {
-                   description => "The LVM logical volume group name.",
-                   type => 'string',
-               },
-           },
-       },
-    },
-    code => sub {
-       my ($param) = @_;
-
-       my $res = PVE::Storage::LVMPlugin::lvm_vgs();
-       return PVE::RESTHandler::hash_to_array($res, 'vg');
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'lvmthinscan',
-    path => 'lvmthin',
-    method => 'GET',
-    description => "List local LVM Thin Pools.",
-    protected => 1,
-    proxyto => "node",
-    permissions => {
-       check => ['perm', '/storage', ['Datastore.Allocate']],
-    },
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-           vg => {
-               type => 'string',
-               pattern => '[a-zA-Z0-9\.\+\_][a-zA-Z0-9\.\+\_\-]+', # see lvm(8) manpage
-               maxLength => 100,
-           },
-       },
-    },
-    returns => {
-       type => 'array',
-       items => {
-           type => "object",
-           properties => {
-               lv => {
-                   description => "The LVM Thin Pool name (LVM logical volume).",
-                   type => 'string',
-               },
-           },
-       },
-    },
-    code => sub {
-       my ($param) = @_;
-
-       return PVE::Storage::LvmThinPlugin::list_thinpools($param->{vg});
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'zfsscan',
-    path => 'zfs',
-    method => 'GET',
-    description => "Scan zfs pool list on local node.",
-    protected => 1,
-    proxyto => "node",
-    permissions => {
-       check => ['perm', '/storage', ['Datastore.Allocate']],
-    },
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-       },
-    },
-    returns => {
-       type => 'array',
-       items => {
-           type => "object",
-           properties => {
-               pool => {
-                   description => "ZFS pool name.",
-                   type => 'string',
-               },
-           },
-       },
-    },
-    code => sub {
-       my ($param) = @_;
-
-       return PVE::Storage::scan_zfs();
+       return {
+           dryrun => $dryrun,
+           list => $list,
+       };
     }});
 
 our $cmddef = {
@@ -704,7 +522,7 @@ our $cmddef = {
     free => [ "PVE::API2::Storage::Content", 'delete', ['volume'],
              { node => $nodename } ],
     scan => {
-       nfs => [ __PACKAGE__, 'nfsscan', ['server'], { node => $nodename }, sub  {
+       nfs => [ "PVE::API2::Storage::Scan", 'nfsscan', ['server'], { node => $nodename }, sub  {
            my $res = shift;
 
            my $maxlen = 0;
@@ -716,7 +534,7 @@ our $cmddef = {
                printf "%-${maxlen}s %s\n", $rec->{path}, $rec->{options};
            }
        }],
-       cifs => [ __PACKAGE__, 'cifsscan', ['server'], { node => $nodename }, sub  {
+       cifs => [ "PVE::API2::Storage::Scan", 'cifsscan', ['server'], { node => $nodename }, sub  {
            my $res = shift;
 
            my $maxlen = 0;
@@ -728,14 +546,14 @@ our $cmddef = {
                printf "%-${maxlen}s %s\n", $rec->{share}, $rec->{description};
            }
        }],
-       glusterfs => [ __PACKAGE__, 'glusterfsscan', ['server'], { node => $nodename }, sub  {
+       glusterfs => [ "PVE::API2::Storage::Scan", 'glusterfsscan', ['server'], { node => $nodename }, sub  {
            my $res = shift;
 
            foreach my $rec (@$res) {
                printf "%s\n", $rec->{volname};
            }
        }],
-       iscsi => [ __PACKAGE__, 'iscsiscan', ['portal'], { node => $nodename }, sub  {
+       iscsi => [ "PVE::API2::Storage::Scan", 'iscsiscan', ['portal'], { node => $nodename }, sub  {
            my $res = shift;
 
            my $maxlen = 0;
@@ -747,19 +565,19 @@ our $cmddef = {
                printf "%-${maxlen}s %s\n", $rec->{target}, $rec->{portal};
            }
        }],
-       lvm => [ __PACKAGE__, 'lvmscan', [], { node => $nodename }, sub  {
+       lvm => [ "PVE::API2::Storage::Scan", 'lvmscan', [], { node => $nodename }, sub  {
            my $res = shift;
            foreach my $rec (@$res) {
                printf "$rec->{vg}\n";
            }
        }],
-       lvmthin => [ __PACKAGE__, 'lvmthinscan', ['vg'], { node => $nodename }, sub  {
+       lvmthin => [ "PVE::API2::Storage::Scan", 'lvmthinscan', ['vg'], { node => $nodename }, sub  {
            my $res = shift;
            foreach my $rec (@$res) {
                printf "$rec->{lv}\n";
            }
        }],
-       zfs => [ __PACKAGE__, 'zfsscan', [], { node => $nodename }, sub  {
+       zfs => [ "PVE::API2::Storage::Scan", 'zfsscan', [], { node => $nodename }, sub  {
            my $res = shift;
 
            foreach my $rec (@$res) {
@@ -777,7 +595,52 @@ our $cmddef = {
     path => [ __PACKAGE__, 'path', ['volume']],
     extractconfig => [__PACKAGE__, 'extractconfig', ['volume']],
     export => [ __PACKAGE__, 'export', ['volume', 'format', 'filename']],
-    import => [ __PACKAGE__, 'import', ['volume', 'format', 'filename']],
+    import => [ __PACKAGE__, 'import', ['volume', 'format', 'filename'], {}, sub  {
+       my $volid = shift;
+       print PVE::Storage::volume_imported_message($volid);
+    }],
+    apiinfo => [ __PACKAGE__, 'apiinfo', [], {}, sub {
+       my $res = shift;
+
+       print "APIVER $res->{apiver}\n";
+       print "APIAGE $res->{apiage}\n";
+    }],
+    'prune-backups' => [ __PACKAGE__, 'prunebackups', ['storage'], { node => $nodename }, sub {
+       my $res = shift;
+
+       my ($dryrun, $list) = ($res->{dryrun}, $res->{list});
+
+       return if !$dryrun;
+
+       if (!scalar(@{$list})) {
+           print "No backups found\n";
+           return;
+       }
+
+       print "NOTE: this is only a preview and might not be what a subsequent\n" .
+             "prune call does if backups are removed/added in the meantime.\n\n";
+
+       my @sorted = sort {
+           my $vmcmp = PVE::Tools::safe_compare($a->{vmid}, $b->{vmid}, sub { $_[0] <=> $_[1] });
+           return $vmcmp if $vmcmp ne 0;
+           return $a->{ctime} <=> $b->{ctime};
+       } @{$list};
+
+       my $maxlen = 0;
+       foreach my $backup (@sorted) {
+           my $volid = $backup->{volid};
+           $maxlen = length($volid) if length($volid) > $maxlen;
+       }
+       $maxlen+=1;
+
+       printf("%-${maxlen}s %15s %10s\n", 'Backup', 'Backup-ID', 'Prune-Mark');
+       foreach my $backup (@sorted) {
+           my $type = $backup->{type};
+           my $vmid = $backup->{vmid};
+           my $backup_id = defined($vmid) ? "$type/$vmid" : "$type";
+           printf("%-${maxlen}s %15s %10s\n", $backup->{volid}, $backup_id, $backup->{mark});
+       }
+    }],
 };
 
 1;