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