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