]> git.proxmox.com Git - pve-storage.git/blob - PVE/CLI/pvesm.pm
replace read_password with param_mapping
[pve-storage.git] / PVE / CLI / pvesm.pm
1 package PVE::CLI::pvesm;
2
3 use strict;
4 use warnings;
5
6 use POSIX qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC);
7 use Fcntl ':flock';
8 use File::Path;
9
10 use PVE::SafeSyslog;
11 use PVE::Cluster;
12 use PVE::INotify;
13 use PVE::RPCEnvironment;
14 use PVE::Storage;
15 use PVE::API2::Storage::Config;
16 use PVE::API2::Storage::Content;
17 use PVE::API2::Storage::Status;
18 use PVE::API2::Storage::Scan;
19 use PVE::JSONSchema qw(get_standard_option);
20 use PVE::PTY;
21
22 use PVE::CLIHandler;
23
24 use base qw(PVE::CLIHandler);
25
26 my $KNOWN_EXPORT_FORMATS = ['raw+size', 'tar+size', 'qcow2+size', 'vmdk+size', 'zfs'];
27
28 my $nodename = PVE::INotify::nodename();
29
30 sub param_mapping {
31 my ($name) = @_;
32
33 my $password_map = PVE::CLIHandler::get_standard_mapping('pve-password', {
34 func => sub {
35 my ($value) = @_;
36 return $value if $value;
37 return PVE::PTY::read_password("Enter Password: ");
38 },
39 });
40 my $mapping = {
41 'cifsscan' => [ $password_map ],
42 'create' => [ $password_map ],
43 };
44 return $mapping->{$name};
45 }
46
47 sub setup_environment {
48 PVE::RPCEnvironment->setup_default_cli_env();
49 }
50
51 __PACKAGE__->register_method ({
52 name => 'path',
53 path => 'path',
54 method => 'GET',
55 description => "Get filesystem path for specified volume",
56 parameters => {
57 additionalProperties => 0,
58 properties => {
59 volume => {
60 description => "Volume identifier",
61 type => 'string', format => 'pve-volume-id',
62 completion => \&PVE::Storage::complete_volume,
63 },
64 },
65 },
66 returns => { type => 'null' },
67
68 code => sub {
69 my ($param) = @_;
70
71 my $cfg = PVE::Storage::config();
72
73 my $path = PVE::Storage::path ($cfg, $param->{volume});
74
75 print "$path\n";
76
77 return undef;
78
79 }});
80
81 __PACKAGE__->register_method ({
82 name => 'extractconfig',
83 path => 'extractconfig',
84 method => 'GET',
85 description => "Extract configuration from vzdump backup archive.",
86 permissions => {
87 description => "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.",
88 user => 'all',
89 },
90 protected => 1,
91 parameters => {
92 additionalProperties => 0,
93 properties => {
94 volume => {
95 description => "Volume identifier",
96 type => 'string',
97 completion => \&PVE::Storage::complete_volume,
98 },
99 },
100 },
101 returns => { type => 'null' },
102 code => sub {
103 my ($param) = @_;
104 my $volume = $param->{volume};
105
106 my $rpcenv = PVE::RPCEnvironment::get();
107 my $authuser = $rpcenv->get_user();
108
109 my $storage_cfg = PVE::Storage::config();
110 PVE::Storage::check_volume_access($rpcenv, $authuser, $storage_cfg, undef, $volume);
111
112 my $config_raw = PVE::Storage::extract_vzdump_config($storage_cfg, $volume);
113
114 print "$config_raw\n";
115 return;
116 }});
117
118 my $print_content = sub {
119 my ($list) = @_;
120
121 my $maxlenname = 0;
122 foreach my $info (@$list) {
123
124 my $volid = $info->{volid};
125 my $sidlen = length ($volid);
126 $maxlenname = $sidlen if $sidlen > $maxlenname;
127 }
128
129 foreach my $info (@$list) {
130 next if !$info->{vmid};
131 my $volid = $info->{volid};
132
133 printf "%-${maxlenname}s %5s %10d %d\n", $volid,
134 $info->{format}, $info->{size}, $info->{vmid};
135 }
136
137 foreach my $info (sort { $a->{format} cmp $b->{format} } @$list) {
138 next if $info->{vmid};
139 my $volid = $info->{volid};
140
141 printf "%-${maxlenname}s %5s %10d\n", $volid,
142 $info->{format}, $info->{size};
143 }
144 };
145
146 my $print_status = sub {
147 my $res = shift;
148
149 my $maxlen = 0;
150 foreach my $res (@$res) {
151 my $storeid = $res->{storage};
152 $maxlen = length ($storeid) if length ($storeid) > $maxlen;
153 }
154 $maxlen+=1;
155
156 printf "%-${maxlen}s %10s %10s %15s %15s %15s %8s\n", 'Name', 'Type',
157 'Status', 'Total', 'Used', 'Available', '%';
158
159 foreach my $res (sort { $a->{storage} cmp $b->{storage} } @$res) {
160 my $storeid = $res->{storage};
161
162 my $active = $res->{active} ? 'active' : 'inactive';
163 my ($per, $per_fmt) = (0, '% 7.2f%%');
164 $per = ($res->{used}*100)/$res->{total} if $res->{total} > 0;
165
166 if (!$res->{enabled}) {
167 $per = 'N/A';
168 $per_fmt = '% 8s';
169 $active = 'disabled';
170 }
171
172 printf "%-${maxlen}s %10s %10s %15d %15d %15d $per_fmt\n", $storeid,
173 $res->{type}, $active, $res->{total}/1024, $res->{used}/1024,
174 $res->{avail}/1024, $per;
175 }
176 };
177
178 __PACKAGE__->register_method ({
179 name => 'export',
180 path => 'export',
181 method => 'GET',
182 description => "Export a volume.",
183 protected => 1,
184 parameters => {
185 additionalProperties => 0,
186 properties => {
187 volume => {
188 description => "Volume identifier",
189 type => 'string',
190 completion => \&PVE::Storage::complete_volume,
191 },
192 format => {
193 description => "Export stream format",
194 type => 'string',
195 enum => $KNOWN_EXPORT_FORMATS,
196 },
197 filename => {
198 description => "Destination file name",
199 type => 'string',
200 },
201 base => {
202 description => "Snapshot to start an incremental stream from",
203 type => 'string',
204 pattern => qr/[a-z0-9_\-]{1,40}/,
205 maxLength => 40,
206 optional => 1,
207 },
208 snapshot => {
209 description => "Snapshot to export",
210 type => 'string',
211 pattern => qr/[a-z0-9_\-]{1,40}/,
212 maxLength => 40,
213 optional => 1,
214 },
215 'with-snapshots' => {
216 description =>
217 "Whether to include intermediate snapshots in the stream",
218 type => 'boolean',
219 optional => 1,
220 default => 0,
221 },
222 },
223 },
224 returns => { type => 'null' },
225 code => sub {
226 my ($param) = @_;
227
228 my $filename = $param->{filename};
229
230 my $outfh;
231 if ($filename eq '-') {
232 $outfh = \*STDOUT;
233 } else {
234 sysopen($outfh, $filename, O_CREAT|O_WRONLY|O_TRUNC)
235 or die "open($filename): $!\n";
236 }
237
238 eval {
239 my $cfg = PVE::Storage::config();
240 PVE::Storage::volume_export($cfg, $outfh, $param->{volume}, $param->{format},
241 $param->{snapshot}, $param->{base}, $param->{'with-snapshots'});
242 };
243 my $err = $@;
244 if ($filename ne '-') {
245 close($outfh);
246 unlink($filename) if $err;
247 }
248 die $err if $err;
249 return;
250 }
251 });
252
253 __PACKAGE__->register_method ({
254 name => 'import',
255 path => 'import',
256 method => 'PUT',
257 description => "Import a volume.",
258 protected => 1,
259 parameters => {
260 additionalProperties => 0,
261 properties => {
262 volume => {
263 description => "Volume identifier",
264 type => 'string',
265 completion => \&PVE::Storage::complete_volume,
266 },
267 format => {
268 description => "Import stream format",
269 type => 'string',
270 enum => $KNOWN_EXPORT_FORMATS,
271 },
272 filename => {
273 description => "Source file name",
274 type => 'string',
275 },
276 base => {
277 description => "Base snapshot of an incremental stream",
278 type => 'string',
279 pattern => qr/[a-z0-9_\-]{1,40}/,
280 maxLength => 40,
281 optional => 1,
282 },
283 'with-snapshots' => {
284 description =>
285 "Whether the stream includes intermediate snapshots",
286 type => 'boolean',
287 optional => 1,
288 default => 0,
289 },
290 'delete-snapshot' => {
291 description => "A snapshot to delete on success",
292 type => 'string',
293 pattern => qr/[a-z0-9_\-]{1,80}/,
294 maxLength => 80,
295 optional => 1,
296 },
297 },
298 },
299 returns => { type => 'null' },
300 code => sub {
301 my ($param) = @_;
302
303 my $filename = $param->{filename};
304
305 my $infh;
306 if ($filename eq '-') {
307 $infh = \*STDIN;
308 } else {
309 sysopen($infh, $filename, O_RDONLY)
310 or die "open($filename): $!\n";
311 }
312
313 my $cfg = PVE::Storage::config();
314 my $volume = $param->{volume};
315 my $delete = $param->{'delete-snapshot'};
316 PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format},
317 $param->{base}, $param->{'with-snapshots'});
318 PVE::Storage::volume_snapshot_delete($cfg, $volume, $delete)
319 if defined($delete);
320 return;
321 }
322 });
323
324 our $cmddef = {
325 add => [ "PVE::API2::Storage::Config", 'create', ['type', 'storage'] ],
326 set => [ "PVE::API2::Storage::Config", 'update', ['storage'] ],
327 remove => [ "PVE::API2::Storage::Config", 'delete', ['storage'] ],
328 status => [ "PVE::API2::Storage::Status", 'index', [],
329 { node => $nodename }, $print_status ],
330 list => [ "PVE::API2::Storage::Content", 'index', ['storage'],
331 { node => $nodename }, $print_content ],
332 alloc => [ "PVE::API2::Storage::Content", 'create', ['storage', 'vmid', 'filename', 'size'],
333 { node => $nodename }, sub {
334 my $volid = shift;
335 print "successfully created '$volid'\n";
336 }],
337 free => [ "PVE::API2::Storage::Content", 'delete', ['volume'],
338 { node => $nodename } ],
339 nfsscan => [ "PVE::API2::Storage::Scan", 'nfsscan', ['server'],
340 { node => $nodename }, sub {
341 my $res = shift;
342
343 my $maxlen = 0;
344 foreach my $rec (@$res) {
345 my $len = length ($rec->{path});
346 $maxlen = $len if $len > $maxlen;
347 }
348 foreach my $rec (@$res) {
349 printf "%-${maxlen}s %s\n", $rec->{path}, $rec->{options};
350 }
351 }],
352 cifsscan => [ "PVE::API2::Storage::Scan", 'cifsscan', ['server'],
353 { node => $nodename }, sub {
354 my $res = shift;
355
356 my $maxlen = 0;
357 foreach my $rec (@$res) {
358 my $len = length ($rec->{share});
359 $maxlen = $len if $len > $maxlen;
360 }
361 foreach my $rec (@$res) {
362 printf "%-${maxlen}s %s\n", $rec->{share}, $rec->{description};
363 }
364 }],
365 glusterfsscan => [ "PVE::API2::Storage::Scan", 'glusterfsscan', ['server'],
366 { node => $nodename }, sub {
367 my $res = shift;
368
369 foreach my $rec (@$res) {
370 printf "%s\n", $rec->{volname};
371 }
372 }],
373 iscsiscan => [ "PVE::API2::Storage::Scan", 'iscsiscan', ['server'],
374 { node => $nodename }, sub {
375 my $res = shift;
376
377 my $maxlen = 0;
378 foreach my $rec (@$res) {
379 my $len = length ($rec->{target});
380 $maxlen = $len if $len > $maxlen;
381 }
382 foreach my $rec (@$res) {
383 printf "%-${maxlen}s %s\n", $rec->{target}, $rec->{portal};
384 }
385 }],
386 lvmscan => [ "PVE::API2::Storage::Scan", 'lvmscan', [],
387 { node => $nodename }, sub {
388 my $res = shift;
389 foreach my $rec (@$res) {
390 printf "$rec->{vg}\n";
391 }
392 }],
393 lvmthinscan => [ "PVE::API2::Storage::Scan", 'lvmthinscan', ['vg'],
394 { node => $nodename }, sub {
395 my $res = shift;
396 foreach my $rec (@$res) {
397 printf "$rec->{lv}\n";
398 }
399 }],
400 zfsscan => [ "PVE::API2::Storage::Scan", 'zfsscan', [],
401 { node => $nodename }, sub {
402 my $res = shift;
403
404 foreach my $rec (@$res) {
405 printf "$rec->{pool}\n";
406 }
407 }],
408 path => [ __PACKAGE__, 'path', ['volume']],
409 extractconfig => [__PACKAGE__, 'extractconfig', ['volume']],
410 export => [ __PACKAGE__, 'export', ['volume', 'format', 'filename']],
411 import => [ __PACKAGE__, 'import', ['volume', 'format', 'filename']],
412 };
413
414 1;