# Harmony : Open Infrastructure Orchestration ## Quick demo `cargo run -p example-tui` This will launch Harmony's minimalist terminal ui which embeds a few demo scores. Usage instructions will be displayed at the bottom of the TUI. `cargo run --bin example-cli -- --help` This is the harmony CLI, a minimal implementation The current help text: ```` Usage: example-cli [OPTIONS] Options: -y, --yes Run score(s) or not -f, --filter Filter query -i, --interactive Run interactive TUI or not -a, --all Run all or nth, defaults to all -n, --number Run nth matching, zero indexed [default: 0] -l, --list list scores, will also be affected by run filter -h, --help Print help -V, --version Print version``` ## Core architecture ![Harmony Core Architecture](docs/diagrams/Harmony_Core_Architecture.drawio.svg) ```` ## 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/src/modules/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 ) ... ```