]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/bin/proxmox-backup-manager.rs
switch from failure to anyhow
[proxmox-backup.git] / src / bin / proxmox-backup-manager.rs
index 0dac2c163203c94be9d66f042941acc2da47fcfb..c71900d8bcd67de5d99d19c42254b6924be719b0 100644 (file)
@@ -1,14 +1,15 @@
-use failure::*;
-use serde_json::{json, Value};
 use std::path::PathBuf;
 use std::collections::HashMap;
 
-use proxmox::api::{api, cli::*};
+use anyhow::{bail, format_err, Error};
+use serde_json::{json, Value};
+
+use proxmox::api::{api, cli::*, RpcEnvironment, ApiHandler};
 
 use proxmox_backup::configdir;
 use proxmox_backup::tools;
 use proxmox_backup::config::{self, remote::{self, Remote}};
-use proxmox_backup::api2::types::*;
+use proxmox_backup::api2::{self, types::* };
 use proxmox_backup::client::*;
 use proxmox_backup::tools::ticket::*;
 use proxmox_backup::auth_helpers::*;
@@ -34,22 +35,59 @@ fn connect() -> Result<HttpClient, Error> {
 
     let uid = nix::unistd::Uid::current();
 
+    let mut options = HttpClientOptions::new()
+        .prefix(Some("proxmox-backup".to_string()))
+        .verify_cert(false); // not required for connection to localhost
+
     let client = if uid.is_root()  {
         let ticket = assemble_rsa_ticket(private_auth_key(), "PBS", Some("root@pam"), None)?;
-        HttpClient::new("localhost", "root@pam", Some(ticket))?
+        options = options.password(Some(ticket));
+        HttpClient::new("localhost", "root@pam", options)?
     } else {
-        HttpClient::new("localhost", "root@pam", None)?
+        options = options.ticket_cache(true).interactive(true);
+        HttpClient::new("localhost", "root@pam", options)?
     };
 
     Ok(client)
 }
 
