-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::*;
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(¶m);
+
+ 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(¶m);
+
+ 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(¶m);
+
+ 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))
.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)
/// 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(¶m);
let store = tools::required_string_param(¶m, "store")?;
/// 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(¶m);
let store = tools::required_string_param(¶m, "store")?;
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)
}
/// 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(¶m);
let client = connect()?;
"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)
}
cmd_def.into()
}
+// fixme: avoid API redefinition
#[api(
input: {
properties: {
"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,
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(¶m);
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?;
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())
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
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 {