wip: OKd installation, some cleanup of unused and some refactoring
All checks were successful
Run Check Script / check (pull_request) Successful in 1m13s

This commit is contained in:
Jean-Gabriel Gill-Couture 2025-09-01 23:36:35 -04:00
parent 35a459f63c
commit 241980ebec
9 changed files with 177 additions and 39 deletions

18
Cargo.lock generated
View File

@ -1775,6 +1775,24 @@ dependencies = [
"url", "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]] [[package]]
name = "example-opnsense" name = "example-opnsense"
version = "0.1.0" version = "0.1.0"

View File

@ -5,7 +5,7 @@ use std::{
use cidr::Ipv4Cidr; use cidr::Ipv4Cidr;
use harmony::{ use harmony::{
hardware::{Location, PhysicalHost, SwitchGroup}, hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
infra::opnsense::OPNSenseManagementInterface, infra::opnsense::OPNSenseManagementInterface,
inventory::Inventory, inventory::Inventory,
modules::{ modules::{
@ -13,7 +13,7 @@ use harmony::{
okd::{ okd::{
bootstrap_dhcp::OKDBootstrapDhcpScore, bootstrap_dhcp::OKDBootstrapDhcpScore,
bootstrap_load_balancer::OKDBootstrapLoadBalancerScore, dhcp::OKDDhcpScore, bootstrap_load_balancer::OKDBootstrapLoadBalancerScore, dhcp::OKDDhcpScore,
dns::OKDDnsScore, dns::OKDDnsScore, ipxe::OkdIpxeScore,
}, },
tftp::TftpScore, tftp::TftpScore,
}, },
@ -130,7 +130,16 @@ async fn main() {
)), )),
files: vec![], 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( harmony_tui::run(
inventory, inventory,

View File

@ -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

View File

@ -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();
}

View File

@ -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::<OPNSenseFirewallConfig>().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![],
}
}

View File

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHAAAAJikacCNpGnA
jQAAAAtzc2gtZWQyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHA
AAAECiiKk4V6Q5cVs6axDM4sjAzZn/QCZLQekmYQXS9XbEYxx6bDylvC68cVpjKfEFtLQJ
/dOFi6PVS2vsIOqPDJIcAAAAEGplYW5nYWJAbGlsaWFuZTIBAgMEBQ==
-----END OPENSSH PRIVATE KEY-----

View File

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx6bDylvC68cVpjKfEFtLQJ/dOFi6PVS2vsIOqPDJIc jeangab@liliane2

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
hardware::PhysicalHost, hardware::PhysicalHost,
inventory::{InventoryRepository, RepoError}, inventory::{HostRole, InventoryRepository, RepoError},
}; };
use async_trait::async_trait; use async_trait::async_trait;
use harmony_types::id::Id; use harmony_types::id::Id;
@ -55,6 +55,18 @@ impl InventoryRepository for SqliteInventoryRepository {
.await?; .await?;
todo!() todo!()
} }
async fn get_all_hosts(&self) -> Result<Vec<PhysicalHost>, 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; use sqlx::types::Json;

View File

