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