1 use std
::collections
::HashMap
;
3 use anyhow
::{format_err, Error}
;
4 use serde_json
::{json, Value}
;
6 use proxmox
::api
::{api, cli::*, RpcEnvironment}
;
8 use proxmox_backup
::tools
;
9 use proxmox_backup
::config
;
10 use proxmox_backup
::api2
::{self, types::* }
;
11 use proxmox_backup
::client
::*;
12 use proxmox_backup
::tools
::ticket
::*;
13 use proxmox_backup
::auth_helpers
::*;
15 mod proxmox_backup_manager
;
16 use proxmox_backup_manager
::*;
18 async
fn view_task_result(
22 ) -> Result
<(), Error
> {
23 let data
= &result
["data"];
24 if output_format
== "text" {
25 if let Some(upid
) = data
.as_str() {
26 display_task_log(client
, upid
, true).await?
;
29 format_and_print_result(&data
, &output_format
);
35 // Note: local workers should print logs to stdout, so there is no need
36 // to fetch/display logs. We just wait for the worker to finish.
37 pub async
fn wait_for_local_worker(upid_str
: &str) -> Result
<(), Error
> {
39 let upid
: proxmox_backup
::server
::UPID
= upid_str
.parse()?
;
41 let sleep_duration
= core
::time
::Duration
::new(0, 100_000_000);
44 if proxmox_backup
::server
::worker_is_active_local(&upid
) {
45 tokio
::time
::delay_for(sleep_duration
).await
;
53 fn connect() -> Result
<HttpClient
, Error
> {
55 let uid
= nix
::unistd
::Uid
::current();
57 let mut options
= HttpClientOptions
::new()
58 .prefix(Some("proxmox-backup".to_string()))
59 .verify_cert(false); // not required for connection to localhost
61 let client
= if uid
.is_root() {
62 let ticket
= assemble_rsa_ticket(private_auth_key(), "PBS", Some("root@pam"), None
)?
;
63 options
= options
.password(Some(ticket
));
64 HttpClient
::new("localhost", "root@pam", options
)?
66 options
= options
.ticket_cache(true).interactive(true);
67 HttpClient
::new("localhost", "root@pam", options
)?
77 schema
: DATASTORE_SCHEMA
,
80 schema
: OUTPUT_FORMAT
,
86 /// Start garbage collection for a specific datastore.
87 async
fn start_garbage_collection(param
: Value
) -> Result
<Value
, Error
> {
89 let output_format
= get_output_format(¶m
);
91 let store
= tools
::required_string_param(¶m
, "store")?
;
93 let mut client
= connect()?
;
95 let path
= format
!("api2/json/admin/datastore/{}/gc", store
);
97 let result
= client
.post(&path
, None
).await?
;
99 view_task_result(client
, result
, &output_format
).await?
;
108 schema
: DATASTORE_SCHEMA
,
111 schema
: OUTPUT_FORMAT
,
117 /// Show garbage collection status for a specific datastore.
118 async
fn garbage_collection_status(param
: Value
) -> Result
<Value
, Error
> {
120 let output_format
= get_output_format(¶m
);
122 let store
= tools
::required_string_param(¶m
, "store")?
;
124 let client
= connect()?
;
126 let path
= format
!("api2/json/admin/datastore/{}/gc", store
);
128 let mut result
= client
.get(&path
, None
).await?
;
129 let mut data
= result
["data"].take();
130 let schema
= api2
::admin
::datastore
::API_RETURN_SCHEMA_GARBAGE_COLLECTION_STATUS
;
132 let options
= default_table_format_options();
134 format_and_print_result_full(&mut data
, schema
, &output_format
, &options
);
139 fn garbage_collection_commands() -> CommandLineInterface
{
141 let cmd_def
= CliCommandMap
::new()
143 CliCommand
::new(&API_METHOD_GARBAGE_COLLECTION_STATUS
)
144 .arg_param(&["store"])
145 .completion_cb("store", config
::datastore
::complete_datastore_name
)
148 CliCommand
::new(&API_METHOD_START_GARBAGE_COLLECTION
)
149 .arg_param(&["store"])
150 .completion_cb("store", config
::datastore
::complete_datastore_name
)
160 description
: "The maximal number of tasks to list.",
168 schema
: OUTPUT_FORMAT
,
173 description
: "Also list stopped tasks.",
179 /// List running server tasks.
180 async
fn task_list(param
: Value
) -> Result
<Value
, Error
> {
182 let output_format
= get_output_format(¶m
);
184 let client
= connect()?
;
186 let limit
= param
["limit"].as_u64().unwrap_or(50) as usize;
187 let running
= !param
["all"].as_bool().unwrap_or(false);
193 let mut result
= client
.get("api2/json/nodes/localhost/tasks", Some(args
)).await?
;
195 let mut data
= result
["data"].take();
196 let schema
= api2
::node
::tasks
::API_RETURN_SCHEMA_LIST_TASKS
;
198 let options
= default_table_format_options()
199 .column(ColumnConfig
::new("starttime").right_align(false).renderer(tools
::format
::render_epoch
))
200 .column(ColumnConfig
::new("endtime").right_align(false).renderer(tools
::format
::render_epoch
))
201 .column(ColumnConfig
::new("upid"))
202 .column(ColumnConfig
::new("status").renderer(tools
::format
::render_task_status
));
204 format_and_print_result_full(&mut data
, schema
, &output_format
, &options
);
218 /// Display the task log.
219 async
fn task_log(param
: Value
) -> Result
<Value
, Error
> {
221 let upid
= tools
::required_string_param(¶m
, "upid")?
;
223 let client
= connect()?
;
225 display_task_log(client
, upid
, true).await?
;
239 /// Try to stop a specific task.
240 async
fn task_stop(param
: Value
) -> Result
<Value
, Error
> {
242 let upid_str
= tools
::required_string_param(¶m
, "upid")?
;
244 let mut client
= connect()?
;
246 let path
= format
!("api2/json/nodes/localhost/tasks/{}", upid_str
);
247 let _
= client
.delete(&path
, None
).await?
;
252 fn task_mgmt_cli() -> CommandLineInterface
{
254 let task_log_cmd_def
= CliCommand
::new(&API_METHOD_TASK_LOG
)
255 .arg_param(&["upid"]);
257 let task_stop_cmd_def
= CliCommand
::new(&API_METHOD_TASK_STOP
)
258 .arg_param(&["upid"]);
260 let cmd_def
= CliCommandMap
::new()
261 .insert("list", CliCommand
::new(&API_METHOD_TASK_LIST
))
262 .insert("log", task_log_cmd_def
)
263 .insert("stop", task_stop_cmd_def
);
268 // fixme: avoid API redefinition
273 schema
: DATASTORE_SCHEMA
,
276 schema
: REMOTE_ID_SCHEMA
,
279 schema
: DATASTORE_SCHEMA
,
282 schema
: REMOVE_VANISHED_BACKUPS_SCHEMA
,
286 schema
: OUTPUT_FORMAT
,
292 /// Sync datastore from another repository
293 async
fn pull_datastore(
295 remote_store
: String
,
297 remove_vanished
: Option
<bool
>,
299 ) -> Result
<Value
, Error
> {
301 let output_format
= get_output_format(¶m
);
303 let mut client
= connect()?
;
305 let mut args
= json
!({
306 "store": local_store
,
308 "remote-store": remote_store
,
311 if let Some(remove_vanished
) = remove_vanished
{
312 args
["remove-vanished"] = Value
::from(remove_vanished
);
315 let result
= client
.post("api2/json/pull", Some(args
)).await?
;
317 view_task_result(client
, result
, &output_format
).await?
;
324 let cmd_def
= CliCommandMap
::new()
325 .insert("acl", acl_commands())
326 .insert("datastore", datastore_commands())
327 .insert("disk", disk_commands())
328 .insert("dns", dns_commands())
329 .insert("network", network_commands())
330 .insert("user", user_commands())
331 .insert("remote", remote_commands())
332 .insert("garbage-collection", garbage_collection_commands())
333 .insert("cert", cert_mgmt_cli())
334 .insert("sync-job", sync_job_commands())
335 .insert("task", task_mgmt_cli())
338 CliCommand
::new(&API_METHOD_PULL_DATASTORE
)
339 .arg_param(&["remote", "remote-store", "local-store"])
340 .completion_cb("local-store", config
::datastore
::complete_datastore_name
)
341 .completion_cb("remote", config
::remote
::complete_remote_name
)
342 .completion_cb("remote-store", complete_remote_datastore_name
)
345 let mut rpcenv
= CliEnvironment
::new();
346 rpcenv
.set_user(Some(String
::from("root@pam")));
348 proxmox_backup
::tools
::runtime
::main(run_async_cli_command(cmd_def
, rpcenv
));
351 // shell completion helper
352 pub fn complete_remote_datastore_name(_arg
: &str, param
: &HashMap
<String
, String
>) -> Vec
<String
> {
354 let mut list
= Vec
::new();
356 let _
= proxmox
::try_block
!({
357 let remote
= param
.get("remote").ok_or_else(|| format_err
!("no remote"))?
;
358 let (remote_config
, _digest
) = config
::remote
::config()?
;
360 let remote
: config
::remote
::Remote
= remote_config
.lookup("remote", &remote
)?
;
362 let options
= HttpClientOptions
::new()
363 .password(Some(remote
.password
.clone()))
364 .fingerprint(remote
.fingerprint
.clone());
366 let client
= HttpClient
::new(
372 let result
= crate::tools
::runtime
::block_on(client
.get("api2/json/admin/datastore", None
))?
;
374 if let Some(data
) = result
["data"].as_array() {
376 if let Some(store
) = item
["store"].as_str() {
377 list
.push(store
.to_owned());
383 }).map_err(|_err
: Error
| { /* ignore */ }
);