2021-09-29 07:04:19 +00:00
|
|
|
use std::collections::HashMap;
|
2021-10-01 05:29:11 +00:00
|
|
|
use std::future::Future;
|
|
|
|
use std::pin::Pin;
|
2022-04-06 14:55:39 +00:00
|
|
|
use std::sync::Mutex;
|
2021-09-29 07:04:19 +00:00
|
|
|
|
|
|
|
use anyhow::{bail, format_err, Error};
|
2021-10-05 09:01:05 +00:00
|
|
|
use http::request::Parts;
|
|
|
|
use http::HeaderMap;
|
2022-04-06 14:55:39 +00:00
|
|
|
use hyper::{Body, Method, Response};
|
|
|
|
use lazy_static::lazy_static;
|
2021-09-29 07:04:19 +00:00
|
|
|
|
2022-04-06 14:55:39 +00:00
|
|
|
use proxmox_router::{
|
|
|
|
list_subdirs_api_method, Router, RpcEnvironmentType, SubdirMap, UserInformation,
|
|
|
|
};
|
2021-10-08 09:19:37 +00:00
|
|
|
use proxmox_schema::api;
|
|
|
|
|
2022-04-06 14:55:39 +00:00
|
|
|
use proxmox_rest_server::{ApiConfig, AuthError, RestEnvironment, RestServer, ServerAdapter};
|
2021-10-05 09:01:05 +00:00
|
|
|
|
|
|
|
// Create a Dummy User information system
|
2021-09-29 07:04:19 +00:00
|
|
|
struct DummyUserInfo;
|
|
|
|
|
|
|
|
impl UserInformation for DummyUserInfo {
|
|
|
|
fn is_superuser(&self, _userid: &str) -> bool {
|
2021-10-05 09:01:05 +00:00
|
|
|
// Always return true here, so we have access to everthing
|
2021-09-29 07:04:19 +00:00
|
|
|
true
|
|
|
|
}
|
|
|
|
fn is_group_member(&self, _userid: &str, group: &str) -> bool {
|
|
|
|
group == "Group"
|
|
|
|
}
|
|
|
|
fn lookup_privs(&self, _userid: &str, _path: &[&str]) -> u64 {
|
|
|
|
u64::MAX
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-05 09:01:05 +00:00
|
|
|
struct MinimalServer;
|
|
|
|
|
|
|
|
// implement the server adapter
|
|
|
|
impl ServerAdapter for MinimalServer {
|
|
|
|
// normally this would check and authenticate the user
|
|
|
|
fn check_auth(
|
|
|
|
&self,
|
|
|
|
_headers: &HeaderMap,
|
|
|
|
_method: &Method,
|
2022-04-06 14:55:39 +00:00
|
|
|
) -> Pin<
|
|
|
|
Box<
|
|
|
|
dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>>
|
|
|
|
+ Send,
|
|
|
|
>,
|
|
|
|
> {
|
2021-10-01 05:29:11 +00:00
|
|
|
Box::pin(async move {
|
|
|
|
// get some global/cached userinfo
|
|
|
|
let userinfo: Box<dyn UserInformation + Sync + Send> = Box::new(DummyUserInfo);
|
|
|
|
// Do some user checks, e.g. cookie/csrf
|
|
|
|
Ok(("User".to_string(), userinfo))
|
|
|
|
})
|
2021-09-29 07:04:19 +00:00
|
|
|
}
|
|
|
|
|
2022-05-12 09:57:51 +00:00
|
|
|
// this should return the index page of the webserver, iow. what the user browses to
|
2021-10-05 09:01:05 +00:00
|
|
|
fn get_index(
|
|
|
|
&self,
|
|
|
|
_env: RestEnvironment,
|
|
|
|
_parts: Parts,
|
|
|
|
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send>> {
|
|
|
|
Box::pin(async move {
|
|
|
|
// build an index page
|
|
|
|
http::Response::builder()
|
|
|
|
.body("hello world".into())
|
|
|
|
.unwrap()
|
|
|
|
})
|
|
|
|
}
|
2021-09-29 07:04:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// a few examples on how to do api calls with the Router
|
|
|
|
|
|
|
|
#[api]
|
|
|
|
/// A simple ping method. returns "pong"
|
|
|
|
fn ping() -> Result<String, Error> {
|
|
|
|
Ok("pong".to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
static ref ITEM_MAP: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[api]
|
|
|
|
/// Lists all current items
|
|
|
|
fn list_items() -> Result<Vec<String>, Error> {
|
2022-02-08 13:57:16 +00:00
|
|
|
Ok(ITEM_MAP.lock().unwrap().keys().cloned().collect())
|
2021-09-29 07:04:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
name: {
|
|
|
|
type: String,
|
|
|
|
description: "The name",
|
|
|
|
},
|
|
|
|
value: {
|
|
|
|
type: String,
|
|
|
|
description: "The value",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// creates a new item
|
|
|
|
fn create_item(name: String, value: String) -> Result<(), Error> {
|
|
|
|
let mut map = ITEM_MAP.lock().unwrap();
|
|
|
|
if map.contains_key(&name) {
|
|
|
|
bail!("{} already exists", name);
|
|
|
|
}
|
|
|
|
|
|
|
|
map.insert(name, value);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
name: {
|
|
|
|
type: String,
|
|
|
|
description: "The name",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// returns the value of an item
|
|
|
|
fn get_item(name: String) -> Result<String, Error> {
|
2022-04-06 14:55:39 +00:00
|
|
|
ITEM_MAP
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.get(&name)
|
|
|
|
.map(|s| s.to_string())
|
|
|
|
.ok_or_else(|| format_err!("no such item '{}'", name))
|
2021-09-29 07:04:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
name: {
|
|
|
|
type: String,
|
|
|
|
description: "The name",
|
|
|
|
},
|
|
|
|
value: {
|
|
|
|
type: String,
|
|
|
|
description: "The value",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// updates an item
|
|
|
|
fn update_item(name: String, value: String) -> Result<(), Error> {
|
|
|
|
if let Some(val) = ITEM_MAP.lock().unwrap().get_mut(&name) {
|
|
|
|
*val = value;
|
|
|
|
} else {
|
|
|
|
bail!("no such item '{}'", name);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
name: {
|
|
|
|
type: String,
|
|
|
|
description: "The name",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// deletes an item
|
|
|
|
fn delete_item(name: String) -> Result<(), Error> {
|
|
|
|
if ITEM_MAP.lock().unwrap().remove(&name).is_none() {
|
|
|
|
bail!("no such item '{}'", name);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
const ITEM_ROUTER: Router = Router::new()
|
|
|
|
.get(&API_METHOD_GET_ITEM)
|
|
|
|
.put(&API_METHOD_UPDATE_ITEM)
|
|
|
|
.delete(&API_METHOD_DELETE_ITEM);
|
|
|
|
|
|
|
|
const SUBDIRS: SubdirMap = &[
|
|
|
|
(
|
|
|
|
"items",
|
|
|
|
&Router::new()
|
|
|
|
.get(&API_METHOD_LIST_ITEMS)
|
|
|
|
.post(&API_METHOD_CREATE_ITEM)
|
2022-04-06 14:55:39 +00:00
|
|
|
.match_all("name", &ITEM_ROUTER),
|
2021-09-29 07:04:19 +00:00
|
|
|
),
|
2022-04-06 14:55:39 +00:00
|
|
|
("ping", &Router::new().get(&API_METHOD_PING)),
|
2021-09-29 07:04:19 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
const ROUTER: Router = Router::new()
|
|
|
|
.get(&list_subdirs_api_method!(SUBDIRS))
|
|
|
|
.subdirs(SUBDIRS);
|
|
|
|
|
|
|
|
async fn run() -> Result<(), Error> {
|
|
|
|
// we first have to configure the api environment (basedir etc.)
|
|
|
|
|
|
|
|
let config = ApiConfig::new(
|
|
|
|
"/var/tmp/",
|
|
|
|
&ROUTER,
|
|
|
|
RpcEnvironmentType::PUBLIC,
|
2021-10-05 09:01:05 +00:00
|
|
|
MinimalServer,
|
2021-09-29 07:04:19 +00:00
|
|
|
)?;
|
|
|
|
let rest_server = RestServer::new(config);
|
|
|
|
|
2022-05-12 09:57:51 +00:00
|
|
|
// then we have to create a daemon that listens, accepts and serves the api to clients
|
rest server: daemon: update PID file before sending MAINPID notification
There is a race upon reload, where it can happen that:
1. systemd forks off /bin/kill -HUP $MAINPID
2. Current instance forks off new one and notifies systemd with the
new MAINPID.
3. systemd sets new MAINPID.
4. systemd receives SIGCHLD for the kill process (which is the current
control process for the service) and reads the PID of the old
instance from the PID file, resetting MAINPID to the PID of the old
instance.
5. Old instance exits.
6. systemd receives SIGCHLD for the old instance, reads the PID of the
old instance from the PID file once more. systemd sees that the
MAINPID matches the child PID and considers the service exited.
7. systemd receivese notification from the new PID and is confused.
The service won't get active, because the notification wasn't
handled.
To fix it, update the PID file before sending the MAINPID
notification, similar to what a comment in systemd's
src/core/service.c suggests:
> /* Forking services may occasionally move to a new PID.
> * As long as they update the PID file before exiting the old
> * PID, they're fine. */
but for our Type=notify "before sending the notification" rather than
"before exiting", because otherwise, the mix-up in 4. could still
happen (although it might not actually be problematic without the
mix-up in 6., it still seems better to avoid).
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2022-05-04 11:33:24 +00:00
|
|
|
proxmox_rest_server::daemon::create_daemon(
|
|
|
|
([127, 0, 0, 1], 65000).into(),
|
|
|
|
move |listener| {
|
|
|
|
let incoming = hyper::server::conn::AddrIncoming::from_listener(listener)?;
|
2021-09-29 07:04:19 +00:00
|
|
|
|
rest server: daemon: update PID file before sending MAINPID notification
There is a race upon reload, where it can happen that:
1. systemd forks off /bin/kill -HUP $MAINPID
2. Current instance forks off new one and notifies systemd with the
new MAINPID.
3. systemd sets new MAINPID.
4. systemd receives SIGCHLD for the kill process (which is the current
control process for the service) and reads the PID of the old
instance from the PID file, resetting MAINPID to the PID of the old
instance.
5. Old instance exits.
6. systemd receives SIGCHLD for the old instance, reads the PID of the
old instance from the PID file once more. systemd sees that the
MAINPID matches the child PID and considers the service exited.
7. systemd receivese notification from the new PID and is confused.
The service won't get active, because the notification wasn't
handled.
To fix it, update the PID file before sending the MAINPID
notification, similar to what a comment in systemd's
src/core/service.c suggests:
> /* Forking services may occasionally move to a new PID.
> * As long as they update the PID file before exiting the old
> * PID, they're fine. */
but for our Type=notify "before sending the notification" rather than
"before exiting", because otherwise, the mix-up in 4. could still
happen (although it might not actually be problematic without the
mix-up in 6., it still seems better to avoid).
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2022-05-04 11:33:24 +00:00
|
|
|
Ok(async move {
|
|
|
|
hyper::Server::builder(incoming).serve(rest_server).await?;
|
2021-09-29 09:21:32 +00:00
|
|
|
|
rest server: daemon: update PID file before sending MAINPID notification
There is a race upon reload, where it can happen that:
1. systemd forks off /bin/kill -HUP $MAINPID
2. Current instance forks off new one and notifies systemd with the
new MAINPID.
3. systemd sets new MAINPID.
4. systemd receives SIGCHLD for the kill process (which is the current
control process for the service) and reads the PID of the old
instance from the PID file, resetting MAINPID to the PID of the old
instance.
5. Old instance exits.
6. systemd receives SIGCHLD for the old instance, reads the PID of the
old instance from the PID file once more. systemd sees that the
MAINPID matches the child PID and considers the service exited.
7. systemd receivese notification from the new PID and is confused.
The service won't get active, because the notification wasn't
handled.
To fix it, update the PID file before sending the MAINPID
notification, similar to what a comment in systemd's
src/core/service.c suggests:
> /* Forking services may occasionally move to a new PID.
> * As long as they update the PID file before exiting the old
> * PID, they're fine. */
but for our Type=notify "before sending the notification" rather than
"before exiting", because otherwise, the mix-up in 4. could still
happen (although it might not actually be problematic without the
mix-up in 6., it still seems better to avoid).
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2022-05-04 11:33:24 +00:00
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
2022-04-06 14:55:39 +00:00
|
|
|
.await?;
|
2021-09-29 07:04:19 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> Result<(), Error> {
|
|
|
|
let rt = tokio::runtime::Runtime::new()?;
|
|
|
|
rt.block_on(async { run().await })
|
|
|
|
}
|