pub mod api { use rocket::{get, post, serde::json::Json, Build, Config, Rocket}; use rocket_okapi::{ openapi, openapi_get_routes, rapidoc::{make_rapidoc, GeneralConfig, HideShowConfig, RapiDocConfig}, settings::UrlObject, swagger_ui::{make_swagger_ui, SwaggerUIConfig}, }; use std::net::Ipv4Addr; use zone_core::Container; /// # Get all containers /// /// Returns all containers. #[openapi(tag = "Containers")] #[get("/containers/list")] fn get_all_containers() -> Json> { let containers = vec![]; Json(containers) } /// # Get a user's containers /// /// Returns all containers belonging to a single user. #[openapi(tag = "Containers")] #[get("/containers/list/")] fn get_containers_by_user(user: String) -> Json> { let containers = vec![]; let _user = user; Json(containers) } /// # Create container #[openapi(tag = "Containers")] #[post("/container", data = "")] fn create_container(container: Json) -> Json { container } pub fn build_rocket() -> Rocket { let config = Config { address: Ipv4Addr::new(127, 0, 0, 1).into(), port: 8000, ..Config::debug_default() }; rocket::custom(config) .mount( "/", openapi_get_routes![get_all_containers, get_containers_by_user, create_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() }), ) } } pub mod zfs { use anyhow::{anyhow, Result}; use chrono::{DateTime, Utc}; use std::{ ffi::{OsStr, OsString}, fmt::Display, path::PathBuf, process::{Command, Output}, }; #[derive(Debug)] pub struct FileSystem { value: OsString, mountpoint: Option, } impl Display for FileSystem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.value.to_string_lossy()) } } // pool/000.0/nkollack-1 impl TryFrom for FileSystem { type Error = anyhow::Error; fn try_from(value: OsString) -> Result { Ok(FileSystem { value, mountpoint: None, }) } } impl TryFrom<&str> for FileSystem { type Error = anyhow::Error; fn try_from(value: &str) -> Result { value.try_into() } } impl AsRef for FileSystem { fn as_ref(&self) -> &OsStr { self.value.as_ref() } } impl From for PathBuf { fn from(val: FileSystem) -> Self { PathBuf::from(val.value) } } impl From for String { fn from(val: FileSystem) -> Self { val.value.to_string_lossy().to_string() } } impl TryFrom for FileSystem { type Error = anyhow::Error; fn try_from(value: Output) -> Result { std::str::from_utf8(&value.stdout)?.try_into() } } impl FileSystem { pub fn get_snapshots(&self) -> Result> { let output = Command::new("zfs") .arg("list") .arg("-H") .arg("-o") .arg("name") .arg("-t") .arg("snapshot") .arg(self) .output()? .stdout; String::from_utf8(output)? .split_whitespace() .map(|s| s.try_into()) .collect() } pub fn get_latest_snapshot(&self) -> Result> { // pool/447.0@210119221709 Ok(self .get_snapshots()? .into_iter() .max_by_key(|s| s.timestamp)) } } #[derive()] pub struct Snapshot { // pool/000.0@00000000000 value: OsString, filesystem: FileSystem, timestamp: DateTime, } impl TryFrom<&str> for Snapshot { // filesystem, '@', timestamp type Error = anyhow::Error; fn try_from(value: &str) -> Result { match value.split('@').collect::>()[..] { [filesystem, name] => Ok(Snapshot { filesystem: FileSystem::try_from(filesystem)?, value: filesystem.into(), timestamp: name.parse().unwrap_or(chrono::MIN_DATETIME), }), _ => Err(anyhow!("Failed to parse")), } } } impl Snapshot { pub fn clone_into_fs( &self, fs_name: String, mountpoint: Option, ) -> Result { let new_fs = FileSystem { value: PathBuf::from(&self.filesystem.value).join(fs_name).into(), mountpoint, }; let mut command = Command::new("zfs"); command.arg("clone"); if let Some(mp) = &new_fs.mountpoint { command .arg("-o") .arg(format!("mountpoint={}", mp.to_string_lossy())); }; match command.arg(&self.value).arg(&new_fs).status()?.success() { true => Ok(new_fs), false => Err(anyhow!("Failed to clone snapshot")), } } } pub fn get_filesystems() -> Result> { let output = Command::new("zfs") .arg("list") .arg("-H") .arg("-o") .arg("name") .output()? .stdout; std::str::from_utf8(&output)? .split_whitespace() .map(|fs| fs.try_into()) .collect() } #[cfg(test)] mod tests { #[test] fn zfs_list() { use super::*; assert!(get_filesystems().is_ok()); } } } pub mod nspawn {}