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