172 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # 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>  Filter query
 | |
|   -i, --interactive      Run interactive TUI or not
 | |
|   -a, --all              Run all or nth, defaults to all
 | |
|   -n, --number <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
 | |
| 
 | |
| 
 | |
| ````
 | |
| ## 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<String>,
 | |
| ```
 | |
| 
 | |
| 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<String>,
 | |
| ```
 | |
| 
 | |
| 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
 | |
|             )
 | |
|             ...
 | |
| ```
 |