]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/proxmox-backup-manager.rs
move more tools for the client into subcrates
[proxmox-backup.git] / src / bin / proxmox-backup-manager.rs
CommitLineData
331b869d 1use std::collections::HashMap;
941342f7 2use std::io::{self, Write};
ea0b8b6e 3
b29d046e 4use anyhow::{format_err, Error};
9894469e 5use serde_json::{json, Value};
9894469e 6
380bd7df 7use proxmox::api::{api, cli::*, RpcEnvironment};
769f8c99 8
4805edc4
WB
9use pbs_tools::percent_encoding::percent_encode_component;
10
769f8c99 11use proxmox_backup::tools;
a220a456 12use proxmox_backup::config;
9894469e 13use proxmox_backup::api2::{self, types::* };
769f8c99 14use proxmox_backup::client::*;
769f8c99 15
a220a456
DM
16mod proxmox_backup_manager;
17use proxmox_backup_manager::*;
18
769f8c99
DM
19#[api(
20 input: {
21 properties: {
22 store: {
23 schema: DATASTORE_SCHEMA,
24 },
25 "output-format": {
26 schema: OUTPUT_FORMAT,
27 optional: true,
28 },
29 }
30 }
31)]
32/// Start garbage collection for a specific datastore.
33async fn start_garbage_collection(param: Value) -> Result<Value, Error> {
691c89a0 34
ac3faaf5 35 let output_format = get_output_format(&param);
691c89a0 36
769f8c99
DM
37 let store = tools::required_string_param(&param, "store")?;
38
6b68e5d5 39 let mut client = connect_to_localhost()?;
769f8c99
DM
40
41 let path = format!("api2/json/admin/datastore/{}/gc", store);
42
43 let result = client.post(&path, None).await?;
44
e68269fc 45 view_task_result(&mut client, result, &output_format).await?;
769f8c99
DM
46
47 Ok(Value::Null)
48}
49
50#[api(
51 input: {
52 properties: {
53 store: {
54 schema: DATASTORE_SCHEMA,
55 },
56 "output-format": {
57 schema: OUTPUT_FORMAT,
58 optional: true,
59 },
60 }
61 }
62)]
63/// Show garbage collection status for a specific datastore.
64async fn garbage_collection_status(param: Value) -> Result<Value, Error> {
65
ac3faaf5 66 let output_format = get_output_format(&param);
769f8c99
DM
67
68 let store = tools::required_string_param(&param, "store")?;
69
6b68e5d5 70 let client = connect_to_localhost()?;
769f8c99
DM
71
72 let path = format!("api2/json/admin/datastore/{}/gc", store);
73
9894469e
DM
74 let mut result = client.get(&path, None).await?;
75 let mut data = result["data"].take();
b2362a12 76 let return_type = &api2::admin::datastore::API_METHOD_GARBAGE_COLLECTION_STATUS.returns;
9894469e 77
ac3faaf5 78 let options = default_table_format_options();
9894469e 79
b2362a12 80 format_and_print_result_full(&mut data, return_type, &output_format, &options);
769f8c99
DM
81
82 Ok(Value::Null)
83}
84
85fn garbage_collection_commands() -> CommandLineInterface {
691c89a0
DM
86
87 let cmd_def = CliCommandMap::new()
88 .insert("status",
769f8c99 89 CliCommand::new(&API_METHOD_GARBAGE_COLLECTION_STATUS)
49fddd98 90 .arg_param(&["store"])
9ac1045c 91 .completion_cb("store", config::datastore::complete_datastore_name)
48ef3c33 92 )
691c89a0 93 .insert("start",
769f8c99 94 CliCommand::new(&API_METHOD_START_GARBAGE_COLLECTION)
49fddd98 95 .arg_param(&["store"])
9ac1045c 96 .completion_cb("store", config::datastore::complete_datastore_name)
48ef3c33 97 );
691c89a0
DM
98
99 cmd_def.into()
100}
101
47d47121
DM
102#[api(
103 input: {
104 properties: {
105 limit: {
106 description: "The maximal number of tasks to list.",
107 type: Integer,
108 optional: true,
109 minimum: 1,
110 maximum: 1000,
111 default: 50,
112 },
113 "output-format": {
114 schema: OUTPUT_FORMAT,
115 optional: true,
116 },
117 all: {
118 type: Boolean,
119 description: "Also list stopped tasks.",
120 optional: true,
121 }
122 }
123 }
124)]
125/// List running server tasks.
126async fn task_list(param: Value) -> Result<Value, Error> {
127
ac3faaf5 128 let output_format = get_output_format(&param);
47d47121 129
6b68e5d5 130 let client = connect_to_localhost()?;
47d47121
DM
131
132 let limit = param["limit"].as_u64().unwrap_or(50) as usize;
133 let running = !param["all"].as_bool().unwrap_or(false);
134 let args = json!({
135 "running": running,
136 "start": 0,
137 "limit": limit,
138 });
9894469e 139 let mut result = client.get("api2/json/nodes/localhost/tasks", Some(args)).await?;
47d47121 140
9894469e 141 let mut data = result["data"].take();
b2362a12 142 let return_type = &api2::node::tasks::API_METHOD_LIST_TASKS.returns;
47d47121 143
770a36e5 144 use pbs_tools::format::{render_epoch, render_task_status};
ac3faaf5 145 let options = default_table_format_options()
770a36e5
WB
146 .column(ColumnConfig::new("starttime").right_align(false).renderer(render_epoch))
147 .column(ColumnConfig::new("endtime").right_align(false).renderer(render_epoch))
93fbb4ef 148 .column(ColumnConfig::new("upid"))
770a36e5 149 .column(ColumnConfig::new("status").renderer(render_task_status));
9894469e 150
b2362a12 151 format_and_print_result_full(&mut data, return_type, &output_format, &options);
47d47121
DM
152
153 Ok(Value::Null)
154}
155
156#[api(
157 input: {
158 properties: {
159 upid: {
160 schema: UPID_SCHEMA,
161 },
162 }
163 }
164)]
165/// Display the task log.
166async fn task_log(param: Value) -> Result<Value, Error> {
167
168 let upid = tools::required_string_param(&param, "upid")?;
169
e68269fc 170 let mut client = connect_to_localhost()?;
47d47121 171
e68269fc 172 display_task_log(&mut client, upid, true).await?;
47d47121
DM
173
174 Ok(Value::Null)
175}
176
177#[api(
178 input: {
179 properties: {
180 upid: {
181 schema: UPID_SCHEMA,
182 },
183 }
184 }
185)]
186/// Try to stop a specific task.
187async fn task_stop(param: Value) -> Result<Value, Error> {
188
189 let upid_str = tools::required_string_param(&param, "upid")?;
190
6b68e5d5 191 let mut client = connect_to_localhost()?;
47d47121 192
4805edc4 193 let path = format!("api2/json/nodes/localhost/tasks/{}", percent_encode_component(upid_str));
47d47121
DM
194 let _ = client.delete(&path, None).await?;
195
196 Ok(Value::Null)
197}
198
199fn task_mgmt_cli() -> CommandLineInterface {
200
201 let task_log_cmd_def = CliCommand::new(&API_METHOD_TASK_LOG)
202 .arg_param(&["upid"]);
203
204 let task_stop_cmd_def = CliCommand::new(&API_METHOD_TASK_STOP)
205 .arg_param(&["upid"]);
206
207 let cmd_def = CliCommandMap::new()
208 .insert("list", CliCommand::new(&API_METHOD_TASK_LIST))
209 .insert("log", task_log_cmd_def)
210 .insert("stop", task_stop_cmd_def);
211
212 cmd_def.into()
213}
214
4b4eba0b 215// fixme: avoid API redefinition
0eb0e024
DM
216#[api(
217 input: {
218 properties: {
eb506c83 219 "local-store": {
0eb0e024
DM
220 schema: DATASTORE_SCHEMA,
221 },
222 remote: {
167971ed 223 schema: REMOTE_ID_SCHEMA,
0eb0e024
DM
224 },
225 "remote-store": {
226 schema: DATASTORE_SCHEMA,
227 },
40dc1031
DC
228 "remove-vanished": {
229 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
4b4eba0b 230 optional: true,
4b4eba0b 231 },
0eb0e024
DM
232 "output-format": {
233 schema: OUTPUT_FORMAT,
234 optional: true,
235 },
236 }
237 }
238)]
eb506c83
DM
239/// Sync datastore from another repository
240async fn pull_datastore(
0eb0e024
DM
241 remote: String,
242 remote_store: String,
eb506c83 243 local_store: String,
40dc1031 244 remove_vanished: Option<bool>,
ac3faaf5 245 param: Value,
0eb0e024
DM
246) -> Result<Value, Error> {
247
ac3faaf5 248 let output_format = get_output_format(&param);
0eb0e024 249
6b68e5d5 250 let mut client = connect_to_localhost()?;
0eb0e024 251
8c877436 252 let mut args = json!({
eb506c83 253 "store": local_store,
94609e23 254 "remote": remote,
0eb0e024 255 "remote-store": remote_store,
0eb0e024
DM
256 });
257
8c877436
DC
258 if let Some(remove_vanished) = remove_vanished {
259 args["remove-vanished"] = Value::from(remove_vanished);
260 }
261
eb506c83 262 let result = client.post("api2/json/pull", Some(args)).await?;
0eb0e024 263
e68269fc 264 view_task_result(&mut client, result, &output_format).await?;
0eb0e024
DM
265
266 Ok(Value::Null)
267}
268
355c055e
DM
269#[api(
270 input: {
271 properties: {
272 "store": {
273 schema: DATASTORE_SCHEMA,
274 },
60abf03f
HL
275 "ignore-verified": {
276 schema: IGNORE_VERIFIED_BACKUPS_SCHEMA,
277 optional: true,
278 },
279 "outdated-after": {
280 schema: VERIFICATION_OUTDATED_AFTER_SCHEMA,
281 optional: true,
282 },
355c055e
DM
283 "output-format": {
284 schema: OUTPUT_FORMAT,
285 optional: true,
286 },
287 }
288 }
289)]
290/// Verify backups
291async fn verify(
292 store: String,
60abf03f 293 mut param: Value,
355c055e
DM
294) -> Result<Value, Error> {
295
60abf03f 296 let output_format = extract_output_format(&mut param);
355c055e 297
6b68e5d5 298 let mut client = connect_to_localhost()?;
355c055e 299
60abf03f 300 let args = json!(param);
355c055e
DM
301
302 let path = format!("api2/json/admin/datastore/{}/verify", store);
303
304 let result = client.post(&path, Some(args)).await?;
305
e68269fc 306 view_task_result(&mut client, result, &output_format).await?;
355c055e
DM
307
308 Ok(Value::Null)
309}
310
9a556c8a
HL
311#[api()]
312/// System report
313async fn report() -> Result<Value, Error> {
941342f7
TL
314 let report = proxmox_backup::server::generate_report();
315 io::stdout().write_all(report.as_bytes())?;
9a556c8a
HL
316 Ok(Value::Null)
317}
318
c100fe91
ML
319#[api(
320 input: {
321 properties: {
322 verbose: {
323 type: Boolean,
324 optional: true,
325 default: false,
326 description: "Output verbose package information. It is ignored if output-format is specified.",
327 },
328 "output-format": {
329 schema: OUTPUT_FORMAT,
330 optional: true,
331 }
332 }
333 }
334)]
335/// List package versions for important Proxmox Backup Server packages.
336async fn get_versions(verbose: bool, param: Value) -> Result<Value, Error> {
294466ee 337 let output_format = get_output_format(&param);
c100fe91 338
5e293f13 339 let packages = crate::api2::node::apt::get_versions()?;
fc5a0120 340 let mut packages = json!(if verbose { &packages[..] } else { &packages[1..2] });
c100fe91 341
294466ee
TL
342 let options = default_table_format_options()
343 .disable_sort()
d1d74c43 344 .noborder(true) // just not helpful for version info which gets copy pasted often
294466ee
TL
345 .column(ColumnConfig::new("Package"))
346 .column(ColumnConfig::new("Version"))
347 .column(ColumnConfig::new("ExtraInfo").header("Extra Info"))
348 ;
b2362a12 349 let return_type = &crate::api2::node::apt::API_METHOD_GET_VERSIONS.returns;
294466ee 350
b2362a12 351 format_and_print_result_full(&mut packages, return_type, &output_format, &options);
c100fe91
ML
352
353 Ok(Value::Null)
354}
355
211fabd7
DM
356fn main() {
357
ac7513e3
DM
358 proxmox_backup::tools::setup_safe_path_env();
359
6460764d 360 let cmd_def = CliCommandMap::new()
ed3e60ae 361 .insert("acl", acl_commands())
48ef3c33 362 .insert("datastore", datastore_commands())
8e40aa63 363 .insert("disk", disk_commands())
14627d67 364 .insert("dns", dns_commands())
ca0e5347 365 .insert("network", network_commands())
72e311c6 366 .insert("node", node_commands())
579728c6 367 .insert("user", user_commands())
0decd11e 368 .insert("openid", openid_commands())
f357390c 369 .insert("remote", remote_commands())
47d47121 370 .insert("garbage-collection", garbage_collection_commands())
72bd8293 371 .insert("acme", acme_mgmt_cli())
550e0d88 372 .insert("cert", cert_mgmt_cli())
2762481c 373 .insert("subscription", subscription_commands())
a3016d65 374 .insert("sync-job", sync_job_commands())
4a874665 375 .insert("verify-job", verify_job_commands())
0eb0e024
DM
376 .insert("task", task_mgmt_cli())
377 .insert(
eb506c83
DM
378 "pull",
379 CliCommand::new(&API_METHOD_PULL_DATASTORE)
380 .arg_param(&["remote", "remote-store", "local-store"])
381 .completion_cb("local-store", config::datastore::complete_datastore_name)
f357390c 382 .completion_cb("remote", config::remote::complete_remote_name)
331b869d 383 .completion_cb("remote-store", complete_remote_datastore_name)
355c055e
DM
384 )
385 .insert(
386 "verify",
387 CliCommand::new(&API_METHOD_VERIFY)
388 .arg_param(&["store"])
389 .completion_cb("store", config::datastore::complete_datastore_name)
9a556c8a
HL
390 )
391 .insert("report",
392 CliCommand::new(&API_METHOD_REPORT)
c100fe91
ML
393 )
394 .insert("versions",
395 CliCommand::new(&API_METHOD_GET_VERSIONS)
0eb0e024 396 );
34d3ba52 397
355c055e
DM
398
399
7b22acd0 400 let mut rpcenv = CliEnvironment::new();
e6dc35ac 401 rpcenv.set_auth_id(Some(String::from("root@pam")));
525008f7 402
d420962f 403 pbs_runtime::main(run_async_cli_command(cmd_def, rpcenv));
ea0b8b6e 404}
331b869d
DM
405
406// shell completion helper
407pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
408
409 let mut list = Vec::new();
410
9ea4bce4 411 let _ = proxmox::try_block!({
331b869d 412 let remote = param.get("remote").ok_or_else(|| format_err!("no remote"))?;
331b869d 413
d420962f 414 let data = pbs_runtime::block_on(async move {
e0100d61
FG
415 crate::api2::config::remote::scan_remote_datastores(remote.clone()).await
416 })?;
331b869d 417
e0100d61
FG
418 for item in data {
419 list.push(item.store);
331b869d
DM
420 }
421
422 Ok(())
423 }).map_err(|_err: Error| { /* ignore */ });
424
425 list
426}