]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/proxmox-backup-manager.rs
fix inserting of worker tasks
[proxmox-backup.git] / src / bin / proxmox-backup-manager.rs
CommitLineData
331b869d 1use std::collections::HashMap;
ea0b8b6e 2
b29d046e 3use anyhow::{format_err, Error};
9894469e 4use serde_json::{json, Value};
9894469e 5
380bd7df 6use proxmox::api::{api, cli::*, RpcEnvironment};
769f8c99
DM
7
8use proxmox_backup::tools;
a220a456 9use proxmox_backup::config;
9894469e 10use proxmox_backup::api2::{self, types::* };
769f8c99
DM
11use proxmox_backup::client::*;
12use proxmox_backup::tools::ticket::*;
13use proxmox_backup::auth_helpers::*;
14
a220a456
DM
15mod proxmox_backup_manager;
16use proxmox_backup_manager::*;
17
769f8c99
DM
18async 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
35fn 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.
69async fn start_garbage_collection(param: Value) -> Result<Value, Error> {
691c89a0 70
ac3faaf5 71 let output_format = get_output_format(&param);
691c89a0 72
769f8c99
DM
73 let store = tools::required_string_param(&param, "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.
100async fn garbage_collection_status(param: Value) -> Result<Value, Error> {
101
ac3faaf5 102 let output_format = get_output_format(&param);
769f8c99
DM
103
104 let store = tools::required_string_param(&param, "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
121fn 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.
162async fn task_list(param: Value) -> Result<Value, Error> {
163
ac3faaf5 164 let output_format = get_output_format(&param);
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.
201async fn task_log(param: Value) -> Result<Value, Error> {
202
203 let upid = tools::required_string_param(&param, "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.
222async fn task_stop(param: Value) -> Result<Value, Error> {
223
224 let upid_str = tools::required_string_param(&param, "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
234fn 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
275async 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(&param);
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
301fn 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
330pub 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}