]>
Commit | Line | Data |
---|---|---|
0c9209b0 DM |
1 | use std::sync::Arc; |
2 | ||
a65e3e4b DC |
3 | use anyhow::Error; |
4 | use serde_json::{json, Value}; | |
5 | ||
0c9209b0 DM |
6 | use proxmox::{ |
7 | api::{api, cli::*}, | |
8 | tools::fs::file_get_contents, | |
9 | }; | |
10 | ||
731eeef2 DM |
11 | use 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 | |
23 | use 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 |
38 | use 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. | |
60 | async fn list_snapshots(param: Value) -> Result<Value, Error> { | |
61 | ||
62 | let repo = extract_repository_from_value(¶m)?; | |
63 | ||
64 | let output_format = get_output_format(¶m); | |
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. | |
128 | async fn list_snapshot_files(param: Value) -> Result<Value, Error> { | |
129 | ||
130 | let repo = extract_repository_from_value(¶m)?; | |
131 | ||
132 | let path = tools::required_string_param(¶m, "snapshot")?; | |
133 | let snapshot: BackupDir = path.parse()?; | |
134 | ||
135 | let output_format = get_output_format(¶m); | |
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. | |
176 | async fn forget_snapshots(param: Value) -> Result<Value, Error> { | |
177 | ||
178 | let repo = extract_repository_from_value(¶m)?; | |
179 | ||
180 | let path = tools::required_string_param(¶m, "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. | |
229 | async fn upload_log(param: Value) -> Result<Value, Error> { | |
230 | ||
231 | let logfile = tools::required_string_param(¶m, "logfile")?; | |
232 | let repo = extract_repository_from_value(¶m)?; | |
233 | ||
234 | let snapshot = tools::required_string_param(¶m, "snapshot")?; | |
235 | let snapshot: BackupDir = snapshot.parse()?; | |
236 | ||
237 | let mut client = connect(&repo)?; | |
238 | ||
c6a7ea0a | 239 | let crypto = crypto_parameters(¶m)?; |
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 | |
292 | async fn show_notes(param: Value) -> Result<Value, Error> { | |
293 | let repo = extract_repository_from_value(¶m)?; | |
294 | let path = tools::required_string_param(¶m, "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(¶m); | |
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 | |
348 | async fn update_notes(param: Value) -> Result<Value, Error> { | |
349 | let repo = extract_repository_from_value(¶m)?; | |
350 | let path = tools::required_string_param(¶m, "snapshot")?; | |
351 | let notes = tools::required_string_param(¶m, "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 | ||
370 | fn 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 | ||
386 | pub 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 | } |