diff --git a/README.md b/README.md index 277356d..e378d6d 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,142 @@ RUST_LOG=info cargo watch --ignore-nothing -w harmony -w private_repos/ -x 'run ``` This will run the nationtech bin (likely `private_repos/nationtech/src/main.rs`) on any change in the harmony or private_repos folders. + +## Supporting a new field in OPNSense `config.xml` + +Two steps: +- Supporting the field in `opnsense-config-xml` +- Enabling Harmony to control the field + +We'll use the `filename` field in the `dhcpcd` section of the file as an example. + +### Supporting the field + +As type checking if enforced, every field from `config.xml` must be known by the code. Each subsection of `config.xml` has its `.rs` file. For the `dhcpcd` section, we'll modify `opnsense-config-xml/src/data/dhcpd.rs`. + +When a new field appears in the xml file, an error like this will be thrown and Harmony will panic : +``` + Running `/home/stremblay/nt/dir/harmony/target/debug/example-nanodc` +Found unauthorized element filename +thread 'main' panicked at opnsense-config-xml/src/data/opnsense.rs:54:14: +OPNSense received invalid string, should be full XML: () + +``` + +Define the missing field (`filename`) in the `DhcpInterface` struct of `opnsense-config-xml/src/data/dhcpd.rs`: +``` +pub struct DhcpInterface { + ... + pub filename: Option, +``` + +Harmony should now be fixed, build and run. + +### Controlling the field + +Define the `xml field setter` in `opnsense-config-xml/src/data/dhcpd.rs`. +``` +impl<'a> DhcpConfig<'a> { + ... + pub fn set_filename(&mut self, filename: &str) { + self.enable_netboot(); + self.get_lan_dhcpd().filename = Some(filename.to_string()); + }, + ... +``` + +Define the `value setter` in the `DhcpServer trait` in `domain/topology/network.rs` +``` +#[async_trait] +pub trait DhcpServer: Send + Sync { + ... + async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError>; + ... +``` + +Implement the `value setter` in each `DhcpServer` implementation. +`infra/opnsense/dhcp.rs`: +``` +#[async_trait] +impl DhcpServer for OPNSenseFirewall { + ... + async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError> { + { + let mut writable_opnsense = self.opnsense_config.write().await; + writable_opnsense.dhcp().set_filename(filename); + debug!("OPNsense dhcp server set filename {filename}"); + } + + Ok(()) + }, + ... +``` + +`domain/topology/ha_cluster.rs` +``` +#[async_trait] +impl DhcpServer for DummyInfra { + ... + async fn set_filename(&self, _filename: &str) -> Result<(), ExecutorError> { + unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) + }, + ... +``` + +Add the new field to the DhcpScore in `modules/dhcp.rs` +``` +pub struct DhcpScore { + ... + pub filename: Option, +``` + +Define it in its implementation in `modules/okd/dhcp.rs` +``` +impl OKDDhcpScore { + ... + Self { + dhcp_score: DhcpScore { + ... + filename: Some("undionly.kpxe".to_string()), +``` + +Define it in its implementation in `modules/okd/bootstrap_dhcp.rs` +``` +impl OKDDhcpScore { + ... + Self { + dhcp_score: DhcpScore::new( + ... + Some("undionly.kpxe".to_string()), +``` + +Update the interpret (function called by the `execute` fn of the interpret) so it now updates the `filename` field value in `modules/dhcp.rs` +``` +impl DhcpInterpret { + ... + let filename_outcome = match &self.score.filename { + Some(filename) => { + let dhcp_server = Arc::new(topology.dhcp_server.clone()); + dhcp_server.set_filename(&filename).await?; + Outcome::new( + InterpretStatus::SUCCESS, + format!("Dhcp Interpret Set filename to {filename}"), + ) + } + None => Outcome::noop(), + }; + + if next_server_outcome.status == InterpretStatus::NOOP + && boot_filename_outcome.status == InterpretStatus::NOOP + && filename_outcome.status == InterpretStatus::NOOP + + ... + + Ok(Outcome::new( + InterpretStatus::SUCCESS, + format!( + "Dhcp Interpret Set next boot to [{:?}], boot_filename to [{:?}], filename to [{:?}]", + self.score.boot_filename, self.score.boot_filename, self.score.filename + ), + ... +```