]>
Commit | Line | Data |
---|---|---|
efd2713a DC |
1 | use std::collections::HashMap; |
2 | ||
3 | use anyhow::{bail, format_err, Error}; | |
e92c7581 | 4 | use serde_json::{json, Value}; |
583a68a4 | 5 | |
6ef1b649 WB |
6 | use proxmox_io::ReadExt; |
7 | use proxmox_router::RpcEnvironment; | |
8 | use proxmox_router::cli::*; | |
9 | use proxmox_schema::api; | |
10 | use proxmox_section_config::SectionConfigData; | |
11 | use proxmox_time::strftime_local; | |
583a68a4 | 12 | |
01a08021 | 13 | use pbs_client::view_task_result; |
770a36e5 | 14 | use pbs_tools::format::{ |
770a36e5 WB |
15 | render_epoch, |
16 | render_bytes_human_readable, | |
17 | }; | |
18 | ||
1ce8e905 | 19 | use pbs_config::drive::complete_drive_name; |
aad2d162 | 20 | use pbs_config::media_pool::complete_pool_name; |
e7d4be9d | 21 | use pbs_config::datastore::complete_datastore_name; |
1ce8e905 | 22 | |
6227654a DM |
23 | use pbs_api_types::{ |
24 | Userid, Authid, DATASTORE_SCHEMA, DATASTORE_MAP_LIST_SCHEMA, | |
25 | DRIVE_NAME_SCHEMA, MEDIA_LABEL_SCHEMA, MEDIA_POOL_NAME_SCHEMA, | |
efd2713a | 26 | TAPE_RESTORE_SNAPSHOT_SCHEMA, GROUP_FILTER_LIST_SCHEMA, GroupListItem, |
a58a5cf7 | 27 | HumanByte |
6227654a | 28 | }; |
048b43af DM |
29 | use pbs_tape::{ |
30 | PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0, BlockReadError, MediaContentHeader, | |
31 | }; | |
6227654a | 32 | |
583a68a4 | 33 | use proxmox_backup::{ |
6227654a | 34 | api2, |
e49f0c03 | 35 | tape::{ |
25aa55b5 DM |
36 | drive::{ |
37 | open_drive, | |
38 | lock_tape_device, | |
926d05ef | 39 | set_tape_device_state, |
25aa55b5 | 40 | }, |
8446fbca | 41 | complete_media_label_text, |
b9b4b312 | 42 | complete_media_set_uuid, |
0ecdaa0d | 43 | complete_media_set_snapshots, |
ac461bd6 | 44 | file_formats::{ |
f47e0357 | 45 | proxmox_tape_magic_to_text, |
ac461bd6 | 46 | }, |
e49f0c03 | 47 | }, |
01a08021 | 48 | client_helpers::connect_to_localhost, |
e6604cf3 DM |
49 | }; |
50 | ||
51 | mod proxmox_tape; | |
52 | use proxmox_tape::*; | |
53 | ||
efd2713a DC |
54 | async fn get_backup_groups(store: &str) -> Result<Vec<GroupListItem>, Error> { |
55 | let client = connect_to_localhost()?; | |
56 | let api_res = client | |
57 | .get(&format!("api2/json/admin/datastore/{}/groups", store), None) | |
58 | .await?; | |
59 | ||
60 | match api_res.get("data") { | |
61 | Some(data) => Ok(serde_json::from_value::<Vec<GroupListItem>>(data.to_owned())?), | |
62 | None => bail!("could not get group list"), | |
63 | } | |
64 | } | |
65 | ||
66 | // shell completion helper | |
67 | pub fn complete_datastore_group_filter(_arg: &str, param: &HashMap<String, String>) -> Vec<String> { | |
68 | ||
69 | let mut list = Vec::new(); | |
70 | ||
71 | list.push("regex:".to_string()); | |
72 | list.push("type:ct".to_string()); | |
73 | list.push("type:host".to_string()); | |
74 | list.push("type:vm".to_string()); | |
75 | ||
76 | if let Some(store) = param.get("store") { | |
9a1b24b6 | 77 | let groups = proxmox_async::runtime::block_on(async { get_backup_groups(store).await }); |
efd2713a DC |
78 | if let Ok(groups) = groups { |
79 | list.extend(groups.iter().map(|group| format!("group:{}/{}", group.backup_type, group.backup_id))); | |
80 | } | |
81 | } | |
82 | ||
83 | list | |
84 | } | |
85 | ||
85cdc4f3 DC |
86 | pub fn extract_drive_name( |
87 | param: &mut Value, | |
583a68a4 DM |
88 | config: &SectionConfigData, |
89 | ) -> Result<String, Error> { | |
90 | ||
91 | let drive = param["drive"] | |
92 | .as_str() | |
93 | .map(String::from) | |
94 | .or_else(|| std::env::var("PROXMOX_TAPE_DRIVE").ok()) | |
95 | .or_else(|| { | |
96 | ||
97 | let mut drive_names = Vec::new(); | |
98 | ||
99 | for (name, (section_type, _)) in config.sections.iter() { | |
100 | ||
101 | if !(section_type == "linux" || section_type == "virtual") { continue; } | |
102 | drive_names.push(name); | |
103 | } | |
104 | ||
105 | if drive_names.len() == 1 { | |
106 | Some(drive_names[0].to_owned()) | |
107 | } else { | |
108 | None | |
109 | } | |
110 | }) | |
111 | .ok_or_else(|| format_err!("unable to get (default) drive name"))?; | |
112 | ||
85cdc4f3 DC |
113 | if let Some(map) = param.as_object_mut() { |
114 | map.remove("drive"); | |
115 | } | |
116 | ||
583a68a4 DM |
117 | Ok(drive) |
118 | } | |
119 | ||
120 | #[api( | |
121 | input: { | |
122 | properties: { | |
123 | drive: { | |
49c965a4 | 124 | schema: DRIVE_NAME_SCHEMA, |
583a68a4 DM |
125 | optional: true, |
126 | }, | |
127 | fast: { | |
128 | description: "Use fast erase.", | |
129 | type: bool, | |
130 | optional: true, | |
131 | default: true, | |
132 | }, | |
eb1dfb02 DM |
133 | "output-format": { |
134 | schema: OUTPUT_FORMAT, | |
135 | optional: true, | |
136 | }, | |
137 | }, | |
583a68a4 DM |
138 | }, |
139 | )] | |
e29f456e DM |
140 | /// Format media |
141 | async fn format_media(mut param: Value) -> Result<(), Error> { | |
eb1dfb02 | 142 | |
671c6a96 | 143 | let output_format = extract_output_format(&mut param); |
583a68a4 | 144 | |
1ce8e905 | 145 | let (config, _digest) = pbs_config::drive::config()?; |
583a68a4 | 146 | |
85cdc4f3 | 147 | let drive = extract_drive_name(&mut param, &config)?; |
583a68a4 | 148 | |
d4877712 | 149 | let client = connect_to_localhost()?; |
583a68a4 | 150 | |
e29f456e | 151 | let path = format!("api2/json/tape/drive/{}/format-media", drive); |
eb1dfb02 | 152 | let result = client.post(&path, Some(param)).await?; |
583a68a4 | 153 | |
d4877712 | 154 | view_task_result(&client, result, &output_format).await?; |
663ef859 | 155 | |
583a68a4 DM |
156 | Ok(()) |
157 | } | |
158 | ||
5fb694e8 DM |
159 | #[api( |
160 | input: { | |
161 | properties: { | |
162 | drive: { | |
49c965a4 | 163 | schema: DRIVE_NAME_SCHEMA, |
5fb694e8 DM |
164 | optional: true, |
165 | }, | |
eb1dfb02 DM |
166 | "output-format": { |
167 | schema: OUTPUT_FORMAT, | |
168 | optional: true, | |
169 | }, | |
5fb694e8 DM |
170 | }, |
171 | }, | |
172 | )] | |
173 | /// Rewind tape | |
85cdc4f3 | 174 | async fn rewind(mut param: Value) -> Result<(), Error> { |
eb1dfb02 | 175 | |
671c6a96 | 176 | let output_format = extract_output_format(&mut param); |
0098b712 | 177 | |
1ce8e905 | 178 | let (config, _digest) = pbs_config::drive::config()?; |
5fb694e8 | 179 | |
85cdc4f3 | 180 | let drive = extract_drive_name(&mut param, &config)?; |
5fb694e8 | 181 | |
d4877712 | 182 | let client = connect_to_localhost()?; |
5fb694e8 | 183 | |
eb1dfb02 DM |
184 | let path = format!("api2/json/tape/drive/{}/rewind", drive); |
185 | let result = client.post(&path, Some(param)).await?; | |
5fb694e8 | 186 | |
d4877712 | 187 | view_task_result(&client, result, &output_format).await?; |
663ef859 | 188 | |
5fb694e8 DM |
189 | Ok(()) |
190 | } | |
191 | ||
0098b712 DM |
192 | #[api( |
193 | input: { | |
194 | properties: { | |
195 | drive: { | |
49c965a4 | 196 | schema: DRIVE_NAME_SCHEMA, |
0098b712 DM |
197 | optional: true, |
198 | }, | |
41dacd5d DM |
199 | "output-format": { |
200 | schema: OUTPUT_FORMAT, | |
201 | optional: true, | |
202 | }, | |
0098b712 DM |
203 | }, |
204 | }, | |
205 | )] | |
206 | /// Eject/Unload drive media | |
85cdc4f3 | 207 | async fn eject_media(mut param: Value) -> Result<(), Error> { |
41dacd5d | 208 | |
671c6a96 | 209 | let output_format = extract_output_format(&mut param); |
0098b712 | 210 | |
1ce8e905 | 211 | let (config, _digest) = pbs_config::drive::config()?; |
0098b712 | 212 | |
85cdc4f3 | 213 | let drive = extract_drive_name(&mut param, &config)?; |
0098b712 | 214 | |
d4877712 | 215 | let client = connect_to_localhost()?; |
0098b712 | 216 | |
41dacd5d DM |
217 | let path = format!("api2/json/tape/drive/{}/eject-media", drive); |
218 | let result = client.post(&path, Some(param)).await?; | |
219 | ||
d4877712 | 220 | view_task_result(&client, result, &output_format).await?; |
0098b712 DM |
221 | |
222 | Ok(()) | |
223 | } | |
224 | ||
e49f0c03 DM |
225 | #[api( |
226 | input: { | |
227 | properties: { | |
228 | drive: { | |
49c965a4 | 229 | schema: DRIVE_NAME_SCHEMA, |
e49f0c03 DM |
230 | optional: true, |
231 | }, | |
8446fbca | 232 | "label-text": { |
e49f0c03 DM |
233 | schema: MEDIA_LABEL_SCHEMA, |
234 | }, | |
d0647e5a DM |
235 | "output-format": { |
236 | schema: OUTPUT_FORMAT, | |
237 | optional: true, | |
238 | }, | |
e49f0c03 DM |
239 | }, |
240 | }, | |
241 | )] | |
46a1863f | 242 | /// Load media with specified label |
85cdc4f3 | 243 | async fn load_media(mut param: Value) -> Result<(), Error> { |
e49f0c03 | 244 | |
671c6a96 | 245 | let output_format = extract_output_format(&mut param); |
d0647e5a | 246 | |
1ce8e905 | 247 | let (config, _digest) = pbs_config::drive::config()?; |
e49f0c03 | 248 | |
85cdc4f3 | 249 | let drive = extract_drive_name(&mut param, &config)?; |
e49f0c03 | 250 | |
d4877712 | 251 | let client = connect_to_localhost()?; |
e49f0c03 | 252 | |
5243df47 | 253 | let path = format!("api2/json/tape/drive/{}/load-media", drive); |
d0647e5a DM |
254 | let result = client.post(&path, Some(param)).await?; |
255 | ||
d4877712 | 256 | view_task_result(&client, result, &output_format).await?; |
e49f0c03 DM |
257 | |
258 | Ok(()) | |
259 | } | |
260 | ||
483da89d DM |
261 | #[api( |
262 | input: { | |
263 | properties: { | |
264 | drive: { | |
265 | schema: DRIVE_NAME_SCHEMA, | |
266 | optional: true, | |
267 | }, | |
8446fbca | 268 | "label-text": { |
483da89d DM |
269 | schema: MEDIA_LABEL_SCHEMA, |
270 | }, | |
271 | }, | |
272 | }, | |
273 | )] | |
274 | /// Export media with specified label | |
85cdc4f3 | 275 | async fn export_media(mut param: Value) -> Result<(), Error> { |
483da89d | 276 | |
1ce8e905 | 277 | let (config, _digest) = pbs_config::drive::config()?; |
483da89d | 278 | |
85cdc4f3 | 279 | let drive = extract_drive_name(&mut param, &config)?; |
483da89d | 280 | |
d4877712 | 281 | let client = connect_to_localhost()?; |
483da89d | 282 | |
5243df47 DM |
283 | let path = format!("api2/json/tape/drive/{}/export-media", drive); |
284 | client.put(&path, Some(param)).await?; | |
483da89d DM |
285 | |
286 | Ok(()) | |
287 | } | |
288 | ||
46a1863f DM |
289 | #[api( |
290 | input: { | |
291 | properties: { | |
292 | drive: { | |
293 | schema: DRIVE_NAME_SCHEMA, | |
294 | optional: true, | |
295 | }, | |
296 | "source-slot": { | |
297 | description: "Source slot number.", | |
298 | type: u64, | |
299 | minimum: 1, | |
300 | }, | |
301 | }, | |
302 | }, | |
303 | )] | |
304 | /// Load media from the specified slot | |
85cdc4f3 | 305 | async fn load_media_from_slot(mut param: Value) -> Result<(), Error> { |
46a1863f | 306 | |
1ce8e905 | 307 | let (config, _digest) = pbs_config::drive::config()?; |
46a1863f | 308 | |
85cdc4f3 | 309 | let drive = extract_drive_name(&mut param, &config)?; |
46a1863f | 310 | |
d4877712 | 311 | let client = connect_to_localhost()?; |
46a1863f | 312 | |
5243df47 DM |
313 | let path = format!("api2/json/tape/drive/{}/load-slot", drive); |
314 | client.put(&path, Some(param)).await?; | |
46a1863f DM |
315 | |
316 | Ok(()) | |
317 | } | |
318 | ||
319 | #[api( | |
320 | input: { | |
321 | properties: { | |
322 | drive: { | |
323 | schema: DRIVE_NAME_SCHEMA, | |
324 | optional: true, | |
325 | }, | |
326 | "target-slot": { | |
327 | description: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.", | |
328 | type: u64, | |
329 | minimum: 1, | |
330 | optional: true, | |
331 | }, | |
d0647e5a DM |
332 | "output-format": { |
333 | schema: OUTPUT_FORMAT, | |
334 | optional: true, | |
335 | }, | |
46a1863f DM |
336 | }, |
337 | }, | |
338 | )] | |
339 | /// Unload media via changer | |
85cdc4f3 | 340 | async fn unload_media(mut param: Value) -> Result<(), Error> { |
46a1863f | 341 | |
671c6a96 | 342 | let output_format = extract_output_format(&mut param); |
d0647e5a | 343 | |
1ce8e905 | 344 | let (config, _digest) = pbs_config::drive::config()?; |
46a1863f | 345 | |
85cdc4f3 | 346 | let drive = extract_drive_name(&mut param, &config)?; |
46a1863f | 347 | |
d4877712 | 348 | let client = connect_to_localhost()?; |
46a1863f | 349 | |
5243df47 | 350 | let path = format!("api2/json/tape/drive/{}/unload", drive); |
d0647e5a DM |
351 | let result = client.post(&path, Some(param)).await?; |
352 | ||
d4877712 | 353 | view_task_result(&client, result, &output_format).await?; |
46a1863f DM |
354 | |
355 | Ok(()) | |
356 | } | |
357 | ||
7bb720cb DM |
358 | #[api( |
359 | input: { | |
360 | properties: { | |
361 | pool: { | |
362 | schema: MEDIA_POOL_NAME_SCHEMA, | |
363 | optional: true, | |
364 | }, | |
365 | drive: { | |
49c965a4 | 366 | schema: DRIVE_NAME_SCHEMA, |
7bb720cb DM |
367 | optional: true, |
368 | }, | |
8446fbca | 369 | "label-text": { |
7bb720cb DM |
370 | schema: MEDIA_LABEL_SCHEMA, |
371 | }, | |
5243df47 DM |
372 | "output-format": { |
373 | schema: OUTPUT_FORMAT, | |
374 | optional: true, | |
375 | }, | |
376 | }, | |
7bb720cb DM |
377 | }, |
378 | )] | |
379 | /// Label media | |
85cdc4f3 | 380 | async fn label_media(mut param: Value) -> Result<(), Error> { |
5243df47 | 381 | |
671c6a96 | 382 | let output_format = extract_output_format(&mut param); |
7bb720cb | 383 | |
1ce8e905 | 384 | let (config, _digest) = pbs_config::drive::config()?; |
7bb720cb | 385 | |
85cdc4f3 | 386 | let drive = extract_drive_name(&mut param, &config)?; |
7bb720cb | 387 | |
d4877712 | 388 | let client = connect_to_localhost()?; |
7bb720cb | 389 | |
5243df47 DM |
390 | let path = format!("api2/json/tape/drive/{}/label-media", drive); |
391 | let result = client.post(&path, Some(param)).await?; | |
7bb720cb | 392 | |
d4877712 | 393 | view_task_result(&client, result, &output_format).await?; |
6dbad5b4 | 394 | |
7bb720cb DM |
395 | Ok(()) |
396 | } | |
397 | ||
4606f343 DM |
398 | #[api( |
399 | input: { | |
400 | properties: { | |
401 | drive: { | |
49c965a4 | 402 | schema: DRIVE_NAME_SCHEMA, |
4606f343 DM |
403 | optional: true, |
404 | }, | |
781da7f6 DM |
405 | inventorize: { |
406 | description: "Inventorize media", | |
407 | type: bool, | |
408 | optional: true, | |
409 | }, | |
410 | "output-format": { | |
4606f343 DM |
411 | schema: OUTPUT_FORMAT, |
412 | optional: true, | |
413 | }, | |
414 | }, | |
415 | }, | |
416 | )] | |
417 | /// Read media label | |
85cdc4f3 | 418 | async fn read_label(mut param: Value) -> Result<(), Error> { |
5243df47 | 419 | |
671c6a96 | 420 | let output_format = extract_output_format(&mut param); |
83abc749 | 421 | |
1ce8e905 | 422 | let (config, _digest) = pbs_config::drive::config()?; |
4606f343 | 423 | |
85cdc4f3 | 424 | let drive = extract_drive_name(&mut param, &config)?; |
5243df47 DM |
425 | |
426 | let client = connect_to_localhost()?; | |
427 | ||
428 | let path = format!("api2/json/tape/drive/{}/read-label", drive); | |
429 | let mut result = client.get(&path, Some(param)).await?; | |
430 | let mut data = result["data"].take(); | |
4606f343 | 431 | |
4606f343 | 432 | let info = &api2::tape::drive::API_METHOD_READ_LABEL; |
4606f343 DM |
433 | |
434 | let options = default_table_format_options() | |
8446fbca | 435 | .column(ColumnConfig::new("label-text")) |
4606f343 DM |
436 | .column(ColumnConfig::new("uuid")) |
437 | .column(ColumnConfig::new("ctime").renderer(render_epoch)) | |
438 | .column(ColumnConfig::new("pool")) | |
439 | .column(ColumnConfig::new("media-set-uuid")) | |
440 | .column(ColumnConfig::new("media-set-ctime").renderer(render_epoch)) | |
8a0046f5 | 441 | .column(ColumnConfig::new("encryption-key-fingerprint")) |
4606f343 DM |
442 | ; |
443 | ||
b2362a12 | 444 | format_and_print_result_full(&mut data, &info.returns, &output_format, &options); |
4606f343 DM |
445 | |
446 | Ok(()) | |
83abc749 DM |
447 | } |
448 | ||
449 | #[api( | |
450 | input: { | |
451 | properties: { | |
452 | "output-format": { | |
453 | schema: OUTPUT_FORMAT, | |
454 | optional: true, | |
455 | }, | |
456 | drive: { | |
49c965a4 | 457 | schema: DRIVE_NAME_SCHEMA, |
83abc749 DM |
458 | optional: true, |
459 | }, | |
460 | "read-labels": { | |
461 | description: "Load unknown tapes and try read labels", | |
462 | type: bool, | |
463 | optional: true, | |
464 | }, | |
465 | "read-all-labels": { | |
466 | description: "Load all tapes and try read labels (even if already inventoried)", | |
467 | type: bool, | |
468 | optional: true, | |
469 | }, | |
470 | }, | |
471 | }, | |
472 | )] | |
e92c7581 DM |
473 | /// List (and update) media labels (Changer Inventory) |
474 | async fn inventory( | |
475 | read_labels: Option<bool>, | |
476 | read_all_labels: Option<bool>, | |
85cdc4f3 | 477 | mut param: Value, |
83abc749 DM |
478 | ) -> Result<(), Error> { |
479 | ||
671c6a96 | 480 | let output_format = extract_output_format(&mut param); |
e92c7581 | 481 | |
1ce8e905 | 482 | let (config, _digest) = pbs_config::drive::config()?; |
85cdc4f3 | 483 | let drive = extract_drive_name(&mut param, &config)?; |
83abc749 | 484 | |
e92c7581 DM |
485 | let do_read = read_labels.unwrap_or(false) || read_all_labels.unwrap_or(false); |
486 | ||
d4877712 | 487 | let client = connect_to_localhost()?; |
e68269fc DM |
488 | |
489 | let path = format!("api2/json/tape/drive/{}/inventory", drive); | |
490 | ||
e92c7581 | 491 | if do_read { |
e68269fc DM |
492 | |
493 | let mut param = json!({}); | |
e92c7581 DM |
494 | if let Some(true) = read_all_labels { |
495 | param["read-all-labels"] = true.into(); | |
496 | } | |
e68269fc DM |
497 | |
498 | let result = client.put(&path, Some(param)).await?; // update inventory | |
d4877712 | 499 | view_task_result(&client, result, &output_format).await?; |
e92c7581 | 500 | } |
83abc749 | 501 | |
e68269fc DM |
502 | let mut result = client.get(&path, None).await?; |
503 | let mut data = result["data"].take(); | |
e92c7581 | 504 | |
e68269fc | 505 | let info = &api2::tape::drive::API_METHOD_INVENTORY; |
4606f343 | 506 | |
83abc749 | 507 | let options = default_table_format_options() |
8446fbca | 508 | .column(ColumnConfig::new("label-text")) |
83abc749 DM |
509 | .column(ColumnConfig::new("uuid")) |
510 | ; | |
511 | ||
b2362a12 | 512 | format_and_print_result_full(&mut data, &info.returns, &output_format, &options); |
83abc749 DM |
513 | |
514 | Ok(()) | |
4606f343 | 515 | } |
83abc749 | 516 | |
bff7e3f3 DM |
517 | #[api( |
518 | input: { | |
519 | properties: { | |
520 | pool: { | |
521 | schema: MEDIA_POOL_NAME_SCHEMA, | |
522 | optional: true, | |
523 | }, | |
524 | drive: { | |
49c965a4 | 525 | schema: DRIVE_NAME_SCHEMA, |
bff7e3f3 DM |
526 | optional: true, |
527 | }, | |
c297835b DM |
528 | "output-format": { |
529 | schema: OUTPUT_FORMAT, | |
530 | optional: true, | |
531 | }, | |
bff7e3f3 DM |
532 | }, |
533 | }, | |
534 | )] | |
535 | /// Label media with barcodes from changer device | |
85cdc4f3 | 536 | async fn barcode_label_media(mut param: Value) -> Result<(), Error> { |
c297835b | 537 | |
671c6a96 | 538 | let output_format = extract_output_format(&mut param); |
bff7e3f3 | 539 | |
1ce8e905 | 540 | let (config, _digest) = pbs_config::drive::config()?; |
bff7e3f3 | 541 | |
85cdc4f3 | 542 | let drive = extract_drive_name(&mut param, &config)?; |
bff7e3f3 | 543 | |
d4877712 | 544 | let client = connect_to_localhost()?; |
bff7e3f3 | 545 | |
c297835b DM |
546 | let path = format!("api2/json/tape/drive/{}/barcode-label-media", drive); |
547 | let result = client.post(&path, Some(param)).await?; | |
bff7e3f3 | 548 | |
d4877712 | 549 | view_task_result(&client, result, &output_format).await?; |
6dbad5b4 | 550 | |
bff7e3f3 DM |
551 | Ok(()) |
552 | } | |
553 | ||
ce955e16 DM |
554 | #[api( |
555 | input: { | |
556 | properties: { | |
557 | drive: { | |
558 | schema: DRIVE_NAME_SCHEMA, | |
559 | optional: true, | |
560 | }, | |
561 | }, | |
562 | }, | |
563 | )] | |
564 | /// Move to end of media (MTEOM, used to debug) | |
85cdc4f3 | 565 | fn move_to_eom(mut param: Value) -> Result<(), Error> { |
ce955e16 | 566 | |
1ce8e905 | 567 | let (config, _digest) = pbs_config::drive::config()?; |
ce955e16 | 568 | |
85cdc4f3 | 569 | let drive = extract_drive_name(&mut param, &config)?; |
25aa55b5 DM |
570 | |
571 | let _lock = lock_tape_device(&config, &drive)?; | |
926d05ef | 572 | set_tape_device_state(&drive, "moving to eom")?; |
25aa55b5 | 573 | |
ce955e16 DM |
574 | let mut drive = open_drive(&config, &drive)?; |
575 | ||
7b11a809 | 576 | drive.move_to_eom(false)?; |
ce955e16 DM |
577 | |
578 | Ok(()) | |
579 | } | |
580 | ||
ac461bd6 DM |
581 | #[api( |
582 | input: { | |
583 | properties: { | |
584 | drive: { | |
585 | schema: DRIVE_NAME_SCHEMA, | |
586 | optional: true, | |
587 | }, | |
588 | }, | |
589 | }, | |
590 | )] | |
591 | /// Rewind, then read media contents and print debug info | |
3f803af0 DM |
592 | /// |
593 | /// Note: This reads unless the driver returns an IO Error, so this | |
594 | /// method is expected to fails when we reach EOT. | |
85cdc4f3 | 595 | fn debug_scan(mut param: Value) -> Result<(), Error> { |
ac461bd6 | 596 | |
1ce8e905 | 597 | let (config, _digest) = pbs_config::drive::config()?; |
ac461bd6 | 598 | |
85cdc4f3 | 599 | let drive = extract_drive_name(&mut param, &config)?; |
25aa55b5 DM |
600 | |
601 | let _lock = lock_tape_device(&config, &drive)?; | |
926d05ef | 602 | set_tape_device_state(&drive, "debug scan")?; |
25aa55b5 | 603 | |
ac461bd6 DM |
604 | let mut drive = open_drive(&config, &drive)?; |
605 | ||
606 | println!("rewinding tape"); | |
607 | drive.rewind()?; | |
608 | ||
609 | loop { | |
610 | let file_number = drive.current_file_number()?; | |
611 | ||
318b3106 DM |
612 | match drive.read_next_file() { |
613 | Err(BlockReadError::EndOfFile) => { | |
614 | println!("filemark number {}", file_number); | |
ac461bd6 | 615 | continue; |
318b3106 DM |
616 | } |
617 | Err(BlockReadError::EndOfStream) => { | |
618 | println!("got EOT"); | |
619 | return Ok(()); | |
620 | } | |
621 | Err(BlockReadError::Error(err)) => { | |
622 | return Err(err.into()); | |
623 | } | |
624 | Ok(mut reader) => { | |
ac461bd6 DM |
625 | println!("got file number {}", file_number); |
626 | ||
627 | let header: Result<MediaContentHeader, _> = unsafe { reader.read_le_value() }; | |
628 | match header { | |
629 | Ok(header) => { | |
630 | if header.magic != PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0 { | |
631 | println!("got MediaContentHeader with wrong magic: {:?}", header.magic); | |
f47e0357 | 632 | } else if let Some(name) = proxmox_tape_magic_to_text(&header.content_magic) { |
6334bdc1 FG |
633 | println!("got content header: {}", name); |
634 | println!(" uuid: {}", header.content_uuid()); | |
635 | println!(" ctime: {}", strftime_local("%c", header.ctime)?); | |
636 | println!(" hsize: {}", HumanByte::from(header.size as usize)); | |
637 | println!(" part: {}", header.part_number); | |
ac461bd6 | 638 | } else { |
6334bdc1 | 639 | println!("got unknown content header: {:?}", header.content_magic); |
ac461bd6 DM |
640 | } |
641 | } | |
642 | Err(err) => { | |
643 | println!("unable to read content header - {}", err); | |
644 | } | |
645 | } | |
90461b76 | 646 | let bytes = reader.skip_data()?; |
3f803af0 | 647 | println!("skipped {}", HumanByte::from(bytes)); |
90461b76 DM |
648 | if let Ok(true) = reader.has_end_marker() { |
649 | if reader.is_incomplete()? { | |
650 | println!("WARNING: file is incomplete"); | |
651 | } | |
652 | } else { | |
653 | println!("WARNING: file without end marker"); | |
654 | } | |
ac461bd6 DM |
655 | } |
656 | } | |
657 | } | |
658 | } | |
659 | ||
1e20f819 DM |
660 | #[api( |
661 | input: { | |
662 | properties: { | |
663 | drive: { | |
664 | schema: DRIVE_NAME_SCHEMA, | |
665 | optional: true, | |
666 | }, | |
c297835b | 667 | "output-format": { |
1e20f819 DM |
668 | schema: OUTPUT_FORMAT, |
669 | optional: true, | |
c297835b | 670 | }, |
1e20f819 DM |
671 | }, |
672 | }, | |
673 | )] | |
ee01737e | 674 | /// Read Cartridge Memory (Medium auxiliary memory attributes) |
85cdc4f3 | 675 | async fn cartridge_memory(mut param: Value) -> Result<(), Error> { |
c297835b | 676 | |
671c6a96 | 677 | let output_format = extract_output_format(&mut param); |
1e20f819 | 678 | |
1ce8e905 | 679 | let (config, _digest) = pbs_config::drive::config()?; |
1e20f819 | 680 | |
85cdc4f3 | 681 | let drive = extract_drive_name(&mut param, &config)?; |
1e20f819 | 682 | |
c297835b | 683 | let client = connect_to_localhost()?; |
1e20f819 | 684 | |
c297835b DM |
685 | let path = format!("api2/json/tape/drive/{}/cartridge-memory", drive); |
686 | let mut result = client.get(&path, Some(param)).await?; | |
687 | let mut data = result["data"].take(); | |
688 | ||
689 | let info = &api2::tape::drive::API_METHOD_CARTRIDGE_MEMORY; | |
1e20f819 DM |
690 | |
691 | let options = default_table_format_options() | |
692 | .column(ColumnConfig::new("id")) | |
693 | .column(ColumnConfig::new("name")) | |
694 | .column(ColumnConfig::new("value")) | |
695 | ; | |
696 | ||
20128259 | 697 | format_and_print_result_full(&mut data, &info.returns, &output_format, &options); |
1e20f819 DM |
698 | Ok(()) |
699 | } | |
700 | ||
5f34d69b DM |
701 | #[api( |
702 | input: { | |
703 | properties: { | |
704 | drive: { | |
705 | schema: DRIVE_NAME_SCHEMA, | |
706 | optional: true, | |
707 | }, | |
c297835b | 708 | "output-format": { |
5f34d69b DM |
709 | schema: OUTPUT_FORMAT, |
710 | optional: true, | |
c297835b | 711 | }, |
5f34d69b DM |
712 | }, |
713 | }, | |
714 | )] | |
715 | /// Read Volume Statistics (SCSI log page 17h) | |
85cdc4f3 | 716 | async fn volume_statistics(mut param: Value) -> Result<(), Error> { |
c297835b | 717 | |
671c6a96 | 718 | let output_format = extract_output_format(&mut param); |
5f34d69b | 719 | |
1ce8e905 | 720 | let (config, _digest) = pbs_config::drive::config()?; |
5f34d69b | 721 | |
85cdc4f3 | 722 | let drive = extract_drive_name(&mut param, &config)?; |
5f34d69b | 723 | |
c297835b | 724 | let client = connect_to_localhost()?; |
5f34d69b | 725 | |
c297835b DM |
726 | let path = format!("api2/json/tape/drive/{}/volume-statistics", drive); |
727 | let mut result = client.get(&path, Some(param)).await?; | |
728 | let mut data = result["data"].take(); | |
729 | ||
730 | let info = &api2::tape::drive::API_METHOD_VOLUME_STATISTICS; | |
5f34d69b DM |
731 | |
732 | let options = default_table_format_options(); | |
733 | ||
734 | format_and_print_result_full(&mut data, &info.returns, &output_format, &options); | |
c297835b | 735 | |
5f34d69b DM |
736 | Ok(()) |
737 | } | |
738 | ||
cb80d900 DM |
739 | #[api( |
740 | input: { | |
741 | properties: { | |
742 | drive: { | |
743 | schema: DRIVE_NAME_SCHEMA, | |
744 | optional: true, | |
745 | }, | |
746 | "output-format": { | |
747 | schema: OUTPUT_FORMAT, | |
748 | optional: true, | |
749 | }, | |
750 | }, | |
751 | }, | |
752 | )] | |
5ae86dfa | 753 | /// Get drive/media status |
85cdc4f3 | 754 | async fn status(mut param: Value) -> Result<(), Error> { |
c297835b | 755 | |
671c6a96 | 756 | let output_format = extract_output_format(&mut param); |
cb80d900 | 757 | |
1ce8e905 | 758 | let (config, _digest) = pbs_config::drive::config()?; |
cb80d900 | 759 | |
85cdc4f3 | 760 | let drive = extract_drive_name(&mut param, &config)?; |
cb80d900 | 761 | |
c297835b | 762 | let client = connect_to_localhost()?; |
cb80d900 | 763 | |
c297835b DM |
764 | let path = format!("api2/json/tape/drive/{}/status", drive); |
765 | let mut result = client.get(&path, Some(param)).await?; | |
766 | let mut data = result["data"].take(); | |
767 | ||
768 | let info = &api2::tape::drive::API_METHOD_STATUS; | |
cb80d900 | 769 | |
337ff5a3 DM |
770 | let render_percentage = |value: &Value, _record: &Value| { |
771 | match value.as_f64() { | |
772 | Some(wearout) => Ok(format!("{:.2}%", wearout*100.0)), | |
773 | None => Ok(String::from("ERROR")), // should never happen | |
774 | } | |
775 | }; | |
776 | ||
cb80d900 DM |
777 | let options = default_table_format_options() |
778 | .column(ColumnConfig::new("blocksize")) | |
779 | .column(ColumnConfig::new("density")) | |
0892a512 DM |
780 | .column(ColumnConfig::new("compression")) |
781 | .column(ColumnConfig::new("buffer-mode")) | |
782 | .column(ColumnConfig::new("write-protect")) | |
470f1c79 | 783 | .column(ColumnConfig::new("alert-flags")) |
cb80d900 DM |
784 | .column(ColumnConfig::new("file-number")) |
785 | .column(ColumnConfig::new("block-number")) | |
5ae86dfa DM |
786 | .column(ColumnConfig::new("manufactured").renderer(render_epoch)) |
787 | .column(ColumnConfig::new("bytes-written").renderer(render_bytes_human_readable)) | |
788 | .column(ColumnConfig::new("bytes-read").renderer(render_bytes_human_readable)) | |
b40ab10d | 789 | .column(ColumnConfig::new("medium-passes")) |
337ff5a3 | 790 | .column(ColumnConfig::new("medium-wearout").renderer(render_percentage)) |
b40ab10d | 791 | .column(ColumnConfig::new("volume-mounts")) |
cb80d900 DM |
792 | ; |
793 | ||
794 | format_and_print_result_full(&mut data, &info.returns, &output_format, &options); | |
c297835b | 795 | |
cb80d900 DM |
796 | Ok(()) |
797 | } | |
798 | ||
df69a4fc DM |
799 | #[api( |
800 | input: { | |
801 | properties: { | |
802 | drive: { | |
803 | schema: DRIVE_NAME_SCHEMA, | |
804 | optional: true, | |
805 | }, | |
c297835b DM |
806 | "output-format": { |
807 | schema: OUTPUT_FORMAT, | |
808 | optional: true, | |
809 | }, | |
df69a4fc DM |
810 | }, |
811 | }, | |
812 | )] | |
813 | /// Clean drive | |
85cdc4f3 | 814 | async fn clean_drive(mut param: Value) -> Result<(), Error> { |
c297835b | 815 | |
671c6a96 | 816 | let output_format = extract_output_format(&mut param); |
df69a4fc | 817 | |
1ce8e905 | 818 | let (config, _digest) = pbs_config::drive::config()?; |
df69a4fc | 819 | |
85cdc4f3 | 820 | let drive = extract_drive_name(&mut param, &config)?; |
df69a4fc | 821 | |
d4877712 | 822 | let client = connect_to_localhost()?; |
df69a4fc | 823 | |
0320deb0 DC |
824 | let path = format!("api2/json/tape/drive/{}/clean", drive); |
825 | let result = client.put(&path, Some(param)).await?; | |
df69a4fc | 826 | |
d4877712 | 827 | view_task_result(&client, result, &output_format).await?; |
df69a4fc DM |
828 | |
829 | Ok(()) | |
830 | } | |
831 | ||
88356646 | 832 | #[api( |
5830e562 | 833 | input: { |
88356646 | 834 | properties: { |
0023cfa3 DM |
835 | |
836 | // Note: We cannot use TapeBackupJobSetup, because drive needs to be optional here | |
837 | //setup: { | |
838 | // type: TapeBackupJobSetup, | |
839 | // flatten: true, | |
840 | //}, | |
841 | ||
842 | store: { | |
843 | schema: DATASTORE_SCHEMA, | |
844 | }, | |
845 | pool: { | |
846 | schema: MEDIA_POOL_NAME_SCHEMA, | |
847 | }, | |
848 | drive: { | |
849 | schema: DRIVE_NAME_SCHEMA, | |
850 | optional: true, | |
851 | }, | |
852 | "eject-media": { | |
853 | description: "Eject media upon job completion.", | |
854 | type: bool, | |
855 | optional: true, | |
856 | }, | |
857 | "export-media-set": { | |
858 | description: "Export media set upon job completion.", | |
859 | type: bool, | |
860 | optional: true, | |
861 | }, | |
862 | "latest-only": { | |
863 | description: "Backup latest snapshots only.", | |
864 | type: bool, | |
865 | optional: true, | |
edb90f6a | 866 | }, |
097ccfe1 DC |
867 | "notify-user": { |
868 | optional: true, | |
869 | type: Userid, | |
870 | }, | |
efd2713a DC |
871 | groups: { |
872 | schema: GROUP_FILTER_LIST_SCHEMA, | |
873 | optional: true, | |
874 | }, | |
e384f16a DC |
875 | "force-media-set": { |
876 | description: "Ignore the allocation policy and start a new media-set.", | |
877 | optional: true, | |
878 | type: bool, | |
879 | default: false, | |
880 | }, | |
87f4be79 DM |
881 | "output-format": { |
882 | schema: OUTPUT_FORMAT, | |
883 | optional: true, | |
884 | }, | |
88356646 DM |
885 | }, |
886 | }, | |
887 | )] | |
888 | /// Backup datastore to tape media pool | |
9883b54c | 889 | async fn backup(mut param: Value) -> Result<(), Error> { |
88356646 | 890 | |
671c6a96 | 891 | let output_format = extract_output_format(&mut param); |
88356646 | 892 | |
1ce8e905 | 893 | let (config, _digest) = pbs_config::drive::config()?; |
9883b54c | 894 | |
85cdc4f3 | 895 | param["drive"] = extract_drive_name(&mut param, &config)?.into(); |
9883b54c | 896 | |
d4877712 | 897 | let client = connect_to_localhost()?; |
88356646 | 898 | |
87f4be79 DM |
899 | let result = client.post("api2/json/tape/backup", Some(param)).await?; |
900 | ||
d4877712 | 901 | view_task_result(&client, result, &output_format).await?; |
88356646 DM |
902 | |
903 | Ok(()) | |
904 | } | |
c297835b | 905 | |
b9b4b312 DM |
906 | #[api( |
907 | input: { | |
908 | properties: { | |
909 | store: { | |
4c4e5c2b | 910 | schema: DATASTORE_MAP_LIST_SCHEMA, |
b9b4b312 | 911 | }, |
9883b54c DM |
912 | drive: { |
913 | schema: DRIVE_NAME_SCHEMA, | |
914 | optional: true, | |
915 | }, | |
b9b4b312 DM |
916 | "media-set": { |
917 | description: "Media set UUID.", | |
918 | type: String, | |
919 | }, | |
5a5ee032 DC |
920 | "notify-user": { |
921 | type: Userid, | |
922 | optional: true, | |
923 | }, | |
0ecdaa0d DC |
924 | "snapshots": { |
925 | description: "List of snapshots.", | |
926 | type: Array, | |
927 | optional: true, | |
928 | items: { | |
929 | schema: TAPE_RESTORE_SNAPSHOT_SCHEMA, | |
930 | }, | |
931 | }, | |
e3613503 DC |
932 | owner: { |
933 | type: Authid, | |
934 | optional: true, | |
935 | }, | |
c297835b DM |
936 | "output-format": { |
937 | schema: OUTPUT_FORMAT, | |
938 | optional: true, | |
939 | }, | |
b9b4b312 DM |
940 | }, |
941 | }, | |
942 | )] | |
943 | /// Restore data from media-set | |
9883b54c | 944 | async fn restore(mut param: Value) -> Result<(), Error> { |
c297835b | 945 | |
671c6a96 | 946 | let output_format = extract_output_format(&mut param); |
b9b4b312 | 947 | |
1ce8e905 | 948 | let (config, _digest) = pbs_config::drive::config()?; |
9883b54c | 949 | |
85cdc4f3 | 950 | param["drive"] = extract_drive_name(&mut param, &config)?.into(); |
9883b54c | 951 | |
d4877712 | 952 | let client = connect_to_localhost()?; |
b9b4b312 | 953 | |
c297835b | 954 | let result = client.post("api2/json/tape/restore", Some(param)).await?; |
b9b4b312 | 955 | |
d4877712 | 956 | view_task_result(&client, result, &output_format).await?; |
b9b4b312 DM |
957 | |
958 | Ok(()) | |
959 | } | |
88356646 | 960 | |
b017bbc4 DM |
961 | #[api( |
962 | input: { | |
963 | properties: { | |
964 | drive: { | |
965 | schema: DRIVE_NAME_SCHEMA, | |
966 | optional: true, | |
967 | }, | |
968 | force: { | |
969 | description: "Force overriding existing index.", | |
970 | type: bool, | |
971 | optional: true, | |
972 | }, | |
c553407e DM |
973 | scan: { |
974 | description: "Re-read the whole tape to reconstruct the catalog instead of restoring saved versions.", | |
975 | type: bool, | |
976 | optional: true, | |
977 | }, | |
b017bbc4 DM |
978 | verbose: { |
979 | description: "Verbose mode - log all found chunks.", | |
980 | type: bool, | |
981 | optional: true, | |
982 | }, | |
983 | "output-format": { | |
984 | schema: OUTPUT_FORMAT, | |
985 | optional: true, | |
986 | }, | |
987 | }, | |
988 | }, | |
989 | )] | |
990 | /// Scan media and record content | |
85cdc4f3 | 991 | async fn catalog_media(mut param: Value) -> Result<(), Error> { |
c297835b | 992 | |
671c6a96 | 993 | let output_format = extract_output_format(&mut param); |
b017bbc4 | 994 | |
1ce8e905 | 995 | let (config, _digest) = pbs_config::drive::config()?; |
b017bbc4 | 996 | |
85cdc4f3 | 997 | let drive = extract_drive_name(&mut param, &config)?; |
b017bbc4 | 998 | |
d4877712 | 999 | let client = connect_to_localhost()?; |
b017bbc4 | 1000 | |
c297835b DM |
1001 | let path = format!("api2/json/tape/drive/{}/catalog", drive); |
1002 | let result = client.post(&path, Some(param)).await?; | |
b017bbc4 | 1003 | |
d4877712 | 1004 | view_task_result(&client, result, &output_format).await?; |
b017bbc4 DM |
1005 | |
1006 | Ok(()) | |
1007 | } | |
1008 | ||
e6604cf3 DM |
1009 | fn main() { |
1010 | ||
1011 | let cmd_def = CliCommandMap::new() | |
88356646 DM |
1012 | .insert( |
1013 | "backup", | |
1014 | CliCommand::new(&API_METHOD_BACKUP) | |
1015 | .arg_param(&["store", "pool"]) | |
5830e562 | 1016 | .completion_cb("drive", complete_drive_name) |
88356646 DM |
1017 | .completion_cb("store", complete_datastore_name) |
1018 | .completion_cb("pool", complete_pool_name) | |
efd2713a | 1019 | .completion_cb("groups", complete_datastore_group_filter) |
88356646 | 1020 | ) |
b9b4b312 DM |
1021 | .insert( |
1022 | "restore", | |
1023 | CliCommand::new(&API_METHOD_RESTORE) | |
0ecdaa0d | 1024 | .arg_param(&["media-set", "store", "snapshots"]) |
b9b4b312 DM |
1025 | .completion_cb("store", complete_datastore_name) |
1026 | .completion_cb("media-set", complete_media_set_uuid) | |
0ecdaa0d | 1027 | .completion_cb("snapshots", complete_media_set_snapshots) |
b9b4b312 | 1028 | ) |
bff7e3f3 DM |
1029 | .insert( |
1030 | "barcode-label", | |
1031 | CliCommand::new(&API_METHOD_BARCODE_LABEL_MEDIA) | |
1032 | .completion_cb("drive", complete_drive_name) | |
1033 | .completion_cb("pool", complete_pool_name) | |
1034 | ) | |
5fb694e8 DM |
1035 | .insert( |
1036 | "rewind", | |
1037 | CliCommand::new(&API_METHOD_REWIND) | |
1038 | .completion_cb("drive", complete_drive_name) | |
1039 | ) | |
ac461bd6 DM |
1040 | .insert( |
1041 | "scan", | |
1042 | CliCommand::new(&API_METHOD_DEBUG_SCAN) | |
1043 | .completion_cb("drive", complete_drive_name) | |
1044 | ) | |
cb80d900 DM |
1045 | .insert( |
1046 | "status", | |
1047 | CliCommand::new(&API_METHOD_STATUS) | |
1048 | .completion_cb("drive", complete_drive_name) | |
1049 | ) | |
ce955e16 DM |
1050 | .insert( |
1051 | "eod", | |
1052 | CliCommand::new(&API_METHOD_MOVE_TO_EOM) | |
1053 | .completion_cb("drive", complete_drive_name) | |
1054 | ) | |
583a68a4 | 1055 | .insert( |
e29f456e DM |
1056 | "format", |
1057 | CliCommand::new(&API_METHOD_FORMAT_MEDIA) | |
583a68a4 DM |
1058 | .completion_cb("drive", complete_drive_name) |
1059 | ) | |
0098b712 DM |
1060 | .insert( |
1061 | "eject", | |
1062 | CliCommand::new(&API_METHOD_EJECT_MEDIA) | |
1063 | .completion_cb("drive", complete_drive_name) | |
83abc749 DM |
1064 | ) |
1065 | .insert( | |
1066 | "inventory", | |
1067 | CliCommand::new(&API_METHOD_INVENTORY) | |
1068 | .completion_cb("drive", complete_drive_name) | |
0098b712 | 1069 | ) |
4606f343 DM |
1070 | .insert( |
1071 | "read-label", | |
1072 | CliCommand::new(&API_METHOD_READ_LABEL) | |
1073 | .completion_cb("drive", complete_drive_name) | |
1074 | ) | |
b017bbc4 DM |
1075 | .insert( |
1076 | "catalog", | |
1077 | CliCommand::new(&API_METHOD_CATALOG_MEDIA) | |
1078 | .completion_cb("drive", complete_drive_name) | |
1079 | ) | |
1e20f819 | 1080 | .insert( |
ee01737e DM |
1081 | "cartridge-memory", |
1082 | CliCommand::new(&API_METHOD_CARTRIDGE_MEMORY) | |
1e20f819 DM |
1083 | .completion_cb("drive", complete_drive_name) |
1084 | ) | |
5f34d69b DM |
1085 | .insert( |
1086 | "volume-statistics", | |
1087 | CliCommand::new(&API_METHOD_VOLUME_STATISTICS) | |
1088 | .completion_cb("drive", complete_drive_name) | |
1089 | ) | |
df69a4fc DM |
1090 | .insert( |
1091 | "clean", | |
1092 | CliCommand::new(&API_METHOD_CLEAN_DRIVE) | |
1093 | .completion_cb("drive", complete_drive_name) | |
1094 | ) | |
7bb720cb DM |
1095 | .insert( |
1096 | "label", | |
1097 | CliCommand::new(&API_METHOD_LABEL_MEDIA) | |
1098 | .completion_cb("drive", complete_drive_name) | |
1099 | .completion_cb("pool", complete_pool_name) | |
1100 | ||
1101 | ) | |
e6604cf3 DM |
1102 | .insert("changer", changer_commands()) |
1103 | .insert("drive", drive_commands()) | |
9700d537 | 1104 | .insert("pool", pool_commands()) |
fba0b774 | 1105 | .insert("media", media_commands()) |
d5a48b5c | 1106 | .insert("key", encryption_key_commands()) |
be327dbc | 1107 | .insert("backup-job", backup_job_commands()) |
e49f0c03 DM |
1108 | .insert( |
1109 | "load-media", | |
1110 | CliCommand::new(&API_METHOD_LOAD_MEDIA) | |
8446fbca | 1111 | .arg_param(&["label-text"]) |
e49f0c03 | 1112 | .completion_cb("drive", complete_drive_name) |
8446fbca | 1113 | .completion_cb("label-text", complete_media_label_text) |
e49f0c03 | 1114 | ) |
46a1863f DM |
1115 | .insert( |
1116 | "load-media-from-slot", | |
1117 | CliCommand::new(&API_METHOD_LOAD_MEDIA_FROM_SLOT) | |
b63f833d | 1118 | .arg_param(&["source-slot"]) |
46a1863f DM |
1119 | .completion_cb("drive", complete_drive_name) |
1120 | ) | |
1121 | .insert( | |
1122 | "unload", | |
1123 | CliCommand::new(&API_METHOD_UNLOAD_MEDIA) | |
1124 | .completion_cb("drive", complete_drive_name) | |
1125 | ) | |
483da89d DM |
1126 | .insert( |
1127 | "export-media", | |
1128 | CliCommand::new(&API_METHOD_EXPORT_MEDIA) | |
8446fbca | 1129 | .arg_param(&["label-text"]) |
483da89d | 1130 | .completion_cb("drive", complete_drive_name) |
8446fbca | 1131 | .completion_cb("label-text", complete_media_label_text) |
483da89d | 1132 | ) |
e6604cf3 DM |
1133 | ; |
1134 | ||
1135 | let mut rpcenv = CliEnvironment::new(); | |
1136 | rpcenv.set_auth_id(Some(String::from("root@pam"))); | |
1137 | ||
9a1b24b6 | 1138 | proxmox_async::runtime::main(run_async_cli_command(cmd_def, rpcenv)); |
e6604cf3 | 1139 | } |