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