]>
Commit | Line | Data |
---|---|---|
583a68a4 | 1 | use anyhow::{format_err, Error}; |
e92c7581 | 2 | use serde_json::{json, Value}; |
583a68a4 | 3 | |
e6604cf3 DM |
4 | use proxmox::{ |
5 | api::{ | |
583a68a4 | 6 | api, |
e6604cf3 | 7 | cli::*, |
583a68a4 | 8 | ApiHandler, |
e6604cf3 | 9 | RpcEnvironment, |
583a68a4 DM |
10 | section_config::SectionConfigData, |
11 | }, | |
3f803af0 | 12 | tools::{ |
3f803af0 DM |
13 | time::strftime_local, |
14 | io::ReadExt, | |
15 | }, | |
583a68a4 DM |
16 | }; |
17 | ||
18 | use proxmox_backup::{ | |
3f803af0 DM |
19 | tools::format::{ |
20 | HumanByte, | |
21 | render_epoch, | |
22 | }, | |
6dbad5b4 DM |
23 | server::{ |
24 | UPID, | |
25 | worker_is_active_local, | |
26 | }, | |
583a68a4 DM |
27 | api2::{ |
28 | self, | |
29 | types::{ | |
88356646 | 30 | DATASTORE_SCHEMA, |
49c965a4 | 31 | DRIVE_NAME_SCHEMA, |
e49f0c03 | 32 | MEDIA_LABEL_SCHEMA, |
7bb720cb | 33 | MEDIA_POOL_NAME_SCHEMA, |
583a68a4 DM |
34 | }, |
35 | }, | |
36 | config::{ | |
37 | self, | |
88356646 | 38 | datastore::complete_datastore_name, |
583a68a4 | 39 | drive::complete_drive_name, |
7bb720cb | 40 | media_pool::complete_pool_name, |
e6604cf3 | 41 | }, |
e49f0c03 | 42 | tape::{ |
ac461bd6 | 43 | open_drive, |
e49f0c03 | 44 | complete_media_changer_id, |
ac461bd6 DM |
45 | file_formats::{ |
46 | PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0, | |
47 | PROXMOX_BACKUP_CONTENT_NAME, | |
48 | MediaContentHeader, | |
49 | }, | |
e49f0c03 | 50 | }, |
e6604cf3 DM |
51 | }; |
52 | ||
53 | mod proxmox_tape; | |
54 | use proxmox_tape::*; | |
55 | ||
6dbad5b4 DM |
56 | // Note: local workers should print logs to stdout, so there is no need |
57 | // to fetch/display logs. We just wait for the worker to finish. | |
58 | pub async fn wait_for_local_worker(upid_str: &str) -> Result<(), Error> { | |
59 | ||
60 | let upid: UPID = upid_str.parse()?; | |
61 | ||
62 | let sleep_duration = core::time::Duration::new(0, 100_000_000); | |
63 | ||
64 | loop { | |
65 | if worker_is_active_local(&upid) { | |
66 | tokio::time::delay_for(sleep_duration).await; | |
67 | } else { | |
68 | break; | |
69 | } | |
70 | } | |
71 | Ok(()) | |
72 | } | |
73 | ||
583a68a4 DM |
74 | fn lookup_drive_name( |
75 | param: &Value, | |
76 | config: &SectionConfigData, | |
77 | ) -> Result<String, Error> { | |
78 | ||
79 | let drive = param["drive"] | |
80 | .as_str() | |
81 | .map(String::from) | |
82 | .or_else(|| std::env::var("PROXMOX_TAPE_DRIVE").ok()) | |
83 | .or_else(|| { | |
84 | ||
85 | let mut drive_names = Vec::new(); | |
86 | ||
87 | for (name, (section_type, _)) in config.sections.iter() { | |
88 | ||
89 | if !(section_type == "linux" || section_type == "virtual") { continue; } | |
90 | drive_names.push(name); | |
91 | } | |
92 | ||
93 | if drive_names.len() == 1 { | |
94 | Some(drive_names[0].to_owned()) | |
95 | } else { | |
96 | None | |
97 | } | |
98 | }) | |
99 | .ok_or_else(|| format_err!("unable to get (default) drive name"))?; | |
100 | ||
101 | Ok(drive) | |
102 | } | |
103 | ||
104 | #[api( | |
105 | input: { | |
106 | properties: { | |
107 | drive: { | |
49c965a4 | 108 | schema: DRIVE_NAME_SCHEMA, |
583a68a4 DM |
109 | optional: true, |
110 | }, | |
111 | fast: { | |
112 | description: "Use fast erase.", | |
113 | type: bool, | |
114 | optional: true, | |
115 | default: true, | |
116 | }, | |
117 | }, | |
118 | }, | |
119 | )] | |
120 | /// Erase media | |
663ef859 | 121 | async fn erase_media( |
583a68a4 DM |
122 | mut param: Value, |
123 | rpcenv: &mut dyn RpcEnvironment, | |
124 | ) -> Result<(), Error> { | |
125 | ||
126 | let (config, _digest) = config::drive::config()?; | |
127 | ||
128 | param["drive"] = lookup_drive_name(¶m, &config)?.into(); | |
129 | ||
130 | let info = &api2::tape::drive::API_METHOD_ERASE_MEDIA; | |
131 | ||
663ef859 | 132 | let result = match info.handler { |
583a68a4 DM |
133 | ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, |
134 | _ => unreachable!(), | |
135 | }; | |
136 | ||
663ef859 DM |
137 | wait_for_local_worker(result.as_str().unwrap()).await?; |
138 | ||
583a68a4 DM |
139 | Ok(()) |
140 | } | |
141 | ||
5fb694e8 DM |
142 | #[api( |
143 | input: { | |
144 | properties: { | |
145 | drive: { | |
49c965a4 | 146 | schema: DRIVE_NAME_SCHEMA, |
5fb694e8 DM |
147 | optional: true, |
148 | }, | |
149 | }, | |
150 | }, | |
151 | )] | |
152 | /// Rewind tape | |
663ef859 | 153 | async fn rewind( |
5fb694e8 DM |
154 | mut param: Value, |
155 | rpcenv: &mut dyn RpcEnvironment, | |
156 | ) -> Result<(), Error> { | |
0098b712 | 157 | |
5fb694e8 DM |
158 | let (config, _digest) = config::drive::config()?; |
159 | ||
160 | param["drive"] = lookup_drive_name(¶m, &config)?.into(); | |
161 | ||
162 | let info = &api2::tape::drive::API_METHOD_REWIND; | |
163 | ||
663ef859 | 164 | let result = match info.handler { |
5fb694e8 DM |
165 | ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, |
166 | _ => unreachable!(), | |
167 | }; | |
168 | ||
663ef859 DM |
169 | wait_for_local_worker(result.as_str().unwrap()).await?; |
170 | ||
5fb694e8 DM |
171 | Ok(()) |
172 | } | |
173 | ||
0098b712 DM |
174 | #[api( |
175 | input: { | |
176 | properties: { | |
177 | drive: { | |
49c965a4 | 178 | schema: DRIVE_NAME_SCHEMA, |
0098b712 DM |
179 | optional: true, |
180 | }, | |
181 | }, | |
182 | }, | |
183 | )] | |
184 | /// Eject/Unload drive media | |
6fe9aedd | 185 | async fn eject_media( |
0098b712 DM |
186 | mut param: Value, |
187 | rpcenv: &mut dyn RpcEnvironment, | |
188 | ) -> Result<(), Error> { | |
189 | ||
190 | let (config, _digest) = config::drive::config()?; | |
191 | ||
192 | param["drive"] = lookup_drive_name(¶m, &config)?.into(); | |
193 | ||
194 | let info = &api2::tape::drive::API_METHOD_EJECT_MEDIA; | |
195 | ||
196 | match info.handler { | |
6fe9aedd | 197 | ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?, |
0098b712 DM |
198 | _ => unreachable!(), |
199 | }; | |
200 | ||
201 | Ok(()) | |
202 | } | |
203 | ||
e49f0c03 DM |
204 | #[api( |
205 | input: { | |
206 | properties: { | |
207 | drive: { | |
49c965a4 | 208 | schema: DRIVE_NAME_SCHEMA, |
e49f0c03 DM |
209 | optional: true, |
210 | }, | |
211 | "changer-id": { | |
212 | schema: MEDIA_LABEL_SCHEMA, | |
213 | }, | |
214 | }, | |
215 | }, | |
216 | )] | |
217 | /// Load media | |
6fe9aedd | 218 | async fn load_media( |
e49f0c03 DM |
219 | mut param: Value, |
220 | rpcenv: &mut dyn RpcEnvironment, | |
221 | ) -> Result<(), Error> { | |
222 | ||
223 | let (config, _digest) = config::drive::config()?; | |
224 | ||
225 | param["drive"] = lookup_drive_name(¶m, &config)?.into(); | |
226 | ||
227 | let info = &api2::tape::drive::API_METHOD_LOAD_MEDIA; | |
228 | ||
229 | match info.handler { | |
6fe9aedd | 230 | ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?, |
e49f0c03 DM |
231 | _ => unreachable!(), |
232 | }; | |
233 | ||
234 | Ok(()) | |
235 | } | |
236 | ||
7bb720cb DM |
237 | #[api( |
238 | input: { | |
239 | properties: { | |
240 | pool: { | |
241 | schema: MEDIA_POOL_NAME_SCHEMA, | |
242 | optional: true, | |
243 | }, | |
244 | drive: { | |
49c965a4 | 245 | schema: DRIVE_NAME_SCHEMA, |
7bb720cb DM |
246 | optional: true, |
247 | }, | |
248 | "changer-id": { | |
249 | schema: MEDIA_LABEL_SCHEMA, | |
250 | }, | |
251 | }, | |
252 | }, | |
253 | )] | |
254 | /// Label media | |
6dbad5b4 | 255 | async fn label_media( |
7bb720cb DM |
256 | mut param: Value, |
257 | rpcenv: &mut dyn RpcEnvironment, | |
258 | ) -> Result<(), Error> { | |
259 | ||
260 | let (config, _digest) = config::drive::config()?; | |
261 | ||
262 | param["drive"] = lookup_drive_name(¶m, &config)?.into(); | |
263 | ||
264 | let info = &api2::tape::drive::API_METHOD_LABEL_MEDIA; | |
265 | ||
6dbad5b4 | 266 | let result = match info.handler { |
7bb720cb DM |
267 | ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, |
268 | _ => unreachable!(), | |
269 | }; | |
270 | ||
6dbad5b4 DM |
271 | wait_for_local_worker(result.as_str().unwrap()).await?; |
272 | ||
7bb720cb DM |
273 | Ok(()) |
274 | } | |
275 | ||
4606f343 DM |
276 | #[api( |
277 | input: { | |
278 | properties: { | |
279 | drive: { | |
49c965a4 | 280 | schema: DRIVE_NAME_SCHEMA, |
4606f343 DM |
281 | optional: true, |
282 | }, | |
283 | "output-format": { | |
284 | schema: OUTPUT_FORMAT, | |
285 | optional: true, | |
286 | }, | |
287 | }, | |
288 | }, | |
289 | )] | |
290 | /// Read media label | |
6fe9aedd | 291 | async fn read_label( |
4606f343 DM |
292 | mut param: Value, |
293 | rpcenv: &mut dyn RpcEnvironment, | |
294 | ) -> Result<(), Error> { | |
83abc749 DM |
295 | |
296 | let (config, _digest) = config::drive::config()?; | |
4606f343 DM |
297 | |
298 | param["drive"] = lookup_drive_name(¶m, &config)?.into(); | |
299 | ||
300 | let output_format = get_output_format(¶m); | |
301 | let info = &api2::tape::drive::API_METHOD_READ_LABEL; | |
302 | let mut data = match info.handler { | |
6fe9aedd | 303 | ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?, |
4606f343 DM |
304 | _ => unreachable!(), |
305 | }; | |
306 | ||
307 | let options = default_table_format_options() | |
308 | .column(ColumnConfig::new("changer-id")) | |
309 | .column(ColumnConfig::new("uuid")) | |
310 | .column(ColumnConfig::new("ctime").renderer(render_epoch)) | |
311 | .column(ColumnConfig::new("pool")) | |
312 | .column(ColumnConfig::new("media-set-uuid")) | |
313 | .column(ColumnConfig::new("media-set-ctime").renderer(render_epoch)) | |
314 | ; | |
315 | ||
b2362a12 | 316 | format_and_print_result_full(&mut data, &info.returns, &output_format, &options); |
4606f343 DM |
317 | |
318 | Ok(()) | |
83abc749 DM |
319 | } |
320 | ||
321 | #[api( | |
322 | input: { | |
323 | properties: { | |
324 | "output-format": { | |
325 | schema: OUTPUT_FORMAT, | |
326 | optional: true, | |
327 | }, | |
328 | drive: { | |
49c965a4 | 329 | schema: DRIVE_NAME_SCHEMA, |
83abc749 DM |
330 | optional: true, |
331 | }, | |
332 | "read-labels": { | |
333 | description: "Load unknown tapes and try read labels", | |
334 | type: bool, | |
335 | optional: true, | |
336 | }, | |
337 | "read-all-labels": { | |
338 | description: "Load all tapes and try read labels (even if already inventoried)", | |
339 | type: bool, | |
340 | optional: true, | |
341 | }, | |
342 | }, | |
343 | }, | |
344 | )] | |
e92c7581 DM |
345 | /// List (and update) media labels (Changer Inventory) |
346 | async fn inventory( | |
347 | read_labels: Option<bool>, | |
348 | read_all_labels: Option<bool>, | |
349 | param: Value, | |
83abc749 DM |
350 | rpcenv: &mut dyn RpcEnvironment, |
351 | ) -> Result<(), Error> { | |
352 | ||
e92c7581 DM |
353 | let output_format = get_output_format(¶m); |
354 | ||
83abc749 | 355 | let (config, _digest) = config::drive::config()?; |
e92c7581 | 356 | let drive = lookup_drive_name(¶m, &config)?; |
83abc749 | 357 | |
e92c7581 DM |
358 | let do_read = read_labels.unwrap_or(false) || read_all_labels.unwrap_or(false); |
359 | ||
360 | if do_read { | |
361 | let mut param = json!({ | |
362 | "drive": &drive, | |
363 | }); | |
364 | if let Some(true) = read_all_labels { | |
365 | param["read-all-labels"] = true.into(); | |
366 | } | |
367 | let info = &api2::tape::drive::API_METHOD_UPDATE_INVENTORY; | |
368 | let result = match info.handler { | |
369 | ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, | |
370 | _ => unreachable!(), | |
371 | }; | |
372 | wait_for_local_worker(result.as_str().unwrap()).await?; | |
373 | } | |
83abc749 | 374 | |
83abc749 | 375 | let info = &api2::tape::drive::API_METHOD_INVENTORY; |
e92c7581 DM |
376 | |
377 | let param = json!({ "drive": &drive }); | |
83abc749 | 378 | let mut data = match info.handler { |
6fe9aedd | 379 | ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?, |
83abc749 DM |
380 | _ => unreachable!(), |
381 | }; | |
4606f343 | 382 | |
83abc749 DM |
383 | let options = default_table_format_options() |
384 | .column(ColumnConfig::new("changer-id")) | |
385 | .column(ColumnConfig::new("uuid")) | |
386 | ; | |
387 | ||
b2362a12 | 388 | format_and_print_result_full(&mut data, &info.returns, &output_format, &options); |
83abc749 DM |
389 | |
390 | Ok(()) | |
4606f343 | 391 | } |
83abc749 | 392 | |
bff7e3f3 DM |
393 | #[api( |
394 | input: { | |
395 | properties: { | |
396 | pool: { | |
397 | schema: MEDIA_POOL_NAME_SCHEMA, | |
398 | optional: true, | |
399 | }, | |
400 | drive: { | |
49c965a4 | 401 | schema: DRIVE_NAME_SCHEMA, |
bff7e3f3 DM |
402 | optional: true, |
403 | }, | |
404 | }, | |
405 | }, | |
406 | )] | |
407 | /// Label media with barcodes from changer device | |
6dbad5b4 | 408 | async fn barcode_label_media( |
bff7e3f3 DM |
409 | mut param: Value, |
410 | rpcenv: &mut dyn RpcEnvironment, | |
411 | ) -> Result<(), Error> { | |
412 | ||
413 | let (config, _digest) = config::drive::config()?; | |
414 | ||
415 | param["drive"] = lookup_drive_name(¶m, &config)?.into(); | |
416 | ||
417 | let info = &api2::tape::drive::API_METHOD_BARCODE_LABEL_MEDIA; | |
418 | ||
6dbad5b4 | 419 | let result = match info.handler { |
bff7e3f3 DM |
420 | ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, |
421 | _ => unreachable!(), | |
422 | }; | |
423 | ||
6dbad5b4 DM |
424 | wait_for_local_worker(result.as_str().unwrap()).await?; |
425 | ||
bff7e3f3 DM |
426 | Ok(()) |
427 | } | |
428 | ||
ce955e16 DM |
429 | #[api( |
430 | input: { | |
431 | properties: { | |
432 | drive: { | |
433 | schema: DRIVE_NAME_SCHEMA, | |
434 | optional: true, | |
435 | }, | |
436 | }, | |
437 | }, | |
438 | )] | |
439 | /// Move to end of media (MTEOM, used to debug) | |
440 | fn move_to_eom(param: Value) -> Result<(), Error> { | |
441 | ||
442 | let (config, _digest) = config::drive::config()?; | |
443 | ||
444 | let drive = lookup_drive_name(¶m, &config)?; | |
445 | let mut drive = open_drive(&config, &drive)?; | |
446 | ||
447 | drive.move_to_eom()?; | |
448 | ||
449 | Ok(()) | |
450 | } | |
451 | ||
ac461bd6 DM |
452 | #[api( |
453 | input: { | |
454 | properties: { | |
455 | drive: { | |
456 | schema: DRIVE_NAME_SCHEMA, | |
457 | optional: true, | |
458 | }, | |
459 | }, | |
460 | }, | |
461 | )] | |
462 | /// Rewind, then read media contents and print debug info | |
3f803af0 DM |
463 | /// |
464 | /// Note: This reads unless the driver returns an IO Error, so this | |
465 | /// method is expected to fails when we reach EOT. | |
ac461bd6 DM |
466 | fn debug_scan(param: Value) -> Result<(), Error> { |
467 | ||
ac461bd6 DM |
468 | let (config, _digest) = config::drive::config()?; |
469 | ||
470 | let drive = lookup_drive_name(¶m, &config)?; | |
471 | let mut drive = open_drive(&config, &drive)?; | |
472 | ||
473 | println!("rewinding tape"); | |
474 | drive.rewind()?; | |
475 | ||
476 | loop { | |
477 | let file_number = drive.current_file_number()?; | |
478 | ||
479 | match drive.read_next_file()? { | |
480 | None => { | |
481 | println!("EOD"); | |
482 | continue; | |
483 | }, | |
484 | Some(mut reader) => { | |
485 | println!("got file number {}", file_number); | |
486 | ||
487 | let header: Result<MediaContentHeader, _> = unsafe { reader.read_le_value() }; | |
488 | match header { | |
489 | Ok(header) => { | |
490 | if header.magic != PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0 { | |
491 | println!("got MediaContentHeader with wrong magic: {:?}", header.magic); | |
492 | } else { | |
493 | if let Some(name) = PROXMOX_BACKUP_CONTENT_NAME.get(&header.content_magic) { | |
494 | println!("got content header: {}", name); | |
af07ec8f | 495 | println!(" uuid: {}", header.content_uuid()); |
3f803af0 DM |
496 | println!(" ctime: {}", strftime_local("%c", header.ctime)?); |
497 | println!(" hsize: {}", HumanByte::from(header.size as usize)); | |
498 | println!(" part: {}", header.part_number); | |
ac461bd6 DM |
499 | } else { |
500 | println!("got unknown content header: {:?}", header.content_magic); | |
501 | } | |
502 | } | |
503 | } | |
504 | Err(err) => { | |
505 | println!("unable to read content header - {}", err); | |
506 | } | |
507 | } | |
508 | let bytes = reader.skip_to_end()?; | |
3f803af0 | 509 | println!("skipped {}", HumanByte::from(bytes)); |
ac461bd6 DM |
510 | } |
511 | } | |
512 | } | |
513 | } | |
514 | ||
1e20f819 DM |
515 | #[api( |
516 | input: { | |
517 | properties: { | |
518 | drive: { | |
519 | schema: DRIVE_NAME_SCHEMA, | |
520 | optional: true, | |
521 | }, | |
522 | "output-format": { | |
523 | schema: OUTPUT_FORMAT, | |
524 | optional: true, | |
525 | }, | |
526 | }, | |
527 | }, | |
528 | )] | |
529 | /// Read Medium auxiliary memory attributes (Cartridge Memory) | |
530 | fn mam_attributes( | |
531 | mut param: Value, | |
532 | rpcenv: &mut dyn RpcEnvironment, | |
533 | ) -> Result<(), Error> { | |
534 | ||
535 | let (config, _digest) = config::drive::config()?; | |
536 | ||
537 | param["drive"] = lookup_drive_name(¶m, &config)?.into(); | |
538 | ||
539 | let output_format = get_output_format(¶m); | |
540 | let info = &api2::tape::drive::API_METHOD_MAM_ATTRIBUTES; | |
541 | ||
542 | let mut data = match info.handler { | |
543 | ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, | |
544 | _ => unreachable!(), | |
545 | }; | |
546 | ||
547 | let options = default_table_format_options() | |
548 | .column(ColumnConfig::new("id")) | |
549 | .column(ColumnConfig::new("name")) | |
550 | .column(ColumnConfig::new("value")) | |
551 | ; | |
552 | ||
553 | format_and_print_result_full(&mut data, info.returns, &output_format, &options); | |
554 | Ok(()) | |
555 | } | |
556 | ||
88356646 DM |
557 | #[api( |
558 | input: { | |
559 | properties: { | |
560 | store: { | |
561 | schema: DATASTORE_SCHEMA, | |
562 | }, | |
563 | pool: { | |
564 | schema: MEDIA_POOL_NAME_SCHEMA, | |
565 | }, | |
566 | }, | |
567 | }, | |
568 | )] | |
569 | /// Backup datastore to tape media pool | |
570 | async fn backup( | |
571 | param: Value, | |
572 | rpcenv: &mut dyn RpcEnvironment, | |
573 | ) -> Result<(), Error> { | |
574 | ||
575 | let info = &api2::tape::backup::API_METHOD_BACKUP; | |
576 | ||
577 | let result = match info.handler { | |
578 | ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, | |
579 | _ => unreachable!(), | |
580 | }; | |
581 | ||
582 | wait_for_local_worker(result.as_str().unwrap()).await?; | |
583 | ||
584 | Ok(()) | |
585 | } | |
586 | ||
e6604cf3 DM |
587 | fn main() { |
588 | ||
589 | let cmd_def = CliCommandMap::new() | |
88356646 DM |
590 | .insert( |
591 | "backup", | |
592 | CliCommand::new(&API_METHOD_BACKUP) | |
593 | .arg_param(&["store", "pool"]) | |
594 | .completion_cb("store", complete_datastore_name) | |
595 | .completion_cb("pool", complete_pool_name) | |
596 | ) | |
bff7e3f3 DM |
597 | .insert( |
598 | "barcode-label", | |
599 | CliCommand::new(&API_METHOD_BARCODE_LABEL_MEDIA) | |
600 | .completion_cb("drive", complete_drive_name) | |
601 | .completion_cb("pool", complete_pool_name) | |
602 | ) | |
5fb694e8 DM |
603 | .insert( |
604 | "rewind", | |
605 | CliCommand::new(&API_METHOD_REWIND) | |
606 | .completion_cb("drive", complete_drive_name) | |
607 | ) | |
ac461bd6 DM |
608 | .insert( |
609 | "scan", | |
610 | CliCommand::new(&API_METHOD_DEBUG_SCAN) | |
611 | .completion_cb("drive", complete_drive_name) | |
612 | ) | |
ce955e16 DM |
613 | .insert( |
614 | "eod", | |
615 | CliCommand::new(&API_METHOD_MOVE_TO_EOM) | |
616 | .completion_cb("drive", complete_drive_name) | |
617 | ) | |
583a68a4 DM |
618 | .insert( |
619 | "erase", | |
620 | CliCommand::new(&API_METHOD_ERASE_MEDIA) | |
621 | .completion_cb("drive", complete_drive_name) | |
622 | ) | |
0098b712 DM |
623 | .insert( |
624 | "eject", | |
625 | CliCommand::new(&API_METHOD_EJECT_MEDIA) | |
626 | .completion_cb("drive", complete_drive_name) | |
83abc749 DM |
627 | ) |
628 | .insert( | |
629 | "inventory", | |
630 | CliCommand::new(&API_METHOD_INVENTORY) | |
631 | .completion_cb("drive", complete_drive_name) | |
0098b712 | 632 | ) |
4606f343 DM |
633 | .insert( |
634 | "read-label", | |
635 | CliCommand::new(&API_METHOD_READ_LABEL) | |
636 | .completion_cb("drive", complete_drive_name) | |
637 | ) | |
1e20f819 DM |
638 | .insert( |
639 | "mam", | |
640 | CliCommand::new(&API_METHOD_MAM_ATTRIBUTES) | |
641 | .completion_cb("drive", complete_drive_name) | |
642 | ) | |
7bb720cb DM |
643 | .insert( |
644 | "label", | |
645 | CliCommand::new(&API_METHOD_LABEL_MEDIA) | |
646 | .completion_cb("drive", complete_drive_name) | |
647 | .completion_cb("pool", complete_pool_name) | |
648 | ||
649 | ) | |
e6604cf3 DM |
650 | .insert("changer", changer_commands()) |
651 | .insert("drive", drive_commands()) | |
9700d537 | 652 | .insert("pool", pool_commands()) |
fba0b774 | 653 | .insert("media", media_commands()) |
e49f0c03 DM |
654 | .insert( |
655 | "load-media", | |
656 | CliCommand::new(&API_METHOD_LOAD_MEDIA) | |
657 | .arg_param(&["changer-id"]) | |
658 | .completion_cb("drive", complete_drive_name) | |
659 | .completion_cb("changer-id", complete_media_changer_id) | |
660 | ) | |
e6604cf3 DM |
661 | ; |
662 | ||
663 | let mut rpcenv = CliEnvironment::new(); | |
664 | rpcenv.set_auth_id(Some(String::from("root@pam"))); | |
665 | ||
666 | proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv)); | |
667 | } |