@ -44,12 +44,10 @@
//! which must be configured on host AND switch to connect properly. //! which must be configured on host AND switch to connect properly.
//! //!
//! Configuration knobs //! Configuration knobs
//! - lan_cidr: CIDR to scan/allow for discovery endpoints.
//! - public_domain: External wildcard/apps domain (e.g., apps.example.com). //! - public_domain: External wildcard/apps domain (e.g., apps.example.com).
//! - internal_domain: Internal cluster domain (e.g., cluster.local or harmony.mcd). //! - internal_domain: Internal cluster domain (e.g., cluster.local or harmony.mcd).
use async_trait::async_trait; use async_trait::async_trait;
use chrono::Duration;
use derive_new::new; use derive_new::new;
use harmony_types::id::Id; use harmony_types::id::Id;
use log::{error, info, warn}; use log::{error, info, warn};
@ -75,14 +73,7 @@ use crate::{
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
#[derive(Debug, Clone, Serialize, Deserialize, new)] #[derive(Debug, Clone, Serialize, Deserialize, new)]
pub struct OKDInstallationScore { 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,
}
impl Score<HAClusterTopology> for OKDInstallationScore { impl Score<HAClusterTopology> for OKDInstallationScore {
fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
@ -123,7 +114,7 @@ impl OKDInstallationInterpret {
// 1) Prepare DNS and DHCP lease registration (optional) // 1) Prepare DNS and DHCP lease registration (optional)
// 2) Serve default iPXE + Kickstart and poll discovery // 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?; discovery_score.interpret(inventory, topology).await?;
Ok(()) Ok(())
@ -135,10 +126,7 @@ impl OKDInstallationInterpret {
topology: &HAClusterTopology, topology: &HAClusterTopology,
) -> Result<(), InterpretError> { ) -> Result<(), InterpretError> {
// Select and provision bootstrap // Select and provision bootstrap
let bootstrap_score = OKDSetup02BootstrapScore::new( let bootstrap_score = OKDSetup02BootstrapScore::new();
self.score.public_domain.clone(),
self.score.internal_domain.clone(),
);
bootstrap_score.interpret(inventory, topology).await?; bootstrap_score.interpret(inventory, topology).await?;
Ok(()) Ok(())
} }
@ -178,10 +166,7 @@ impl OKDInstallationInterpret {
inventory: &Inventory, inventory: &Inventory,
topology: &HAClusterTopology, topology: &HAClusterTopology,
) -> Result<(), InterpretError> { ) -> Result<(), InterpretError> {
let report_score = OKDSetup06InstallationReportScore::new( let report_score = OKDSetup06InstallationReportScore::new();
self.score.public_domain.clone(),
self.score.internal_domain.clone(),
);
report_score.interpret(inventory, topology).await?; report_score.interpret(inventory, topology).await?;
Ok(()) Ok(())
} }
@ -212,10 +197,7 @@ impl Interpret<HAClusterTopology> for OKDInstallationInterpret {
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
instrument(HarmonyEvent::HarmonyStarted).ok(); instrument(HarmonyEvent::HarmonyStarted).ok();
info!( info!("Starting OKD installation pipeline",);
"Starting OKD installation pipeline for public_domain={} internal_domain={} lan_cidr={}",
self.score.public_domain, self.score.internal_domain, self.score.lan_cidr
);
self.run_inventory_phase(inventory, topology).await?; self.run_inventory_phase(inventory, topology).await?;
@ -400,10 +382,7 @@ impl Interpret<HAClusterTopology> for OKDSetup01InventoryInterpret {
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
#[derive(Debug, Clone, Serialize, new)] #[derive(Debug, Clone, Serialize, new)]
struct OKDSetup02BootstrapScore { struct OKDSetup02BootstrapScore {}
public_domain: String,
internal_domain: String,
}
impl Score<HAClusterTopology> for OKDSetup02BootstrapScore { impl Score<HAClusterTopology> for OKDSetup02BootstrapScore {
fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
@ -754,10 +733,7 @@ impl Interpret<HAClusterTopology> for OKDSetup05SanityCheckInterpret {
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
#[derive(Debug, Clone, Serialize, new)] #[derive(Debug, Clone, Serialize, new)]
struct OKDSetup06InstallationReportScore { struct OKDSetup06InstallationReportScore {}
public_domain: String,
internal_domain: String,
}
impl Score<HAClusterTopology> for OKDSetup06InstallationReportScore { impl Score<HAClusterTopology> for OKDSetup06InstallationReportScore {
fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
@ -787,10 +763,7 @@ impl OKDSetup06InstallationReportInterpret {
} }
async fn generate(&self) -> Result<(), InterpretError> { async fn generate(&self) -> Result<(), InterpretError> {
info!( info!("[Report] Generating OKD installation report",);
"[Report] Generating installation report for {} / {}",
self.score.public_domain, self.score.internal_domain
);
Ok(()) Ok(())
} }
} }