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