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
::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
= Ticket
::new("PBS", Userid
::root_userid())?
63 .sign(private_auth_key(), None
)?
;
64 options
= options
.password(Some(ticket
));
65 HttpClient
::new("localhost", 8007, Userid
::root_userid(), options
)?
67 options
= options
.ticket_cache(true).interactive(true);
68 HttpClient
::new("localhost", 8007, Userid
::root_userid(), options
)?
78 schema
: DATASTORE_SCHEMA
,
81 schema
: OUTPUT_FORMAT
,
87 /// Start garbage collection for a specific datastore.
88 async
fn start_garbage_collection(param
: Value
) -> Result
<Value
, Error
> {
90 let output_format
= get_output_format(¶m
);
92 let store
= tools
::required_string_param(¶m
, "store")?
;
94 let mut client
= connect()?
;
96 let path
= format
!("api2/json/admin/datastore/{}/gc", store
);
98 let result
= client
.post(&path
, None
).await?
;
100 view_task_result(client
, result
, &output_format
).await?
;
109 schema
: DATASTORE_SCHEMA
,
112 schema
: OUTPUT_FORMAT
,
118 /// Show garbage collection status for a specific datastore.
119 async
fn garbage_collection_status(param
: Value
) -> Result
<Value
, Error
> {
121 let output_format
= get_output_format(¶m
);
123 let store
= tools
::required_string_param(¶m
, "store")?
;
125 let client
= connect()?
;
127 let path
= format
!("api2/json/admin/datastore/{}/gc", store
);
129 let mut result
= client
.get(&path
, None
).await?
;
130 let mut data
= result
["data"].take();
131 let schema
= &api2
::admin
::datastore
::API_RETURN_SCHEMA_GARBAGE_COLLECTION_STATUS
;
133 let options
= default_table_format_options();
135 format_and_print_result_full(&mut data
, schema
, &output_format
, &options
);
140 fn garbage_collection_commands() -> CommandLineInterface
{
142 let cmd_def
= CliCommandMap
::new()
144 CliCommand
::new(&API_METHOD_GARBAGE_COLLECTION_STATUS
)
145 .arg_param(&["store"])
146 .completion_cb("store", config
::datastore
::complete_datastore_name
)
149 CliCommand
::new(&API_METHOD_START_GARBAGE_COLLECTION
)
150 .arg_param(&["store"])
151 .completion_cb("store", config
::datastore
::complete_datastore_name
)
161 description
: "The maximal number of tasks to list.",
169 schema
: OUTPUT_FORMAT
,
174 description
: "Also list stopped tasks.",
180 /// List running server tasks.
181 async
fn task_list(param
: Value
) -> Result
<Value
, Error
> {
183 let output_format
= get_output_format(¶m
);
185 let client
= connect()?
;
187 let limit
= param
["limit"].as_u64().unwrap_or(50) as usize;
188 let running
= !param
["all"].as_bool().unwrap_or(false);
194 let mut result
= client
.get("api2/json/nodes/localhost/tasks", Some(args
)).await?
;
196 let mut data
= result
["data"].take();
197 let schema
= &api2
::node
::tasks
::API_RETURN_SCHEMA_LIST_TASKS
;
199 let options
= default_table_format_options()
200 .column(ColumnConfig
::new("starttime").right_align(false).renderer(tools
::format
::render_epoch
))
201 .column(ColumnConfig
::new("endtime").right_align(false).renderer(tools
::format
::render_epoch
))
202 .column(ColumnConfig
::new("upid"))
203 .column(ColumnConfig
::new("status").renderer(tools
::format
::render_task_status
));
205 format_and_print_result_full(&mut data
, schema
, &output_format
, &options
);
219 /// Display the task log.
220 async
fn task_log(param
: Value
) -> Result
<Value
, Error
> {
222 let upid
= tools
::required_string_param(¶m
, "upid")?
;
224 let client
= connect()?
;
226 display_task_log(client
, upid
, true).await?
;
240 /// Try to stop a specific task.
241 async
fn task_stop(param
: Value
) -> Result
<Value
, Error
> {
243 let upid_str
= tools
::required_string_param(¶m
, "upid")?
;
245 let mut client
= connect()?
;
247 let path
= format
!("api2/json/nodes/localhost/tasks/{}", upid_str
);
248 let _
= client
.delete(&path
, None
).await?
;
253 fn task_mgmt_cli() -> CommandLineInterface
{
255 let task_log_cmd_def
= CliCommand
::new(&API_METHOD_TASK_LOG
)
256 .arg_param(&["upid"]);
258 let task_stop_cmd_def
= CliCommand
::new(&API_METHOD_TASK_STOP
)
259 .arg_param(&["upid"]);
261 let cmd_def
= CliCommandMap
::new()
262 .insert("list", CliCommand
::new(&API_METHOD_TASK_LIST
))
263 .insert("log", task_log_cmd_def
)
264 .insert("stop", task_stop_cmd_def
);
269 // fixme: avoid API redefinition
274 schema
: DATASTORE_SCHEMA
,
277 schema
: REMOTE_ID_SCHEMA
,
280 schema
: DATASTORE_SCHEMA
,
283 schema
: REMOVE_VANISHED_BACKUPS_SCHEMA
,
287 schema
: OUTPUT_FORMAT
,
293 /// Sync datastore from another repository
294 async
fn pull_datastore(
296 remote_store
: String
,
298 remove_vanished
: Option
<bool
>,
300 ) -> Result
<Value
, Error
> {
302 let output_format
= get_output_format(¶m
);
304 let mut client
= connect()?
;
306 let mut args
= json
!({
307 "store": local_store
,
309 "remote-store": remote_store
,
312 if let Some(remove_vanished
) = remove_vanished
{
313 args
["remove-vanished"] = Value
::from(remove_vanished
);
316 let result
= client
.post("api2/json/pull", Some(args
)).await?
;
318 view_task_result(client
, result
, &output_format
).await?
;
327 schema
: DATASTORE_SCHEMA
,
330 schema
: OUTPUT_FORMAT
,
340 ) -> Result
<Value
, Error
> {
342 let output_format
= get_output_format(¶m
);
344 let mut client
= connect()?
;
346 let args
= json
!({}
);
348 let path
= format
!("api2/json/admin/datastore/{}/verify", store
);
350 let result
= client
.post(&path
, Some(args
)).await?
;
352 view_task_result(client
, result
, &output_format
).await?
;
359 proxmox_backup
::tools
::setup_safe_path_env();
361 let cmd_def
= CliCommandMap
::new()
362 .insert("acl", acl_commands())
363 .insert("datastore", datastore_commands())
364 .insert("disk", disk_commands())
365 .insert("dns", dns_commands())
366 .insert("network", network_commands())
367 .insert("user", user_commands())
368 .insert("remote", remote_commands())
369 .insert("garbage-collection", garbage_collection_commands())
370 .insert("cert", cert_mgmt_cli())
371 .insert("sync-job", sync_job_commands())
372 .insert("task", task_mgmt_cli())
375 CliCommand
::new(&API_METHOD_PULL_DATASTORE
)
376 .arg_param(&["remote", "remote-store", "local-store"])
377 .completion_cb("local-store", config
::datastore
::complete_datastore_name
)
378 .completion_cb("remote", config
::remote
::complete_remote_name
)
379 .completion_cb("remote-store", complete_remote_datastore_name
)
383 CliCommand
::new(&API_METHOD_VERIFY
)
384 .arg_param(&["store"])
385 .completion_cb("store", config
::datastore
::complete_datastore_name
)
390 let mut rpcenv
= CliEnvironment
::new();
391 rpcenv
.set_user(Some(String
::from("root@pam")));
393 proxmox_backup
::tools
::runtime
::main(run_async_cli_command(cmd_def
, rpcenv
));
396 // shell completion helper
397 pub fn complete_remote_datastore_name(_arg
: &str, param
: &HashMap
<String
, String
>) -> Vec
<String
> {
399 let mut list
= Vec
::new();
401 let _
= proxmox
::try_block
!({
402 let remote
= param
.get("remote").ok_or_else(|| format_err
!("no remote"))?
;
403 let (remote_config
, _digest
) = config
::remote
::config()?
;
405 let remote
: config
::remote
::Remote
= remote_config
.lookup("remote", &remote
)?
;
407 let options
= HttpClientOptions
::new()
408 .password(Some(remote
.password
.clone()))
409 .fingerprint(remote
.fingerprint
.clone());
411 let client
= HttpClient
::new(
413 remote
.port
.unwrap_or(8007),
418 let result
= crate::tools
::runtime
::block_on(client
.get("api2/json/admin/datastore", None
))?
;
420 if let Some(data
) = result
["data"].as_array() {
422 if let Some(store
) = item
["store"].as_str() {
423 list
.push(store
.to_owned());
429 }).map_err(|_err
: Error
| { /* ignore */ }
);