]> git.proxmox.com Git - proxmox-backup.git/blob - src/bin/proxmox_backup_client/snapshot.rs
adaptions for proxmox 0.9 and proxmox-api-macro 0.3
[proxmox-backup.git] / src / bin / proxmox_backup_client / snapshot.rs
1 use std::sync::Arc;
2
3 use anyhow::Error;
4 use serde_json::{json, Value};
5
6 use proxmox::{
7 api::{api, cli::*},
8 tools::fs::file_get_contents,
9 };
10
11 use proxmox_backup::{
12 tools,
13 api2::types::*,
14 backup::{
15 CryptMode,
16 CryptConfig,
17 DataBlob,
18 BackupGroup,
19 decrypt_key,
20 }
21 };
22
23 use crate::{
24 REPO_URL_SCHEMA,
25 KEYFILE_SCHEMA,
26 KEYFD_SCHEMA,
27 BackupDir,
28 api_datastore_list_snapshots,
29 complete_backup_snapshot,
30 complete_backup_group,
31 complete_repository,
32 connect,
33 extract_repository_from_value,
34 record_repository,
35 keyfile_parameters,
36 };
37
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(&param)?;
61
62 let output_format = get_output_format(&param);
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
100 let return_type = &proxmox_backup::api2::admin::datastore::API_METHOD_LIST_SNAPSHOTS.returns;
101
102 format_and_print_result_full(&mut data, return_type, &output_format, &options);
103
104 Ok(Value::Null)
105 }
106
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(&param)?;
129
130 let path = tools::required_string_param(&param, "snapshot")?;
131 let snapshot: BackupDir = path.parse()?;
132
133 let output_format = get_output_format(&param);
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
147 let return_type =
148 &proxmox_backup::api2::admin::datastore::API_METHOD_LIST_SNAPSHOT_FILES.returns;
149
150 let mut data: Value = result["data"].take();
151
152 let options = default_table_format_options();
153
154 format_and_print_result_full(&mut data, return_type, &output_format, &options);
155
156 Ok(Value::Null)
157 }
158
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(&param)?;
177
178 let path = tools::required_string_param(&param, "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
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(&param, "logfile")?;
230 let repo = extract_repository_from_value(&param)?;
231
232 let snapshot = tools::required_string_param(&param, "snapshot")?;
233 let snapshot: BackupDir = snapshot.parse()?;
234
235 let mut client = connect(&repo)?;
236
237 let (keydata, crypt_mode) = keyfile_parameters(&param)?;
238
239 let crypt_config = match keydata {
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?
251 let blob = match crypt_mode {
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
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(&param)?;
292 let path = tools::required_string_param(&param, "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(&param);
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(&param)?;
348 let path = tools::required_string_param(&param, "snapshot")?;
349 let notes = tools::required_string_param(&param, "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 {
385 CliCommandMap::new()
386 .insert("notes", notes_cli())
387 .insert(
388 "list",
389 CliCommand::new(&API_METHOD_LIST_SNAPSHOTS)
390 .arg_param(&["group"])
391 .completion_cb("group", complete_backup_group)
392 .completion_cb("repository", complete_repository)
393 )
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 )
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 )
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 )
417 }