From b85741215186f2aa94054e48bc4c8ff60a4fc2c1 Mon Sep 17 00:00:00 2001 From: Ian Letourneau Date: Fri, 29 Aug 2025 09:52:11 -0400 Subject: [PATCH] extract related logic into an OkdIpxeScore --- Cargo.lock | 2 +- examples/okd_pxe/Cargo.toml | 1 - examples/okd_pxe/src/main.rs | 93 ++--------- examples/okd_pxe/src/topology.rs | 3 +- harmony/Cargo.toml | 1 + harmony/src/modules/okd/ipxe.rs | 148 ++++++++++++++++++ harmony/src/modules/okd/mod.rs | 1 + .../templates/boot.ipxe.j2 | 0 .../templates/fallback.ipxe.j2 | 0 .../templates/inventory.kickstart.j2 | 0 10 files changed, 162 insertions(+), 87 deletions(-) create mode 100644 harmony/src/modules/okd/ipxe.rs rename {examples/okd_pxe => harmony}/templates/boot.ipxe.j2 (100%) rename {examples/okd_pxe => harmony}/templates/fallback.ipxe.j2 (100%) rename {examples/okd_pxe => harmony}/templates/inventory.kickstart.j2 (100%) diff --git a/Cargo.lock b/Cargo.lock index f09f6ce..de204b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1732,7 +1732,6 @@ dependencies = [ name = "example-pxe" version = "0.1.0" dependencies = [ - "askama", "cidr", "env_logger", "harmony", @@ -2169,6 +2168,7 @@ dependencies = [ name = "harmony" version = "0.1.0" dependencies = [ + "askama", "async-trait", "base64 0.22.1", "bollard", diff --git a/examples/okd_pxe/Cargo.toml b/examples/okd_pxe/Cargo.toml index 23f3cf4..f75f42b 100644 --- a/examples/okd_pxe/Cargo.toml +++ b/examples/okd_pxe/Cargo.toml @@ -18,5 +18,4 @@ harmony_macros = { path = "../../harmony_macros" } log = { workspace = true } env_logger = { workspace = true } url = { workspace = true } -askama = "0.14.0" serde.workspace = true diff --git a/examples/okd_pxe/src/main.rs b/examples/okd_pxe/src/main.rs index 0267a0d..42e4729 100644 --- a/examples/okd_pxe/src/main.rs +++ b/examples/okd_pxe/src/main.rs @@ -1,97 +1,24 @@ mod topology; -use std::net::IpAddr; - -use askama::Template; -use harmony::{ - data::{FileContent, FilePath}, - modules::{dhcp::DhcpScore, http::StaticFilesHttpScore, tftp::TftpScore}, - score::Score, - topology::{HAClusterTopology, Url}, -}; - use crate::topology::{get_inventory, get_topology}; +use harmony::modules::okd::ipxe::OkdIpxeScore; #[tokio::main] async fn main() { let inventory = get_inventory(); let topology = get_topology().await; - let gateway_ip = &topology.router.get_gateway(); - let kickstart_filename = "inventory.kickstart"; - let cluster_pubkey_filename = "cluster_ssh_key.pub"; - let harmony_inventory_agent = "harmony_inventory_agent"; + let kickstart_filename = "inventory.kickstart".to_string(); + let cluster_pubkey_filename = "cluster_ssh_key.pub".to_string(); + let harmony_inventory_agent = "harmony_inventory_agent".to_string(); - // TODO: this should be a single IPXEScore instead of having the user do this step by step - let scores: Vec>> = vec![ - Box::new(DhcpScore { - host_binding: vec![], - next_server: Some(topology.router.get_gateway()), - boot_filename: None, - filename: Some("undionly.kpxe".to_string()), - filename64: Some("ipxe.efi".to_string()), - filenameipxe: Some(format!("http://{gateway_ip}:8080/boot.ipxe").to_string()), - }), - Box::new(TftpScore { - files_to_serve: Url::LocalFolder("./data/pxe/okd/tftpboot/".to_string()), - }), - Box::new(StaticFilesHttpScore { - // TODO The current russh based copy is way too slow, check for a lib update or use scp - // when available - // - // For now just run : - // scp -r data/pxe/okd/http_files/* root@192.168.1.1:/usr/local/http/ - // - folder_to_serve: None, - // folder_to_serve: Some(Url::LocalFolder("./data/pxe/okd/http_files/".to_string())), - files: vec![ - FileContent { - path: FilePath::Relative("boot.ipxe".to_string()), - content: BootIpxeTpl { gateway_ip }.to_string(), - }, - FileContent { - path: FilePath::Relative(kickstart_filename.to_string()), - content: InventoryKickstartTpl { - gateway_ip, - harmony_inventory_agent, - cluster_pubkey_filename, - } - .to_string(), - }, - FileContent { - path: FilePath::Relative("fallback.ipxe".to_string()), - content: FallbackIpxeTpl { - gateway_ip, - kickstart_filename, - } - .to_string(), - }, - ], - }), - ]; + let ipxe_score = OkdIpxeScore { + kickstart_filename, + harmony_inventory_agent, + cluster_pubkey_filename, + }; - harmony_cli::run(inventory, topology, scores, None) + harmony_cli::run(inventory, topology, vec![Box::new(ipxe_score)], None) .await .unwrap(); } - -#[derive(Template)] -#[template(path = "boot.ipxe.j2")] -struct BootIpxeTpl<'a> { - gateway_ip: &'a IpAddr, -} - -#[derive(Template)] -#[template(path = "fallback.ipxe.j2")] -struct FallbackIpxeTpl<'a> { - gateway_ip: &'a IpAddr, - kickstart_filename: &'a str, -} - -#[derive(Template)] -#[template(path = "inventory.kickstart.j2")] -struct InventoryKickstartTpl<'a> { - gateway_ip: &'a IpAddr, - cluster_pubkey_filename: &'a str, - harmony_inventory_agent: &'a str, -} diff --git a/examples/okd_pxe/src/topology.rs b/examples/okd_pxe/src/topology.rs index 198226c..eb23908 100644 --- a/examples/okd_pxe/src/topology.rs +++ b/examples/okd_pxe/src/topology.rs @@ -1,5 +1,3 @@ -use std::{net::IpAddr, sync::Arc}; - use cidr::Ipv4Cidr; use harmony::{ hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup}, @@ -10,6 +8,7 @@ use harmony::{ use harmony_macros::{ip, ipv4}; use harmony_secret::{Secret, SecretManager}; use serde::{Deserialize, Serialize}; +use std::{net::IpAddr, sync::Arc}; #[derive(Secret, Serialize, Deserialize, Debug, PartialEq)] struct OPNSenseFirewallConfig { diff --git a/harmony/Cargo.toml b/harmony/Cargo.toml index d09862a..234f990 100644 --- a/harmony/Cargo.toml +++ b/harmony/Cargo.toml @@ -69,6 +69,7 @@ base64.workspace = true once_cell = "1.21.3" harmony_inventory_agent = { path = "../harmony_inventory_agent" } harmony_secret_derive = { version = "0.1.0", path = "../harmony_secret_derive" } +askama = "0.14.0" [dev-dependencies] pretty_assertions.workspace = true diff --git a/harmony/src/modules/okd/ipxe.rs b/harmony/src/modules/okd/ipxe.rs new file mode 100644 index 0000000..a0b32d4 --- /dev/null +++ b/harmony/src/modules/okd/ipxe.rs @@ -0,0 +1,148 @@ +use askama::Template; +use async_trait::async_trait; +use derive_new::new; +use serde::Serialize; +use std::net::IpAddr; + +use crate::{ + data::{FileContent, FilePath, Id, Version}, + interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, + inventory::Inventory, + modules::{dhcp::DhcpScore, http::StaticFilesHttpScore, tftp::TftpScore}, + score::Score, + topology::{DhcpServer, HttpServer, Router, TftpServer, Topology, Url}, +}; + +#[derive(Debug, new, Clone, Serialize)] +pub struct OkdIpxeScore { + pub kickstart_filename: String, + pub harmony_inventory_agent: String, + pub cluster_pubkey_filename: String, +} + +impl Score for OkdIpxeScore { + fn create_interpret(&self) -> Box> { + Box::new(IpxeInterpret::new(self.clone())) + } + + fn name(&self) -> String { + "OkdIpxeScore".to_string() + } +} + +#[derive(Debug, new, Clone)] +pub struct IpxeInterpret { + score: OkdIpxeScore, +} + +#[async_trait] +impl Interpret for IpxeInterpret { + async fn execute( + &self, + inventory: &Inventory, + topology: &T, + ) -> Result { + let gateway_ip = topology.get_gateway(); + + let scores: Vec>> = vec![ + Box::new(DhcpScore { + host_binding: vec![], + next_server: Some(topology.get_gateway()), + boot_filename: None, + filename: Some("undionly.kpxe".to_string()), + filename64: Some("ipxe.efi".to_string()), + filenameipxe: Some(format!("http://{gateway_ip}:8080/boot.ipxe").to_string()), + }), + Box::new(TftpScore { + files_to_serve: Url::LocalFolder("./data/pxe/okd/tftpboot/".to_string()), + }), + Box::new(StaticFilesHttpScore { + // TODO The current russh based copy is way too slow, check for a lib update or use scp + // when available + // + // For now just run : + // scp -r data/pxe/okd/http_files/* root@192.168.1.1:/usr/local/http/ + // + folder_to_serve: None, + // folder_to_serve: Some(Url::LocalFolder("./data/pxe/okd/http_files/".to_string())), + files: vec![ + FileContent { + path: FilePath::Relative("boot.ipxe".to_string()), + content: BootIpxeTpl { + gateway_ip: &gateway_ip, + } + .to_string(), + }, + FileContent { + path: FilePath::Relative(self.score.kickstart_filename.clone()), + content: InventoryKickstartTpl { + gateway_ip: &gateway_ip, + harmony_inventory_agent: &self.score.harmony_inventory_agent, + cluster_pubkey_filename: &self.score.cluster_pubkey_filename, + } + .to_string(), + }, + FileContent { + path: FilePath::Relative("fallback.ipxe".to_string()), + content: FallbackIpxeTpl { + gateway_ip: &gateway_ip, + kickstart_filename: &self.score.kickstart_filename, + } + .to_string(), + }, + ], + }), + ]; + + for score in scores { + let result = score.interpret(inventory, topology).await; + match result { + Ok(outcome) => match outcome.status { + InterpretStatus::SUCCESS => continue, + InterpretStatus::NOOP => continue, + _ => return Err(InterpretError::new(outcome.message)), + }, + Err(e) => return Err(e), + }; + } + + Ok(Outcome::success("Ipxe installed".to_string())) + } + + fn get_name(&self) -> InterpretName { + InterpretName::Ipxe + } + + fn get_version(&self) -> Version { + todo!() + } + + fn get_status(&self) -> InterpretStatus { + todo!() + } + + fn get_children(&self) -> Vec { + todo!() + } +} + +#[derive(Template)] +#[template(path = "boot.ipxe.j2")] +struct BootIpxeTpl<'a> { + gateway_ip: &'a IpAddr, +} + +#[derive(Template)] +#[template(path = "fallback.ipxe.j2")] +struct FallbackIpxeTpl<'a> { + gateway_ip: &'a IpAddr, + kickstart_filename: &'a str, +} + +#[derive(Template)] +#[template(path = "inventory.kickstart.j2")] +struct InventoryKickstartTpl<'a> { + gateway_ip: &'a IpAddr, + cluster_pubkey_filename: &'a str, + harmony_inventory_agent: &'a str, +} diff --git a/harmony/src/modules/okd/mod.rs b/harmony/src/modules/okd/mod.rs index 8811771..fe61b1e 100644 --- a/harmony/src/modules/okd/mod.rs +++ b/harmony/src/modules/okd/mod.rs @@ -2,5 +2,6 @@ pub mod bootstrap_dhcp; pub mod bootstrap_load_balancer; pub mod dhcp; pub mod dns; +pub mod ipxe; pub mod load_balancer; pub mod upgrade; diff --git a/examples/okd_pxe/templates/boot.ipxe.j2 b/harmony/templates/boot.ipxe.j2 similarity index 100% rename from examples/okd_pxe/templates/boot.ipxe.j2 rename to harmony/templates/boot.ipxe.j2 diff --git a/examples/okd_pxe/templates/fallback.ipxe.j2 b/harmony/templates/fallback.ipxe.j2 similarity index 100% rename from examples/okd_pxe/templates/fallback.ipxe.j2 rename to harmony/templates/fallback.ipxe.j2 diff --git a/examples/okd_pxe/templates/inventory.kickstart.j2 b/harmony/templates/inventory.kickstart.j2 similarity index 100% rename from examples/okd_pxe/templates/inventory.kickstart.j2 rename to harmony/templates/inventory.kickstart.j2