use crate::netlink::config::{ApplyResult as NetworkApplyResult, ConfigDiff, NetworkConfig};
use crate::netlink::genl::wireguard::{
WireguardApplyResult, WireguardConfig, WireguardConfigDiff,
};
use crate::netlink::nftables::config::{NftablesConfig, NftablesDiff};
use crate::Result;
use super::{apply, diff};
#[derive(Debug, Clone, Default)]
#[must_use = "Stack does nothing unless apply() or diff() is called"]
pub struct Stack {
pub network: Option<NetworkConfig>,
pub nftables: Option<NftablesConfig>,
pub wireguard: Option<WireguardConfig>,
}
impl Stack {
pub fn new() -> Self {
Self::default()
}
pub fn network(mut self, cfg: NetworkConfig) -> Self {
self.network = Some(cfg);
self
}
pub fn nftables(mut self, cfg: NftablesConfig) -> Self {
self.nftables = Some(cfg);
self
}
pub fn wireguard(mut self, cfg: WireguardConfig) -> Self {
self.wireguard = Some(cfg);
self
}
pub async fn apply(&self) -> Result<StackApplyReport> {
let _validation = self.diff().await?;
let mut report = StackApplyReport::default();
if let Some(cfg) = &self.network {
report.network = Some(apply::network(cfg).await?);
}
if let Some(cfg) = &self.nftables {
report.nftables_change_count = Some(apply::nftables(cfg).await?);
}
if let Some(cfg) = &self.wireguard {
report.wireguard = Some(apply::wireguard(cfg).await?);
}
Ok(report)
}
pub async fn apply_in_namespace(&self, ns: &str) -> Result<StackApplyReport> {
let _validation = self.diff_in_namespace(ns).await?;
let mut report = StackApplyReport::default();
if let Some(cfg) = &self.network {
report.network = Some(apply::network_in_namespace(ns, cfg).await?);
}
if let Some(cfg) = &self.nftables {
report.nftables_change_count = Some(apply::nftables_in_namespace(ns, cfg).await?);
}
if let Some(cfg) = &self.wireguard {
report.wireguard = Some(apply::wireguard_in_namespace(ns, cfg).await?);
}
Ok(report)
}
pub async fn diff(&self) -> Result<StackDiff> {
let mut out = StackDiff::default();
if let Some(cfg) = &self.network {
out.network = Some(diff::network(cfg).await?);
}
if let Some(cfg) = &self.nftables {
out.nftables = Some(diff::nftables(cfg).await?);
}
if let Some(cfg) = &self.wireguard {
out.wireguard = Some(diff::wireguard(cfg).await?);
}
Ok(out)
}
pub async fn diff_in_namespace(&self, ns: &str) -> Result<StackDiff> {
let mut out = StackDiff::default();
if let Some(cfg) = &self.network {
out.network = Some(diff::network_in_namespace(ns, cfg).await?);
}
if let Some(cfg) = &self.nftables {
out.nftables = Some(diff::nftables_in_namespace(ns, cfg).await?);
}
if let Some(cfg) = &self.wireguard {
out.wireguard = Some(diff::wireguard_in_namespace(ns, cfg).await?);
}
Ok(out)
}
}
#[derive(Debug, Default)]
#[must_use = "Inspect per-layer fields to learn what Stack::apply changed"]
pub struct StackApplyReport {
pub network: Option<NetworkApplyResult>,
pub nftables_change_count: Option<usize>,
pub wireguard: Option<WireguardApplyResult>,
}
impl StackApplyReport {
pub fn is_noop(&self) -> bool {
self.network.as_ref().is_none_or(|r| r.changes_made == 0)
&& self.nftables_change_count.is_none_or(|c| c == 0)
&& self.wireguard.as_ref().is_none_or(|r| r.total_writes() == 0)
}
}
#[derive(Debug, Default)]
#[must_use = "Diffs do nothing unless inspected or passed to apply()"]
pub struct StackDiff {
pub network: Option<ConfigDiff>,
pub nftables: Option<NftablesDiff>,
pub wireguard: Option<WireguardConfigDiff>,
}
impl StackDiff {
pub fn is_empty(&self) -> bool {
self.network.as_ref().is_none_or(|d| d.is_empty())
&& self.nftables.as_ref().is_none_or(|d| d.is_empty())
&& self.wireguard.as_ref().is_none_or(|d| d.is_empty())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_stack_diff_is_empty_no_op() {
let s = StackDiff::default();
assert!(s.is_empty());
}
#[test]
fn empty_stack_apply_is_noop() {
let r = StackApplyReport::default();
assert!(r.is_noop());
}
#[test]
fn stack_builder_layers_optional() {
let s = Stack::new();
assert!(s.network.is_none());
assert!(s.nftables.is_none());
assert!(s.wireguard.is_none());
let s = s.network(NetworkConfig::new());
assert!(s.network.is_some());
}
}