]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/proxmox-backup-manager.rs
ui: add missing monStoreErrors
[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 },
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
277async 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(&param);
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
306fn 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
335pub 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}