]> git.proxmox.com Git - proxmox-backup.git/blob - src/bin/proxmox-backup-manager.rs
fix cli pull api call
[proxmox-backup.git] / src / bin / proxmox-backup-manager.rs
1 use std::collections::HashMap;
2
3 use anyhow::{format_err, Error};
4 use serde_json::{json, Value};
5
6 use proxmox::api::{api, cli::*, RpcEnvironment};
7
8 use proxmox_backup::tools;
9 use proxmox_backup::config;
10 use proxmox_backup::api2::{self, types::* };
11 use proxmox_backup::client::*;
12 use proxmox_backup::tools::ticket::*;
13 use proxmox_backup::auth_helpers::*;
14
15 mod proxmox_backup_manager;
16 use proxmox_backup_manager::*;
17
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 }
34
35 fn connect() -> Result<HttpClient, Error> {
36
37 let uid = nix::unistd::Uid::current();
38
39 let mut options = HttpClientOptions::new()
40 .prefix(Some("proxmox-backup".to_string()))
41 .verify_cert(false); // not required for connection to localhost
42
43 let client = if uid.is_root() {
44 let ticket = assemble_rsa_ticket(private_auth_key(), "PBS", Some("root@pam"), None)?;
45 options = options.password(Some(ticket));
46 HttpClient::new("localhost", "root@pam", options)?
47 } else {
48 options = options.ticket_cache(true).interactive(true);
49 HttpClient::new("localhost", "root@pam", options)?
50 };
51
52 Ok(client)
53 }
54
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> {
70
71 let output_format = get_output_format(&param);
72
73 let store = tools::required_string_param(&param, "store")?;
74
75 let mut client = connect()?;
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
102 let output_format = get_output_format(&param);
103
104 let store = tools::required_string_param(&param, "store")?;
105
106 let client = connect()?;
107
108 let path = format!("api2/json/admin/datastore/{}/gc", store);
109
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
114 let options = default_table_format_options();
115
116 format_and_print_result_full(&mut data, schema, &output_format, &options);
117
118 Ok(Value::Null)
119 }
120
121 fn garbage_collection_commands() -> CommandLineInterface {
122
123 let cmd_def = CliCommandMap::new()
124 .insert("status",
125 CliCommand::new(&API_METHOD_GARBAGE_COLLECTION_STATUS)
126 .arg_param(&["store"])
127 .completion_cb("store", config::datastore::complete_datastore_name)
128 )
129 .insert("start",
130 CliCommand::new(&API_METHOD_START_GARBAGE_COLLECTION)
131 .arg_param(&["store"])
132 .completion_cb("store", config::datastore::complete_datastore_name)
133 );
134
135 cmd_def.into()
136 }
137
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
164 let output_format = get_output_format(&param);
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 });
175 let mut result = client.get("api2/json/nodes/localhost/tasks", Some(args)).await?;
176
177 let mut data = result["data"].take();
178 let schema = api2::node::tasks::API_RETURN_SCHEMA_LIST_TASKS;
179
180 let options = default_table_format_options()
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))
183 .column(ColumnConfig::new("upid"))
184 .column(ColumnConfig::new("status").renderer(tools::format::render_task_status));
185
186 format_and_print_result_full(&mut data, schema, &output_format, &options);
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(&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.
222 async 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
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
250 // fixme: avoid API redefinition
251 #[api(
252 input: {
253 properties: {
254 "local-store": {
255 schema: DATASTORE_SCHEMA,
256 },
257 remote: {
258 schema: REMOTE_ID_SCHEMA,
259 },
260 "remote-store": {
261 schema: DATASTORE_SCHEMA,
262 },
263 "remove-vanished": {
264 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
265 optional: true,
266 },
267 "output-format": {
268 schema: OUTPUT_FORMAT,
269 optional: true,
270 },
271 }
272 }
273 )]
274 /// Sync datastore from another repository
275 async fn pull_datastore(
276 remote: String,
277 remote_store: String,
278 local_store: String,
279 remove_vanished: Option<bool>,
280 param: Value,
281 ) -> Result<Value, Error> {
282
283 let output_format = get_output_format(&param);
284
285 let mut client = connect()?;
286
287 let args = json!({
288 "store": local_store,
289 "remote": remote,
290 "remote-store": remote_store,
291 "remove-vanished": remove_vanished,
292 });
293
294 let result = client.post("api2/json/pull", Some(args)).await?;
295
296 view_task_result(client, result, &output_format).await?;
297
298 Ok(Value::Null)
299 }
300
301 fn main() {
302
303 let cmd_def = CliCommandMap::new()
304 .insert("acl", acl_commands())
305 .insert("datastore", datastore_commands())
306 .insert("dns", dns_commands())
307 .insert("network", network_commands())
308 .insert("user", user_commands())
309 .insert("remote", remote_commands())
310 .insert("garbage-collection", garbage_collection_commands())
311 .insert("cert", cert_mgmt_cli())
312 .insert("sync-job", sync_job_commands())
313 .insert("task", task_mgmt_cli())
314 .insert(
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)
319 .completion_cb("remote", config::remote::complete_remote_name)
320 .completion_cb("remote-store", complete_remote_datastore_name)
321 );
322
323 let mut rpcenv = CliEnvironment::new();
324 rpcenv.set_user(Some(String::from("root@pam")));
325
326 proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv));
327 }
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
334 let _ = proxmox::try_block!({
335 let remote = param.get("remote").ok_or_else(|| format_err!("no remote"))?;
336 let (remote_config, _digest) = config::remote::config()?;
337
338 let remote: config::remote::Remote = remote_config.lookup("remote", &remote)?;
339
340 let options = HttpClientOptions::new()
341 .password(Some(remote.password.clone()))
342 .fingerprint(remote.fingerprint.clone());
343
344 let client = HttpClient::new(
345 &remote.host,
346 &remote.userid,
347 options,
348 )?;
349
350 let result = crate::tools::runtime::block_on(client.get("api2/json/admin/datastore", None))?;
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 }