diff --git a/Cargo.lock b/Cargo.lock index 9232ffb..5b0243e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1775,6 +1775,24 @@ dependencies = [ "url", ] +[[package]] +name = "example-okd-install" +version = "0.1.0" +dependencies = [ + "cidr", + "env_logger", + "harmony", + "harmony_cli", + "harmony_macros", + "harmony_secret", + "harmony_secret_derive", + "harmony_types", + "log", + "serde", + "tokio", + "url", +] + [[package]] name = "example-opnsense" version = "0.1.0" diff --git a/examples/nanodc/src/main.rs b/examples/nanodc/src/main.rs index f66bac9..aa355a9 100644 --- a/examples/nanodc/src/main.rs +++ b/examples/nanodc/src/main.rs @@ -5,7 +5,7 @@ use std::{ use cidr::Ipv4Cidr; use harmony::{ - hardware::{Location, PhysicalHost, SwitchGroup}, + hardware::{HostCategory, Location, PhysicalHost, SwitchGroup}, infra::opnsense::OPNSenseManagementInterface, inventory::Inventory, modules::{ @@ -13,7 +13,7 @@ use harmony::{ okd::{ bootstrap_dhcp::OKDBootstrapDhcpScore, bootstrap_load_balancer::OKDBootstrapLoadBalancerScore, dhcp::OKDDhcpScore, - dns::OKDDnsScore, + dns::OKDDnsScore, ipxe::OkdIpxeScore, }, tftp::TftpScore, }, @@ -130,7 +130,16 @@ async fn main() { )), files: vec![], }; - let ipxe_score = IpxeScore::new(); + + 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(); + + let ipxe_score = OkdIpxeScore { + kickstart_filename, + harmony_inventory_agent, + cluster_pubkey_filename, + }; harmony_tui::run( inventory, diff --git a/examples/okd_installation/Cargo.toml b/examples/okd_installation/Cargo.toml new file mode 100644 index 0000000..7314e4f --- /dev/null +++ b/examples/okd_installation/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "example-okd-install" +edition = "2024" +version.workspace = true +readme.workspace = true +license.workspace = true +publish = false + +[dependencies] +harmony = { path = "../../harmony" } +harmony_cli = { path = "../../harmony_cli" } +harmony_types = { path = "../../harmony_types" } +harmony_secret = { path = "../../harmony_secret" } +harmony_secret_derive = { path = "../../harmony_secret_derive" } +cidr = { workspace = true } +tokio = { workspace = true } +harmony_macros = { path = "../../harmony_macros" } +log = { workspace = true } +env_logger = { workspace = true } +url = { workspace = true } +serde.workspace = true diff --git a/examples/okd_installation/src/main.rs b/examples/okd_installation/src/main.rs new file mode 100644 index 0000000..21f754d --- /dev/null +++ b/examples/okd_installation/src/main.rs @@ -0,0 +1,20 @@ +mod topology; + +use crate::topology::{get_inventory, get_topology}; +use harmony::modules::okd::{installation::OKDInstallationScore, ipxe::OkdIpxeScore}; + +#[tokio::main] +async fn main() { + let inventory = get_inventory(); + let topology = get_topology().await; + + 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(); + + let okd_install = Box::new(OKDInstallationScore {}); + + harmony_cli::run(inventory, topology, vec![okd_install], None) + .await + .unwrap(); +} diff --git a/examples/okd_installation/src/topology.rs b/examples/okd_installation/src/topology.rs new file mode 100644 index 0000000..27eb8c0 --- /dev/null +++ b/examples/okd_installation/src/topology.rs @@ -0,0 +1,77 @@ +use cidr::Ipv4Cidr; +use harmony::{ + hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup}, + infra::opnsense::OPNSenseManagementInterface, + inventory::Inventory, + topology::{HAClusterTopology, LogicalHost, UnmanagedRouter}, +}; +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 { + username: String, + password: String, +} + +pub async fn get_topology() -> HAClusterTopology { + let firewall = harmony::topology::LogicalHost { + ip: ip!("192.168.1.1"), + name: String::from("opnsense-1"), + }; + + let config = SecretManager::get::().await; + let config = config.unwrap(); + + let opnsense = Arc::new( + harmony::infra::opnsense::OPNSenseFirewall::new( + firewall, + None, + &config.username, + &config.password, + ) + .await, + ); + let lan_subnet = ipv4!("192.168.1.0"); + let gateway_ipv4 = ipv4!("192.168.1.1"); + let gateway_ip = IpAddr::V4(gateway_ipv4); + harmony::topology::HAClusterTopology { + domain_name: "demo.harmony.mcd".to_string(), + router: Arc::new(UnmanagedRouter::new( + gateway_ip, + Ipv4Cidr::new(lan_subnet, 24).unwrap(), + )), + load_balancer: opnsense.clone(), + firewall: opnsense.clone(), + tftp_server: opnsense.clone(), + http_server: opnsense.clone(), + dhcp_server: opnsense.clone(), + dns_server: opnsense.clone(), + control_plane: vec![LogicalHost { + ip: ip!("10.100.8.20"), + name: "cp0".to_string(), + }], + bootstrap_host: LogicalHost { + ip: ip!("10.100.8.20"), + name: "cp0".to_string(), + }, + workers: vec![], + switch: vec![], + } +} + +pub fn get_inventory() -> Inventory { + Inventory { + location: Location::new( + "Some virtual machine or maybe a physical machine if you're cool".to_string(), + "testopnsense".to_string(), + ), + switch: SwitchGroup::from([]), + firewall_mgmt: Box::new(OPNSenseManagementInterface::new()), + storage_host: vec![], + worker_host: vec![], + control_plane_host: vec![], + } +} diff --git a/examples/okd_installation/ssh_example_key b/examples/okd_installation/ssh_example_key new file mode 100644 index 0000000..272bfb3 --- /dev/null +++ b/examples/okd_installation/ssh_example_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHAAAAJikacCNpGnA +jQAAAAtzc2gtZWQyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHA +AAAECiiKk4V6Q5cVs6axDM4sjAzZn/QCZLQekmYQXS9XbEYxx6bDylvC68cVpjKfEFtLQJ +/dOFi6PVS2vsIOqPDJIcAAAAEGplYW5nYWJAbGlsaWFuZTIBAgMEBQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/examples/okd_installation/ssh_example_key.pub b/examples/okd_installation/ssh_example_key.pub new file mode 100644 index 0000000..8a68662 --- /dev/null +++ b/examples/okd_installation/ssh_example_key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx6bDylvC68cVpjKfEFtLQJ/dOFi6PVS2vsIOqPDJIc jeangab@liliane2 diff --git a/harmony/src/infra/inventory/sqlite.rs b/harmony/src/infra/inventory/sqlite.rs index 967bcb9..63c662c 100644 --- a/harmony/src/infra/inventory/sqlite.rs +++ b/harmony/src/infra/inventory/sqlite.rs @@ -1,6 +1,6 @@ use crate::{ hardware::PhysicalHost, - inventory::{InventoryRepository, RepoError}, + inventory::{HostRole, InventoryRepository, RepoError}, }; use async_trait::async_trait; use harmony_types::id::Id; @@ -55,6 +55,18 @@ impl InventoryRepository for SqliteInventoryRepository { .await?; todo!() } + + async fn get_all_hosts(&self) -> Result, RepoError> { + todo!() + } + + async fn save_role_mapping( + &self, + role: &HostRole, + host: &PhysicalHost, + ) -> Result<(), RepoError> { + todo!("save role, host.id in the table host_role_mapping") + } } use sqlx::types::Json; diff --git a/harmony/src/modules/okd/installation.rs b/harmony/src/modules/okd/installation.rs index 94d5e01..ca0d704 100644 --- a/harmony/src/modules/okd/installation.rs +++ b/harmony/src/modules/okd/installation.rs @@ -44,12 +44,10 @@ //! which must be configured on host AND switch to connect properly. //! //! Configuration knobs -//! - lan_cidr: CIDR to scan/allow for discovery endpoints. //! - public_domain: External wildcard/apps domain (e.g., apps.example.com). //! - internal_domain: Internal cluster domain (e.g., cluster.local or harmony.mcd). use async_trait::async_trait; -use chrono::Duration; use derive_new::new; use harmony_types::id::Id; use log::{error, info, warn}; @@ -75,14 +73,7 @@ use crate::{ // ------------------------------------------------------------------------------------------------- #[derive(Debug, Clone, Serialize, Deserialize, new)] -pub struct OKDInstallationScore { - /// The LAN CIDR where discovery endpoints live (e.g., 192.168.10.0/24) - pub lan_cidr: String, - /// Public external domain (e.g., example.com). Used for api/apps wildcard, etc. - pub public_domain: String, - /// Internal cluster domain (e.g., harmony.mcd). Used for internal svc/ingress and DNS. - pub internal_domain: String, -} +pub struct OKDInstallationScore {} impl Score for OKDInstallationScore { fn create_interpret(&self) -> Box> { @@ -123,7 +114,7 @@ impl OKDInstallationInterpret { // 1) Prepare DNS and DHCP lease registration (optional) // 2) Serve default iPXE + Kickstart and poll discovery - let discovery_score = OKDSetup01InventoryScore::new(self.score.lan_cidr.clone()); + let discovery_score = OKDSetup01InventoryScore::new(); discovery_score.interpret(inventory, topology).await?; Ok(()) @@ -135,10 +126,7 @@ impl OKDInstallationInterpret { topology: &HAClusterTopology, ) -> Result<(), InterpretError> { // Select and provision bootstrap - let bootstrap_score = OKDSetup02BootstrapScore::new( - self.score.public_domain.clone(), - self.score.internal_domain.clone(), - ); + let bootstrap_score = OKDSetup02BootstrapScore::new(); bootstrap_score.interpret(inventory, topology).await?; Ok(()) } @@ -178,10 +166,7 @@ impl OKDInstallationInterpret { inventory: &Inventory, topology: &HAClusterTopology, ) -> Result<(), InterpretError> { - let report_score = OKDSetup06InstallationReportScore::new( - self.score.public_domain.clone(), - self.score.internal_domain.clone(), - ); + let report_score = OKDSetup06InstallationReportScore::new(); report_score.interpret(inventory, topology).await?; Ok(()) } @@ -212,10 +197,7 @@ impl Interpret for OKDInstallationInterpret { ) -> Result { instrument(HarmonyEvent::HarmonyStarted).ok(); - info!( - "Starting OKD installation pipeline for public_domain={} internal_domain={} lan_cidr={}", - self.score.public_domain, self.score.internal_domain, self.score.lan_cidr - ); + info!("Starting OKD installation pipeline",); self.run_inventory_phase(inventory, topology).await?; @@ -400,10 +382,7 @@ impl Interpret for OKDSetup01InventoryInterpret { // ------------------------------------------------------------------------------------------------- #[derive(Debug, Clone, Serialize, new)] -struct OKDSetup02BootstrapScore { - public_domain: String, - internal_domain: String, -} +struct OKDSetup02BootstrapScore {} impl Score for OKDSetup02BootstrapScore { fn create_interpret(&self) -> Box> { @@ -754,10 +733,7 @@ impl Interpret for OKDSetup05SanityCheckInterpret { // ------------------------------------------------------------------------------------------------- #[derive(Debug, Clone, Serialize, new)] -struct OKDSetup06InstallationReportScore { - public_domain: String, - internal_domain: String, -} +struct OKDSetup06InstallationReportScore {} impl Score for OKDSetup06InstallationReportScore { fn create_interpret(&self) -> Box> { @@ -787,10 +763,7 @@ impl OKDSetup06InstallationReportInterpret { } async fn generate(&self) -> Result<(), InterpretError> { - info!( - "[Report] Generating installation report for {} / {}", - self.score.public_domain, self.score.internal_domain - ); + info!("[Report] Generating OKD installation report",); Ok(()) } }