use crate::Result;
use crate::netlink::config::{ApplyResult as NetworkApplyResult, ConfigDiff, NetworkConfig};
use crate::netlink::genl::wireguard::{
WireguardApplyResult, WireguardConfig, WireguardConfigDiff,
};
use crate::netlink::namespace::NamespaceSpec;
use crate::netlink::nftables::config::{NftablesConfig, NftablesDiff};
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> {
self.apply_in(NamespaceSpec::Default).await
}
pub async fn apply_in_namespace(&self, ns: &str) -> Result<StackApplyReport> {
self.apply_in(NamespaceSpec::Named(ns)).await
}
pub async fn apply_in(&self, ns: NamespaceSpec<'_>) -> Result<StackApplyReport> {
let _validation = self.diff_in(ns.clone()).await?;
let mut report = StackApplyReport::default();
if let Some(cfg) = &self.network {
report.network = Some(apply::network_in(ns.clone(), cfg).await?);
}
if let Some(cfg) = &self.nftables {
report.nftables_change_count = Some(apply::nftables_in(ns.clone(), cfg).await?);
}
if let Some(cfg) = &self.wireguard {
report.wireguard = Some(apply::wireguard_in(ns, cfg).await?);
}
Ok(report)
}
pub async fn diff(&self) -> Result<StackDiff> {
self.diff_in(NamespaceSpec::Default).await
}
pub async fn diff_in_namespace(&self, ns: &str) -> Result<StackDiff> {
self.diff_in(NamespaceSpec::Named(ns)).await
}
pub async fn diff_in(&self, ns: NamespaceSpec<'_>) -> Result<StackDiff> {
let mut out = StackDiff::default();
if let Some(cfg) = &self.network {
out.network = Some(diff::network_in(ns.clone(), cfg).await?);
}
if let Some(cfg) = &self.nftables {
out.nftables = Some(diff::nftables_in(ns.clone(), cfg).await?);
}
if let Some(cfg) = &self.wireguard {
out.wireguard = Some(diff::wireguard_in(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)
}
pub fn change_count(&self) -> usize {
self.network.as_ref().map_or(0, |r| r.changes_made)
+ self.nftables_change_count.unwrap_or(0)
+ self.wireguard.as_ref().map_or(0, |r| r.total_writes())
}
}
#[derive(Debug, Default)]
#[non_exhaustive]
#[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())
}
pub fn change_count(&self) -> usize {
self.network.as_ref().map_or(0, |d| d.change_count())
+ self.nftables.as_ref().map_or(0, |d| d.change_count())
+ self.wireguard.as_ref().map_or(0, |d| d.change_count())
}
}
#[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_change_counts_sum_layers() {
let mut diff = StackDiff::default();
assert_eq!(diff.change_count(), 0);
let mut wg = WireguardConfigDiff::default();
wg.devices_to_add.push("wg0".to_string());
diff.wireguard = Some(wg);
assert_eq!(diff.change_count(), 1);
assert!(!diff.is_empty());
let mut report = StackApplyReport::default();
assert_eq!(report.change_count(), 0);
report.nftables_change_count = Some(3);
assert_eq!(report.change_count(), 3);
assert!(!report.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());
}
}