]>
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 | }, | |
40dc1031 DC |
263 | "remove-vanished": { |
264 | schema: REMOVE_VANISHED_BACKUPS_SCHEMA, | |
4b4eba0b | 265 | optional: true, |
4b4eba0b | 266 | }, |
0eb0e024 DM |
267 | "output-format": { |
268 | schema: OUTPUT_FORMAT, | |
269 | optional: true, | |
270 | }, | |
271 | } | |
272 | } | |
273 | )] | |
eb506c83 DM |
274 | /// Sync datastore from another repository |
275 | async fn pull_datastore( | |
0eb0e024 DM |
276 | remote: String, |
277 | remote_store: String, | |
eb506c83 | 278 | local_store: String, |
40dc1031 | 279 | remove_vanished: Option<bool>, |
ac3faaf5 | 280 | param: Value, |
0eb0e024 DM |
281 | ) -> Result<Value, Error> { |
282 | ||
ac3faaf5 | 283 | let output_format = get_output_format(¶m); |
0eb0e024 DM |
284 | |
285 | let mut client = connect()?; | |
286 | ||
40dc1031 | 287 | let args = json!({ |
eb506c83 | 288 | "store": local_store, |
94609e23 | 289 | "remote": remote, |
0eb0e024 | 290 | "remote-store": remote_store, |
40dc1031 | 291 | "remove-vanished": remove_vanished, |
0eb0e024 DM |
292 | }); |
293 | ||
eb506c83 | 294 | let result = client.post("api2/json/pull", Some(args)).await?; |
0eb0e024 DM |
295 | |
296 | view_task_result(client, result, &output_format).await?; | |
297 | ||
298 | Ok(Value::Null) | |
299 | } | |
300 | ||
211fabd7 DM |
301 | fn main() { |
302 | ||
6460764d | 303 | let cmd_def = CliCommandMap::new() |
ed3e60ae | 304 | .insert("acl", acl_commands()) |
48ef3c33 | 305 | .insert("datastore", datastore_commands()) |
14627d67 | 306 | .insert("dns", dns_commands()) |
ca0e5347 | 307 | .insert("network", network_commands()) |
579728c6 | 308 | .insert("user", user_commands()) |
f357390c | 309 | .insert("remote", remote_commands()) |
47d47121 | 310 | .insert("garbage-collection", garbage_collection_commands()) |
550e0d88 | 311 | .insert("cert", cert_mgmt_cli()) |
a3016d65 | 312 | .insert("sync-job", sync_job_commands()) |
0eb0e024 DM |
313 | .insert("task", task_mgmt_cli()) |
314 | .insert( | |
eb506c83 DM |
315 | "pull", |
316 | CliCommand::new(&API_METHOD_PULL_DATASTORE) | |
317 | .arg_param(&["remote", "remote-store", "local-store"]) | |
318 | .completion_cb("local-store", config::datastore::complete_datastore_name) | |
f357390c | 319 | .completion_cb("remote", config::remote::complete_remote_name) |
331b869d | 320 | .completion_cb("remote-store", complete_remote_datastore_name) |
0eb0e024 | 321 | ); |
34d3ba52 | 322 | |
7b22acd0 DM |
323 | let mut rpcenv = CliEnvironment::new(); |
324 | rpcenv.set_user(Some(String::from("root@pam"))); | |
525008f7 | 325 | |
7b22acd0 | 326 | proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv)); |
ea0b8b6e | 327 | } |
331b869d DM |
328 | |
329 | // shell completion helper | |
330 | pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> { | |
331 | ||
332 | let mut list = Vec::new(); | |
333 | ||
9ea4bce4 | 334 | let _ = proxmox::try_block!({ |
331b869d | 335 | let remote = param.get("remote").ok_or_else(|| format_err!("no remote"))?; |
a220a456 | 336 | let (remote_config, _digest) = config::remote::config()?; |
331b869d | 337 | |
a220a456 | 338 | let remote: config::remote::Remote = remote_config.lookup("remote", &remote)?; |
331b869d | 339 | |
d59dbeca DM |
340 | let options = HttpClientOptions::new() |
341 | .password(Some(remote.password.clone())) | |
342 | .fingerprint(remote.fingerprint.clone()); | |
343 | ||
331b869d DM |
344 | let client = HttpClient::new( |
345 | &remote.host, | |
346 | &remote.userid, | |
d59dbeca | 347 | options, |
331b869d DM |
348 | )?; |
349 | ||
03ac286c | 350 | let result = crate::tools::runtime::block_on(client.get("api2/json/admin/datastore", None))?; |
331b869d DM |
351 | |
352 | if let Some(data) = result["data"].as_array() { | |
353 | for item in data { | |
354 | if let Some(store) = item["store"].as_str() { | |
355 | list.push(store.to_owned()); | |
356 | } | |
357 | } | |
358 | } | |
359 | ||
360 | Ok(()) | |
361 | }).map_err(|_err: Error| { /* ignore */ }); | |
362 | ||
363 | list | |
364 | } |