-fn remote_commands() -> CommandLineInterface {
+#[api(
+    input: {
+        properties: {
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
+        }
+    }
+)]
+/// List configured remotes.
+fn list_remotes(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
+
+    let output_format = get_output_format(&param);
+
+    let info = &api2::config::remote::API_METHOD_LIST_REMOTES;
+    let mut data = match info.handler {
+        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+        _ => unreachable!(),
+    };
 
-    use proxmox_backup::api2;
+    let options = default_table_format_options()
+        .column(ColumnConfig::new("name"))
+        .column(ColumnConfig::new("host"))
+        .column(ColumnConfig::new("userid"))
+        .column(ColumnConfig::new("fingerprint"))
+        .column(ColumnConfig::new("comment"));
+
+    format_and_print_result_full(&mut data, info.returns, &output_format, &options);
+
+    Ok(Value::Null)
+}
+
+fn remote_commands() -> CommandLineInterface {
 
     let cmd_def = CliCommandMap::new()
-        .insert("list", CliCommand::new(&api2::config::remote::API_METHOD_LIST_REMOTES))
+        .insert("list", CliCommand::new(&&API_METHOD_LIST_REMOTES))
         .insert(
             "create",
             // fixme: howto handle password parameter?
@@ -72,9 +110,130 @@ fn remote_commands() -> CommandLineInterface {
     cmd_def.into()
 }
 
-fn datastore_commands() -> CommandLineInterface {
+#[api(
+    input: {
+        properties: {
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
+        }
+    }
+)]
+/// List configured users.
+fn list_users(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
+
+    let output_format = get_output_format(&param);
+
+    let info = &api2::access::user::API_METHOD_LIST_USERS;
+    let mut data = match info.handler {
+        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+        _ => unreachable!(),
+    };
+
+    let options = default_table_format_options()
+        .column(ColumnConfig::new("userid"))
+        .column(ColumnConfig::new("enable"))
+        .column(ColumnConfig::new("expire"))
+        .column(ColumnConfig::new("firstname"))
+        .column(ColumnConfig::new("lastname"))
+        .column(ColumnConfig::new("email"))
+        .column(ColumnConfig::new("comment"));
+
+    format_and_print_result_full(&mut data, info.returns, &output_format, &options);
 
-    use proxmox_backup::api2;
+    Ok(Value::Null)
+}
+
+fn user_commands() -> CommandLineInterface {
+
+    let cmd_def = CliCommandMap::new()
+        .insert("list", CliCommand::new(&&API_METHOD_LIST_USERS))
+        .insert(
+            "create",
+            // fixme: howto handle password parameter?
+            CliCommand::new(&api2::access::user::API_METHOD_CREATE_USER)
+                .arg_param(&["userid"])
+        )
+        .insert(
+            "update",
+            CliCommand::new(&api2::access::user::API_METHOD_UPDATE_USER)
+                .arg_param(&["userid"])
+                .completion_cb("userid", config::user::complete_user_name)
+        )
+        .insert(
+            "remove",
+            CliCommand::new(&api2::access::user::API_METHOD_DELETE_USER)
+                .arg_param(&["userid"])
+                .completion_cb("userid", config::user::complete_user_name)
+        );
+
+    cmd_def.into()
+}
+
+#[api(
+    input: {
+        properties: {
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
+        }
+    }
+)]
+/// Access Control list.
+fn list_acls(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
+
+    let output_format = get_output_format(&param);
+
+    let info = &api2::access::acl::API_METHOD_READ_ACL;
+    let mut data = match info.handler {
+        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+        _ => unreachable!(),
+    };
+
+    fn render_ugid(value: &Value, record: &Value) -> Result<String, Error> {
+        if value.is_null() { return Ok(String::new()); }
+        let ugid = value.as_str().unwrap();
+        let ugid_type = record["ugid_type"].as_str().unwrap();
+
+        if ugid_type == "user" {
+            Ok(ugid.to_string())
+        } else if ugid_type == "group" {
+            Ok(format!("@{}", ugid))
+        } else {
+            bail!("render_ugid: got unknown ugid_type");
+        }
+    }
+
+    let options = default_table_format_options()
+        .column(ColumnConfig::new("ugid").renderer(render_ugid))
+        .column(ColumnConfig::new("path"))
+        .column(ColumnConfig::new("propagate"))
+        .column(ColumnConfig::new("roleid"));
+
+    format_and_print_result_full(&mut data, info.returns, &output_format, &options);
+
+    Ok(Value::Null)
+}
+
+fn acl_commands() -> CommandLineInterface {
+
+    let cmd_def = CliCommandMap::new()
+        .insert("list", CliCommand::new(&&API_METHOD_LIST_ACLS))
+        .insert(
+            "update",
+            CliCommand::new(&api2::access::acl::API_METHOD_UPDATE_ACL)
+                .arg_param(&["path", "role"])
+                .completion_cb("userid", config::user::complete_user_name)
+                .completion_cb("path", config::datastore::complete_acl_path)
+
+        );
+
+    cmd_def.into()
+}
+
+fn datastore_commands() -> CommandLineInterface {
 
     let cmd_def = CliCommandMap::new()
         .insert("list", CliCommand::new(&api2::config::datastore::API_METHOD_LIST_DATASTORES))
@@ -113,7 +272,7 @@ fn datastore_commands() -> CommandLineInterface {
 /// Start garbage collection for a specific datastore.
 async fn start_garbage_collection(param: Value) -> Result<Value, Error> {
 
-    let output_format = param["output-format"].as_str().unwrap_or("text").to_owned();
+    let output_format = get_output_format(&param);
 
     let store = tools::required_string_param(&param, "store")?;
 
@@ -144,7 +303,7 @@ async fn start_garbage_collection(param: Value) -> Result<Value, Error> {
 /// Show garbage collection status for a specific datastore.
 async fn garbage_collection_status(param: Value) -> Result<Value, Error> {
 
-    let output_format = param["output-format"].as_str().unwrap_or("text").to_owned();
+    let output_format = get_output_format(&param);
 
     let store = tools::required_string_param(&param, "store")?;
 
@@ -152,13 +311,13 @@ async fn garbage_collection_status(param: Value) -> Result<Value, Error> {
 
     let path = format!("api2/json/admin/datastore/{}/gc", store);
 
-    let result = client.get(&path, None).await?;
-    let data = &result["data"];
-    if output_format == "text" {
-        format_and_print_result(&data, "json-pretty");
-     } else {
-        format_and_print_result(&data, &output_format);
-    }
+    let mut result = client.get(&path, None).await?;
+    let mut data = result["data"].take();
+    let schema = api2::admin::datastore::API_RETURN_SCHEMA_GARBAGE_COLLECTION_STATUS;
+
+    let options = default_table_format_options();
+
+    format_and_print_result_full(&mut data, schema, &output_format, &options);
 
     Ok(Value::Null)
 }
@@ -206,7 +365,7 @@ fn garbage_collection_commands() -> CommandLineInterface {
 /// List running server tasks.
 async fn task_list(param: Value) -> Result<Value, Error> {
 
-    let output_format = param["output-format"].as_str().unwrap_or("text").to_owned();
+    let output_format = get_output_format(&param);
 
     let client = connect()?;
 
@@ -217,21 +376,18 @@ async fn task_list(param: Value) -> Result<Value, Error> {
         "start": 0,
         "limit": limit,
     });
-    let result = client.get("api2/json/nodes/localhost/tasks", Some(args)).await?;
+    let mut result = client.get("api2/json/nodes/localhost/tasks", Some(args)).await?;
 
-    let data = &result["data"];
+    let mut data = result["data"].take();
+    let schema = api2::node::tasks::API_RETURN_SCHEMA_LIST_TASKS;
 
-    if output_format == "text" {
-        for item in data.as_array().unwrap() {
-            println!(
-                "{} {}",
-                item["upid"].as_str().unwrap(),
-                item["status"].as_str().unwrap_or("running"),
-            );
-        }
-    } else {
-        format_and_print_result(data, &output_format);
-    }
+    let options = default_table_format_options()
+        .column(ColumnConfig::new("starttime").right_align(false).renderer(tools::format::render_epoch))
+        .column(ColumnConfig::new("endtime").right_align(false).renderer(tools::format::render_epoch))
+        .column(ColumnConfig::new("upid"))
+        .column(ColumnConfig::new("status").renderer(tools::format::render_task_status));
+
+    format_and_print_result_full(&mut data, schema, &output_format, &options);
 
     Ok(Value::Null)
 }
@@ -418,10 +574,10 @@ async fn pull_datastore(
     remote_store: String,
     local_store: String,
     delete: Option<bool>,
-    output_format: Option<String>,
+    param: Value,
 ) -> Result<Value, Error> {
 
-    let output_format = output_format.unwrap_or("text".to_string());
+    let output_format = get_output_format(&param);
 
     let mut client = connect()?;
 
@@ -445,7 +601,9 @@ async fn pull_datastore(
 fn main() {
 
     let cmd_def = CliCommandMap::new()
+        .insert("acl", acl_commands())
         .insert("datastore", datastore_commands())
+        .insert("user", user_commands())
         .insert("remote", remote_commands())
         .insert("garbage-collection", garbage_collection_commands())
         .insert("cert", cert_mgmt_cli())
@@ -459,7 +617,7 @@ fn main() {
                 .completion_cb("remote-store", complete_remote_datastore_name)
         );
 
-    proxmox_backup::tools::runtime::main(run_cli_command(cmd_def));
+    proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def));
 }
 
 // shell completion helper
@@ -473,16 +631,17 @@ pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String
 
         let remote: Remote = remote_config.lookup("remote", &remote)?;
 
+        let options = HttpClientOptions::new()
+            .password(Some(remote.password.clone()))
+            .fingerprint(remote.fingerprint.clone());
+
         let client = HttpClient::new(
             &remote.host,
             &remote.userid,
-            Some(remote.password),
-            remote.fingerprint,
-            false,
+            options,
         )?;
 
-        let mut rt = tokio::runtime::Runtime::new().unwrap();
-        let result = rt.block_on(client.get("api2/json/admin/datastore", None))?;
+        let result = crate::tools::runtime::block_on(client.get("api2/json/admin/datastore", None))?;
 
         if let Some(data) = result["data"].as_array() {
             for item in data {