]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/proxmox_backup_client/snapshot.rs
proxmox_client_tools: move common key related functions to key_source.rs
[proxmox-backup.git] / src / bin / proxmox_backup_client / snapshot.rs
CommitLineData
0c9209b0
DM
1use std::sync::Arc;
2
a65e3e4b
DC
3use anyhow::Error;
4use serde_json::{json, Value};
5
0c9209b0
DM
6use proxmox::{
7 api::{api, cli::*},
8 tools::fs::file_get_contents,
9};
10
731eeef2
DM
11use proxmox_backup::{
12 tools,
13 api2::types::*,
14 backup::{
0c9209b0
DM
15 CryptMode,
16 CryptConfig,
17 DataBlob,
731eeef2 18 BackupGroup,
0c9209b0 19 decrypt_key,
731eeef2
DM
20 }
21};
a65e3e4b
DC
22
23use crate::{
731eeef2 24 REPO_URL_SCHEMA,
0c9209b0
DM
25 KEYFILE_SCHEMA,
26 KEYFD_SCHEMA,
731eeef2
DM
27 BackupDir,
28 api_datastore_list_snapshots,
29 complete_backup_snapshot,
30 complete_backup_group,
31 complete_repository,
32 connect,
c6a7ea0a 33 crypto_parameters,
731eeef2
DM
34 extract_repository_from_value,
35 record_repository,
a65e3e4b
DC
36};
37
ff8945fd
SR
38use crate::proxmox_client_tools::key_source::get_encryption_key_password;
39
731eeef2
DM
40#[api(
41 input: {
42 properties: {
43 repository: {
44 schema: REPO_URL_SCHEMA,
45 optional: true,
46 },
47 group: {
48 type: String,
49 description: "Backup group.",
50 optional: true,
51 },
52 "output-format": {
53 schema: OUTPUT_FORMAT,
54 optional: true,
55 },
56 }
57 }
58)]
59/// List backup snapshots.
60async fn list_snapshots(param: Value) -> Result<Value, Error> {
61
62 let repo = extract_repository_from_value(&param)?;
63
64 let output_format = get_output_format(&param);
65
66 let client = connect(&repo)?;
67
68 let group: Option<BackupGroup> = if let Some(path) = param["group"].as_str() {
69 Some(path.parse()?)
70 } else {
71 None
72 };
73
74 let mut data = api_datastore_list_snapshots(&client, repo.store(), group).await?;
75
76 record_repository(&repo);
77
78 let render_snapshot_path = |_v: &Value, record: &Value| -> Result<String, Error> {
79 let item: SnapshotListItem = serde_json::from_value(record.to_owned())?;
80 let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.backup_time)?;
81 Ok(snapshot.relative_path().to_str().unwrap().to_owned())
82 };
83
84 let render_files = |_v: &Value, record: &Value| -> Result<String, Error> {
85 let item: SnapshotListItem = serde_json::from_value(record.to_owned())?;
86 let mut filenames = Vec::new();
87 for file in &item.files {
88 filenames.push(file.filename.to_string());
89 }
90 Ok(tools::format::render_backup_file_list(&filenames[..]))
91 };
92
93 let options = default_table_format_options()
94 .sortby("backup-type", false)
95 .sortby("backup-id", false)
96 .sortby("backup-time", false)
97 .column(ColumnConfig::new("backup-id").renderer(render_snapshot_path).header("snapshot"))
98 .column(ColumnConfig::new("size").renderer(tools::format::render_bytes_human_readable))
99 .column(ColumnConfig::new("files").renderer(render_files))
100 ;
101
b2362a12 102 let return_type = &proxmox_backup::api2::admin::datastore::API_METHOD_LIST_SNAPSHOTS.returns;
731eeef2 103
b2362a12 104 format_and_print_result_full(&mut data, return_type, &output_format, &options);
731eeef2
DM
105
106 Ok(Value::Null)
107}
108
61205f00
DM
109#[api(
110 input: {
111 properties: {
112 repository: {
113 schema: REPO_URL_SCHEMA,
114 optional: true,
115 },
116 snapshot: {
117 type: String,
118 description: "Snapshot path.",
119 },
120 "output-format": {
121 schema: OUTPUT_FORMAT,
122 optional: true,
123 },
124 }
125 }
126)]
127/// List snapshot files.
128async fn list_snapshot_files(param: Value) -> Result<Value, Error> {
129
130 let repo = extract_repository_from_value(&param)?;
131
132 let path = tools::required_string_param(&param, "snapshot")?;
133 let snapshot: BackupDir = path.parse()?;
134
135 let output_format = get_output_format(&param);
136
137 let client = connect(&repo)?;
138
139 let path = format!("api2/json/admin/datastore/{}/files", repo.store());
140
141 let mut result = client.get(&path, Some(json!({
142 "backup-type": snapshot.group().backup_type(),
143 "backup-id": snapshot.group().backup_id(),
144 "backup-time": snapshot.backup_time(),
145 }))).await?;
146
147 record_repository(&repo);
148
b2362a12
WB
149 let return_type =
150 &proxmox_backup::api2::admin::datastore::API_METHOD_LIST_SNAPSHOT_FILES.returns;
61205f00
DM
151
152 let mut data: Value = result["data"].take();
153
154 let options = default_table_format_options();
155
b2362a12 156 format_and_print_result_full(&mut data, return_type, &output_format, &options);
61205f00
DM
157
158 Ok(Value::Null)
159}
160
edebd523
DM
161#[api(
162 input: {
163 properties: {
164 repository: {
165 schema: REPO_URL_SCHEMA,
166 optional: true,
167 },
168 snapshot: {
169 type: String,
170 description: "Snapshot path.",
171 },
172 }
173 }
174)]
175/// Forget (remove) backup snapshots.
176async fn forget_snapshots(param: Value) -> Result<Value, Error> {
177
178 let repo = extract_repository_from_value(&param)?;
179
180 let path = tools::required_string_param(&param, "snapshot")?;
181 let snapshot: BackupDir = path.parse()?;
182
183 let mut client = connect(&repo)?;
184
185 let path = format!("api2/json/admin/datastore/{}/snapshots", repo.store());
186
187 let result = client.delete(&path, Some(json!({
188 "backup-type": snapshot.group().backup_type(),
189 "backup-id": snapshot.group().backup_id(),
190 "backup-time": snapshot.backup_time(),
191 }))).await?;
192
193 record_repository(&repo);
194
195 Ok(result)
196}
197
0c9209b0
DM
198#[api(
199 input: {
200 properties: {
201 repository: {
202 schema: REPO_URL_SCHEMA,
203 optional: true,
204 },
205 snapshot: {
206 type: String,
207 description: "Group/Snapshot path.",
208 },
209 logfile: {
210 type: String,
211 description: "The path to the log file you want to upload.",
212 },
213 keyfile: {
214 schema: KEYFILE_SCHEMA,
215 optional: true,
216 },
217 "keyfd": {
218 schema: KEYFD_SCHEMA,
219 optional: true,
220 },
221 "crypt-mode": {
222 type: CryptMode,
223 optional: true,
224 },
225 }
226 }
227)]
228/// Upload backup log file.
229async fn upload_log(param: Value) -> Result<Value, Error> {
230
231 let logfile = tools::required_string_param(&param, "logfile")?;
232 let repo = extract_repository_from_value(&param)?;
233
234 let snapshot = tools::required_string_param(&param, "snapshot")?;
235 let snapshot: BackupDir = snapshot.parse()?;
236
237 let mut client = connect(&repo)?;
238
c6a7ea0a 239 let crypto = crypto_parameters(&param)?;
0c9209b0 240
c6a7ea0a 241 let crypt_config = match crypto.enc_key {
0c9209b0
DM
242 None => None,
243 Some(key) => {
ff8945fd 244 let (key, _created, _) = decrypt_key(&key.key, &get_encryption_key_password)?;
0c9209b0
DM
245 let crypt_config = CryptConfig::new(key)?;
246 Some(Arc::new(crypt_config))
247 }
248 };
249
250 let data = file_get_contents(logfile)?;
251
252 // fixme: howto sign log?
c6a7ea0a 253 let blob = match crypto.mode {
0c9209b0
DM
254 CryptMode::None | CryptMode::SignOnly => DataBlob::encode(&data, None, true)?,
255 CryptMode::Encrypt => DataBlob::encode(&data, crypt_config.as_ref().map(Arc::as_ref), true)?,
256 };
257
258 let raw_data = blob.into_inner();
259
260 let path = format!("api2/json/admin/datastore/{}/upload-backup-log", repo.store());
261
262 let args = json!({
263 "backup-type": snapshot.group().backup_type(),
264 "backup-id": snapshot.group().backup_id(),
265 "backup-time": snapshot.backup_time(),
266 });
267
268 let body = hyper::Body::from(raw_data);
269
270 client.upload("application/octet-stream", body, &path, Some(args)).await
271}
272
a65e3e4b
DC
273#[api(
274 input: {
275 properties: {
276 repository: {
277 schema: REPO_URL_SCHEMA,
278 optional: true,
279 },
280 snapshot: {
281 type: String,
282 description: "Snapshot path.",
283 },
284 "output-format": {
285 schema: OUTPUT_FORMAT,
286 optional: true,
287 },
288 }
289 }
290)]
291/// Show notes
292async fn show_notes(param: Value) -> Result<Value, Error> {
293 let repo = extract_repository_from_value(&param)?;
294 let path = tools::required_string_param(&param, "snapshot")?;
295
296 let snapshot: BackupDir = path.parse()?;
297 let client = connect(&repo)?;
298
299 let path = format!("api2/json/admin/datastore/{}/notes", repo.store());
300
301 let args = json!({
302 "backup-type": snapshot.group().backup_type(),
303 "backup-id": snapshot.group().backup_id(),
304 "backup-time": snapshot.backup_time(),
305 });
306
307 let output_format = get_output_format(&param);
308
309 let mut result = client.get(&path, Some(args)).await?;
310
311 let notes = result["data"].take();
312
313 if output_format == "text" {
314 if let Some(notes) = notes.as_str() {
315 println!("{}", notes);
316 }
317 } else {
318 format_and_print_result(
319 &json!({
320 "notes": notes,
321 }),
322 &output_format,
323 );
324 }
325
326 Ok(Value::Null)
327}
328
329#[api(
330 input: {
331 properties: {
332 repository: {
333 schema: REPO_URL_SCHEMA,
334 optional: true,
335 },
336 snapshot: {
337 type: String,
338 description: "Snapshot path.",
339 },
340 notes: {
341 type: String,
342 description: "The Notes.",
343 },
344 }
345 }
346)]
347/// Update Notes
348async fn update_notes(param: Value) -> Result<Value, Error> {
349 let repo = extract_repository_from_value(&param)?;
350 let path = tools::required_string_param(&param, "snapshot")?;
351 let notes = tools::required_string_param(&param, "notes")?;
352
353 let snapshot: BackupDir = path.parse()?;
354 let mut client = connect(&repo)?;
355
356 let path = format!("api2/json/admin/datastore/{}/notes", repo.store());
357
358 let args = json!({
359 "backup-type": snapshot.group().backup_type(),
360 "backup-id": snapshot.group().backup_id(),
361 "backup-time": snapshot.backup_time(),
362 "notes": notes,
363 });
364
365 client.put(&path, Some(args)).await?;
366
367 Ok(Value::Null)
368}
369
370fn notes_cli() -> CliCommandMap {
371 CliCommandMap::new()
372 .insert(
373 "show",
374 CliCommand::new(&API_METHOD_SHOW_NOTES)
375 .arg_param(&["snapshot"])
376 .completion_cb("snapshot", complete_backup_snapshot),
377 )
378 .insert(
379 "update",
380 CliCommand::new(&API_METHOD_UPDATE_NOTES)
381 .arg_param(&["snapshot", "notes"])
382 .completion_cb("snapshot", complete_backup_snapshot),
383 )
384}
385
386pub fn snapshot_mgtm_cli() -> CliCommandMap {
731eeef2
DM
387 CliCommandMap::new()
388 .insert("notes", notes_cli())
389 .insert(
61205f00
DM
390 "list",
391 CliCommand::new(&API_METHOD_LIST_SNAPSHOTS)
731eeef2
DM
392 .arg_param(&["group"])
393 .completion_cb("group", complete_backup_group)
394 .completion_cb("repository", complete_repository)
395 )
61205f00
DM
396 .insert(
397 "files",
398 CliCommand::new(&API_METHOD_LIST_SNAPSHOT_FILES)
399 .arg_param(&["snapshot"])
400 .completion_cb("repository", complete_repository)
401 .completion_cb("snapshot", complete_backup_snapshot)
402 )
edebd523
DM
403 .insert(
404 "forget",
405 CliCommand::new(&API_METHOD_FORGET_SNAPSHOTS)
406 .arg_param(&["snapshot"])
407 .completion_cb("repository", complete_repository)
408 .completion_cb("snapshot", complete_backup_snapshot)
409 )
0c9209b0
DM
410 .insert(
411 "upload-log",
412 CliCommand::new(&API_METHOD_UPLOAD_LOG)
413 .arg_param(&["snapshot", "logfile"])
414 .completion_cb("snapshot", complete_backup_snapshot)
415 .completion_cb("logfile", tools::complete_file_name)
416 .completion_cb("keyfile", tools::complete_file_name)
417 .completion_cb("repository", complete_repository)
418 )
a65e3e4b 419}