forked from proxmox-mirrors/proxmox
import the new api definitions
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
068d56ed3d
commit
b489c2ceeb
@ -9,9 +9,13 @@ bytes = "0.4"
|
|||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
futures-preview = "0.3.0-alpha"
|
futures-preview = "0.3.0-alpha"
|
||||||
http = "0.1"
|
http = "0.1"
|
||||||
|
hyper = { version = "0.13.0-alpha.1" }
|
||||||
|
proxmox-tools = { version = "0.1", path = "../proxmox-tools" }
|
||||||
|
regex = "1.0"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
url = "1.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lazy_static = "1.3"
|
lazy_static = "1.3"
|
||||||
|
@ -1,13 +1,44 @@
|
|||||||
//! Proxmox API module. This provides utilities for HTTP and command line APIs.
|
//! Proxmox API module. This provides utilities for HTTP and command line APIs.
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use http::Response;
|
use hyper::http::request::Parts;
|
||||||
|
use hyper::{Body, Response};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
/// Return type of an API method.
|
pub mod router;
|
||||||
pub type ApiOutput<Body> = Result<Response<Body>, Error>;
|
pub mod rpc_environment;
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
/// Future type of an API method. In order to support `async fn` this is a pinned box.
|
#[doc(inline)]
|
||||||
pub type ApiFuture<Body> = Pin<Box<dyn Future<Output = ApiOutput<Body>> + Send>>;
|
pub use rpc_environment::{RpcEnvironment, RpcEnvironmentType};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use router::ApiMethod;
|
||||||
|
|
||||||
|
/// A synchronous API handler gets a json Value as input and returns a json Value as output.
|
||||||
|
pub type ApiHandlerFn = &'static (dyn Fn(Value, &ApiMethod, &mut dyn RpcEnvironment) -> Result<Value, Error>
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static);
|
||||||
|
|
||||||
|
/// Asynchronous API handlers get more lower level access to request data.
|
||||||
|
pub type ApiAsyncHandlerFn = &'static (dyn Fn(
|
||||||
|
Parts,
|
||||||
|
Body,
|
||||||
|
Value,
|
||||||
|
&'static ApiMethod,
|
||||||
|
Box<dyn RpcEnvironment>,
|
||||||
|
) -> Result<ApiFuture, Error>
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static);
|
||||||
|
|
||||||
|
/// The output of an asynchronous API handler is a futrue yielding a `Response`.
|
||||||
|
pub type ApiFuture = Box<dyn Future<Output = Result<Response<Body>, failure::Error>> + Send>;
|
||||||
|
|
||||||
|
pub enum ApiHandler {
|
||||||
|
Sync(ApiHandlerFn),
|
||||||
|
Async(ApiAsyncHandlerFn),
|
||||||
|
}
|
||||||
|
264
proxmox-api/src/router.rs
Normal file
264
proxmox-api/src/router.rs
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use failure::{Error, Fail};
|
||||||
|
use hyper::{Method, StatusCode};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::schema::{ObjectSchema, Schema};
|
||||||
|
use crate::ApiHandler;
|
||||||
|
use crate::RpcEnvironment;
|
||||||
|
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
pub struct HttpError {
|
||||||
|
pub code: StatusCode,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpError {
|
||||||
|
pub fn new(code: StatusCode, message: String) -> Self {
|
||||||
|
HttpError { code, message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for HttpError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! http_err {
|
||||||
|
($status:ident, $msg:expr) => {{
|
||||||
|
Error::from(HttpError::new(StatusCode::$status, $msg))
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct defines synchronous API call which returns the restulkt as json `Value`
|
||||||
|
pub struct ApiMethod {
|
||||||
|
/// The protected flag indicates that the provides function should be forwarded
|
||||||
|
/// to the deaemon running in priviledged mode.
|
||||||
|
pub protected: bool,
|
||||||
|
/// This flag indicates that the provided method may change the local timezone, so the server
|
||||||
|
/// should do a tzset afterwards
|
||||||
|
pub reload_timezone: bool,
|
||||||
|
/// Parameter type Schema
|
||||||
|
pub parameters: &'static ObjectSchema,
|
||||||
|
/// Return type Schema
|
||||||
|
pub returns: &'static Schema,
|
||||||
|
/// Handler function
|
||||||
|
pub handler: &'static ApiHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for ApiMethod {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "ApiMethod {{ ")?;
|
||||||
|
write!(f, " parameters: {:?}", self.parameters)?;
|
||||||
|
write!(f, " returns: {:?}", self.returns)?;
|
||||||
|
write!(f, " handler: {:p}", &self.handler)?;
|
||||||
|
write!(f, "}}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NULL_SCHEMA: Schema = Schema::Null;
|
||||||
|
|
||||||
|
fn dummy_handler_fn(
|
||||||
|
_arg: Value,
|
||||||
|
_method: &ApiMethod,
|
||||||
|
_env: &mut dyn RpcEnvironment,
|
||||||
|
) -> Result<Value, Error> {
|
||||||
|
// do nothing
|
||||||
|
Ok(Value::Null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DUMMY_HANDLER: ApiHandler = ApiHandler::Sync(&dummy_handler_fn);
|
||||||
|
|
||||||
|
impl ApiMethod {
|
||||||
|
pub const fn new(handler: &'static ApiHandler, parameters: &'static ObjectSchema) -> Self {
|
||||||
|
Self {
|
||||||
|
parameters,
|
||||||
|
handler,
|
||||||
|
returns: &NULL_SCHEMA,
|
||||||
|
protected: false,
|
||||||
|
reload_timezone: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn new_dummy(parameters: &'static ObjectSchema) -> Self {
|
||||||
|
Self {
|
||||||
|
parameters,
|
||||||
|
handler: &DUMMY_HANDLER,
|
||||||
|
returns: &NULL_SCHEMA,
|
||||||
|
protected: false,
|
||||||
|
reload_timezone: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn returns(mut self, schema: &'static Schema) -> Self {
|
||||||
|
self.returns = schema;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn protected(mut self, protected: bool) -> Self {
|
||||||
|
self.protected = protected;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn reload_timezone(mut self, reload_timezone: bool) -> Self {
|
||||||
|
self.reload_timezone = reload_timezone;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SubdirMap = &'static [(&'static str, &'static Router)];
|
||||||
|
|
||||||
|
pub enum SubRoute {
|
||||||
|
//Hash(HashMap<String, Router>),
|
||||||
|
Map(SubdirMap),
|
||||||
|
MatchAll {
|
||||||
|
router: &'static Router,
|
||||||
|
param_name: &'static str,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro to create an ApiMethod to list entries from SubdirMap
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! list_subdirs_api_method {
|
||||||
|
($map:expr) => {
|
||||||
|
ApiMethod::new(
|
||||||
|
&ApiHandler::Sync( & |_, _, _| {
|
||||||
|
let index = serde_json::json!(
|
||||||
|
$map.iter().map(|s| serde_json::json!({ "subdir": s.0}))
|
||||||
|
.collect::<Vec<serde_json::Value>>()
|
||||||
|
);
|
||||||
|
Ok(index)
|
||||||
|
}),
|
||||||
|
&crate::api_schema::ObjectSchema::new("Directory index.", &[]).additional_properties(true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Router {
|
||||||
|
pub get: Option<&'static ApiMethod>,
|
||||||
|
pub put: Option<&'static ApiMethod>,
|
||||||
|
pub post: Option<&'static ApiMethod>,
|
||||||
|
pub delete: Option<&'static ApiMethod>,
|
||||||
|
pub subroute: Option<SubRoute>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Router {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
get: None,
|
||||||
|
put: None,
|
||||||
|
post: None,
|
||||||
|
delete: None,
|
||||||
|
subroute: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn subdirs(mut self, map: SubdirMap) -> Self {
|
||||||
|
self.subroute = Some(SubRoute::Map(map));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn match_all(mut self, param_name: &'static str, router: &'static Router) -> Self {
|
||||||
|
self.subroute = Some(SubRoute::MatchAll { router, param_name });
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn get(mut self, m: &'static ApiMethod) -> Self {
|
||||||
|
self.get = Some(m);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn put(mut self, m: &'static ApiMethod) -> Self {
|
||||||
|
self.put = Some(m);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn post(mut self, m: &'static ApiMethod) -> Self {
|
||||||
|
self.post = Some(m);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as post, buth async (fixme: expect Async)
|
||||||
|
pub const fn upload(mut self, m: &'static ApiMethod) -> Self {
|
||||||
|
self.post = Some(m);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as get, but async (fixme: expect Async)
|
||||||
|
pub const fn download(mut self, m: &'static ApiMethod) -> Self {
|
||||||
|
self.get = Some(m);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as get, but async (fixme: expect Async)
|
||||||
|
pub const fn upgrade(mut self, m: &'static ApiMethod) -> Self {
|
||||||
|
self.get = Some(m);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn delete(mut self, m: &'static ApiMethod) -> Self {
|
||||||
|
self.delete = Some(m);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_route(
|
||||||
|
&self,
|
||||||
|
components: &[&str],
|
||||||
|
uri_param: &mut HashMap<String, String>,
|
||||||
|
) -> Option<&Router> {
|
||||||
|
if components.is_empty() {
|
||||||
|
return Some(self);
|
||||||
|
};
|
||||||
|
|
||||||
|
let (dir, rest) = (components[0], &components[1..]);
|
||||||
|
|
||||||
|
match self.subroute {
|
||||||
|
None => {}
|
||||||
|
Some(SubRoute::Map(dirmap)) => {
|
||||||
|
if let Ok(ind) = dirmap.binary_search_by_key(&dir, |(name, _)| name) {
|
||||||
|
let (_name, router) = dirmap[ind];
|
||||||
|
//println!("FOUND SUBDIR {}", dir);
|
||||||
|
return router.find_route(rest, uri_param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(SubRoute::MatchAll { router, param_name }) => {
|
||||||
|
//println!("URI PARAM {} = {}", param_name, dir); // fixme: store somewhere
|
||||||
|
uri_param.insert(param_name.to_owned(), dir.into());
|
||||||
|
return router.find_route(rest, uri_param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_method(
|
||||||
|
&self,
|
||||||
|
components: &[&str],
|
||||||
|
method: Method,
|
||||||
|
uri_param: &mut HashMap<String, String>,
|
||||||
|
) -> Option<&ApiMethod> {
|
||||||
|
if let Some(info) = self.find_route(components, uri_param) {
|
||||||
|
return match method {
|
||||||
|
Method::GET => info.get,
|
||||||
|
Method::PUT => info.put,
|
||||||
|
Method::POST => info.post,
|
||||||
|
Method::DELETE => info.delete,
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Router {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
37
proxmox-api/src/rpc_environment.rs
Normal file
37
proxmox-api/src/rpc_environment.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use proxmox_tools::AsAny;
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
/// Abstract Interface for API methods to interact with the environment
|
||||||
|
pub trait RpcEnvironment: std::any::Any + AsAny + Send {
|
||||||
|
/// Use this to pass additional result data. It is up to the environment
|
||||||
|
/// how the data is used.
|
||||||
|
fn set_result_attrib(&mut self, name: &str, value: Value);
|
||||||
|
|
||||||
|
/// Query additional result data.
|
||||||
|
fn get_result_attrib(&self, name: &str) -> Option<&Value>;
|
||||||
|
|
||||||
|
/// The environment type
|
||||||
|
fn env_type(&self) -> RpcEnvironmentType;
|
||||||
|
|
||||||
|
/// Set user name
|
||||||
|
fn set_user(&mut self, user: Option<String>);
|
||||||
|
|
||||||
|
/// Get user name
|
||||||
|
fn get_user(&self) -> Option<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Environment Type
|
||||||
|
///
|
||||||
|
/// We use this to enumerate the different environment types. Some methods
|
||||||
|
/// needs to do different things when started from the command line interface,
|
||||||
|
/// or when executed from a privileged server running as root.
|
||||||
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
|
pub enum RpcEnvironmentType {
|
||||||
|
/// Command started from command line
|
||||||
|
CLI,
|
||||||
|
/// Access from public accessible server
|
||||||
|
PUBLIC,
|
||||||
|
/// Access from privileged server (run as root)
|
||||||
|
PRIVILEGED,
|
||||||
|
}
|
1051
proxmox-api/src/schema.rs
Normal file
1051
proxmox-api/src/schema.rs
Normal file
File diff suppressed because it is too large
Load Diff
14
proxmox-tools/src/as_any.rs
Normal file
14
proxmox-tools/src/as_any.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
/// An easy way to convert types to Any
|
||||||
|
///
|
||||||
|
/// Mostly useful to downcast trait objects (see RpcEnvironment).
|
||||||
|
pub trait AsAny {
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Any> AsAny for T {
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
use failure::*;
|
use failure::*;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
pub mod as_any;
|
||||||
pub mod borrow;
|
pub mod borrow;
|
||||||
pub mod common_regex;
|
pub mod common_regex;
|
||||||
pub mod fd;
|
pub mod fd;
|
||||||
@ -16,6 +17,9 @@ pub mod vec;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use uuid::Uuid;
|
pub use uuid::Uuid;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use as_any::AsAny;
|
||||||
|
|
||||||
/// An identity (nop) macro. Used by the `#[sortable]` proc macro.
|
/// An identity (nop) macro. Used by the `#[sortable]` proc macro.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! identity {
|
macro_rules! identity {
|
||||||
|
Loading…
Reference in New Issue
Block a user