]>
Commit | Line | Data |
---|---|---|
331b869d | 1 | use std::collections::HashMap; |
ea0b8b6e | 2 | |
b29d046e | 3 | use anyhow::{format_err, Error}; |
9894469e | 4 | use serde_json::{json, Value}; |
9894469e | 5 | |
380bd7df | 6 | use proxmox::api::{api, cli::*, RpcEnvironment}; |
769f8c99 DM |
7 | |
8 | use proxmox_backup::tools; | |
a220a456 | 9 | use proxmox_backup::config; |
9894469e | 10 | use proxmox_backup::api2::{self, types::* }; |
769f8c99 DM |
11 | use proxmox_backup::client::*; |
12 | use proxmox_backup::tools::ticket::*; | |
13 | use proxmox_backup::auth_helpers::*; | |
14 | ||
a220a456 DM |
15 | mod proxmox_backup_manager; |
16 | use proxmox_backup_manager::*; | |
17 | ||
769f8c99 DM |
18 | async fn view_task_result( |
19 | client: HttpClient, | |
20 | result: Value, | |
21 | output_format: &str, | |
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?; | |
27 | } | |
28 | } else { | |
29 | format_and_print_result(&data, &output_format); | |
30 | } | |
31 | ||
32 | Ok(()) | |
33 | } | |
211fabd7 | 34 | |
47d47121 DM |
35 | fn connect() -> Result<HttpClient, Error> { |
36 | ||
37 | let uid = nix::unistd::Uid::current(); | |
38 | ||
d59dbeca | 39 | let mut options = HttpClientOptions::new() |
5030b7ce | 40 | .prefix(Some("proxmox-backup".to_string())) |
d59dbeca DM |
41 | .verify_cert(false); // not required for connection to localhost |
42 | ||
47d47121 DM |
43 | let client = if uid.is_root() { |
44 | let ticket = assemble_rsa_ticket(private_auth_key(), "PBS", Some("root@pam"), None)?; | |
d59dbeca DM |
45 | options = options.password(Some(ticket)); |
46 | HttpClient::new("localhost", "root@pam", options)? | |
47d47121 | 47 | } else { |
d59dbeca DM |
48 | options = options.ticket_cache(true).interactive(true); |
49 | HttpClient::new("localhost", "root@pam", options)? | |
47d47121 DM |
50 | }; |
51 | ||
52 | Ok(client) | |
53 | } | |
54 | ||
769f8c99 DM |
55 | #[api( |
56 | input: { | |
57 | properties: { | |
58 | store: { | |
59 | schema: DATASTORE_SCHEMA, | |
60 | }, | |
61 | "output-format": { | |
62 | schema: OUTPUT_FORMAT, | |
63 | optional: true, | |
64 | }, | |
65 | } | |
66 | } | |
67 | )] | |
68 | /// Start garbage collection for a specific datastore. | |
69 | async fn start_garbage_collection(param: Value) -> Result<Value, Error> { | |
691c89a0 | 70 | |
ac3faaf5 | 71 | let output_format = get_output_format(¶m); |
691c89a0 | 72 | |
769f8c99 DM |
73 | let store = tools::required_string_param(¶m, "store")?; |
74 | ||
47d47121 | 75 | let mut client = connect()?; |
769f8c99 DM |
76 | |
77 | let path = format!("api2/json/admin/datastore/{}/gc", store); | |
78 | ||
79 | let result = client.post(&path, None).await?; | |
80 | ||
81 | view_task_result(client, result, &output_format).await?; | |
82 | ||
83 | Ok(Value::Null) | |
84 | } | |
85 | ||
86 | #[api( | |
87 | input: { | |
88 | properties: { | |
89 | store: { | |
90 | schema: DATASTORE_SCHEMA, | |
91 | }, | |
92 | "output-format": { | |
93 | schema: OUTPUT_FORMAT, | |
94 | optional: true, | |
95 | }, | |
96 | } | |
97 | } | |
98 | )] | |
99 | /// Show garbage collection status for a specific datastore. | |
100 | async fn garbage_collection_status(param: Value) -> Result<Value, Error> { | |
101 | ||
ac3faaf5 | 102 | let output_format = get_output_format(¶m); |
769f8c99 DM |
103 | |
104 | let store = tools::required_string_param(¶m, "store")?; | |
105 | ||
47d47121 | 106 | let client = connect()?; |
769f8c99 DM |
107 | |
108 | let path = format!("api2/json/admin/datastore/{}/gc", store); | |
109 | ||
9894469e DM |
110 | let mut result = client.get(&path, None).await?; |
111 | let mut data = result["data"].take(); | |
112 | let schema = api2::admin::datastore::API_RETURN_SCHEMA_GARBAGE_COLLECTION_STATUS; | |
113 | ||
ac3faaf5 | 114 | let options = default_table_format_options(); |
9894469e DM |
115 | |
116 | format_and_print_result_full(&mut data, schema, &output_format, &options); | |
769f8c99 DM |
117 | |
118 | Ok(Value::Null) | |
119 | } | |
120 | ||
121 | fn garbage_collection_commands() -> CommandLineInterface { | |
691c89a0 DM |
122 | |
123 | let cmd_def = CliCommandMap::new() | |
124 | .insert("status", | |
769f8c99 | 125 | CliCommand::new(&API_METHOD_GARBAGE_COLLECTION_STATUS) |
49fddd98 | 126 | .arg_param(&["store"]) |
9ac1045c | 127 | .completion_cb("store", config::datastore::complete_datastore_name) |
48ef3c33 | 128 | ) |
691c89a0 | 129 | .insert("start", |
769f8c99 | 130 | CliCommand::new(&API_METHOD_START_GARBAGE_COLLECTION) |
49fddd98 | 131 | .arg_param(&["store"]) |
9ac1045c | 132 | .completion_cb("store", config::datastore::complete_datastore_name) |
48ef3c33 | 133 | ); |
691c89a0 DM |
134 | |
135 | cmd_def.into() | |
136 | } | |
137 | ||
47d47121 DM |
138 | #[api( |
139 | input: { | |
140 | properties: { | |
141 | limit: { | |
142 | description: "The maximal number of tasks to list.", | |
143 | type: Integer, | |
144 | optional: true, | |
145 | minimum: 1, | |
146 | maximum: 1000, | |
147 | default: 50, | |
148 | }, | |
149 | "output-format": { | |
150 | schema: OUTPUT_FORMAT, | |
151 | optional: true, | |
152 | }, | |
153 | all: { | |
154 | type: Boolean, | |
155 | description: "Also list stopped tasks.", | |
156 | optional: true, | |
157 | } | |
158 | } | |
159 | } | |
160 | )] | |
161 | /// List running server tasks. | |
162 | async fn task_list(param: Value) -> Result<Value, Error> { | |
163 | ||
ac3faaf5 | 164 | let output_format = get_output_format(¶m); |
47d47121 DM |
165 | |
166 | let client = connect()?; | |
167 | ||
168 | let limit = param["limit"].as_u64().unwrap_or(50) as usize; | |
169 | let running = !param["all"].as_bool().unwrap_or(false); | |
170 | let args = json!({ | |
171 | "running": running, | |
172 | "start": 0, | |
173 | "limit": limit, | |
174 | }); | |
9894469e | 175 | let mut result = client.get("api2/json/nodes/localhost/tasks", Some(args)).await?; |
47d47121 | 176 | |
9894469e DM |
177 | let mut data = result["data"].take(); |
178 | let schema = api2::node::tasks::API_RETURN_SCHEMA_LIST_TASKS; | |
47d47121 | 179 | |
ac3faaf5 | 180 | let options = default_table_format_options() |
4939255f DM |
181 | .column(ColumnConfig::new("starttime").right_align(false).renderer(tools::format::render_epoch)) |
182 | .column(ColumnConfig::new("endtime").right_align(false).renderer(tools::format::render_epoch)) | |
93fbb4ef | 183 | .column(ColumnConfig::new("upid")) |
4939255f | 184 | .column(ColumnConfig::new("status").renderer(tools::format::render_task_status)); |
9894469e DM |
185 | |
186 | format_and_print_result_full(&mut data, schema, &output_format, &options); | |
47d47121 DM |
187 | |
188 | Ok(Value::Null) | |
189 | } | |
190 | ||
191 | #[api( | |
192 | input: { | |
193 | properties: { | |
194 | upid: { | |
195 | schema: UPID_SCHEMA, | |
196 | }, | |
197 | } | |
198 | } | |
199 | )] | |
200 | /// Display the task log. | |
201 | async fn task_log(param: Value) -> Result<Value, Error> { | |
202 | ||
203 | let upid = tools::required_string_param(¶m, "upid")?; | |
204 | ||
205 | let client = connect()?; | |
206 | ||
207 | display_task_log(client, upid, true).await?; | |
208 | ||
209 | Ok(Value::Null) | |
210 | } | |
211 | ||
212 | #[api( | |
213 | input: { | |
214 | properties: { | |
215 | upid: { | |
216 | schema: UPID_SCHEMA, | |
217 | }, | |
218 | } | |
219 | } | |
220 | )] | |
221 | /// Try to stop a specific task. | |
222 | async fn task_stop(param: Value) -> Result<Value, Error> { | |
223 | ||
224 | let upid_str = tools::required_string_param(¶m, "upid")?; | |
225 | ||
226 | let mut client = connect()?; | |
227 | ||
228 | let path = format!("api2/json/nodes/localhost/tasks/{}", upid_str); | |
229 | let _ = client.delete(&path, None).await?; | |
230 | ||
231 | Ok(Value::Null) | |
232 | } | |
233 | ||
234 | fn task_mgmt_cli() -> CommandLineInterface { | |
235 | ||
236 | let task_log_cmd_def = CliCommand::new(&API_METHOD_TASK_LOG) | |
237 | .arg_param(&["upid"]); | |
238 | ||
239 | let task_stop_cmd_def = CliCommand::new(&API_METHOD_TASK_STOP) | |
240 | .arg_param(&["upid"]); | |
241 | ||
242 | let cmd_def = CliCommandMap::new() | |
243 | .insert("list", CliCommand::new(&API_METHOD_TASK_LIST)) | |
244 | .insert("log", task_log_cmd_def) | |
245 | .insert("stop", task_stop_cmd_def); | |
246 | ||
247 | cmd_def.into() | |
248 | } | |
249 | ||
4b4eba0b | 250 | // fixme: avoid API redefinition |
0eb0e024 DM |
251 | #[api( |
252 | input: { | |
253 | properties: { | |
eb506c83 | 254 | "local-store": { |
0eb0e024 DM |
255 | schema: DATASTORE_SCHEMA, |
256 | }, | |
257 | remote: { | |
167971ed | 258 | schema: REMOTE_ID_SCHEMA, |
0eb0e024 DM |
259 | }, |
260 | "remote-store": { | |
261 | schema: DATASTORE_SCHEMA, | |
262 | }, | |
4b4eba0b DM |
263 | delete: { |
264 | description: "Delete vanished backups. This remove the local copy if the remote backup was deleted.", | |
265 | type: Boolean, | |
266 | optional: true, | |
267 | default: true, | |
268 | }, | |
0eb0e024 DM |
269 | "output-format": { |
270 | schema: OUTPUT_FORMAT, | |
271 | optional: true, | |
272 | }, | |
273 | } | |
274 | } | |
275 | )] | |
eb506c83 DM |
276 | /// Sync datastore from another repository |
277 | async fn pull_datastore( | |
0eb0e024 DM |
278 | remote: String, |
279 | remote_store: String, | |
eb506c83 | 280 | local_store: String, |
4b4eba0b | 281 | delete: Option<bool>, |
ac3faaf5 | 282 | param: Value, |
0eb0e024 DM |
283 | ) -> Result<Value, Error> { |
284 | ||
ac3faaf5 | 285 | let output_format = get_output_format(¶m); |
0eb0e024 DM |
286 | |
287 | let mut client = connect()?; | |
288 | ||
4b4eba0b | 289 | let mut args = json!({ |
eb506c83 | 290 | "store": local_store, |
94609e23 | 291 | "remote": remote, |
0eb0e024 | 292 | "remote-store": remote_store, |
0eb0e024 DM |
293 | }); |
294 | ||
4b4eba0b DM |
295 | if let Some(delete) = delete { |
296 | args["delete"] = delete.into(); | |
297 | } | |
298 | ||
eb506c83 | 299 | let result = client.post("api2/json/pull", Some(args)).await?; |
0eb0e024 DM |
300 | |
301 | view_task_result(client, result, &output_format).await?; | |
302 | ||
303 | Ok(Value::Null) | |
304 | } | |
305 | ||
211fabd7 DM |
306 | fn main() { |
307 | ||
6460764d | 308 | let cmd_def = CliCommandMap::new() |
ed3e60ae | 309 | .insert("acl", acl_commands()) |
48ef3c33 | 310 | .insert("datastore", datastore_commands()) |
14627d67 | 311 | .insert("dns", dns_commands()) |
ca0e5347 | 312 | .insert("network", network_commands()) |
579728c6 | 313 | .insert("user", user_commands()) |
f357390c | 314 | .insert("remote", remote_commands()) |
47d47121 | 315 | .insert("garbage-collection", garbage_collection_commands()) |
550e0d88 | 316 | .insert("cert", cert_mgmt_cli()) |
a3016d65 | 317 | .insert("sync-job", sync_job_commands()) |
0eb0e024 DM |
318 | .insert("task", task_mgmt_cli()) |
319 | .insert( | |
eb506c83 DM |
320 | "pull", |
321 | CliCommand::new(&API_METHOD_PULL_DATASTORE) | |
322 | .arg_param(&["remote", "remote-store", "local-store"]) | |
323 | .completion_cb("local-store", config::datastore::complete_datastore_name) | |
f357390c | 324 | .completion_cb("remote", config::remote::complete_remote_name) |
331b869d | 325 | .completion_cb("remote-store", complete_remote_datastore_name) |
0eb0e024 | 326 | ); |
34d3ba52 | 327 | |
7b22acd0 DM |
328 | let mut rpcenv = CliEnvironment::new(); |
329 | rpcenv.set_user(Some(String::from("root@pam"))); | |
525008f7 | 330 | |
7b22acd0 | 331 | proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv)); |
ea0b8b6e | 332 | } |
331b869d DM |
333 | |
334 | // shell completion helper | |
335 | pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> { | |
336 | ||
337 | let mut list = Vec::new(); | |
338 | ||
9ea4bce4 | 339 | let _ = proxmox::try_block!({ |
331b869d | 340 | let remote = param.get("remote").ok_or_else(|| format_err!("no remote"))?; |
a220a456 | 341 | let (remote_config, _digest) = config::remote::config()?; |
331b869d | 342 | |
a220a456 | 343 | let remote: config::remote::Remote = remote_config.lookup("remote", &remote)?; |
331b869d | 344 | |
d59dbeca DM |
345 | let options = HttpClientOptions::new() |
346 | .password(Some(remote.password.clone())) | |
347 | .fingerprint(remote.fingerprint.clone()); | |
348 | ||
331b869d DM |
349 | let client = HttpClient::new( |
350 | &remote.host, | |
351 | &remote.userid, | |
d59dbeca | 352 | options, |
331b869d DM |
353 | )?; |
354 | ||
03ac286c | 355 | let result = crate::tools::runtime::block_on(client.get("api2/json/admin/datastore", None))?; |
331b869d DM |
356 | |
357 | if let Some(data) = result["data"].as_array() { | |
358 | for item in data { | |
359 | if let Some(store) = item["store"].as_str() { | |
360 | list.push(store.to_owned()); | |
361 | } | |
362 | } | |
363 | } | |
364 | ||
365 | Ok(()) | |
366 | }).map_err(|_err: Error| { /* ignore */ }); | |
367 | ||
368 | list | |
369 | } |