diff options
author | Toby Vincent <tobyv13@gmail.com> | 2022-04-15 16:12:16 -0500 |
---|---|---|
committer | Toby Vincent <tobyv13@gmail.com> | 2022-04-15 16:12:16 -0500 |
commit | 67eacfd6476fba491479dfb62013941d35ef3ace (patch) | |
tree | 2e21a6e508fe205cb2bc8fe387ee9f6ebab02f40 | |
parent | 5025496a661b4b280ff1494111d3244ecc74d9c3 (diff) |
refactor: move create container logic to http.rs
-rw-r--r-- | zone_core/src/container.rs | 32 | ||||
-rw-r--r-- | zone_core/src/lib.rs | 2 | ||||
-rw-r--r-- | zone_nspawn/src/config.rs | 32 | ||||
-rw-r--r-- | zone_nspawn/src/container.rs | 93 | ||||
-rw-r--r-- | zone_nspawn/src/lib.rs | 4 | ||||
-rw-r--r-- | zone_nspawn/src/network.rs | 126 | ||||
-rw-r--r-- | zone_nspawn/src/nspawn.rs | 58 | ||||
-rw-r--r-- | zone_zfs/src/snapshot.rs | 2 | ||||
-rw-r--r-- | zone_zfs/src/zfs.rs | 3 | ||||
-rw-r--r-- | zoned/src/config.rs | 21 | ||||
-rw-r--r-- | zoned/src/error.rs | 2 | ||||
-rw-r--r-- | zoned/src/http.rs | 43 |
12 files changed, 96 insertions, 322 deletions
diff --git a/zone_core/src/container.rs b/zone_core/src/container.rs index cd29512..4ac9dbf 100644 --- a/zone_core/src/container.rs +++ b/zone_core/src/container.rs @@ -3,10 +3,8 @@ use derive_builder::Builder; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use tabled::Tabled; -use zone_nspawn::NSpawn; -use zone_zfs::{FileSystem, ZFS}; -use crate::{FilterContainer, Result}; +use crate::FilterContainer; pub use status::ContainerStatus; @@ -15,7 +13,6 @@ mod status; #[derive(Debug, Default, Serialize, Deserialize, Builder, Tabled, Clone, Args)] #[builder( name = "ContainerOptions", - build_fn(private, name = "build_container"), derive(Debug, Serialize, Deserialize), field(public) )] @@ -40,25 +37,10 @@ impl Container { } } -impl ContainerOptions { - pub fn build(&self, zfs: &ZFS, nspawn: &NSpawn) -> Result<Container> { - let container = self.build_container()?; - let user = Self::default() - .user(container.user.clone()) - .template(container.template.clone()) - .to_owned(); - let mut user_containers = zfs.get_file_systems()?.into_iter().filter_container(user); - - user_containers.sort_by_key(|c| c.id); - let current_last_id = user_containers.last().map_or(0, |c| c.id); - - let _ = zfs.clone_from_latest( - PathBuf::from(format!("{}-{}", container.user, current_last_id)), - PathBuf::from(&container.template), - ); - let _ = nspawn.create_container(); - Ok(container) - } +#[derive(Debug, Serialize, Deserialize, Clone, Args)] +pub struct CloneOptions { + pub template: String, + pub user: String, } impl<T> FilterContainer for T @@ -77,10 +59,10 @@ where } } -impl TryFrom<FileSystem> for Container { +impl TryFrom<zone_zfs::FileSystem> for Container { type Error = zone_zfs::Error; - fn try_from(file_system: FileSystem) -> zone_zfs::Result<Self> { + fn try_from(file_system: zone_zfs::FileSystem) -> zone_zfs::Result<Self> { let path_buf = PathBuf::from(file_system.dataset()) .file_name() .ok_or_else(|| { diff --git a/zone_core/src/lib.rs b/zone_core/src/lib.rs index 68c1be9..852fa05 100644 --- a/zone_core/src/lib.rs +++ b/zone_core/src/lib.rs @@ -1,6 +1,6 @@ use std::net::{IpAddr, Ipv4Addr}; -pub use crate::container::{Container, ContainerOptions}; +pub use crate::container::{CloneOptions, Container, ContainerOptions, ContainerStatus}; pub use crate::error::{Error, Result}; mod error; diff --git a/zone_nspawn/src/config.rs b/zone_nspawn/src/config.rs deleted file mode 100644 index 2c948ba..0000000 --- a/zone_nspawn/src/config.rs +++ /dev/null @@ -1,32 +0,0 @@ -use figment::{ - error::Result, - providers::{Format, Serialized, Toml}, - value::{Dict, Map}, - Figment, Metadata, Profile, Provider, -}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] -pub struct Config { - pub network_configs_path: Option<PathBuf>, -} - -impl Config { - pub fn from<T: Provider>(provider: T) -> Result<Config> { - Figment::from(provider).extract() - } - - pub fn figment() -> Figment { - Figment::from(Config::default()).merge(Toml::file("NSpawn.toml").nested()) - } -} - -impl Provider for Config { - fn metadata(&self) -> Metadata { - Metadata::named("NSpawn Config") - } - - fn data(&self) -> Result<Map<Profile, Dict>> { - Serialized::defaults(Config::default()).data() - } -} diff --git a/zone_nspawn/src/container.rs b/zone_nspawn/src/container.rs index 552c180..bffcd06 100644 --- a/zone_nspawn/src/container.rs +++ b/zone_nspawn/src/container.rs @@ -1,12 +1,6 @@ -use crate::Config; -use crate::NetworkConfig; -use crate::{Error, Result}; use derive_builder::Builder; use serde::{Deserialize, Serialize}; -use serde_ini::de::from_bufread; -use std::fs::File; -use std::io::BufReader; -use std::{collections::HashMap, ffi::OsString, fs, path::PathBuf, process::Command}; +use std::ffi::OsString; #[derive(Builder, Debug, Serialize, Deserialize, Clone)] #[builder(build_fn(private, name = "build_container"), derive(Debug))] @@ -27,89 +21,4 @@ pub struct Container { #[builder(setter(skip))] pub addresses: OsString, - - #[builder(setter(each = "add_network_config"))] - #[serde(skip_deserializing)] - pub network_configs: HashMap<PathBuf, NetworkConfig>, - - #[builder(setter(each = "add_option"))] - #[serde(skip_deserializing)] - pub options: Vec<String>, -} - -impl Container { - fn create(&self) -> Result<Container> { - Command::new("systemd-nspawn") - .args(&self.options) - .status()? - .success() - .then(|| self) - .ok_or_else(|| Error::NSpawn(format!("Failed to create container: {:?}", self)))? - .network_configuration() - .map(|_| self.to_owned()) - } - - fn network_configuration(&self) -> Result<()> { - for (filebuf, network_config) in self.network_configs.iter() { - fs::write( - filebuf, - serde_ini::ser::to_vec(network_config).map_err(|err| { - Error::Network(format!("Failed to serialize network config: {:?}", err)) - })?, - ) - .map_err(|err| { - Error::Network(format!("Failed to create network config file: {:?}", err)) - })?; - } - - Ok(()) - } - - pub fn shutdown(&self) -> Result<()> { - Command::new("machinectl") - .arg("poweroff") - .arg(&self.machine) - .status()? - .success() - .then(|| ()) - .ok_or_else(|| Error::NSpawn(format!("Failed to shutdown container: {:?}", self))) - } - - pub fn builder() -> ContainerBuilder { - ContainerBuilder::default() - } -} - -impl ContainerBuilder { - pub fn build(&self) -> Result<Container> { - self.build_container()?.create() - } - - pub fn build_with_config(&mut self, config: &Config) -> Result<Container> { - self.add_network_configs_from_config(config)? - .build_container()? - .create() - } - - fn add_network_configs_from_config(&mut self, config: &Config) -> Result<&mut Self> { - if let Some(dir) = &config.network_configs_path { - if dir.is_dir() { - for entry in fs::read_dir(dir)?.flatten() { - let filename = entry.file_name(); - let file = File::open(entry.path())?; - let file_contents = match from_bufread(BufReader::new(file)) { - Ok(it) => it, - Err(err) => { - return Err(Error::Network(format!( - "Failed to parse network config: {:?}", - err - ))) - } - }; - self.add_network_config((PathBuf::from(filename), file_contents)); - } - } - }; - Ok(self) - } } diff --git a/zone_nspawn/src/lib.rs b/zone_nspawn/src/lib.rs index 9d04b0c..b466f33 100644 --- a/zone_nspawn/src/lib.rs +++ b/zone_nspawn/src/lib.rs @@ -1,14 +1,10 @@ -pub use crate::config::Config; pub use crate::container::Container; pub use crate::container::ContainerBuilder; pub use crate::error::{Error, Result}; -pub use crate::network::NetworkConfig; pub use crate::nspawn::NSpawn; -mod config; mod container; mod error; -mod network; mod nspawn; #[cfg(test)] diff --git a/zone_nspawn/src/network.rs b/zone_nspawn/src/network.rs deleted file mode 100644 index 425637a..0000000 --- a/zone_nspawn/src/network.rs +++ /dev/null @@ -1,126 +0,0 @@ -use derive_builder::Builder; -use serde::{Deserialize, Serialize}; -use std::ffi::OsString; - -#[derive(Debug, Builder, Clone, Deserialize, Serialize)] -#[builder(setter(strip_option), default)] -#[serde(rename_all = "PascalCase")] -pub struct NetworkConfig { - pub net_match: Option<Match>, - pub link: Option<Link>, - pub network: Option<Network>, - pub address: Option<Address>, - pub route: Option<Route>, - pub dhcpv4: Option<DHCPv4>, -} - -#[derive(Debug, Default, Builder, Clone, Deserialize, Serialize)] -#[builder(setter(strip_option), default)] -#[serde(rename_all = "PascalCase")] -#[serde(rename = "Match")] -pub struct Match { - pub name: Option<OsString>, - #[serde(rename = "MACAddress")] - pub mac_address: Option<OsString>, - pub host: Option<OsString>, - #[serde(rename = "Virtualization")] - pub virtualization: Option<bool>, -} - -#[derive(Debug, Default, Builder, Clone, Deserialize, Serialize)] -#[builder(setter(strip_option), default)] -#[serde(rename_all = "PascalCase")] -pub struct Link { - #[serde(rename = "MACAddress")] - pub mac_address: Option<OsString>, - #[serde(rename = "MTUBytes")] - pub mtu_bytes: Option<u64>, - pub muticast: Option<bool>, -} - -#[derive(Debug, Builder, Clone, Deserialize, Serialize)] -#[builder(setter(strip_option), default)] -#[serde(rename_all = "PascalCase")] -pub struct Network { - #[serde(rename = "DHCP")] - pub dhcp: Option<OsString>, - #[serde(rename = "DHCPServer")] - pub dhcp_server: Option<bool>, - #[serde(rename = "MulticastDNS")] - pub multicast_dns: Option<OsString>, - #[serde(rename = "DNSSEC")] - pub dnssec: Option<OsString>, - #[serde(rename = "DNS")] - pub dns: Option<OsString>, - pub domains: Option<OsString>, - #[serde(rename = "IPForward")] - pub ip_forward: Option<OsString>, - #[serde(rename = "IPMasquerade")] - pub ip_masquerade: Option<OsString>, - #[serde(rename = "IPv6PrivacyExtensions")] - pub ipv6_privacy_extensions: Option<OsString>, -} - -#[derive(Debug, Default, Builder, Clone, Deserialize, Serialize)] -#[builder(setter(strip_option), default)] -#[serde(rename_all = "PascalCase")] -pub struct Address { - pub address: Option<OsString>, -} - -#[derive(Debug, Default, Builder, Clone, Deserialize, Serialize)] -#[builder(setter(strip_option), default)] -#[serde(rename_all = "PascalCase")] -pub struct Route { - pub gateway: Option<OsString>, - pub destination: Option<OsString>, -} - -#[derive(Debug, Builder, Clone, Deserialize, Serialize)] -#[builder(setter(strip_option), default)] -#[serde(rename_all = "PascalCase")] -pub struct DHCPv4 { - #[serde(rename = "UseDNS")] - pub use_dns: Option<bool>, - pub anonymize: Option<bool>, - pub use_domains: Option<OsString>, -} - -impl Default for NetworkConfig { - fn default() -> Self { - NetworkConfig { - net_match: Some(Match::default()), - link: Some(Link::default()), - network: Some(Network::default()), - address: Some(Address::default()), - route: Some(Route::default()), - dhcpv4: Some(DHCPv4::default()), - } - } -} - -impl Default for Network { - fn default() -> Self { - Network { - dhcp: Some(OsString::from("false")), - dhcp_server: Some(false), - multicast_dns: Some(OsString::from("false")), - dnssec: Some(OsString::from("false")), - dns: None, - domains: None, - ip_forward: Some(OsString::from("false")), - ip_masquerade: Some(OsString::from("no")), - ipv6_privacy_extensions: Some(OsString::from("false")), - } - } -} - -impl Default for DHCPv4 { - fn default() -> Self { - DHCPv4 { - use_dns: Some(true), - anonymize: Some(false), - use_domains: Some(OsString::from("false")), - } - } -} diff --git a/zone_nspawn/src/nspawn.rs b/zone_nspawn/src/nspawn.rs index 0f8e34a..8c97464 100644 --- a/zone_nspawn/src/nspawn.rs +++ b/zone_nspawn/src/nspawn.rs @@ -1,24 +1,10 @@ -use crate::{Config, Container, Error, Result}; -use figment::{Figment, Provider}; -use std::process::Command; +use crate::{Container, Error, Result}; +use std::{path::PathBuf, process::Command}; #[derive(Default, Debug)] -pub struct NSpawn { - pub config: Config, -} +pub struct NSpawn; impl NSpawn { - pub fn new() -> Result<Self> { - Self::custom(Config::figment()) - } - - pub fn custom<T: Provider>(provider: T) -> Result<Self> { - Config::from(Figment::from(provider)) - .map_err(Error::from) - .map(|config| Self { config }) - } - - /// Uses Command to call to machinectl list -o json - pub fn get_containers() -> Result<Vec<Container>> { + pub fn list(&self) -> Result<Vec<Container>> { Command::new("machinectl") .arg("list") .args(["-o", "json"]) @@ -27,7 +13,39 @@ impl NSpawn { .map_err(Error::from) } - pub fn create_container(&self) -> Result<Container> { - Container::builder().build_with_config(&self.config) + pub fn create(&self, root: PathBuf, name: String) -> Result<()> { + let opts = [ + "--settings=trusted", + "--quiet", + "--private-users=no", + "--link-journal=no", + "--resolv-conf=off", + "--timezone=off", + "--capability=all", + "--boot", + "--bind-ro=/sys/module", + "--bind-ro=/lib/modules", + ]; + + Command::new("systemd-nspawn") + .arg("--machine") + .arg(name) + .arg("--directory") + .arg(root) + .args(opts) + .status()? + .success() + .then(|| ()) + .ok_or_else(|| Error::NSpawn(format!("Failed to create container: {:?}", self))) + } + + pub fn shutdown(name: String) -> Result<()> { + Command::new("machinectl") + .arg("poweroff") + .arg(&name) + .status()? + .success() + .then(|| ()) + .ok_or_else(|| Error::NSpawn(format!("Failed to shutdown container: {:?}", name))) } } diff --git a/zone_zfs/src/snapshot.rs b/zone_zfs/src/snapshot.rs index 402b3b9..84db4e3 100644 --- a/zone_zfs/src/snapshot.rs +++ b/zone_zfs/src/snapshot.rs @@ -30,7 +30,7 @@ impl TryFrom<&str> for Snapshot { } impl Snapshot { - pub fn clone_new(&self, path: PathBuf) -> Result<FileSystem> { + pub fn clone_new(&self, path: String) -> Result<FileSystem> { FileSystem::builder() .dataset(&self.dataset.join(path)) .build_from(self.to_owned()) diff --git a/zone_zfs/src/zfs.rs b/zone_zfs/src/zfs.rs index 70dc98a..0eaa4eb 100644 --- a/zone_zfs/src/zfs.rs +++ b/zone_zfs/src/zfs.rs @@ -21,11 +21,12 @@ impl ZFS { } #[cfg(feature = "chrono")] - pub fn clone_from_latest(&self, name: PathBuf, parent: PathBuf) -> Result<FileSystem> { + pub fn clone_latest(&self, name: String, parent: PathBuf) -> Result<PathBuf> { self.get_file_system(parent)? .get_latest_snapshot()? .ok_or_else(|| Error::Snapshot("No snapshot found".into()))? .clone_new(name) + .map(|fs| fs.mountpoint().into()) } pub fn get_file_systems(&self) -> Result<Vec<FileSystem>> { diff --git a/zoned/src/config.rs b/zoned/src/config.rs index 1122a72..4e3a076 100644 --- a/zoned/src/config.rs +++ b/zoned/src/config.rs @@ -1,10 +1,14 @@ use figment::Figment; use serde::{Deserialize, Serialize}; -use std::net::{IpAddr, SocketAddr}; +use std::{ + net::{IpAddr, SocketAddr}, + path::PathBuf, + sync::Arc, +}; use crate::{Error, Result}; -#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] pub struct Config { pub ip_address: IpAddr, pub port: u16, @@ -18,7 +22,6 @@ impl Default for Config { ip_address: zone_core::DEFAULT_IP_ADDRESS, port: zone_core::DEFAULT_PORT, zfs: Default::default(), - nspawn: Default::default(), } } } @@ -37,6 +40,12 @@ impl From<Config> for SocketAddr { } } +impl Config { + pub fn into_arc(self) -> Arc<Self> { + self.into() + } +} + #[cfg(test)] mod tests { use std::path::PathBuf; @@ -84,9 +93,6 @@ mod tests { quota = 42000000 pool_name = "fool" mountpoint = "/mnt" - - [nspawn] - network_configs_path = "/etc/zoned/network.d" "#, )?; @@ -104,9 +110,6 @@ mod tests { pool_name: String::from("fool"), mountpoint: PathBuf::from("/mnt"), }, - nspawn: zone_nspawn::Config { - network_configs_path: Some(PathBuf::from("/etc/zoned/network.d")), - }, } ); diff --git a/zoned/src/error.rs b/zoned/src/error.rs index 8cd3693..66d1e1b 100644 --- a/zoned/src/error.rs +++ b/zoned/src/error.rs @@ -70,7 +70,7 @@ 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)), + err => (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", err)), }; let body = Json(json!({ diff --git a/zoned/src/http.rs b/zoned/src/http.rs index b9a048d..df82908 100644 --- a/zoned/src/http.rs +++ b/zoned/src/http.rs @@ -7,8 +7,7 @@ use axum::{ }; use std::sync::Arc; use tracing::{info, instrument, warn}; -use zone_core::{Container, ContainerOptions, FilterContainer}; -use zone_nspawn::NSpawn; +use zone_core::{CloneOptions, Container, ContainerOptions, ContainerStatus, FilterContainer}; use crate::{ws, Error, Result, State}; @@ -35,8 +34,9 @@ async fn test_endpoint(Extension(state): Extension<Arc<State>>) -> Json<String> #[instrument(err, ret)] async fn container_list( container: Option<Query<ContainerOptions>>, + Extension(state): Extension<Arc<State>>, ) -> Result<Json<Vec<Container>>> { - let mut containers = NSpawn::get_containers()?.into_iter().filter_map(|c| { + let mut containers = state.nspawn.list()?.into_iter().filter_map(|c| { Container::try_from(c) .map_err(|err| warn!("Ignoring invalid nspawn container {:?}", err)) .ok() @@ -53,13 +53,36 @@ async fn container_list( /// Creates a new container volume from the provided container json data #[instrument(err, ret, skip(state))] async fn clone_container( - Json(container): Json<ContainerOptions>, + Json(container): Json<CloneOptions>, Extension(state): Extension<Arc<State>>, ) -> Result<Json<Container>> { - container - .build(&state.zfs, &state.nspawn) - .map(Json::from) - .map_err(Error::from) + let predicate = Container::builder() + .user(container.user.to_owned()) + .template(container.template.to_owned()) + .to_owned(); + + let id = state + .zfs + .get_file_systems()? + .into_iter() + .filter_container(predicate) + .into_iter() + .max_by_key(|c| c.id) + .map_or(0, |c| c.id + 1); + + let name = format!("{}-{}", container.user, id); + let root = state.zfs.clone_latest(name, (&container.template).into())?; + + + let name = format!("{}-{}-{}", container.template, container.user, id); + state.nspawn.create(root, name)?; + + Ok(Json::from(Container { + id, + template: container.template, + user: container.user, + status: ContainerStatus::Running, + })) } /// Upgrade to websocket @@ -71,8 +94,8 @@ async fn ws_upgrade( user_agent: Option<TypedHeader<headers::UserAgent>>, Extension(state): Extension<Arc<State>>, ) -> impl IntoResponse { - let ua = user_agent.map_or("Unknown".to_string(), |u| u.to_string()); - info!(%ua, "Client connected"); + let agent = user_agent.map_or("Unknown".to_string(), |u| u.to_string()); + info!(%agent, "Client connected"); ws.on_upgrade(|socket| ws::handler(socket, state)) } |