]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/bin/proxmox-backup-manager.rs
start ACL api
[proxmox-backup.git] / src / bin / proxmox-backup-manager.rs
index e1a8eaeee2fceab473e1c73f2cc57a4fd438ffd7..0229d1ce6a8c2796e5443cad539bdcadb95a38ce 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 failure::*;
+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, remotes::{self, Remote}};
-use proxmox_backup::api2::types::*;
+use proxmox_backup::config::{self, remote::{self, Remote}};
+use proxmox_backup::api2::{self, types::* };
 use proxmox_backup::client::*;
 use proxmox_backup::tools::ticket::*;
 use proxmox_backup::auth_helpers::*;
@@ -34,46 +35,197 @@ 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 remotes_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!(),
+    };
+
+    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);
 
-    use proxmox_backup::api2;
+    Ok(Value::Null)
+}
+
+fn remote_commands() -> CommandLineInterface {
 
     let cmd_def = CliCommandMap::new()
-        .insert("list", CliCommand::new(&api2::config::remotes::API_METHOD_LIST_REMOTES))
+        .insert("list", CliCommand::new(&&API_METHOD_LIST_REMOTES))
         .insert(
             "create",
             // fixme: howto handle password parameter?
-            CliCommand::new(&api2::config::remotes::API_METHOD_CREATE_REMOTE)
+            CliCommand::new(&api2::config::remote::API_METHOD_CREATE_REMOTE)
                 .arg_param(&["name"])
         )
         .insert(
             "update",
-            CliCommand::new(&api2::config::remotes::API_METHOD_UPDATE_REMOTE)
+            CliCommand::new(&api2::config::remote::API_METHOD_UPDATE_REMOTE)
                 .arg_param(&["name"])
+                .completion_cb("name", config::remote::complete_remote_name)
         )
         .insert(
             "remove",
-            CliCommand::new(&api2::config::remotes::API_METHOD_DELETE_REMOTE)
+            CliCommand::new(&api2::config::remote::API_METHOD_DELETE_REMOTE)
                 .arg_param(&["name"])
-                .completion_cb("name", config::remotes::complete_remote_name)
+                .completion_cb("name", config::remote::complete_remote_name)
         );
 
     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> {
 
-    use proxmox_backup::api2;
+    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);
+
+    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));
+
+    cmd_def.into()
+}
+
+fn datastore_commands() -> CommandLineInterface {
 
     let cmd_def = CliCommandMap::new()
         .insert("list", CliCommand::new(&api2::config::datastore::API_METHOD_LIST_DATASTORES))
@@ -84,6 +236,7 @@ fn datastore_commands() -> CommandLineInterface {
         .insert("update",
                 CliCommand::new(&api2::config::datastore::API_METHOD_UPDATE_DATASTORE)
                 .arg_param(&["name"])
+                .completion_cb("name", config::datastore::complete_datastore_name)
         )
         .insert("remove",
                 CliCommand::new(&api2::config::datastore::API_METHOD_DELETE_DATASTORE)
@@ -111,7 +264,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")?;
 
@@ -142,7 +295,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")?;
 
@@ -150,13 +303,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)
 }
@@ -204,7 +357,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()?;
 
@@ -215,21 +368,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)
 }
@@ -384,6 +534,7 @@ fn cert_mgmt_cli() -> CommandLineInterface {
     cmd_def.into()
 }
 
+// fixme: avoid API redefinition
 #[api(
    input: {
         properties: {
@@ -396,6 +547,12 @@ fn cert_mgmt_cli() -> CommandLineInterface {
             "remote-store": {
                 schema: DATASTORE_SCHEMA,
             },
+            delete: {
+                description: "Delete vanished backups. This remove the local copy if the remote backup was deleted.",
+                type: Boolean,
+                optional: true,
+                default: true,
+            },
             "output-format": {
                 schema: OUTPUT_FORMAT,
                 optional: true,
@@ -408,24 +565,24 @@ async fn pull_datastore(
     remote: String,
     remote_store: String,
     local_store: String,
-    output_format: Option<String>,
+    delete: Option<bool>,
+    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()?;
 
-    let (remote_config, _digest) = remotes::config()?;
-    let remote: Remote = remote_config.lookup("remote", &remote)?;
-
-    let args = json!({
+    let mut args = json!({
         "store": local_store,
-        "remote-host": remote.host,
-        "remote-user": remote.userid,
+        "remote": remote,
         "remote-store": remote_store,
-        "remote-password": remote.password,
     });
 
+    if let Some(delete) = delete {
+        args["delete"] = delete.into();
+    }
+
     let result = client.post("api2/json/pull", Some(args)).await?;
 
     view_task_result(client, result, &output_format).await?;
@@ -436,8 +593,10 @@ async fn pull_datastore(
 fn main() {
 
     let cmd_def = CliCommandMap::new()
+        .insert("acl", acl_commands())
         .insert("datastore", datastore_commands())
-        .insert("remotes", remotes_commands())
+        .insert("user", user_commands())
+        .insert("remote", remote_commands())
         .insert("garbage-collection", garbage_collection_commands())
         .insert("cert", cert_mgmt_cli())
         .insert("task", task_mgmt_cli())
@@ -446,11 +605,11 @@ fn main() {
             CliCommand::new(&API_METHOD_PULL_DATASTORE)
                 .arg_param(&["remote", "remote-store", "local-store"])
                 .completion_cb("local-store", config::datastore::complete_datastore_name)
-                .completion_cb("remote", config::remotes::complete_remote_name)
+                .completion_cb("remote", config::remote::complete_remote_name)
                 .completion_cb("remote-store", complete_remote_datastore_name)
         );
 
-    run_cli_command(cmd_def);
+    proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def));
 }
 
 // shell completion helper
@@ -458,20 +617,23 @@ pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String
 
     let mut list = Vec::new();
 
-    let _ = proxmox::tools::try_block!({
+    let _ = proxmox::try_block!({
         let remote = param.get("remote").ok_or_else(|| format_err!("no remote"))?;
-        let (remote_config, _digest) = remotes::config()?;
+        let (remote_config, _digest) = remote::config()?;
 
         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)
+            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 {