Merge pull request 'feat: introduce Maestro::initialize function that creates the maestro instance and ensure_ready the topology as well. Also refactor all relevant examples to use this new initialize function' (#18) from feat/maestroinitialize into master

Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/18
Reviewed-by: taha <taha@noreply.git.nationtech.io>
This commit is contained in:
johnride 2025-04-24 17:43:31 +00:00
commit 00c0566533
15 changed files with 73 additions and 43 deletions

1
Cargo.lock generated
View File

@ -961,6 +961,7 @@ dependencies = [
"harmony", "harmony",
"harmony_macros", "harmony_macros",
"http 1.3.1", "http 1.3.1",
"inquire",
"k8s-openapi", "k8s-openapi",
"kube", "kube",
"log", "log",

5
check.sh Normal file
View File

@ -0,0 +1,5 @@
#!/bin/sh
set -e
cargo check --all-targets --all-features --keep-going
cargo fmt --check
cargo test

View File

@ -2,14 +2,14 @@ use harmony::{
inventory::Inventory, inventory::Inventory,
maestro::Maestro, maestro::Maestro,
modules::dummy::{ErrorScore, PanicScore, SuccessScore}, modules::dummy::{ErrorScore, PanicScore, SuccessScore},
topology::HAClusterTopology, topology::LocalhostTopology,
}; };
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let inventory = Inventory::autoload(); let inventory = Inventory::autoload();
let topology = HAClusterTopology::autoload(); let topology = LocalhostTopology::new();
let mut maestro = Maestro::new(inventory, topology); let mut maestro = Maestro::initialize(inventory, topology).await.unwrap();
maestro.register_all(vec![ maestro.register_all(vec![
Box::new(SuccessScore {}), Box::new(SuccessScore {}),

View File

@ -18,3 +18,4 @@ kube = "0.98.0"
k8s-openapi = { version = "0.24.0", features = [ "v1_30" ] } k8s-openapi = { version = "0.24.0", features = [ "v1_30" ] }
http = "1.2.0" http = "1.2.0"
serde_yaml = "0.9.34" serde_yaml = "0.9.34"
inquire.workspace = true

View File

@ -1,6 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use harmony_macros::yaml; use harmony_macros::yaml;
use inquire::Confirm;
use k8s_openapi::{ use k8s_openapi::{
api::{ api::{
apps::v1::{Deployment, DeploymentSpec}, apps::v1::{Deployment, DeploymentSpec},
@ -15,6 +16,17 @@ use kube::{
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let confirmation = Confirm::new(
"This will install various ressources to your default kubernetes cluster. Are you sure?",
)
.with_default(false)
.prompt()
.expect("Unexpected prompt error");
if !confirmation {
return;
}
let client = Client::try_default() let client = Client::try_default()
.await .await
.expect("Should instanciate client from defaults"); .expect("Should instanciate client from defaults");

View File

@ -9,7 +9,7 @@ use harmony::{
async fn main() { async fn main() {
let inventory = Inventory::autoload(); let inventory = Inventory::autoload();
let topology = HAClusterTopology::autoload(); let topology = HAClusterTopology::autoload();
let mut maestro = Maestro::new(inventory, topology); let mut maestro = Maestro::initialize(inventory, topology).await.unwrap();
maestro.register_all(vec![ maestro.register_all(vec![
// ADD scores : // ADD scores :

View File

@ -84,7 +84,7 @@ async fn main() {
let http_score = HttpScore::new(Url::LocalFolder( let http_score = HttpScore::new(Url::LocalFolder(
"./data/watchguard/pxe-http-files".to_string(), "./data/watchguard/pxe-http-files".to_string(),
)); ));
let mut maestro = Maestro::new(inventory, topology); let mut maestro = Maestro::initialize(inventory, topology).await.unwrap();
maestro.register_all(vec![ maestro.register_all(vec![
Box::new(dns_score), Box::new(dns_score),
Box::new(dhcp_score), Box::new(dhcp_score),

View File

@ -9,8 +9,7 @@ use harmony::{
load_balancer::LoadBalancerScore, load_balancer::LoadBalancerScore,
}, },
topology::{ topology::{
BackendServer, HAClusterTopology, HealthCheck, HttpMethod, HttpStatusCode, BackendServer, DummyInfra, HealthCheck, HttpMethod, HttpStatusCode, LoadBalancerService,
LoadBalancerService,
}, },
}; };
use harmony_macros::ipv4; use harmony_macros::ipv4;
@ -18,8 +17,8 @@ use harmony_macros::ipv4;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let inventory = Inventory::autoload(); let inventory = Inventory::autoload();
let topology = HAClusterTopology::autoload(); let topology = DummyInfra {};
let mut maestro = Maestro::new(inventory, topology); let mut maestro = Maestro::initialize(inventory, topology).await.unwrap();
maestro.register_all(vec![ maestro.register_all(vec![
Box::new(SuccessScore {}), Box::new(SuccessScore {}),

View File

@ -28,6 +28,12 @@ impl<T: Topology> Maestro<T> {
} }
} }
pub async fn initialize(inventory: Inventory, topology: T) -> Result<Self, InterpretError> {
let instance = Self::new(inventory, topology);
instance.prepare_topology().await?;
Ok(instance)
}
/// Ensures the associated Topology is ready for operations. /// Ensures the associated Topology is ready for operations.
/// Delegates the readiness check and potential setup actions to the Topology. /// Delegates the readiness check and potential setup actions to the Topology.
pub async fn prepare_topology(&self) -> Result<Outcome, InterpretError> { pub async fn prepare_topology(&self) -> Result<Outcome, InterpretError> {

View File

@ -218,7 +218,7 @@ where
mod tests { mod tests {
use super::*; use super::*;
use crate::modules::dns::DnsScore; use crate::modules::dns::DnsScore;
use crate::topology::{self, HAClusterTopology}; use crate::topology::HAClusterTopology;
#[test] #[test]
fn test_format_values_as_string() { fn test_format_values_as_string() {

View File

@ -1,6 +1,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use harmony_macros::ip; use harmony_macros::ip;
use harmony_types::net::MacAddress; use harmony_types::net::MacAddress;
use log::info;
use crate::executors::ExecutorError; use crate::executors::ExecutorError;
use crate::interpret::InterpretError; use crate::interpret::InterpretError;
@ -223,7 +224,20 @@ impl HttpServer for HAClusterTopology {
} }
#[derive(Debug)] #[derive(Debug)]
struct DummyInfra; pub struct DummyInfra;
#[async_trait]
impl Topology for DummyInfra {
fn name(&self) -> &str {
todo!()
}
async fn ensure_ready(&self) -> Result<Outcome, InterpretError> {
let dummy_msg = "This is a dummy infrastructure that does nothing";
info!("{dummy_msg}");
Ok(Outcome::success(dummy_msg.to_string()))
}
}
const UNIMPLEMENTED_DUMMY_INFRA: &str = "This is a dummy infrastructure, no operation is supported"; const UNIMPLEMENTED_DUMMY_INFRA: &str = "This is a dummy infrastructure, no operation is supported";

View File

@ -62,7 +62,7 @@ impl K8sAnywhereTopology {
} }
async fn try_install_k3d(&self) -> Result<K8sClient, InterpretError> { async fn try_install_k3d(&self) -> Result<K8sClient, InterpretError> {
let maestro = Maestro::new(Inventory::autoload(), LocalhostTopology::new()); let maestro = Maestro::initialize(Inventory::autoload(), LocalhostTopology::new()).await?;
let k3d_score = K3DInstallationScore::new(); let k3d_score = K3DInstallationScore::new();
maestro.interpret(Box::new(k3d_score)).await?; maestro.interpret(Box::new(k3d_score)).await?;
todo!( todo!(
@ -151,22 +151,20 @@ impl Topology for K8sAnywhereTopology {
} }
async fn ensure_ready(&self) -> Result<Outcome, InterpretError> { async fn ensure_ready(&self) -> Result<Outcome, InterpretError> {
let k8s_state = self let k8s_state = self
.k8s_state .k8s_state
.get_or_try_init(|| self.try_get_or_install_k8s_client()) .get_or_try_init(|| self.try_get_or_install_k8s_client())
.await?; .await?;
let k8s_state: &K8sState = k8s_state let k8s_state: &K8sState = k8s_state.as_ref().ok_or(InterpretError::new(
.as_ref() "No K8s client could be found or installed".to_string(),
.ok_or(InterpretError::new( ))?;
"No K8s client could be found or installed".to_string(),
))?;
match self.is_helm_available() { match self.is_helm_available() {
Ok(()) => Ok(Outcome::success(format!( Ok(()) => Ok(Outcome::success(format!(
"{} + helm available", "{} + helm available",
k8s_state.message.clone() k8s_state.message.clone()
))), ))),
Err(_) => Err(InterpretError::new("helm unavailable".to_string())), Err(_) => Err(InterpretError::new("helm unavailable".to_string())),
} }
} }

View File

@ -370,13 +370,10 @@ mod tests {
let result = get_servers_for_backend(&backend, &haproxy); let result = get_servers_for_backend(&backend, &haproxy);
// Check the result // Check the result
assert_eq!( assert_eq!(result, vec![BackendServer {
result, address: "192.168.1.1".to_string(),
vec![BackendServer { port: 80,
address: "192.168.1.1".to_string(), },]);
port: 80,
},]
);
} }
#[test] #[test]
fn test_get_servers_for_backend_no_linked_servers() { fn test_get_servers_for_backend_no_linked_servers() {
@ -433,18 +430,15 @@ mod tests {
// Call the function // Call the function
let result = get_servers_for_backend(&backend, &haproxy); let result = get_servers_for_backend(&backend, &haproxy);
// Check the result // Check the result
assert_eq!( assert_eq!(result, vec![
result, BackendServer {
vec![ address: "some-hostname.test.mcd".to_string(),
BackendServer { port: 80,
address: "some-hostname.test.mcd".to_string(), },
port: 80, BackendServer {
}, address: "192.168.1.2".to_string(),
BackendServer { port: 8080,
address: "192.168.1.2".to_string(), },
port: 8080, ]);
},
]
);
} }
} }

View File

@ -147,7 +147,6 @@ mod test {
modules::dummy::{ErrorScore, PanicScore, SuccessScore}, modules::dummy::{ErrorScore, PanicScore, SuccessScore},
topology::HAClusterTopology, topology::HAClusterTopology,
}; };
use harmony::{score::Score, topology::Topology};
fn init_test_maestro() -> Maestro<HAClusterTopology> { fn init_test_maestro() -> Maestro<HAClusterTopology> {
let inventory = Inventory::autoload(); let inventory = Inventory::autoload();

View File

@ -1,2 +1,3 @@
[package] [package]
name = "example" name = "example"
edition = "2024"