aboutsummaryrefslogtreecommitdiffstats
path: root/zoned
diff options
context:
space:
mode:
authorToby Vincent <tobyv13@gmail.com>2022-03-17 18:19:50 -0500
committerToby Vincent <tobyv13@gmail.com>2022-03-17 18:19:50 -0500
commite18482558a43330cab726ae92518904be346a248 (patch)
tree0ec733e16c2511734a155b5d59c6bd5ddba00aa1 /zoned
parent8858295736b507825f279f51303ab6e9f939f423 (diff)
refactor: migrated from rocket to axum
Diffstat (limited to 'zoned')
-rw-r--r--zoned/Cargo.toml9
-rw-r--r--zoned/src/api.rs109
-rw-r--r--zoned/src/config.rs113
-rw-r--r--zoned/src/error.rs56
-rw-r--r--zoned/src/lib.rs8
-rw-r--r--zoned/src/main.rs39
-rw-r--r--zoned/src/state.rs20
7 files changed, 238 insertions, 116 deletions
diff --git a/zoned/Cargo.toml b/zoned/Cargo.toml
index deb76fa..408a4d4 100644
--- a/zoned/Cargo.toml
+++ b/zoned/Cargo.toml
@@ -13,13 +13,14 @@ description = "daemon for managing containers using systemd-nspawn and ZFS"
[dependencies]
anyhow = "1.0.53"
-figment = "0.10.6"
-lazy_static = "1.4.0"
-rocket = { version = "0.5.0-rc.1", default-features = false, features = ["json"] }
-rocket_okapi = { version = "0.8.0-rc.1", features = ["rapidoc", "swagger"] }
+axum = { version = "0.4.6", features = ["json"] }
+figment = { version = "0.10.6", features = ["toml", "env", "test"] }
serde = "1.0.136"
+serde_json = "1.0.79"
thiserror = "1.0.30"
+tokio = { version = "1.17.0", features = ["full"] }
tracing = "0.1.31"
+tracing-subscriber = "0.3.9"
zone_core = { version = "0.1.0", path = "../zone_core" }
zone_nspawn = { version = "0.1.0", path = "../zone_nspawn" }
zone_zfs = { version = "0.1.0", path = "../zone_zfs", features = ["chrono"] }
diff --git a/zoned/src/api.rs b/zoned/src/api.rs
index 37f2bef..8dc2c39 100644
--- a/zoned/src/api.rs
+++ b/zoned/src/api.rs
@@ -1,84 +1,61 @@
-use crate::{Config, Error, Result};
-use rocket::{get, post, serde::json::Json, Build, Rocket, State};
-use rocket_okapi::{
- openapi, openapi_get_routes,
- rapidoc::{make_rapidoc, GeneralConfig, HideShowConfig, RapiDocConfig},
- settings::UrlObject,
- swagger_ui::{make_swagger_ui, SwaggerUIConfig},
+use axum::{
+ extract::{Extension, Query},
+ routing::{get, post},
+ Json, Router,
};
-use zone_core::Container;
+use std::sync::Arc;
+use tracing::warn;
+use zone_core::{Container, ContainerOptions, FilterContainer};
use zone_nspawn::NSpawn;
+use crate::{Error, Result, State};
+
+pub fn build_routes() -> Router {
+ Router::new()
+ .route("/test", get(test_endpoint))
+ .route("/container", post(clone_container))
+ .route("/container/list?<container..>", get(container_list))
+}
+
/// # Test endpoint
///
/// Returns a list of containers based on the query.
-#[openapi(tag = "Testing")]
-#[get("/test")]
-pub fn test_endpoint(zfs: &State<zone_zfs::ZFS>) -> Json<String> {
- Json(zfs.config.pool_name.to_owned())
+async fn test_endpoint(Extension(state): Extension<Arc<State>>) -> Json<String> {
+ Json(state.zfs.config.pool_name.to_owned())
}
/// List containers
///
/// Returns a list of containers based on the query.
-#[openapi(tag = "Container")]
-#[get("/container/list?<container..>")]
-pub fn container_list(container: Container) -> Result<Json<Vec<Container>>> {
- NSpawn::get_containers()
- .map(|v| container.filter_from(v))
- .map(Json::from)
- .map_err(Error::from)
+async fn container_list(
+ container: Option<Query<ContainerOptions>>,
+) -> Result<Json<Vec<Container>>> {
+ let mut containers = NSpawn::get_containers()?.into_iter().filter_map(|c| {
+ Container::try_from(c)
+ .map_err(|err| warn!("Ignoring invalid nspawn container {:?}", err))
+ .ok()
+ });
+
+ match container {
+ Some(Query(params)) => Ok(containers.filter_container(params).into()),
+ _ => Ok(containers.collect::<Vec<_>>().into()),
+ }
}
/// Create container
///
/// Creates a new container volume from the provided container json data
-#[openapi(tag = "Container")]
-#[post("/container", data = "<container>")]
-fn clone_container(
- container: Json<Container>,
- zfs: &State<zone_zfs::ZFS>,
+async fn clone_container(
+ Json(container): Json<Container>,
+ Extension(state): Extension<Arc<State>>,
) -> Result<Json<Container>> {
- zfs.clone_from_latest(
- format!("{}-{}", container.user, container.id).into(),
- container.template.to_owned().into(),
- )?
- .try_into()
- .map_err(Error::from)
- .map(Container::into)
-}
-
-pub fn build_rocket(config: &Config) -> Result<Rocket<Build>> {
- let nspawn = zone_nspawn::NSpawn::custom(&config.nspawn_config).map_err(Error::from)?;
- let zfs = zone_zfs::ZFS::custom(&config.zfs_config).map_err(Error::from)?;
-
- Ok(rocket::custom(&config.rocket_config)
- .mount(
- "/",
- openapi_get_routes![test_endpoint, container_list, clone_container,],
- )
- .mount(
- "/swagger-ui/",
- make_swagger_ui(&SwaggerUIConfig {
- url: "../openapi.json".to_owned(),
- ..Default::default()
- }),
- )
- .mount(
- "/rapidoc/",
- make_rapidoc(&RapiDocConfig {
- general: GeneralConfig {
- spec_urls: vec![UrlObject::new("General", "../openapi.json")],
- ..Default::default()
- },
- hide_show: HideShowConfig {
- allow_spec_url_load: false,
- allow_spec_file_load: false,
- ..Default::default()
- },
- ..Default::default()
- }),
- )
- .manage(nspawn)
- .manage(zfs))
+ state
+ .zfs
+ .clone_from_latest(
+ format!("{}-{}", container.user, container.id).into(),
+ container.template.into(),
+ )?
+ .try_into()
+ .map_err(Error::from)
+ .map(Container::into)
}
diff --git a/zoned/src/config.rs b/zoned/src/config.rs
index c9cd5f3..f6622f1 100644
--- a/zoned/src/config.rs
+++ b/zoned/src/config.rs
@@ -1,10 +1,115 @@
+use figment::Figment;
use serde::{Deserialize, Serialize};
+use std::net::{IpAddr, SocketAddr};
-#[derive(Default, Serialize, Deserialize)]
+use crate::{Error, Result};
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Config {
- pub(crate) rocket_config: rocket::Config,
+ pub ip_address: IpAddr,
+ pub port: u16,
+ pub zfs: zone_zfs::Config,
+ pub nspawn: zone_nspawn::Config,
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ ip_address: zone_core::DEFAULT_IP_ADDRESS,
+ port: zone_core::DEFAULT_PORT,
+ zfs: Default::default(),
+ nspawn: Default::default(),
+ }
+ }
+}
+
+impl TryFrom<Figment> for Config {
+ type Error = Error;
+
+ fn try_from(value: Figment) -> Result<Self> {
+ value.extract().map_err(Into::into)
+ }
+}
+
+impl From<Config> for SocketAddr {
+ fn from(val: Config) -> Self {
+ SocketAddr::from((val.ip_address, val.port))
+ }
+}
+#[cfg(test)]
+mod tests {
+ use std::path::PathBuf;
+
+ use figment::providers::{Format, Serialized, Toml};
+
+ use super::*;
+
+ #[test]
+ fn defaults() {
+ figment::Jail::expect_with(|jail| {
+ jail.create_file(
+ "Config.toml",
+ r#"
+ ip_address = "127.0.0.1"
+ port = 8000
+
+ [zfs]
+ quota = "16G"
+ pool_name = "pool"
+ mountpoint = "/srv"
+ "#,
+ )?;
+
+ let config: Config = Figment::from(Serialized::defaults(Config::default()))
+ .merge(Toml::file("Config.toml"))
+ .extract()?;
+
+ assert_eq!(config, Config::default());
+
+ Ok(())
+ });
+ }
+
+ #[test]
+ fn custom() {
+ figment::Jail::expect_with(|jail| {
+ jail.create_file(
+ "Config.toml",
+ r#"
+ ip_address = "192.168.1.1"
+ port = 6555
+
+ [zfs]
+ quota = 42000000
+ pool_name = "fool"
+ mountpoint = "/mnt"
+
+ [nspawn]
+ network_configs_path = "/etc/zoned/network.d"
+ "#,
+ )?;
+
+ let config: Config = Figment::from(Serialized::defaults(Config::default()))
+ .merge(Toml::file("Config.toml"))
+ .extract()?;
- pub(crate) zfs_config: zone_zfs::Config,
+ assert_eq!(
+ config,
+ Config {
+ ip_address: [192, 168, 1, 1].into(),
+ port: 6555,
+ zfs: zone_zfs::Config {
+ quota: 42_000_000u64.into(),
+ pool_name: String::from("fool"),
+ mountpoint: PathBuf::from("/mnt"),
+ },
+ nspawn: zone_nspawn::Config {
+ network_configs_path: Some(PathBuf::from("/etc/zoned/network.d")),
+ },
+ }
+ );
- pub(crate) nspawn_config: zone_nspawn::Config,
+ Ok(())
+ });
+ }
}
diff --git a/zoned/src/error.rs b/zoned/src/error.rs
index 14b30db..98fbf2f 100644
--- a/zoned/src/error.rs
+++ b/zoned/src/error.rs
@@ -1,52 +1,54 @@
-use rocket::{
- http::Status,
- response::{self, Responder},
- Request,
-};
-use rocket_okapi::{
- gen::OpenApiGenerator, okapi::openapi3::Responses, response::OpenApiResponderInner,
- util::ensure_status_code_exists,
+use axum::{
+ http::StatusCode,
+ response::{IntoResponse, Response},
+ Json,
};
+use serde_json::json;
use thiserror::Error;
-use zone_core::Container;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
- #[error("Container Error {0:?}")]
- Container(Container),
+ #[error("Container Error: {0:?}")]
+ Container(String),
- #[error("ZFS Error {source:?}")]
+ #[error("ZFS Error: {source:?}")]
ZFS {
#[from]
source: zone_zfs::Error,
},
- #[error("NSpawn Error {source:?}")]
+ #[error("NSpawn Error: {source:?}")]
Nspawn {
#[from]
source: zone_nspawn::Error,
},
+ #[error("Config Error: {source:?}")]
+ Config {
+ #[from]
+ source: figment::Error,
+ },
+
+ #[error("Container not found")]
+ NotFound,
+
#[error(transparent)]
Other(#[from] anyhow::Error),
}
-impl<'r, 'o: 'r> Responder<'r, 'o> for Error {
- fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
- // https://stuarth.github.io/rocket-error-handling/
- // match self {
- // _ => Status::InternalServerError.respond_to(req),
- // }
- Status::InternalServerError.respond_to(req)
- }
-}
+impl IntoResponse for Error {
+ fn into_response(self) -> Response {
+ let (status, error_message) = match self {
+ Error::Container(source) => (StatusCode::NOT_FOUND, source),
+ err => (StatusCode::UNPROCESSABLE_ENTITY, format!("{}", err)),
+ };
+
+ let body = Json(json!({
+ "error": error_message,
+ }));
-impl OpenApiResponderInner for Error {
- fn responses(_gen: &mut OpenApiGenerator) -> rocket_okapi::Result<Responses> {
- let mut responses = Responses::default();
- ensure_status_code_exists(&mut responses, 500);
- Ok(responses)
+ (status, body).into_response()
}
}
diff --git a/zoned/src/lib.rs b/zoned/src/lib.rs
index f6c3926..944a264 100644
--- a/zoned/src/lib.rs
+++ b/zoned/src/lib.rs
@@ -1,7 +1,9 @@
-pub use api::build_rocket;
-pub use config::Config;
-pub use error::{Error, Result};
+pub use crate::api::build_routes;
+pub use crate::config::Config;
+pub use crate::error::{Error, Result};
+pub use crate::state::State;
mod api;
mod config;
mod error;
+mod state;
diff --git a/zoned/src/main.rs b/zoned/src/main.rs
index 7a39ea5..caff470 100644
--- a/zoned/src/main.rs
+++ b/zoned/src/main.rs
@@ -1,27 +1,42 @@
+use axum::AddExtensionLayer;
use figment::{
providers::{Env, Format, Serialized, Toml},
Figment,
};
-use zoned::Config;
+use std::{net::SocketAddr, sync::Arc};
+use tracing::{debug, error};
+use zoned::{build_routes, Config, State};
-#[rocket::main]
+#[tokio::main]
async fn main() {
- let config: Config = Figment::from(Serialized::defaults(Config::default()))
+ tracing_subscriber::fmt::init();
+
+ let figment = Figment::from(Serialized::defaults(Config::default()))
.merge(Toml::file(Env::var_or("ZONED_CONFIG", "/etc/zoned/Config.toml")).nested())
- .merge(Env::prefixed("ZONED_"))
- .extract()
- .expect("Failed to parse config");
+ .merge(Env::prefixed("ZONED_"));
- let rocket = match zoned::build_rocket(&config) {
- Ok(rocket) => rocket,
+ let config = match Config::try_from(figment) {
+ Ok(config) => config,
Err(err) => {
- eprintln!("{}", err);
+ error!("{}", err);
std::process::exit(1)
}
};
- match rocket.launch().await {
- Ok(()) => println!("Rocket shut down gracefully."),
- Err(err) => eprintln!("Rocket had an error: {}", err),
+ let shared_state = match State::try_from(config) {
+ Ok(state) => Arc::new(state),
+ Err(err) => {
+ error!("{}", err);
+ std::process::exit(1)
+ }
};
+
+ let routes = build_routes().layer(AddExtensionLayer::new(shared_state));
+ let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
+
+ debug!("listening on {}", addr);
+ axum::Server::bind(&addr)
+ .serve(routes.into_make_service())
+ .await
+ .unwrap();
}
diff --git a/zoned/src/state.rs b/zoned/src/state.rs
new file mode 100644
index 0000000..68ab0a6
--- /dev/null
+++ b/zoned/src/state.rs
@@ -0,0 +1,20 @@
+use zone_nspawn::NSpawn;
+use zone_zfs::ZFS;
+
+use crate::{Config, Error, Result};
+
+pub struct State {
+ pub zfs: ZFS,
+ pub nspawn: NSpawn,
+}
+
+impl TryFrom<Config> for State {
+ type Error = Error;
+
+ fn try_from(config: Config) -> Result<Self> {
+ Ok(State {
+ zfs: zone_zfs::ZFS::custom(&config.zfs)?,
+ nspawn: zone_nspawn::NSpawn::custom(&config.nspawn)?,
+ })
+ }
+}