4 use serde_json
::{json, Value}
;
6 use proxmox_router
::cli
::*;
7 use proxmox_schema
::api
;
8 use proxmox_sys
::fs
::file_get_contents
;
10 use pbs_api_types
::{BackupGroup, BackupNamespace, CryptMode, SnapshotListItem}
;
11 use pbs_client
::tools
::key_source
::get_encryption_key_password
;
12 use pbs_datastore
::DataBlob
;
13 use pbs_key_config
::decrypt_key
;
14 use pbs_tools
::crypt_config
::CryptConfig
;
15 use pbs_tools
::json
::required_string_param
;
18 api_datastore_list_snapshots
, complete_backup_group
, complete_backup_snapshot
,
19 complete_namespace
, complete_repository
, connect
, crypto_parameters
,
20 extract_repository_from_value
, optional_ns_param
, record_repository
, BackupDir
, KEYFD_SCHEMA
,
21 KEYFILE_SCHEMA
, REPO_URL_SCHEMA
,
24 fn snapshot_args(ns
: &BackupNamespace
, snapshot
: &BackupDir
) -> Result
<Value
, Error
> {
25 let mut args
= serde_json
::to_value(snapshot
)?
;
27 args
["ns"] = serde_json
::to_value(ns
)?
;
36 schema
: REPO_URL_SCHEMA
,
40 type: BackupNamespace
,
45 description
: "Backup group.",
49 schema
: OUTPUT_FORMAT
,
55 /// List backup snapshots.
56 async
fn list_snapshots(param
: Value
) -> Result
<Value
, Error
> {
57 let repo
= extract_repository_from_value(¶m
)?
;
59 let output_format
= get_output_format(¶m
);
61 let client
= connect(&repo
)?
;
63 let group
: Option
<BackupGroup
> = param
["group"]
65 .map(|group
| group
.parse())
68 let backup_ns
= optional_ns_param(¶m
)?
;
71 api_datastore_list_snapshots(&client
, repo
.store(), &backup_ns
, group
.as_ref()).await?
;
73 record_repository(&repo
);
75 let render_snapshot_path
= |_v
: &Value
, record
: &Value
| -> Result
<String
, Error
> {
76 let item
: SnapshotListItem
= serde_json
::from_value(record
.to_owned())?
;
77 Ok(item
.backup
.to_string())
80 let render_files
= |_v
: &Value
, record
: &Value
| -> Result
<String
, Error
> {
81 let item
: SnapshotListItem
= serde_json
::from_value(record
.to_owned())?
;
82 let mut filenames
= Vec
::new();
83 for file
in &item
.files
{
84 filenames
.push(file
.filename
.to_string());
86 Ok(pbs_tools
::format
::render_backup_file_list(&filenames
[..]))
89 let options
= default_table_format_options()
90 .sortby("backup-type", false)
91 .sortby("backup-id", false)
92 .sortby("backup-time", false)
94 ColumnConfig
::new("backup-id")
95 .renderer(render_snapshot_path
)
98 .column(ColumnConfig
::new("size").renderer(pbs_tools
::format
::render_bytes_human_readable
))
99 .column(ColumnConfig
::new("files").renderer(render_files
));
101 let return_type
= &pbs_api_types
::ADMIN_DATASTORE_LIST_SNAPSHOTS_RETURN_TYPE
;
103 format_and_print_result_full(&mut data
, return_type
, &output_format
, &options
);
112 schema
: REPO_URL_SCHEMA
,
116 type: BackupNamespace
,
121 description
: "Snapshot path.",
124 schema
: OUTPUT_FORMAT
,
130 /// List snapshot files.
131 async
fn list_snapshot_files(param
: Value
) -> Result
<Value
, Error
> {
132 let repo
= extract_repository_from_value(¶m
)?
;
134 let backup_ns
= optional_ns_param(¶m
)?
;
135 let path
= required_string_param(¶m
, "snapshot")?
;
136 let snapshot
: BackupDir
= path
.parse()?
;
138 let output_format
= get_output_format(¶m
);
140 let client
= connect(&repo
)?
;
142 let path
= format
!("api2/json/admin/datastore/{}/files", repo
.store());
144 let mut result
= client
145 .get(&path
, Some(snapshot_args(&backup_ns
, &snapshot
)?
))
148 record_repository(&repo
);
150 let return_type
= &pbs_api_types
::ADMIN_DATASTORE_LIST_SNAPSHOT_FILES_RETURN_TYPE
;
152 let mut data
: Value
= result
["data"].take();
154 let options
= default_table_format_options();
156 format_and_print_result_full(&mut data
, return_type
, &output_format
, &options
);
165 schema
: REPO_URL_SCHEMA
,
169 type: BackupNamespace
,
174 description
: "Snapshot path.",
179 /// Forget (remove) backup snapshots.
180 async
fn forget_snapshots(param
: Value
) -> Result
<Value
, Error
> {
181 let repo
= extract_repository_from_value(¶m
)?
;
183 let backup_ns
= optional_ns_param(¶m
)?
;
184 let path
= required_string_param(¶m
, "snapshot")?
;
185 let snapshot
: BackupDir
= path
.parse()?
;
187 let client
= connect(&repo
)?
;
189 let path
= format
!("api2/json/admin/datastore/{}/snapshots", repo
.store());
192 .delete(&path
, Some(snapshot_args(&backup_ns
, &snapshot
)?
))
195 record_repository(&repo
);
204 schema
: REPO_URL_SCHEMA
,
208 type: BackupNamespace
,
213 description
: "Group/Snapshot path.",
217 description
: "The path to the log file you want to upload.",
220 schema
: KEYFILE_SCHEMA
,
224 schema
: KEYFD_SCHEMA
,
234 /// Upload backup log file.
235 async
fn upload_log(param
: Value
) -> Result
<Value
, Error
> {
236 let logfile
= required_string_param(¶m
, "logfile")?
;
237 let repo
= extract_repository_from_value(¶m
)?
;
239 let backup_ns
= optional_ns_param(¶m
)?
;
240 let snapshot
= required_string_param(¶m
, "snapshot")?
;
241 let snapshot
: BackupDir
= snapshot
.parse()?
;
243 let client
= connect(&repo
)?
;
245 let crypto
= crypto_parameters(¶m
)?
;
247 let crypt_config
= match crypto
.enc_key
{
250 let (key
, _created
, _
) = decrypt_key(&key
.key
, &get_encryption_key_password
)?
;
251 let crypt_config
= CryptConfig
::new(key
)?
;
252 Some(Arc
::new(crypt_config
))
256 let data
= file_get_contents(logfile
)?
;
258 // fixme: howto sign log?
259 let blob
= match crypto
.mode
{
260 CryptMode
::None
| CryptMode
::SignOnly
=> DataBlob
::encode(&data
, None
, true)?
,
261 CryptMode
::Encrypt
=> {
262 DataBlob
::encode(&data
, crypt_config
.as_ref().map(Arc
::as_ref
), true)?
266 let raw_data
= blob
.into_inner();
269 "api2/json/admin/datastore/{}/upload-backup-log",
273 let args
= snapshot_args(&backup_ns
, &snapshot
)?
;
274 let body
= hyper
::Body
::from(raw_data
);
277 .upload("application/octet-stream", body
, &path
, Some(args
))
285 schema
: REPO_URL_SCHEMA
,
289 type: BackupNamespace
,
294 description
: "Snapshot path.",
297 schema
: OUTPUT_FORMAT
,
304 async
fn show_notes(param
: Value
) -> Result
<Value
, Error
> {
305 let repo
= extract_repository_from_value(¶m
)?
;
306 let path
= required_string_param(¶m
, "snapshot")?
;
308 let backup_ns
= optional_ns_param(¶m
)?
;
309 let snapshot
: BackupDir
= path
.parse()?
;
310 let client
= connect(&repo
)?
;
312 let path
= format
!("api2/json/admin/datastore/{}/notes", repo
.store());
314 let args
= snapshot_args(&backup_ns
, &snapshot
)?
;
316 let output_format
= get_output_format(¶m
);
318 let mut result
= client
.get(&path
, Some(args
)).await?
;
320 let notes
= result
["data"].take();
322 if output_format
== "text" {
323 if let Some(notes
) = notes
.as_str() {
324 println
!("{}", notes
);
327 format_and_print_result(
342 schema
: REPO_URL_SCHEMA
,
346 type: BackupNamespace
,
351 description
: "Snapshot path.",
355 description
: "The Notes.",
361 async
fn update_notes(param
: Value
) -> Result
<Value
, Error
> {
362 let repo
= extract_repository_from_value(¶m
)?
;
363 let path
= required_string_param(¶m
, "snapshot")?
;
364 let notes
= required_string_param(¶m
, "notes")?
;
366 let backup_ns
= optional_ns_param(¶m
)?
;
367 let snapshot
: BackupDir
= path
.parse()?
;
368 let client
= connect(&repo
)?
;
370 let path
= format
!("api2/json/admin/datastore/{}/notes", repo
.store());
372 let mut args
= snapshot_args(&backup_ns
, &snapshot
)?
;
373 args
["notes"] = Value
::from(notes
);
375 client
.put(&path
, Some(args
)).await?
;
384 schema
: REPO_URL_SCHEMA
,
388 type: BackupNamespace
,
393 description
: "Snapshot path.",
396 schema
: OUTPUT_FORMAT
,
402 /// Show protection status of the specified snapshot
403 async
fn show_protection(param
: Value
) -> Result
<(), Error
> {
404 let repo
= extract_repository_from_value(¶m
)?
;
405 let path
= required_string_param(¶m
, "snapshot")?
;
407 let backup_ns
= optional_ns_param(¶m
)?
;
408 let snapshot
: BackupDir
= path
.parse()?
;
409 let client
= connect(&repo
)?
;
411 let path
= format
!("api2/json/admin/datastore/{}/protected", repo
.store());
413 let args
= snapshot_args(&backup_ns
, &snapshot
)?
;
415 let output_format
= get_output_format(¶m
);
417 let mut result
= client
.get(&path
, Some(args
)).await?
;
419 let protected
= result
["data"].take();
421 if output_format
== "text" {
422 if let Some(protected
) = protected
.as_bool() {
423 println
!("{}", protected
);
426 format_and_print_result(
428 "protected": protected
,
441 schema
: REPO_URL_SCHEMA
,
445 type: BackupNamespace
,
450 description
: "Snapshot path.",
454 description
: "The protection status.",
459 /// Update Protection Status of a snapshot
460 async
fn update_protection(protected
: bool
, param
: Value
) -> Result
<(), Error
> {
461 let repo
= extract_repository_from_value(¶m
)?
;
462 let path
= required_string_param(¶m
, "snapshot")?
;
464 let backup_ns
= optional_ns_param(¶m
)?
;
465 let snapshot
: BackupDir
= path
.parse()?
;
466 let client
= connect(&repo
)?
;
468 let path
= format
!("api2/json/admin/datastore/{}/protected", repo
.store());
470 let mut args
= snapshot_args(&backup_ns
, &snapshot
)?
;
471 args
["protected"] = Value
::from(protected
);
473 client
.put(&path
, Some(args
)).await?
;
478 fn protected_cli() -> CliCommandMap
{
482 CliCommand
::new(&API_METHOD_SHOW_PROTECTION
)
483 .arg_param(&["snapshot"])
484 .completion_cb("ns", complete_namespace
)
485 .completion_cb("snapshot", complete_backup_snapshot
),
489 CliCommand
::new(&API_METHOD_UPDATE_PROTECTION
)
490 .arg_param(&["snapshot", "protected"])
491 .completion_cb("ns", complete_namespace
)
492 .completion_cb("snapshot", complete_backup_snapshot
),
496 fn notes_cli() -> CliCommandMap
{
500 CliCommand
::new(&API_METHOD_SHOW_NOTES
)
501 .arg_param(&["snapshot"])
502 .completion_cb("ns", complete_namespace
)
503 .completion_cb("snapshot", complete_backup_snapshot
),
507 CliCommand
::new(&API_METHOD_UPDATE_NOTES
)
508 .arg_param(&["snapshot", "notes"])
509 .completion_cb("ns", complete_namespace
)
510 .completion_cb("snapshot", complete_backup_snapshot
),
514 pub fn snapshot_mgtm_cli() -> CliCommandMap
{
516 .insert("notes", notes_cli())
517 .insert("protected", protected_cli())
520 CliCommand
::new(&API_METHOD_LIST_SNAPSHOTS
)
521 .arg_param(&["group"])
522 .completion_cb("ns", complete_namespace
)
523 .completion_cb("group", complete_backup_group
)
524 .completion_cb("repository", complete_repository
),
528 CliCommand
::new(&API_METHOD_LIST_SNAPSHOT_FILES
)
529 .arg_param(&["snapshot"])
530 .completion_cb("ns", complete_namespace
)
531 .completion_cb("repository", complete_repository
)
532 .completion_cb("snapshot", complete_backup_snapshot
),
536 CliCommand
::new(&API_METHOD_FORGET_SNAPSHOTS
)
537 .arg_param(&["snapshot"])
538 .completion_cb("ns", complete_namespace
)
539 .completion_cb("repository", complete_repository
)
540 .completion_cb("snapshot", complete_backup_snapshot
),
544 CliCommand
::new(&API_METHOD_UPLOAD_LOG
)
545 .arg_param(&["snapshot", "logfile"])
546 .completion_cb("ns", complete_namespace
)
547 .completion_cb("snapshot", complete_backup_snapshot
)
548 .completion_cb("logfile", complete_file_name
)
549 .completion_cb("keyfile", complete_file_name
)
550 .completion_cb("repository", complete_repository
),