]> git.proxmox.com Git - proxmox-backup.git/blame - proxmox-backup-client/src/snapshot.rs
make datastore BackupGroup/Dir ctors private
[proxmox-backup.git] / proxmox-backup-client / src / snapshot.rs
CommitLineData
0c9209b0
DM
1use std::sync::Arc;
2
a65e3e4b
DC
3use anyhow::Error;
4use serde_json::{json, Value};
5
6ef1b649
WB
6use proxmox_router::cli::*;
7use proxmox_schema::api;
f9a5beaa 8use proxmox_sys::fs::file_get_contents;
0c9209b0 9
db87d93e 10use pbs_api_types::{BackupGroup, CryptMode, SnapshotListItem};
2b7f8dd5 11use pbs_client::tools::key_source::get_encryption_key_password;
f9a5beaa 12use pbs_config::key_config::decrypt_key;
db87d93e 13use pbs_datastore::DataBlob;
f9a5beaa 14use pbs_tools::crypt_config::CryptConfig;
3c8c2827 15use pbs_tools::json::required_string_param;
2b7f8dd5 16
a65e3e4b 17use crate::{
f9a5beaa
TL
18 api_datastore_list_snapshots, complete_backup_group, complete_backup_snapshot,
19 complete_repository, connect, crypto_parameters, extract_repository_from_value,
20 record_repository, BackupDir, KEYFD_SCHEMA, KEYFILE_SCHEMA, REPO_URL_SCHEMA,
a65e3e4b
DC
21};
22
731eeef2
DM
23#[api(
24 input: {
25 properties: {
26 repository: {
27 schema: REPO_URL_SCHEMA,
28 optional: true,
29 },
30 group: {
31 type: String,
32 description: "Backup group.",
33 optional: true,
34 },
35 "output-format": {
36 schema: OUTPUT_FORMAT,
37 optional: true,
38 },
39 }
40 }
41)]
42/// List backup snapshots.
43async fn list_snapshots(param: Value) -> Result<Value, Error> {
731eeef2
DM
44 let repo = extract_repository_from_value(&param)?;
45
46 let output_format = get_output_format(&param);
47
48 let client = connect(&repo)?;
49
50 let group: Option<BackupGroup> = if let Some(path) = param["group"].as_str() {
51 Some(path.parse()?)
52 } else {
53 None
54 };
55
56 let mut data = api_datastore_list_snapshots(&client, repo.store(), group).await?;
57
58 record_repository(&repo);
59
60 let render_snapshot_path = |_v: &Value, record: &Value| -> Result<String, Error> {
61 let item: SnapshotListItem = serde_json::from_value(record.to_owned())?;
db87d93e 62 Ok(item.backup.to_string())
731eeef2
DM
63 };
64
65 let render_files = |_v: &Value, record: &Value| -> Result<String, Error> {
66 let item: SnapshotListItem = serde_json::from_value(record.to_owned())?;
67 let mut filenames = Vec::new();
68 for file in &item.files {
69 filenames.push(file.filename.to_string());
70 }
770a36e5 71 Ok(pbs_tools::format::render_backup_file_list(&filenames[..]))
731eeef2
DM
72 };
73
74 let options = default_table_format_options()
75 .sortby("backup-type", false)
76 .sortby("backup-id", false)
77 .sortby("backup-time", false)
f9a5beaa
TL
78 .column(
79 ColumnConfig::new("backup-id")
80 .renderer(render_snapshot_path)
81 .header("snapshot"),
82 )
770a36e5 83 .column(ColumnConfig::new("size").renderer(pbs_tools::format::render_bytes_human_readable))
f9a5beaa 84 .column(ColumnConfig::new("files").renderer(render_files));
731eeef2 85
e351ac78 86 let return_type = &pbs_api_types::ADMIN_DATASTORE_LIST_SNAPSHOTS_RETURN_TYPE;
731eeef2 87
b2362a12 88 format_and_print_result_full(&mut data, return_type, &output_format, &options);
731eeef2
DM
89
90 Ok(Value::Null)
91}
92
61205f00
DM
93#[api(
94 input: {
95 properties: {
96 repository: {
97 schema: REPO_URL_SCHEMA,
98 optional: true,
99 },
100 snapshot: {
101 type: String,
102 description: "Snapshot path.",
103 },
104 "output-format": {
105 schema: OUTPUT_FORMAT,
106 optional: true,
107 },
108 }
109 }
110)]
111/// List snapshot files.
112async fn list_snapshot_files(param: Value) -> Result<Value, Error> {
61205f00
DM
113 let repo = extract_repository_from_value(&param)?;
114
3c8c2827 115 let path = required_string_param(&param, "snapshot")?;
61205f00
DM
116 let snapshot: BackupDir = path.parse()?;
117
118 let output_format = get_output_format(&param);
119
120 let client = connect(&repo)?;
121
122 let path = format!("api2/json/admin/datastore/{}/files", repo.store());
123
f9a5beaa
TL
124 let mut result = client
125 .get(
126 &path,
127 Some(json!({
db87d93e
WB
128 "backup-type": snapshot.group.ty,
129 "backup-id": snapshot.group.id,
130 "backup-time": snapshot.time,
f9a5beaa
TL
131 })),
132 )
133 .await?;
61205f00
DM
134
135 record_repository(&repo);
136
e351ac78 137 let return_type = &pbs_api_types::ADMIN_DATASTORE_LIST_SNAPSHOT_FILES_RETURN_TYPE;
61205f00
DM
138
139 let mut data: Value = result["data"].take();
140
141 let options = default_table_format_options();
142
b2362a12 143 format_and_print_result_full(&mut data, return_type, &output_format, &options);
61205f00
DM
144
145 Ok(Value::Null)
146}
147
edebd523
DM
148#[api(
149 input: {
150 properties: {
151 repository: {
152 schema: REPO_URL_SCHEMA,
153 optional: true,
154 },
155 snapshot: {
156 type: String,
157 description: "Snapshot path.",
158 },
159 }
160 }
161)]
162/// Forget (remove) backup snapshots.
163async fn forget_snapshots(param: Value) -> Result<Value, Error> {
edebd523
DM
164 let repo = extract_repository_from_value(&param)?;
165
3c8c2827 166 let path = required_string_param(&param, "snapshot")?;
edebd523
DM
167 let snapshot: BackupDir = path.parse()?;
168
d4877712 169 let client = connect(&repo)?;
edebd523
DM
170
171 let path = format!("api2/json/admin/datastore/{}/snapshots", repo.store());
172
f9a5beaa
TL
173 let result = client
174 .delete(
175 &path,
176 Some(json!({
db87d93e
WB
177 "backup-type": snapshot.group.ty,
178 "backup-id": snapshot.group.id,
179 "backup-time": snapshot.time,
f9a5beaa
TL
180 })),
181 )
182 .await?;
edebd523
DM
183
184 record_repository(&repo);
185
186 Ok(result)
187}
188
0c9209b0
DM
189#[api(
190 input: {
191 properties: {
192 repository: {
193 schema: REPO_URL_SCHEMA,
194 optional: true,
195 },
196 snapshot: {
197 type: String,
198 description: "Group/Snapshot path.",
199 },
200 logfile: {
201 type: String,
202 description: "The path to the log file you want to upload.",
203 },
204 keyfile: {
205 schema: KEYFILE_SCHEMA,
206 optional: true,
207 },
208 "keyfd": {
209 schema: KEYFD_SCHEMA,
210 optional: true,
211 },
212 "crypt-mode": {
213 type: CryptMode,
214 optional: true,
215 },
216 }
217 }
218)]
219/// Upload backup log file.
220async fn upload_log(param: Value) -> Result<Value, Error> {
3c8c2827 221 let logfile = required_string_param(&param, "logfile")?;
0c9209b0
DM
222 let repo = extract_repository_from_value(&param)?;
223
3c8c2827 224 let snapshot = required_string_param(&param, "snapshot")?;
0c9209b0
DM
225 let snapshot: BackupDir = snapshot.parse()?;
226
d4877712 227 let client = connect(&repo)?;
0c9209b0 228
c6a7ea0a 229 let crypto = crypto_parameters(&param)?;
0c9209b0 230
c6a7ea0a 231 let crypt_config = match crypto.enc_key {
0c9209b0
DM
232 None => None,
233 Some(key) => {
ff8945fd 234 let (key, _created, _) = decrypt_key(&key.key, &get_encryption_key_password)?;
0c9209b0
DM
235 let crypt_config = CryptConfig::new(key)?;
236 Some(Arc::new(crypt_config))
237 }
238 };
239
240 let data = file_get_contents(logfile)?;
241
242 // fixme: howto sign log?
c6a7ea0a 243 let blob = match crypto.mode {
0c9209b0 244 CryptMode::None | CryptMode::SignOnly => DataBlob::encode(&data, None, true)?,
f9a5beaa
TL
245 CryptMode::Encrypt => {
246 DataBlob::encode(&data, crypt_config.as_ref().map(Arc::as_ref), true)?
247 }
0c9209b0
DM
248 };
249
250 let raw_data = blob.into_inner();
251
f9a5beaa
TL
252 let path = format!(
253 "api2/json/admin/datastore/{}/upload-backup-log",
254 repo.store()
255 );
0c9209b0 256
db87d93e 257 let args = serde_json::to_value(&snapshot)?;
0c9209b0
DM
258 let body = hyper::Body::from(raw_data);
259
f9a5beaa
TL
260 client
261 .upload("application/octet-stream", body, &path, Some(args))
262 .await
0c9209b0
DM
263}
264
a65e3e4b
DC
265#[api(
266 input: {
267 properties: {
268 repository: {
269 schema: REPO_URL_SCHEMA,
270 optional: true,
271 },
272 snapshot: {
273 type: String,
274 description: "Snapshot path.",
275 },
276 "output-format": {
277 schema: OUTPUT_FORMAT,
278 optional: true,
279 },
280 }
281 }
282)]
283/// Show notes
284async fn show_notes(param: Value) -> Result<Value, Error> {
285 let repo = extract_repository_from_value(&param)?;
3c8c2827 286 let path = required_string_param(&param, "snapshot")?;
a65e3e4b
DC
287
288 let snapshot: BackupDir = path.parse()?;
289 let client = connect(&repo)?;
290
291 let path = format!("api2/json/admin/datastore/{}/notes", repo.store());
292
293 let args = json!({
db87d93e
WB
294 "backup-type": snapshot.group.ty,
295 "backup-id": snapshot.group.id,
296 "backup-time": snapshot.time,
a65e3e4b
DC
297 });
298
299 let output_format = get_output_format(&param);
300
301 let mut result = client.get(&path, Some(args)).await?;
302
303 let notes = result["data"].take();
304
305 if output_format == "text" {
306 if let Some(notes) = notes.as_str() {
307 println!("{}", notes);
308 }
309 } else {
310 format_and_print_result(
311 &json!({
312 "notes": notes,
313 }),
314 &output_format,
315 );
316 }
317
318 Ok(Value::Null)
319}
320
321#[api(
322 input: {
323 properties: {
324 repository: {
325 schema: REPO_URL_SCHEMA,
326 optional: true,
327 },
328 snapshot: {
329 type: String,
330 description: "Snapshot path.",
331 },
332 notes: {
333 type: String,
334 description: "The Notes.",
335 },
336 }
337 }
338)]
339/// Update Notes
340async fn update_notes(param: Value) -> Result<Value, Error> {
341 let repo = extract_repository_from_value(&param)?;
3c8c2827
WB
342 let path = required_string_param(&param, "snapshot")?;
343 let notes = required_string_param(&param, "notes")?;
a65e3e4b
DC
344
345 let snapshot: BackupDir = path.parse()?;
d4877712 346 let client = connect(&repo)?;
a65e3e4b
DC
347
348 let path = format!("api2/json/admin/datastore/{}/notes", repo.store());
349
350 let args = json!({
db87d93e
WB
351 "backup-type": snapshot.group.ty,
352 "backup-id": snapshot.group.id,
353 "backup-time": snapshot.time,
a65e3e4b
DC
354 "notes": notes,
355 });
356
357 client.put(&path, Some(args)).await?;
358
359 Ok(Value::Null)
360}
361
87e17fb4
DC
362#[api(
363 input: {
364 properties: {
365 repository: {
366 schema: REPO_URL_SCHEMA,
367 optional: true,
368 },
369 snapshot: {
370 type: String,
371 description: "Snapshot path.",
372 },
373 "output-format": {
374 schema: OUTPUT_FORMAT,
375 optional: true,
376 },
377 }
378 }
379)]
380/// Show protection status of the specified snapshot
9b5ecbe2 381async fn show_protection(param: Value) -> Result<(), Error> {
87e17fb4
DC
382 let repo = extract_repository_from_value(&param)?;
383 let path = required_string_param(&param, "snapshot")?;
384
385 let snapshot: BackupDir = path.parse()?;
386 let client = connect(&repo)?;
387
388 let path = format!("api2/json/admin/datastore/{}/protected", repo.store());
389
390 let args = json!({
db87d93e
WB
391 "backup-type": snapshot.group.ty,
392 "backup-id": snapshot.group.id,
393 "backup-time": snapshot.time,
87e17fb4
DC
394 });
395
396 let output_format = get_output_format(&param);
397
398 let mut result = client.get(&path, Some(args)).await?;
399
400 let protected = result["data"].take();
401
402 if output_format == "text" {
403 if let Some(protected) = protected.as_bool() {
404 println!("{}", protected);
405 }
406 } else {
407 format_and_print_result(
408 &json!({
409 "protected": protected,
410 }),
411 &output_format,
412 );
413 }
414
9b5ecbe2 415 Ok(())
87e17fb4
DC
416}
417
418#[api(
419 input: {
420 properties: {
421 repository: {
422 schema: REPO_URL_SCHEMA,
423 optional: true,
424 },
425 snapshot: {
426 type: String,
427 description: "Snapshot path.",
428 },
429 protected: {
430 type: bool,
431 description: "The protection status.",
432 },
433 }
434 }
435)]
436/// Update Protection Status of a snapshot
9b5ecbe2 437async fn update_protection(protected: bool, param: Value) -> Result<(), Error> {
87e17fb4
DC
438 let repo = extract_repository_from_value(&param)?;
439 let path = required_string_param(&param, "snapshot")?;
440
441 let snapshot: BackupDir = path.parse()?;
d4877712 442 let client = connect(&repo)?;
87e17fb4
DC
443
444 let path = format!("api2/json/admin/datastore/{}/protected", repo.store());
445
446 let args = json!({
db87d93e
WB
447 "backup-type": snapshot.group.ty,
448 "backup-id": snapshot.group.id,
449 "backup-time": snapshot.time,
87e17fb4
DC
450 "protected": protected,
451 });
452
453 client.put(&path, Some(args)).await?;
454
9b5ecbe2 455 Ok(())
87e17fb4
DC
456}
457
458fn protected_cli() -> CliCommandMap {
459 CliCommandMap::new()
460 .insert(
461 "show",
462 CliCommand::new(&API_METHOD_SHOW_PROTECTION)
463 .arg_param(&["snapshot"])
464 .completion_cb("snapshot", complete_backup_snapshot),
465 )
466 .insert(
467 "update",
468 CliCommand::new(&API_METHOD_UPDATE_PROTECTION)
469 .arg_param(&["snapshot", "protected"])
470 .completion_cb("snapshot", complete_backup_snapshot),
471 )
472}
473
a65e3e4b
DC
474fn notes_cli() -> CliCommandMap {
475 CliCommandMap::new()
476 .insert(
477 "show",
478 CliCommand::new(&API_METHOD_SHOW_NOTES)
479 .arg_param(&["snapshot"])
480 .completion_cb("snapshot", complete_backup_snapshot),
481 )
482 .insert(
483 "update",
484 CliCommand::new(&API_METHOD_UPDATE_NOTES)
485 .arg_param(&["snapshot", "notes"])
486 .completion_cb("snapshot", complete_backup_snapshot),
487 )
488}
489
490pub fn snapshot_mgtm_cli() -> CliCommandMap {
731eeef2
DM
491 CliCommandMap::new()
492 .insert("notes", notes_cli())
87e17fb4 493 .insert("protected", protected_cli())
731eeef2 494 .insert(
61205f00
DM
495 "list",
496 CliCommand::new(&API_METHOD_LIST_SNAPSHOTS)
731eeef2
DM
497 .arg_param(&["group"])
498 .completion_cb("group", complete_backup_group)
f9a5beaa 499 .completion_cb("repository", complete_repository),
731eeef2 500 )
61205f00
DM
501 .insert(
502 "files",
503 CliCommand::new(&API_METHOD_LIST_SNAPSHOT_FILES)
504 .arg_param(&["snapshot"])
505 .completion_cb("repository", complete_repository)
f9a5beaa 506 .completion_cb("snapshot", complete_backup_snapshot),
61205f00 507 )
edebd523
DM
508 .insert(
509 "forget",
510 CliCommand::new(&API_METHOD_FORGET_SNAPSHOTS)
511 .arg_param(&["snapshot"])
512 .completion_cb("repository", complete_repository)
f9a5beaa 513 .completion_cb("snapshot", complete_backup_snapshot),
edebd523 514 )
0c9209b0
DM
515 .insert(
516 "upload-log",
517 CliCommand::new(&API_METHOD_UPLOAD_LOG)
518 .arg_param(&["snapshot", "logfile"])
519 .completion_cb("snapshot", complete_backup_snapshot)
b3f279e2
DM
520 .completion_cb("logfile", complete_file_name)
521 .completion_cb("keyfile", complete_file_name)
f9a5beaa 522 .completion_cb("repository", complete_repository),
0c9209b0 523 )
a65e3e4b 524}