1 use std
::collections
::HashMap
;
2 use std
::io
::{self, Write}
;
4 use anyhow
::{format_err, Error}
;
5 use serde_json
::{json, Value}
;
7 use proxmox
::api
::{api, cli::*, RpcEnvironment}
;
9 use proxmox_backup
::tools
;
10 use proxmox_backup
::config
;
11 use proxmox_backup
::api2
::{self, types::* }
;
12 use proxmox_backup
::client
::*;
13 use proxmox_backup
::tools
::ticket
::Ticket
;
14 use proxmox_backup
::auth_helpers
::*;
16 mod proxmox_backup_manager
;
17 use proxmox_backup_manager
::*;
19 async
fn view_task_result(
23 ) -> Result
<(), Error
> {
24 let data
= &result
["data"];
25 if output_format
== "text" {
26 if let Some(upid
) = data
.as_str() {
27 display_task_log(client
, upid
, true).await?
;
30 format_and_print_result(&data
, &output_format
);
36 // Note: local workers should print logs to stdout, so there is no need
37 // to fetch/display logs. We just wait for the worker to finish.
38 pub async
fn wait_for_local_worker(upid_str
: &str) -> Result
<(), Error
> {
40 let upid
: proxmox_backup
::server
::UPID
= upid_str
.parse()?
;
42 let sleep_duration
= core
::time
::Duration
::new(0, 100_000_000);
45 if proxmox_backup
::server
::worker_is_active_local(&upid
) {
46 tokio
::time
::delay_for(sleep_duration
).await
;
54 fn connect() -> Result
<HttpClient
, Error
> {
56 let uid
= nix
::unistd
::Uid
::current();
58 let mut options
= HttpClientOptions
::new()
59 .prefix(Some("proxmox-backup".to_string()))
60 .verify_cert(false); // not required for connection to localhost
62 let client
= if uid
.is_root() {
63 let ticket
= Ticket
::new("PBS", Userid
::root_userid())?
64 .sign(private_auth_key(), None
)?
;
65 options
= options
.password(Some(ticket
));
66 HttpClient
::new("localhost", 8007, Authid
::root_auth_id(), options
)?
68 options
= options
.ticket_cache(true).interactive(true);
69 HttpClient
::new("localhost", 8007, Authid
::root_auth_id(), options
)?
79 schema
: DATASTORE_SCHEMA
,
82 schema
: OUTPUT_FORMAT
,
88 /// Start garbage collection for a specific datastore.
89 async
fn start_garbage_collection(param
: Value
) -> Result
<Value
, Error
> {
91 let output_format
= get_output_format(¶m
);
93 let store
= tools
::required_string_param(¶m
, "store")?
;
95 let mut client
= connect()?
;
97 let path
= format
!("api2/json/admin/datastore/{}/gc", store
);
99 let result
= client
.post(&path
, None
).await?
;
101 view_task_result(client
, result
, &output_format
).await?
;
110 schema
: DATASTORE_SCHEMA
,
113 schema
: OUTPUT_FORMAT
,
119 /// Show garbage collection status for a specific datastore.
120 async
fn garbage_collection_status(param
: Value
) -> Result
<Value
, Error
> {
122 let output_format
= get_output_format(¶m
);
124 let store
= tools
::required_string_param(¶m
, "store")?
;
126 let client
= connect()?
;
128 let path
= format
!("api2/json/admin/datastore/{}/gc", store
);
130 let mut result
= client
.get(&path
, None
).await?
;
131 let mut data
= result
["data"].take();
132 let schema
= &api2
::admin
::datastore
::API_RETURN_SCHEMA_GARBAGE_COLLECTION_STATUS
;
134 let options
= default_table_format_options();
136 format_and_print_result_full(&mut data
, schema
, &output_format
, &options
);
141 fn garbage_collection_commands() -> CommandLineInterface
{
143 let cmd_def
= CliCommandMap
::new()
145 CliCommand
::new(&API_METHOD_GARBAGE_COLLECTION_STATUS
)
146 .arg_param(&["store"])
147 .completion_cb("store", config
::datastore
::complete_datastore_name
)
150 CliCommand
::new(&API_METHOD_START_GARBAGE_COLLECTION
)
151 .arg_param(&["store"])
152 .completion_cb("store", config
::datastore
::complete_datastore_name
)
162 description
: "The maximal number of tasks to list.",
170 schema
: OUTPUT_FORMAT
,
175 description
: "Also list stopped tasks.",
181 /// List running server tasks.
182 async
fn task_list(param
: Value
) -> Result
<Value
, Error
> {
184 let output_format
= get_output_format(¶m
);
186 let client
= connect()?
;
188 let limit
= param
["limit"].as_u64().unwrap_or(50) as usize;
189 let running
= !param
["all"].as_bool().unwrap_or(false);
195 let mut result
= client
.get("api2/json/nodes/localhost/tasks", Some(args
)).await?
;
197 let mut data
= result
["data"].take();
198 let schema
= &api2
::node
::tasks
::API_RETURN_SCHEMA_LIST_TASKS
;
200 let options
= default_table_format_options()
201 .column(ColumnConfig
::new("starttime").right_align(false).renderer(tools
::format
::render_epoch
))
202 .column(ColumnConfig
::new("endtime").right_align(false).renderer(tools
::format
::render_epoch
))
203 .column(ColumnConfig
::new("upid"))
204 .column(ColumnConfig
::new("status").renderer(tools
::format
::render_task_status
));
206 format_and_print_result_full(&mut data
, schema
, &output_format
, &options
);
220 /// Display the task log.
221 async
fn task_log(param
: Value
) -> Result
<Value
, Error
> {
223 let upid
= tools
::required_string_param(¶m
, "upid")?
;
225 let client
= connect()?
;
227 display_task_log(client
, upid
, true).await?
;
241 /// Try to stop a specific task.
242 async
fn task_stop(param
: Value
) -> Result
<Value
, Error
> {
244 let upid_str
= tools
::required_string_param(¶m
, "upid")?
;
246 let mut client
= connect()?
;
248 let path
= format
!("api2/json/nodes/localhost/tasks/{}", upid_str
);
249 let _
= client
.delete(&path
, None
).await?
;
254 fn task_mgmt_cli() -> CommandLineInterface
{
256 let task_log_cmd_def
= CliCommand
::new(&API_METHOD_TASK_LOG
)
257 .arg_param(&["upid"]);
259 let task_stop_cmd_def
= CliCommand
::new(&API_METHOD_TASK_STOP
)
260 .arg_param(&["upid"]);
262 let cmd_def
= CliCommandMap
::new()
263 .insert("list", CliCommand
::new(&API_METHOD_TASK_LIST
))
264 .insert("log", task_log_cmd_def
)
265 .insert("stop", task_stop_cmd_def
);
270 // fixme: avoid API redefinition
275 schema
: DATASTORE_SCHEMA
,
278 schema
: REMOTE_ID_SCHEMA
,
281 schema
: DATASTORE_SCHEMA
,
284 schema
: REMOVE_VANISHED_BACKUPS_SCHEMA
,
288 schema
: OUTPUT_FORMAT
,
294 /// Sync datastore from another repository
295 async
fn pull_datastore(
297 remote_store
: String
,
299 remove_vanished
: Option
<bool
>,
301 ) -> Result
<Value
, Error
> {
303 let output_format
= get_output_format(¶m
);
305 let mut client
= connect()?
;
307 let mut args
= json
!({
308 "store": local_store
,
310 "remote-store": remote_store
,
313 if let Some(remove_vanished
) = remove_vanished
{
314 args
["remove-vanished"] = Value
::from(remove_vanished
);
317 let result
= client
.post("api2/json/pull", Some(args
)).await?
;
319 view_task_result(client
, result
, &output_format
).await?
;
328 schema
: DATASTORE_SCHEMA
,
331 schema
: OUTPUT_FORMAT
,
341 ) -> Result
<Value
, Error
> {
343 let output_format
= get_output_format(¶m
);
345 let mut client
= connect()?
;
347 let args
= json
!({}
);
349 let path
= format
!("api2/json/admin/datastore/{}/verify", store
);
351 let result
= client
.post(&path
, Some(args
)).await?
;
353 view_task_result(client
, result
, &output_format
).await?
;
360 async
fn report() -> Result
<Value
, Error
> {
361 let report
= proxmox_backup
::server
::generate_report();
362 io
::stdout().write_all(report
.as_bytes())?
;
368 proxmox_backup
::tools
::setup_safe_path_env();
370 let cmd_def
= CliCommandMap
::new()
371 .insert("acl", acl_commands())
372 .insert("datastore", datastore_commands())
373 .insert("disk", disk_commands())
374 .insert("dns", dns_commands())
375 .insert("network", network_commands())
376 .insert("user", user_commands())
377 .insert("remote", remote_commands())
378 .insert("garbage-collection", garbage_collection_commands())
379 .insert("cert", cert_mgmt_cli())
380 .insert("subscription", subscription_commands())
381 .insert("sync-job", sync_job_commands())
382 .insert("task", task_mgmt_cli())
385 CliCommand
::new(&API_METHOD_PULL_DATASTORE
)
386 .arg_param(&["remote", "remote-store", "local-store"])
387 .completion_cb("local-store", config
::datastore
::complete_datastore_name
)
388 .completion_cb("remote", config
::remote
::complete_remote_name
)
389 .completion_cb("remote-store", complete_remote_datastore_name
)
393 CliCommand
::new(&API_METHOD_VERIFY
)
394 .arg_param(&["store"])
395 .completion_cb("store", config
::datastore
::complete_datastore_name
)
398 CliCommand
::new(&API_METHOD_REPORT
)
403 let mut rpcenv
= CliEnvironment
::new();
404 rpcenv
.set_auth_id(Some(String
::from("root@pam")));
406 proxmox_backup
::tools
::runtime
::main(run_async_cli_command(cmd_def
, rpcenv
));
409 // shell completion helper
410 pub fn complete_remote_datastore_name(_arg
: &str, param
: &HashMap
<String
, String
>) -> Vec
<String
> {
412 let mut list
= Vec
::new();
414 let _
= proxmox
::try_block
!({
415 let remote
= param
.get("remote").ok_or_else(|| format_err
!("no remote"))?
;
417 let data
= crate::tools
::runtime
::block_on(async
move {
418 crate::api2
::config
::remote
::scan_remote_datastores(remote
.clone()).await
422 list
.push(item
.store
);
426 }).map_err(|_err
: Error
| { /* ignore */ }
);