aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToby Vincent <tobyv13@gmail.com>2022-04-15 16:12:16 -0500
committerToby Vincent <tobyv13@gmail.com>2022-04-15 16:12:16 -0500
commit67eacfd6476fba491479dfb62013941d35ef3ace (patch)
tree2e21a6e508fe205cb2bc8fe387ee9f6ebab02f40
parent5025496a661b4b280ff1494111d3244ecc74d9c3 (diff)
refactor: move create container logic to http.rs
-rw-r--r--zone_core/src/container.rs32
-rw-r--r--zone_core/src/lib.rs2
-rw-r--r--zone_nspawn/src/config.rs32
-rw-r--r--zone_nspawn/src/container.rs93
-rw-r--r--zone_nspawn/src/lib.rs4
-rw-r--r--zone_nspawn/src/network.rs126
-rw-r--r--zone_nspawn/src/nspawn.rs58
-rw-r--r--zone_zfs/src/snapshot.rs2
-rw-r--r--zone_zfs/src/zfs.rs3
-rw-r--r--zoned/src/config.rs21
-rw-r--r--zoned/src/error.rs2
-rw-r--r--zoned/src/http.rs43
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))
}