]> git.proxmox.com Git - proxmox-backup.git/blob - src/bin/proxmox-tape.rs
tape: remove drive from pool config
[proxmox-backup.git] / src / bin / proxmox-tape.rs
1 use anyhow::{format_err, Error};
2 use serde_json::{json, Value};
3
4 use proxmox::{
5 api::{
6 api,
7 cli::*,
8 RpcEnvironment,
9 section_config::SectionConfigData,
10 },
11 tools::{
12 time::strftime_local,
13 io::ReadExt,
14 },
15 };
16
17 use proxmox_backup::{
18 tools::format::{
19 HumanByte,
20 render_epoch,
21 render_bytes_human_readable,
22 },
23 client::{
24 connect_to_localhost,
25 view_task_result,
26 },
27 api2::{
28 self,
29 types::{
30 DATASTORE_SCHEMA,
31 DRIVE_NAME_SCHEMA,
32 MEDIA_LABEL_SCHEMA,
33 MEDIA_POOL_NAME_SCHEMA,
34 },
35 },
36 config::{
37 self,
38 datastore::complete_datastore_name,
39 drive::complete_drive_name,
40 media_pool::complete_pool_name,
41 },
42 tape::{
43 drive::open_drive,
44 complete_media_label_text,
45 complete_media_set_uuid,
46 file_formats::{
47 PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
48 PROXMOX_BACKUP_CONTENT_NAME,
49 MediaContentHeader,
50 },
51 },
52 };
53
54 mod proxmox_tape;
55 use proxmox_tape::*;
56
57 pub fn lookup_drive_name(
58 param: &Value,
59 config: &SectionConfigData,
60 ) -> Result<String, Error> {
61
62 let drive = param["drive"]
63 .as_str()
64 .map(String::from)
65 .or_else(|| std::env::var("PROXMOX_TAPE_DRIVE").ok())
66 .or_else(|| {
67
68 let mut drive_names = Vec::new();
69
70 for (name, (section_type, _)) in config.sections.iter() {
71
72 if !(section_type == "linux" || section_type == "virtual") { continue; }
73 drive_names.push(name);
74 }
75
76 if drive_names.len() == 1 {
77 Some(drive_names[0].to_owned())
78 } else {
79 None
80 }
81 })
82 .ok_or_else(|| format_err!("unable to get (default) drive name"))?;
83
84 Ok(drive)
85 }
86
87 #[api(
88 input: {
89 properties: {
90 drive: {
91 schema: DRIVE_NAME_SCHEMA,
92 optional: true,
93 },
94 fast: {
95 description: "Use fast erase.",
96 type: bool,
97 optional: true,
98 default: true,
99 },
100 "output-format": {
101 schema: OUTPUT_FORMAT,
102 optional: true,
103 },
104 },
105 },
106 )]
107 /// Erase media
108 async fn erase_media(param: Value) -> Result<(), Error> {
109
110 let output_format = get_output_format(&param);
111
112 let (config, _digest) = config::drive::config()?;
113
114 let drive = lookup_drive_name(&param, &config)?;
115
116 let mut client = connect_to_localhost()?;
117
118 let path = format!("api2/json/tape/drive/{}/erase-media", drive);
119 let result = client.post(&path, Some(param)).await?;
120
121 view_task_result(&mut client, result, &output_format).await?;
122
123 Ok(())
124 }
125
126 #[api(
127 input: {
128 properties: {
129 drive: {
130 schema: DRIVE_NAME_SCHEMA,
131 optional: true,
132 },
133 "output-format": {
134 schema: OUTPUT_FORMAT,
135 optional: true,
136 },
137 },
138 },
139 )]
140 /// Rewind tape
141 async fn rewind(param: Value) -> Result<(), Error> {
142
143 let output_format = get_output_format(&param);
144
145 let (config, _digest) = config::drive::config()?;
146
147 let drive = lookup_drive_name(&param, &config)?;
148
149 let mut client = connect_to_localhost()?;
150
151 let path = format!("api2/json/tape/drive/{}/rewind", drive);
152 let result = client.post(&path, Some(param)).await?;
153
154 view_task_result(&mut client, result, &output_format).await?;
155
156 Ok(())
157 }
158
159 #[api(
160 input: {
161 properties: {
162 drive: {
163 schema: DRIVE_NAME_SCHEMA,
164 optional: true,
165 },
166 "output-format": {
167 schema: OUTPUT_FORMAT,
168 optional: true,
169 },
170 },
171 },
172 )]
173 /// Eject/Unload drive media
174 async fn eject_media(param: Value) -> Result<(), Error> {
175
176 let output_format = get_output_format(&param);
177
178 let (config, _digest) = config::drive::config()?;
179
180 let drive = lookup_drive_name(&param, &config)?;
181
182 let mut client = connect_to_localhost()?;
183
184 let path = format!("api2/json/tape/drive/{}/eject-media", drive);
185 let result = client.post(&path, Some(param)).await?;
186
187 view_task_result(&mut client, result, &output_format).await?;
188
189 Ok(())
190 }
191
192 #[api(
193 input: {
194 properties: {
195 drive: {
196 schema: DRIVE_NAME_SCHEMA,
197 optional: true,
198 },
199 "label-text": {
200 schema: MEDIA_LABEL_SCHEMA,
201 },
202 },
203 },
204 )]
205 /// Load media with specified label
206 async fn load_media(param: Value) -> Result<(), Error> {
207
208 let (config, _digest) = config::drive::config()?;
209
210 let drive = lookup_drive_name(&param, &config)?;
211
212 let mut client = connect_to_localhost()?;
213
214 let path = format!("api2/json/tape/drive/{}/load-media", drive);
215 client.put(&path, Some(param)).await?;
216
217 Ok(())
218 }
219
220 #[api(
221 input: {
222 properties: {
223 drive: {
224 schema: DRIVE_NAME_SCHEMA,
225 optional: true,
226 },
227 "label-text": {
228 schema: MEDIA_LABEL_SCHEMA,
229 },
230 },
231 },
232 )]
233 /// Export media with specified label
234 async fn export_media(param: Value) -> Result<(), Error> {
235
236 let (config, _digest) = config::drive::config()?;
237
238 let drive = lookup_drive_name(&param, &config)?;
239
240 let mut client = connect_to_localhost()?;
241
242 let path = format!("api2/json/tape/drive/{}/export-media", drive);
243 client.put(&path, Some(param)).await?;
244
245 Ok(())
246 }
247
248 #[api(
249 input: {
250 properties: {
251 drive: {
252 schema: DRIVE_NAME_SCHEMA,
253 optional: true,
254 },
255 "source-slot": {
256 description: "Source slot number.",
257 type: u64,
258 minimum: 1,
259 },
260 },
261 },
262 )]
263 /// Load media from the specified slot
264 async fn load_media_from_slot(param: Value) -> Result<(), Error> {
265
266 let (config, _digest) = config::drive::config()?;
267
268 let drive = lookup_drive_name(&param, &config)?;
269
270 let mut client = connect_to_localhost()?;
271
272 let path = format!("api2/json/tape/drive/{}/load-slot", drive);
273 client.put(&path, Some(param)).await?;
274
275 Ok(())
276 }
277
278 #[api(
279 input: {
280 properties: {
281 drive: {
282 schema: DRIVE_NAME_SCHEMA,
283 optional: true,
284 },
285 "target-slot": {
286 description: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
287 type: u64,
288 minimum: 1,
289 optional: true,
290 },
291 },
292 },
293 )]
294 /// Unload media via changer
295 async fn unload_media(param: Value) -> Result<(), Error> {
296
297 let (config, _digest) = config::drive::config()?;
298
299 let drive = lookup_drive_name(&param, &config)?;
300
301 let mut client = connect_to_localhost()?;
302
303 let path = format!("api2/json/tape/drive/{}/unload", drive);
304 client.put(&path, Some(param)).await?;
305
306 Ok(())
307 }
308
309 #[api(
310 input: {
311 properties: {
312 pool: {
313 schema: MEDIA_POOL_NAME_SCHEMA,
314 optional: true,
315 },
316 drive: {
317 schema: DRIVE_NAME_SCHEMA,
318 optional: true,
319 },
320 "label-text": {
321 schema: MEDIA_LABEL_SCHEMA,
322 },
323 "output-format": {
324 schema: OUTPUT_FORMAT,
325 optional: true,
326 },
327 },
328 },
329 )]
330 /// Label media
331 async fn label_media(param: Value) -> Result<(), Error> {
332
333 let output_format = get_output_format(&param);
334
335 let (config, _digest) = config::drive::config()?;
336
337 let drive = lookup_drive_name(&param, &config)?;
338
339 let mut client = connect_to_localhost()?;
340
341 let path = format!("api2/json/tape/drive/{}/label-media", drive);
342 let result = client.post(&path, Some(param)).await?;
343
344 view_task_result(&mut client, result, &output_format).await?;
345
346 Ok(())
347 }
348
349 #[api(
350 input: {
351 properties: {
352 drive: {
353 schema: DRIVE_NAME_SCHEMA,
354 optional: true,
355 },
356 inventorize: {
357 description: "Inventorize media",
358 type: bool,
359 optional: true,
360 },
361 "output-format": {
362 schema: OUTPUT_FORMAT,
363 optional: true,
364 },
365 },
366 },
367 )]
368 /// Read media label
369 async fn read_label(param: Value) -> Result<(), Error> {
370
371 let output_format = get_output_format(&param);
372
373 let (config, _digest) = config::drive::config()?;
374
375 let drive = lookup_drive_name(&param, &config)?;
376
377 let client = connect_to_localhost()?;
378
379 let path = format!("api2/json/tape/drive/{}/read-label", drive);
380 let mut result = client.get(&path, Some(param)).await?;
381 let mut data = result["data"].take();
382
383 let info = &api2::tape::drive::API_METHOD_READ_LABEL;
384
385 let options = default_table_format_options()
386 .column(ColumnConfig::new("label-text"))
387 .column(ColumnConfig::new("uuid"))
388 .column(ColumnConfig::new("ctime").renderer(render_epoch))
389 .column(ColumnConfig::new("pool"))
390 .column(ColumnConfig::new("media-set-uuid"))
391 .column(ColumnConfig::new("media-set-ctime").renderer(render_epoch))
392 .column(ColumnConfig::new("encryption-key-fingerprint"))
393 ;
394
395 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
396
397 Ok(())
398 }
399
400 #[api(
401 input: {
402 properties: {
403 "output-format": {
404 schema: OUTPUT_FORMAT,
405 optional: true,
406 },
407 drive: {
408 schema: DRIVE_NAME_SCHEMA,
409 optional: true,
410 },
411 "read-labels": {
412 description: "Load unknown tapes and try read labels",
413 type: bool,
414 optional: true,
415 },
416 "read-all-labels": {
417 description: "Load all tapes and try read labels (even if already inventoried)",
418 type: bool,
419 optional: true,
420 },
421 },
422 },
423 )]
424 /// List (and update) media labels (Changer Inventory)
425 async fn inventory(
426 read_labels: Option<bool>,
427 read_all_labels: Option<bool>,
428 param: Value,
429 ) -> Result<(), Error> {
430
431 let output_format = get_output_format(&param);
432
433 let (config, _digest) = config::drive::config()?;
434 let drive = lookup_drive_name(&param, &config)?;
435
436 let do_read = read_labels.unwrap_or(false) || read_all_labels.unwrap_or(false);
437
438 let mut client = connect_to_localhost()?;
439
440 let path = format!("api2/json/tape/drive/{}/inventory", drive);
441
442 if do_read {
443
444 let mut param = json!({});
445 if let Some(true) = read_all_labels {
446 param["read-all-labels"] = true.into();
447 }
448
449 let result = client.put(&path, Some(param)).await?; // update inventory
450 view_task_result(&mut client, result, &output_format).await?;
451 }
452
453 let mut result = client.get(&path, None).await?;
454 let mut data = result["data"].take();
455
456 let info = &api2::tape::drive::API_METHOD_INVENTORY;
457
458 let options = default_table_format_options()
459 .column(ColumnConfig::new("label-text"))
460 .column(ColumnConfig::new("uuid"))
461 ;
462
463 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
464
465 Ok(())
466 }
467
468 #[api(
469 input: {
470 properties: {
471 pool: {
472 schema: MEDIA_POOL_NAME_SCHEMA,
473 optional: true,
474 },
475 drive: {
476 schema: DRIVE_NAME_SCHEMA,
477 optional: true,
478 },
479 "output-format": {
480 schema: OUTPUT_FORMAT,
481 optional: true,
482 },
483 },
484 },
485 )]
486 /// Label media with barcodes from changer device
487 async fn barcode_label_media(param: Value) -> Result<(), Error> {
488
489 let output_format = get_output_format(&param);
490
491 let (config, _digest) = config::drive::config()?;
492
493 let drive = lookup_drive_name(&param, &config)?;
494
495 let mut client = connect_to_localhost()?;
496
497 let path = format!("api2/json/tape/drive/{}/barcode-label-media", drive);
498 let result = client.post(&path, Some(param)).await?;
499
500 view_task_result(&mut client, result, &output_format).await?;
501
502 Ok(())
503 }
504
505 #[api(
506 input: {
507 properties: {
508 drive: {
509 schema: DRIVE_NAME_SCHEMA,
510 optional: true,
511 },
512 },
513 },
514 )]
515 /// Move to end of media (MTEOM, used to debug)
516 fn move_to_eom(param: Value) -> Result<(), Error> {
517
518 let (config, _digest) = config::drive::config()?;
519
520 let drive = lookup_drive_name(&param, &config)?;
521 let mut drive = open_drive(&config, &drive)?;
522
523 drive.move_to_eom()?;
524
525 Ok(())
526 }
527
528 #[api(
529 input: {
530 properties: {
531 drive: {
532 schema: DRIVE_NAME_SCHEMA,
533 optional: true,
534 },
535 },
536 },
537 )]
538 /// Rewind, then read media contents and print debug info
539 ///
540 /// Note: This reads unless the driver returns an IO Error, so this
541 /// method is expected to fails when we reach EOT.
542 fn debug_scan(param: Value) -> Result<(), Error> {
543
544 let (config, _digest) = config::drive::config()?;
545
546 let drive = lookup_drive_name(&param, &config)?;
547 let mut drive = open_drive(&config, &drive)?;
548
549 println!("rewinding tape");
550 drive.rewind()?;
551
552 loop {
553 let file_number = drive.current_file_number()?;
554
555 match drive.read_next_file()? {
556 None => {
557 println!("EOD");
558 continue;
559 },
560 Some(mut reader) => {
561 println!("got file number {}", file_number);
562
563 let header: Result<MediaContentHeader, _> = unsafe { reader.read_le_value() };
564 match header {
565 Ok(header) => {
566 if header.magic != PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0 {
567 println!("got MediaContentHeader with wrong magic: {:?}", header.magic);
568 } else if let Some(name) = PROXMOX_BACKUP_CONTENT_NAME.get(&header.content_magic) {
569 println!("got content header: {}", name);
570 println!(" uuid: {}", header.content_uuid());
571 println!(" ctime: {}", strftime_local("%c", header.ctime)?);
572 println!(" hsize: {}", HumanByte::from(header.size as usize));
573 println!(" part: {}", header.part_number);
574 } else {
575 println!("got unknown content header: {:?}", header.content_magic);
576 }
577 }
578 Err(err) => {
579 println!("unable to read content header - {}", err);
580 }
581 }
582 let bytes = reader.skip_to_end()?;
583 println!("skipped {}", HumanByte::from(bytes));
584 }
585 }
586 }
587 }
588
589 #[api(
590 input: {
591 properties: {
592 drive: {
593 schema: DRIVE_NAME_SCHEMA,
594 optional: true,
595 },
596 "output-format": {
597 schema: OUTPUT_FORMAT,
598 optional: true,
599 },
600 },
601 },
602 )]
603 /// Read Cartridge Memory (Medium auxiliary memory attributes)
604 async fn cartridge_memory(param: Value) -> Result<(), Error> {
605
606 let output_format = get_output_format(&param);
607
608 let (config, _digest) = config::drive::config()?;
609
610 let drive = lookup_drive_name(&param, &config)?;
611
612 let client = connect_to_localhost()?;
613
614 let path = format!("api2/json/tape/drive/{}/cartridge-memory", drive);
615 let mut result = client.get(&path, Some(param)).await?;
616 let mut data = result["data"].take();
617
618 let info = &api2::tape::drive::API_METHOD_CARTRIDGE_MEMORY;
619
620 let options = default_table_format_options()
621 .column(ColumnConfig::new("id"))
622 .column(ColumnConfig::new("name"))
623 .column(ColumnConfig::new("value"))
624 ;
625
626 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
627 Ok(())
628 }
629
630 #[api(
631 input: {
632 properties: {
633 drive: {
634 schema: DRIVE_NAME_SCHEMA,
635 optional: true,
636 },
637 "output-format": {
638 schema: OUTPUT_FORMAT,
639 optional: true,
640 },
641 },
642 },
643 )]
644 /// Read Volume Statistics (SCSI log page 17h)
645 async fn volume_statistics(param: Value) -> Result<(), Error> {
646
647 let output_format = get_output_format(&param);
648
649 let (config, _digest) = config::drive::config()?;
650
651 let drive = lookup_drive_name(&param, &config)?;
652
653 let client = connect_to_localhost()?;
654
655 let path = format!("api2/json/tape/drive/{}/volume-statistics", drive);
656 let mut result = client.get(&path, Some(param)).await?;
657 let mut data = result["data"].take();
658
659 let info = &api2::tape::drive::API_METHOD_VOLUME_STATISTICS;
660
661 let options = default_table_format_options();
662
663 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
664
665 Ok(())
666 }
667
668 #[api(
669 input: {
670 properties: {
671 drive: {
672 schema: DRIVE_NAME_SCHEMA,
673 optional: true,
674 },
675 "output-format": {
676 schema: OUTPUT_FORMAT,
677 optional: true,
678 },
679 },
680 },
681 )]
682 /// Get drive/media status
683 async fn status(param: Value) -> Result<(), Error> {
684
685 let output_format = get_output_format(&param);
686
687 let (config, _digest) = config::drive::config()?;
688
689 let drive = lookup_drive_name(&param, &config)?;
690
691 let client = connect_to_localhost()?;
692
693 let path = format!("api2/json/tape/drive/{}/status", drive);
694 let mut result = client.get(&path, Some(param)).await?;
695 let mut data = result["data"].take();
696
697 let info = &api2::tape::drive::API_METHOD_STATUS;
698
699 let render_percentage = |value: &Value, _record: &Value| {
700 match value.as_f64() {
701 Some(wearout) => Ok(format!("{:.2}%", wearout*100.0)),
702 None => Ok(String::from("ERROR")), // should never happen
703 }
704 };
705
706 let options = default_table_format_options()
707 .column(ColumnConfig::new("blocksize"))
708 .column(ColumnConfig::new("density"))
709 .column(ColumnConfig::new("status"))
710 .column(ColumnConfig::new("alert-flags"))
711 .column(ColumnConfig::new("file-number"))
712 .column(ColumnConfig::new("block-number"))
713 .column(ColumnConfig::new("manufactured").renderer(render_epoch))
714 .column(ColumnConfig::new("bytes-written").renderer(render_bytes_human_readable))
715 .column(ColumnConfig::new("bytes-read").renderer(render_bytes_human_readable))
716 .column(ColumnConfig::new("medium-passes"))
717 .column(ColumnConfig::new("medium-wearout").renderer(render_percentage))
718 .column(ColumnConfig::new("volume-mounts"))
719 ;
720
721 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
722
723 Ok(())
724 }
725
726 #[api(
727 input: {
728 properties: {
729 drive: {
730 schema: DRIVE_NAME_SCHEMA,
731 optional: true,
732 },
733 "output-format": {
734 schema: OUTPUT_FORMAT,
735 optional: true,
736 },
737 },
738 },
739 )]
740 /// Clean drive
741 async fn clean_drive(param: Value) -> Result<(), Error> {
742
743 let output_format = get_output_format(&param);
744
745 let (config, _digest) = config::drive::config()?;
746
747 let drive = lookup_drive_name(&param, &config)?;
748
749 let mut client = connect_to_localhost()?;
750
751 let path = format!("api2/json/tape/drive/{}/clean-drive", drive);
752 let result = client.post(&path, Some(param)).await?;
753
754 view_task_result(&mut client, result, &output_format).await?;
755
756 Ok(())
757 }
758
759 #[api(
760 input: {
761 properties: {
762 store: {
763 schema: DATASTORE_SCHEMA,
764 },
765 pool: {
766 schema: MEDIA_POOL_NAME_SCHEMA,
767 },
768 drive: {
769 schema: DRIVE_NAME_SCHEMA,
770 optional: true,
771 },
772 "eject-media": {
773 description: "Eject media upon job completion.",
774 type: bool,
775 optional: true,
776 },
777 "export-media-set": {
778 description: "Export media set upon job completion.",
779 type: bool,
780 optional: true,
781 },
782 "output-format": {
783 schema: OUTPUT_FORMAT,
784 optional: true,
785 },
786 },
787 },
788 )]
789 /// Backup datastore to tape media pool
790 async fn backup(mut param: Value) -> Result<(), Error> {
791
792 let output_format = get_output_format(&param);
793
794 let (config, _digest) = config::drive::config()?;
795
796 param["drive"] = lookup_drive_name(&param, &config)?.into();
797
798 let mut client = connect_to_localhost()?;
799
800 let result = client.post("api2/json/tape/backup", Some(param)).await?;
801
802 view_task_result(&mut client, result, &output_format).await?;
803
804 Ok(())
805 }
806
807 #[api(
808 input: {
809 properties: {
810 store: {
811 schema: DATASTORE_SCHEMA,
812 },
813 drive: {
814 schema: DRIVE_NAME_SCHEMA,
815 optional: true,
816 },
817 "media-set": {
818 description: "Media set UUID.",
819 type: String,
820 },
821 "output-format": {
822 schema: OUTPUT_FORMAT,
823 optional: true,
824 },
825 },
826 },
827 )]
828 /// Restore data from media-set
829 async fn restore(mut param: Value) -> Result<(), Error> {
830
831 let output_format = get_output_format(&param);
832
833 let (config, _digest) = config::drive::config()?;
834
835 param["drive"] = lookup_drive_name(&param, &config)?.into();
836
837 let mut client = connect_to_localhost()?;
838
839 let result = client.post("api2/json/tape/restore", Some(param)).await?;
840
841 view_task_result(&mut client, result, &output_format).await?;
842
843 Ok(())
844 }
845
846 #[api(
847 input: {
848 properties: {
849 drive: {
850 schema: DRIVE_NAME_SCHEMA,
851 optional: true,
852 },
853 force: {
854 description: "Force overriding existing index.",
855 type: bool,
856 optional: true,
857 },
858 verbose: {
859 description: "Verbose mode - log all found chunks.",
860 type: bool,
861 optional: true,
862 },
863 "output-format": {
864 schema: OUTPUT_FORMAT,
865 optional: true,
866 },
867 },
868 },
869 )]
870 /// Scan media and record content
871 async fn catalog_media(param: Value) -> Result<(), Error> {
872
873 let output_format = get_output_format(&param);
874
875 let (config, _digest) = config::drive::config()?;
876
877 let drive = lookup_drive_name(&param, &config)?;
878
879 let mut client = connect_to_localhost()?;
880
881 let path = format!("api2/json/tape/drive/{}/catalog", drive);
882 let result = client.post(&path, Some(param)).await?;
883
884 view_task_result(&mut client, result, &output_format).await?;
885
886 Ok(())
887 }
888
889 fn main() {
890
891 let cmd_def = CliCommandMap::new()
892 .insert(
893 "backup",
894 CliCommand::new(&API_METHOD_BACKUP)
895 .arg_param(&["store", "pool"])
896 .completion_cb("store", complete_datastore_name)
897 .completion_cb("pool", complete_pool_name)
898 )
899 .insert(
900 "restore",
901 CliCommand::new(&API_METHOD_RESTORE)
902 .arg_param(&["media-set", "store"])
903 .completion_cb("store", complete_datastore_name)
904 .completion_cb("media-set", complete_media_set_uuid)
905 )
906 .insert(
907 "barcode-label",
908 CliCommand::new(&API_METHOD_BARCODE_LABEL_MEDIA)
909 .completion_cb("drive", complete_drive_name)
910 .completion_cb("pool", complete_pool_name)
911 )
912 .insert(
913 "rewind",
914 CliCommand::new(&API_METHOD_REWIND)
915 .completion_cb("drive", complete_drive_name)
916 )
917 .insert(
918 "scan",
919 CliCommand::new(&API_METHOD_DEBUG_SCAN)
920 .completion_cb("drive", complete_drive_name)
921 )
922 .insert(
923 "status",
924 CliCommand::new(&API_METHOD_STATUS)
925 .completion_cb("drive", complete_drive_name)
926 )
927 .insert(
928 "eod",
929 CliCommand::new(&API_METHOD_MOVE_TO_EOM)
930 .completion_cb("drive", complete_drive_name)
931 )
932 .insert(
933 "erase",
934 CliCommand::new(&API_METHOD_ERASE_MEDIA)
935 .completion_cb("drive", complete_drive_name)
936 )
937 .insert(
938 "eject",
939 CliCommand::new(&API_METHOD_EJECT_MEDIA)
940 .completion_cb("drive", complete_drive_name)
941 )
942 .insert(
943 "inventory",
944 CliCommand::new(&API_METHOD_INVENTORY)
945 .completion_cb("drive", complete_drive_name)
946 )
947 .insert(
948 "read-label",
949 CliCommand::new(&API_METHOD_READ_LABEL)
950 .completion_cb("drive", complete_drive_name)
951 )
952 .insert(
953 "catalog",
954 CliCommand::new(&API_METHOD_CATALOG_MEDIA)
955 .completion_cb("drive", complete_drive_name)
956 )
957 .insert(
958 "cartridge-memory",
959 CliCommand::new(&API_METHOD_CARTRIDGE_MEMORY)
960 .completion_cb("drive", complete_drive_name)
961 )
962 .insert(
963 "volume-statistics",
964 CliCommand::new(&API_METHOD_VOLUME_STATISTICS)
965 .completion_cb("drive", complete_drive_name)
966 )
967 .insert(
968 "clean",
969 CliCommand::new(&API_METHOD_CLEAN_DRIVE)
970 .completion_cb("drive", complete_drive_name)
971 )
972 .insert(
973 "label",
974 CliCommand::new(&API_METHOD_LABEL_MEDIA)
975 .completion_cb("drive", complete_drive_name)
976 .completion_cb("pool", complete_pool_name)
977
978 )
979 .insert("changer", changer_commands())
980 .insert("drive", drive_commands())
981 .insert("pool", pool_commands())
982 .insert("media", media_commands())
983 .insert("key", encryption_key_commands())
984 .insert(
985 "load-media",
986 CliCommand::new(&API_METHOD_LOAD_MEDIA)
987 .arg_param(&["label-text"])
988 .completion_cb("drive", complete_drive_name)
989 .completion_cb("label-text", complete_media_label_text)
990 )
991 .insert(
992 "load-media-from-slot",
993 CliCommand::new(&API_METHOD_LOAD_MEDIA_FROM_SLOT)
994 .arg_param(&["source-slot"])
995 .completion_cb("drive", complete_drive_name)
996 )
997 .insert(
998 "unload",
999 CliCommand::new(&API_METHOD_UNLOAD_MEDIA)
1000 .completion_cb("drive", complete_drive_name)
1001 )
1002 .insert(
1003 "export-media",
1004 CliCommand::new(&API_METHOD_EXPORT_MEDIA)
1005 .arg_param(&["label-text"])
1006 .completion_cb("drive", complete_drive_name)
1007 .completion_cb("label-text", complete_media_label_text)
1008 )
1009 ;
1010
1011 let mut rpcenv = CliEnvironment::new();
1012 rpcenv.set_auth_id(Some(String::from("root@pam")));
1013
1014 proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv));
1015 }