diff --git a/src/apply/networkd/mod.rs b/src/apply/networkd/mod.rs new file mode 100644 index 0000000..15f9e93 --- /dev/null +++ b/src/apply/networkd/mod.rs @@ -0,0 +1,173 @@ +use super::ApplyError; +use crate::{ + definitions::{config::Config, network::NetworkInterfaceType}, + templates, +}; +use std::error::Error; +use tera::Context; +use tracing::{error, info}; + +pub struct File { + pub name: String, + pub content: String, +} + +pub fn apply_networkd(pending_config: Config, current_config: Config) -> Result<(), ApplyError> { + let files = generate_networkd_config_files(pending_config, current_config)?; + info!("Got Files"); + for file in files { + info!("Conf File {}", file.name); + info!("{}", file.content); + } + Ok(()) +} + +pub fn generate_networkd_config_files( + pending_config: Config, + _current_config: Config, +) -> Result, ApplyError> { + let mut files = Vec::new(); + + // Step 1 Generate vlan netdev files + for interface in &pending_config.network.interfaces { + if let NetworkInterfaceType::Vlan { id, .. } = &interface.interface_type { + let mut context = Context::new(); + context.insert("name", &interface.name); + context.insert("vlan_id", &id); + + files.push(generate_config_file( + context, + "networkd/create-vlan.netdev", + format!("10-create-vlan-{}.netdev", &interface.name), + )?); + } + } + + // Step 2 Generate bond netdev files + for interface in &pending_config.network.interfaces { + if let NetworkInterfaceType::Bond { .. } = &interface.interface_type { + let mut context = Context::new(); + context.insert("name", &interface.name); + + files.push(generate_config_file( + context, + "networkd/create-bond.netdev", + format!("20-create-bond-{}.netdev", &interface.name), + )?); + + // Create Membership files + for member in interface + .interface_type + .bond_members(pending_config.clone()) + { + let mut context = Context::new(); + context.insert("bond_name", &interface.name); + + // if interface is a hardware interface then we want to use device instead + match member.interface_type { + NetworkInterfaceType::Hardware { device } => context.insert("name", &device), + _ => context.insert("name", &member.name), + }; + + files.push(generate_config_file( + context, + "networkd/bond-membership.network", + format!("50-bond-membership-{}.network", &member.name), + )?); + } + } + } + + // Step 3 Generate bridge netdev files + for interface in &pending_config.network.interfaces { + if let NetworkInterfaceType::Bridge { .. } = &interface.interface_type { + let mut context = Context::new(); + context.insert("name", &interface.name); + + files.push(generate_config_file( + context, + "networkd/create-bridge.netdev", + format!("30-create-bridge-{}.netdev", &interface.name), + )?); + + // Create Membership files + for member in interface + .interface_type + .bridge_members(pending_config.clone()) + { + let mut context = Context::new(); + context.insert("bridge_name", &interface.name); + + // if interface is a hardware interface then we want to use device instead + match member.interface_type { + NetworkInterfaceType::Hardware { device } => context.insert("name", &device), + _ => context.insert("name", &member.name), + }; + + files.push(generate_config_file( + context, + "networkd/bridge-membership.network", + format!("60-bridge-membership-{}.network", &member.name), + )?); + } + } + } + + // Step 4 Generate wireguard netdev files + /* TODO + for interface in &pending_config.network.interfaces { + if let NetworkInterfaceType::Bridge { .. } = interface.interface_type { + let mut context = Context::new(); + context.insert("name", &interface.name); + + files.push(generate_config_file( + context, + "networkd/create-wireguard.netdev", + format!("40-create-wireguard-{}.netdev", &interface.name), + )?); + } + } + */ + + // Step 5 Generate Addressing network files + /* + for interface in &pending_config.network.interfaces { + if let NetworkInterfaceType::Vlan { id, .. } = &interface.interface_type { + let mut context = Context::new(); + match &interface.interface_type { + NetworkInterfaceType::Hardware { device } => context.insert("name", &device), + _ => context.insert("name", &member.name), + }; + + files.push(generate_config_file( + context, + "networkd/config-addressing.network", + format!("70-config-addressing-{}.network", &interface.name), + )?); + } + } + */ + Ok(files) +} + +fn generate_config_file( + context: Context, + template_name: &str, + file_name: String, +) -> Result { + match templates::TEMPLATES.render(template_name, &context) { + Ok(s) => Ok(File { + name: file_name, + content: s, + }), + Err(e) => { + error!("Error: {}", e); + let mut cause = e.source(); + while let Some(e) = cause { + error!("Reason: {}", e); + cause = e.source(); + } + return Err(ApplyError::TemplateError(e)); + } + } +} diff --git a/src/config_manager.rs b/src/config_manager.rs index cb1a834..32acd62 100644 --- a/src/config_manager.rs +++ b/src/config_manager.rs @@ -1,13 +1,11 @@ -use serde::Serialize; -use validator::Validate; - use super::definitions::config::Config; +use pwhash::sha512_crypt; +use serde::Serialize; use std::fs; use std::sync::{Arc, Mutex, MutexGuard}; - -use pwhash::sha512_crypt; - use thiserror::Error; +use tracing::{error, info}; +use validator::Validate; #[derive(Error, Debug)] pub enum ConfigError { @@ -34,6 +32,11 @@ pub enum ConfigError { pub const CURRENT_CONFIG_PATH: &str = "config.json"; pub const PENDING_CONFIG_PATH: &str = "pending.json"; +static APPLY_FUNCTIONS: &'static [fn( + pending_config: Config, + current_config: Config, +) -> Result<(), super::apply::ApplyError>] = &[super::apply::networkd::apply_networkd]; + #[derive(Clone)] pub struct ConfigManager { shared_data: Arc>, @@ -93,8 +96,36 @@ impl ConfigManager { pub fn apply_pending_changes(&mut self) -> Result<(), ConfigError> { let mut data = self.shared_data.lock().unwrap(); - // TODO run Apply functions - // TODO Revert on Apply Failure and Return + + // TODO Improve Error Handling + for apply_function in APPLY_FUNCTIONS { + match (apply_function)(data.pending_config.clone(), data.current_config.clone()) { + Ok(_) => info!("Applied"), + Err(e) => { + error!("Applying function, Reverting to current config..."); + + for apply_function in APPLY_FUNCTIONS { + match (apply_function)( + // These are swapped for revert + data.current_config.clone(), + data.pending_config.clone(), + ) { + Ok(_) => info!("Applied"), + Err(e) => { + error!("Reverting failed, giving up."); + return Err(ConfigError::ApplyError(e)); + } + } + } + + info!("Revert Done."); + return Err(ConfigError::ApplyError(e)); + } + } + } + + info!("Apply Done."); + write_config_to_file(CURRENT_CONFIG_PATH, data.pending_config.clone())?; // TODO revert if config save fails // TODO Remove Pending Config File diff --git a/src/templates/networkd/bond-membership.network b/src/templates/networkd/bond-membership.network new file mode 100644 index 0000000..af836ef --- /dev/null +++ b/src/templates/networkd/bond-membership.network @@ -0,0 +1,5 @@ +[Match] +Name={{ name }} + +[Network] +Bond={{ bond_name }} diff --git a/src/templates/networkd/bridge-membership.network b/src/templates/networkd/bridge-membership.network new file mode 100644 index 0000000..2e6de73 --- /dev/null +++ b/src/templates/networkd/bridge-membership.network @@ -0,0 +1,5 @@ +[Match] +Name={{ name }} + +[Network] +Bridge={{ bridge_name }} diff --git a/src/templates/networkd/create-bond.netdev b/src/templates/networkd/create-bond.netdev new file mode 100644 index 0000000..10b76a0 --- /dev/null +++ b/src/templates/networkd/create-bond.netdev @@ -0,0 +1,6 @@ +[NetDev] +Name={{ name }} +Kind=bond + +[Bond] +Mode=active-backup \ No newline at end of file diff --git a/src/templates/networkd/create-bridge.netdev b/src/templates/networkd/create-bridge.netdev new file mode 100644 index 0000000..7425640 --- /dev/null +++ b/src/templates/networkd/create-bridge.netdev @@ -0,0 +1,3 @@ +[NetDev] +Name={{ name }} +Kind=bridge \ No newline at end of file diff --git a/src/templates/networkd/create-vlan.netdev b/src/templates/networkd/create-vlan.netdev new file mode 100644 index 0000000..b336621 --- /dev/null +++ b/src/templates/networkd/create-vlan.netdev @@ -0,0 +1,6 @@ +[NetDev] +Name={{ name }} +Kind=vlan + +[VLAN] +Id={{ vlan_id }} \ No newline at end of file