diff --git a/src/api/router.rs b/src/api/router.rs index a70b56ee..d19ced59 100644 --- a/src/api/router.rs +++ b/src/api/router.rs @@ -11,7 +11,14 @@ use hyper::http::request::Parts; pub type BoxFut = Box, Error = failure::Error> + Send>; -type ApiHandlerFn = fn(Value, &ApiMethod) -> Result; +pub trait RpcEnvironment { + + fn set_result_attrib(&mut self, name: &str, value: Value); + + fn get_result_attrib(&self, name: &str) -> Option<&Value>; +} + +type ApiHandlerFn = fn(Value, &ApiMethod, &mut dyn RpcEnvironment) -> Result; type ApiAsyncHandlerFn = fn(Parts, Body, Value, &ApiAsyncMethod) -> Result; diff --git a/src/api2.rs b/src/api2.rs index b0c18ab0..3f59b72d 100644 --- a/src/api2.rs +++ b/src/api2.rs @@ -30,7 +30,11 @@ lazy_static! { -fn test_sync_api_handler(param: Value, _info: &ApiMethod) -> Result { +fn test_sync_api_handler( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { println!("This is a test {}", param); // let force: Option = Some(false); @@ -51,7 +55,7 @@ pub fn router() -> Router { let route4 = Router::new() .get(ApiMethod::new( - |param, _info| { + |param, _info, _rpcenv| { println!("This is a clousure handler: {}", param); Ok(json!(null)) @@ -74,7 +78,7 @@ pub fn router() -> Router { let route = Router::new() .get(ApiMethod::new( - |_,_| Ok(json!([ + |_,_,_| Ok(json!([ {"subdir": "config"}, {"subdir": "admin"}, {"subdir": "nodes"}, diff --git a/src/api2/admin.rs b/src/api2/admin.rs index 7302f191..4f42d900 100644 --- a/src/api2/admin.rs +++ b/src/api2/admin.rs @@ -8,7 +8,7 @@ pub fn router() -> Router { let route = Router::new() .get(ApiMethod::new( - |_,_| Ok(json!([ + |_,_,_| Ok(json!([ {"subdir": "datastore"} ])), ObjectSchema::new("Directory index."))) diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs index a83e51ec..503733e5 100644 --- a/src/api2/admin/datastore.rs +++ b/src/api2/admin/datastore.rs @@ -15,7 +15,11 @@ use crate::backup::datastore::*; mod catar; // this is just a test for mutability/mutex handling - will remove later -fn start_garbage_collection(param: Value, _info: &ApiMethod) -> Result { +fn start_garbage_collection( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { let store = param["store"].as_str().unwrap(); @@ -36,7 +40,11 @@ pub fn api_method_start_garbage_collection() -> ApiMethod { ) } -fn garbage_collection_status(param: Value, _info: &ApiMethod) -> Result { +fn garbage_collection_status( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { let store = param["store"].as_str().unwrap(); @@ -54,7 +62,11 @@ pub fn api_method_garbage_collection_status() -> ApiMethod { ) } -fn get_backup_list(param: Value, _info: &ApiMethod) -> Result { +fn get_backup_list( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { let config = datastore::config()?; @@ -77,7 +89,11 @@ fn get_backup_list(param: Value, _info: &ApiMethod) -> Result { Ok(result) } -fn get_datastore_list(_param: Value, _info: &ApiMethod) -> Result { +fn get_datastore_list( + _param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { let config = datastore::config()?; @@ -89,7 +105,7 @@ pub fn router() -> Router { let datastore_info = Router::new() .get(ApiMethod::new( - |_,_| Ok(json!([ + |_,_,_| Ok(json!([ {"subdir": "backups" }, {"subdir": "catar" }, {"subdir": "status"}, diff --git a/src/api2/config.rs b/src/api2/config.rs index da4177ae..30eca969 100644 --- a/src/api2/config.rs +++ b/src/api2/config.rs @@ -11,7 +11,7 @@ pub fn router() -> Router { let route = Router::new() .get(ApiMethod::new( - |_,_| Ok(json!([ + |_,_,_| Ok(json!([ {"subdir": "datastore"}, ])), ObjectSchema::new("Directory index."))) diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs index 578ea2f2..c12ca5fc 100644 --- a/src/api2/config/datastore.rs +++ b/src/api2/config/datastore.rs @@ -15,7 +15,11 @@ pub fn get() -> ApiMethod { ObjectSchema::new("Directory index.")) } -fn get_datastore_list(_param: Value, _info: &ApiMethod) -> Result { +fn get_datastore_list( + _param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { let config = datastore::config()?; @@ -31,7 +35,11 @@ pub fn post() -> ApiMethod { ) } -fn create_datastore(param: Value, _info: &ApiMethod) -> Result { +fn create_datastore( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { // fixme: locking ? @@ -64,7 +72,11 @@ pub fn delete() -> ApiMethod { .required("name", StringSchema::new("Datastore name."))) } -fn delete_datastore(param: Value, _info: &ApiMethod) -> Result { +fn delete_datastore( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { println!("This is a test {}", param); // fixme: locking ? diff --git a/src/api2/node.rs b/src/api2/node.rs index 4147a02e..ad7995d0 100644 --- a/src/api2/node.rs +++ b/src/api2/node.rs @@ -11,7 +11,7 @@ pub fn router() -> Router { let route = Router::new() .get(ApiMethod::new( - |_,_| Ok(json!([ + |_,_,_| Ok(json!([ {"subdir": "network"}, {"subdir": "syslog"}, {"subdir": "time"}, diff --git a/src/api2/node/dns.rs b/src/api2/node/dns.rs index eaac08f6..0a9184e5 100644 --- a/src/api2/node/dns.rs +++ b/src/api2/node/dns.rs @@ -51,7 +51,11 @@ fn read_etc_resolv_conf() -> Result { Ok(result) } -fn update_dns(param: Value, _info: &ApiMethod) -> Result { +fn update_dns( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { lazy_static! { static ref MUTEX: Arc> = Arc::new(Mutex::new(0)); @@ -93,7 +97,11 @@ fn update_dns(param: Value, _info: &ApiMethod) -> Result { Ok(Value::Null) } -fn get_dns(_param: Value, _info: &ApiMethod) -> Result { +fn get_dns( + _param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { read_etc_resolv_conf() } diff --git a/src/api2/node/network.rs b/src/api2/node/network.rs index 220bc4bd..f9f22595 100644 --- a/src/api2/node/network.rs +++ b/src/api2/node/network.rs @@ -6,7 +6,11 @@ use crate::api::router::*; use serde_json::{json, Value}; -fn get_network_config(_param: Value, _info: &ApiMethod) -> Result { +fn get_network_config( + _param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { Ok(json!({})) } diff --git a/src/api2/node/syslog.rs b/src/api2/node/syslog.rs index cc385fa7..5bf8c496 100644 --- a/src/api2/node/syslog.rs +++ b/src/api2/node/syslog.rs @@ -72,7 +72,11 @@ fn dump_journal( Ok((count, lines)) } -fn get_syslog(param: Value, _info: &ApiMethod) -> Result { +fn get_syslog( + param: Value, + _info: &ApiMethod, + rpcenv: &mut RpcEnvironment, +) -> Result { let (count, lines) = dump_journal( param["start"].as_u64(), @@ -81,7 +85,7 @@ fn get_syslog(param: Value, _info: &ApiMethod) -> Result { param["until"].as_str(), param["service"].as_str())?; - //fixme: $restenv->set_result_attrib('total', $count); + rpcenv.set_result_attrib("total", Value::from(count)); Ok(json!(lines)) } diff --git a/src/api2/node/time.rs b/src/api2/node/time.rs index 7197e31a..c5bc0c13 100644 --- a/src/api2/node/time.rs +++ b/src/api2/node/time.rs @@ -14,7 +14,11 @@ fn read_etc_localtime() -> Result { Ok(line.trim().to_owned()) } -fn get_time(_param: Value, _info: &ApiMethod) -> Result { +fn get_time( + _param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { let datetime = Local::now(); let offset = datetime.offset(); @@ -32,7 +36,11 @@ extern "C" { fn tzset(); } // Note:: this needs root rights ?? -fn set_timezone(param: Value, _info: &ApiMethod) -> Result { +fn set_timezone( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { let timezone = tools::required_string_param(¶m, "timezone")?; diff --git a/src/api2/subscription.rs b/src/api2/subscription.rs index 5bc0bc55..ce9c1d21 100644 --- a/src/api2/subscription.rs +++ b/src/api2/subscription.rs @@ -6,7 +6,11 @@ use crate::api::router::*; use serde_json::{json, Value}; -fn get_subscription(_param: Value, _info: &ApiMethod) -> Result { +fn get_subscription( + _param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { let url = "https://www.proxmox.com/en/proxmox-backup-server/pricing"; Ok(json!({ diff --git a/src/api2/version.rs b/src/api2/version.rs index d70f51df..161bc1ae 100644 --- a/src/api2/version.rs +++ b/src/api2/version.rs @@ -8,7 +8,11 @@ const PROXMOX_PKG_VERSION: &'static str = env!("PROXMOX_PKG_VERSION"); const PROXMOX_PKG_RELEASE: &'static str = env!("PROXMOX_PKG_RELEASE"); const PROXMOX_PKG_REPOID: &'static str = env!("PROXMOX_PKG_REPOID"); -fn get_version(_param: Value, _info: &ApiMethod) -> Result { +fn get_version( + _param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { Ok(json!({ "version": PROXMOX_PKG_VERSION, diff --git a/src/bin/catar.rs b/src/bin/catar.rs index f79cdece..74d24d97 100644 --- a/src/bin/catar.rs +++ b/src/bin/catar.rs @@ -18,10 +18,6 @@ use proxmox_backup::catar::decoder::*; use proxmox_backup::tools::*; -fn required_string_param<'a>(param: &'a Value, name: &str) -> &'a str { - param[name].as_str().expect(&format!("missing parameter '{}'", name)) -} - fn print_goodby_entries(buffer: &[u8]) -> Result<(), Error> { println!("GOODBY START: {}", buffer.len()); @@ -53,9 +49,13 @@ fn print_goodby_entries(buffer: &[u8]) -> Result<(), Error> { Ok(()) } -fn print_filenames(param: Value, _info: &ApiMethod) -> Result { +fn print_filenames( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { - let archive = required_string_param(¶m, "archive"); + let archive = tools::required_string_param(¶m, "archive")?; let file = std::fs::File::open(archive)?; let mut reader = std::io::BufReader::new(file); @@ -72,9 +72,13 @@ fn print_filenames(param: Value, _info: &ApiMethod) -> Result { Ok(Value::Null) } -fn dump_archive(param: Value, _info: &ApiMethod) -> Result { +fn dump_archive( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { - let archive = required_string_param(¶m, "archive"); + let archive = tools::required_string_param(¶m, "archive")?; let mut file = std::fs::File::open(archive)?; println!("CATAR {}", archive); @@ -117,10 +121,14 @@ fn dump_archive(param: Value, _info: &ApiMethod) -> Result { Ok(Value::Null) } -fn create_archive(param: Value, _info: &ApiMethod) -> Result { +fn create_archive( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { - let archive = required_string_param(¶m, "archive"); - let source = required_string_param(¶m, "source"); + let archive = tools::required_string_param(¶m, "archive")?; + let source = tools::required_string_param(¶m, "source")?; let source = std::path::PathBuf::from(source); diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index 10176f39..12e0cdd1 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -65,7 +65,11 @@ fn backup_image(datastore: &DataStore, file: &std::fs::File, size: usize, target } */ -fn list_backups(param: Value, _info: &ApiMethod) -> Result { +fn list_backups( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { let store = tools::required_string_param(¶m, "store")?; @@ -78,7 +82,11 @@ fn list_backups(param: Value, _info: &ApiMethod) -> Result { Ok(result) } -fn create_backup(param: Value, _info: &ApiMethod) -> Result { +fn create_backup( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut RpcEnvironment, +) -> Result { let filename = tools::required_string_param(¶m, "filename")?; let store = tools::required_string_param(¶m, "store")?; diff --git a/src/cli/command.rs b/src/cli/command.rs index 87143d82..b7e2f52f 100644 --- a/src/cli/command.rs +++ b/src/cli/command.rs @@ -9,6 +9,28 @@ use crate::api::router::*; //use crate::api::config::*; use crate::getopts; +struct CliEnvironment { + result_attributes: HashMap, +} + +impl CliEnvironment { + fn new() -> Self { + Self { result_attributes: HashMap::new() } + } +} + +impl RpcEnvironment for CliEnvironment { + + fn set_result_attrib(&mut self, name: &str, value: Value) { + self.result_attributes.insert(name.into(), value); + } + + fn get_result_attrib(&self, name: &str) -> Option<&Value> { + self.result_attributes.get(name) + } +} + + pub fn print_cli_usage() { eprintln!("Usage: TODO"); @@ -260,10 +282,11 @@ pub fn run_cli_command(def: &CommandLineInterface) -> Result<(), Error> { CommandLineInterface::Nested(map) => handle_nested_command(map, args), }; + let mut rpcenv = CliEnvironment::new(); let res = match invocation { Err(e) => return Err(UsageError(e).into()), - Ok(invocation) => (invocation.0.info.handler)(invocation.1, &invocation.0.info)?, + Ok(invocation) => (invocation.0.info.handler)(invocation.1, &invocation.0.info, &mut rpcenv)?, }; println!("Result: {}", serde_json::to_string_pretty(&res).unwrap()); diff --git a/src/server/formatter.rs b/src/server/formatter.rs index decd7063..526c8e07 100644 --- a/src/server/formatter.rs +++ b/src/server/formatter.rs @@ -1,79 +1,22 @@ use failure::*; use serde_json::{json, Value}; +use crate::api::router::RpcEnvironment; + use hyper::{Body, Response, StatusCode}; use hyper::header; pub struct OutputFormatter { - pub format_result: fn(data: Result) -> Response, + pub format_result: fn(data: Value, rpcenv: &RpcEnvironment) -> Response, + + pub format_error: fn(err: Error) -> Response, } -fn json_format_result(data: Result) -> Response { +static json_content_type: &str = "application/json;charset=UTF-8"; - let content_type = "application/json;charset=UTF-8"; - match data { - Ok(value) => { - let result = json!({ - "data": value - }); - - // todo: set result.total result.changes - - let json_str = result.to_string(); - - let raw = json_str.into_bytes(); - - let mut response = Response::new(raw.into()); - response.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static(content_type)); - return response; - } - Err(err) => { - let mut response = Response::new(Body::from(err.to_string())); - response.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static(content_type)); - *response.status_mut() = StatusCode::BAD_REQUEST; - return response; - } - } -} - -pub static JSON_FORMATTER: OutputFormatter = OutputFormatter { - format_result: json_format_result, -}; - -fn extjs_format_result(data: Result) -> Response { - - let content_type = "application/json;charset=UTF-8"; - - let result = match data { - Ok(value) => { - let result = json!({ - "data": value, - "success": true - }); - - // todo: set result.total result.changes - - result - } - Err(err) => { - let mut errors = vec![]; - - errors.push(err.to_string()); - - let result = json!({ - "errors": errors, - "success": false - }); - - result - } - }; +fn json_response(result: Value) -> Response { let json_str = result.to_string(); @@ -82,10 +25,77 @@ fn extjs_format_result(data: Result) -> Response { let mut response = Response::new(raw.into()); response.headers_mut().insert( header::CONTENT_TYPE, - header::HeaderValue::from_static(content_type)); + header::HeaderValue::from_static(json_content_type)); + response } +fn json_format_result(data: Value, rpcenv: &RpcEnvironment) -> Response { + + let mut result = json!({ + "data": data + }); + + if let Some(total) = rpcenv.get_result_attrib("total").and_then(|v| v.as_u64()) { + result["total"] = Value::from(total); + } + + if let Some(changes) = rpcenv.get_result_attrib("changes") { + result["changes"] = changes.clone(); + } + + json_response(result) +} + +fn json_format_error(err: Error) -> Response { + + let mut response = Response::new(Body::from(err.to_string())); + response.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static(json_content_type)); + *response.status_mut() = StatusCode::BAD_REQUEST; + + response +} + +pub static JSON_FORMATTER: OutputFormatter = OutputFormatter { + format_result: json_format_result, + format_error: json_format_error, +}; + +fn extjs_format_result(data: Value, rpcenv: &RpcEnvironment) -> Response { + + let mut result = json!({ + "data": data, + "success": true + }); + + if let Some(total) = rpcenv.get_result_attrib("total").and_then(|v| v.as_u64()) { + result["total"] = Value::from(total); + } + + if let Some(changes) = rpcenv.get_result_attrib("changes") { + result["changes"] = changes.clone(); + } + + + json_response(result) +} + +fn extjs_format_error(err: Error) -> Response { + + let mut errors = vec![]; + errors.push(err.to_string()); + + let result = json!({ + "errors": errors, + "success": false + }); + + json_response(result) +} + pub static EXTJS_FORMATTER: OutputFormatter = OutputFormatter { format_result: extjs_format_result, + format_error: extjs_format_error, }; diff --git a/src/server/rest.rs b/src/server/rest.rs index 97e80e10..3812ffb6 100644 --- a/src/server/rest.rs +++ b/src/server/rest.rs @@ -26,6 +26,27 @@ use hyper::service::{Service, NewService}; use hyper::rt::{Future, Stream}; use hyper::header; +struct RestEnvironment { + result_attributes: HashMap, +} + +impl RestEnvironment { + fn new() -> Self { + Self { result_attributes: HashMap::new() } + } +} + +impl RpcEnvironment for RestEnvironment { + + fn set_result_attrib(&mut self, name: &str, value: Value) { + self.result_attributes.insert(name.into(), value); + } + + fn get_result_attrib(&self, name: &str) -> Option<&Value> { + self.result_attributes.get(name) + } +} + pub struct RestServer { pub api_config: Arc, } @@ -169,10 +190,12 @@ fn handle_sync_api_request( let resp = params .and_then(move |params| { - let res = (info.handler)(params, info)?; - Ok(res) - }).then(move |result| { - Ok((formatter.format_result)(result)) + let mut rpcenv = RestEnvironment::new(); + let resp = match (info.handler)(params, info, &mut rpcenv) { + Ok(data) => (formatter.format_result)(data, &rpcenv), + Err(err) => (formatter.format_error)(err), + }; + Ok(resp) }); Box::new(resp) @@ -203,7 +226,7 @@ fn handle_async_api_request( let params = match parse_parameter_strings(¶m_list, &info.parameters, true) { Ok(v) => v, Err(err) => { - let resp = (formatter.format_result)(Err(Error::from(err))); + let resp = (formatter.format_error)(Error::from(err)); return Box::new(future::ok(resp)); } }; @@ -211,7 +234,7 @@ fn handle_async_api_request( match (info.handler)(parts, req_body, params, info) { Ok(future) => future, Err(err) => { - let resp = (formatter.format_result)(Err(Error::from(err))); + let resp = (formatter.format_error)(Error::from(err)); Box::new(future::ok(resp)) } }