feat: Refactored harmony into a workspace with independent modules per client, completed RusshClient test_connection implementation. Needs refactoring though

This commit is contained in:
jeangab 2024-09-06 16:10:06 -04:00
parent cc01ec5fe5
commit 8592a3bc36
14 changed files with 116 additions and 46 deletions

6
harmony-rs/Cargo.lock generated
View File

@ -619,6 +619,12 @@ dependencies = [
[[package]]
name = "fqm"
version = "0.1.0"
dependencies = [
"env_logger",
"harmony",
"log",
"tokio",
]
[[package]]
name = "funty"

View File

@ -15,3 +15,4 @@ log = "0.4.22"
env_logger = "0.11.5"
derive-new = "0.7.0"
async-trait = "0.1.82"
tokio = { version = "1.40.0", features = ["io-std"] }

View File

@ -4,3 +4,8 @@ version = "0.1.0"
edition = "2021"
[dependencies]
harmony = { path = "../harmony" }
log = { workspace = true }
env_logger = { workspace = true }
tokio = { workspace = true }

View File

@ -1,9 +1,11 @@
use crate::domain::{
hardware::{Location, Host, HostCategory},
use harmony::domain::{
hardware::{Host, HostCategory, Location, NetworkInterface},
inventory::Inventory,
};
pub fn get_fqm_inventory() -> Inventory {
pub fn get_inventory() -> Inventory {
let network = vec![NetworkInterface::new(1_000_000_000, "TODO MAC ADDRESS".into(), true ) ];
let storage = vec![];
Inventory {
location: Location::new(
"1134 Grande Allée Ouest 1er étage, Québec, Qc".into(),
@ -11,8 +13,8 @@ pub fn get_fqm_inventory() -> Inventory {
),
host: vec![Host {
category: HostCategory::Server,
network: vec![],
storage: vec![],
network,
storage,
labels: vec![],
}],
switch: vec![],

View File

@ -1,3 +1,4 @@
pub mod inventory;
pub fn add(left: usize, right: usize) -> usize {
left + right
}

View File

@ -0,0 +1,22 @@
use fqm::inventory::get_inventory;
use harmony::{
domain::{
inventory::{Inventory, InventoryFilter},
maestro::Maestro,
},
modules::opnsense_dhcp::OPNSenseDhcpScore,
};
use log::info;
#[tokio::main]
async fn main() {
env_logger::init();
tokio::spawn(async move {
info!("FQM Harmony Starting");
let maestro = Maestro::new(get_inventory());
let score = OPNSenseDhcpScore::new(InventoryFilter::new(vec![]));
let result = maestro.interpret(score).await.unwrap();
info!("{result}");
}).await;
}

View File

@ -11,7 +11,7 @@ rust-ipmi = "0.1.1"
semver = "1.0.23"
serde = { version = "1.0.209", features = ["derive"] }
serde_json = "1.0.127"
tokio = { version = "1.40.0", features = ["io-std"] }
tokio = { workspace = true }
derive-new = { workspace = true }
log = { workspace = true }
env_logger = { workspace = true }

View File

@ -29,5 +29,5 @@ impl std::error::Error for ExecutorError {}
#[async_trait]
pub trait SshClient {
async fn test_connection(&self, username: String, password: String) -> Result<(), ExecutorError>;
async fn test_connection(&self, username: &str, password: &str) -> Result<(), ExecutorError>;
}

View File

@ -18,14 +18,15 @@ pub enum HostCategory {
Switch,
}
#[derive(Debug)]
#[derive(Debug, new)]
pub struct NetworkInterface {
speed: u64,
mac_address: MacAddress,
plugged_in: bool,
}
type MacAddress = String;
#[derive(Debug)]
#[derive(Debug, new)]
pub enum StorageConnectionType {
Sata3g,
Sata6g,
@ -56,7 +57,10 @@ pub struct Switch {
#[derive(Debug)]
pub struct Firewall {}
#[derive(Debug)]
pub struct Label;
pub struct Label {
name: String,
value: String,
}
pub type Address = String;
#[derive(new, Debug)]

View File

@ -1,7 +1,10 @@
use std::error::Error;
use async_trait::async_trait;
use derive_new::new;
use super::{
data::{Id, Version},
inventory::Inventory,
score::Score,
data::{Id, Version}, executors::ExecutorError, inventory::Inventory, score::Score
};
pub enum InterpretName {
@ -16,19 +19,25 @@ impl std::fmt::Display for InterpretName {
}
}
#[async_trait]
pub trait Interpret {
fn execute(&self, inventory: &Inventory) -> Result<Outcome, InterpretError>;
async fn execute(&self, inventory: &Inventory) -> Result<Outcome, InterpretError>;
fn get_name(&self) -> InterpretName;
fn get_version(&self) -> Version;
fn get_status(&self) -> InterpretStatus;
fn get_children(&self) -> Vec<Id>;
}
#[derive(Debug)]
#[derive(Debug, new)]
pub struct Outcome {
status: InterpretStatus,
message: String,
}
impl std::fmt::Display for Outcome {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("Outcome {}: {}", self.status, self.message))
}
}
#[derive(Debug, Clone)]
pub enum InterpretStatus {
@ -39,7 +48,34 @@ pub enum InterpretStatus {
BLOCKED,
}
impl std::fmt::Display for InterpretStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = match self {
InterpretStatus::SUCCESS => "SUCCESS",
InterpretStatus::FAILURE => "FAILURE",
InterpretStatus::RUNNING => "RUNNING",
InterpretStatus::QUEUED => "QUEUED",
InterpretStatus::BLOCKED => "BLOCKED",
};
f.write_str(msg)
}
}
#[derive(Debug)]
pub struct InterpretError {
msg: String,
}
impl std::fmt::Display for InterpretError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.msg)
}
}
impl Error for InterpretError {}
impl From<ExecutorError> for InterpretError{
fn from(value: ExecutorError) -> Self {
Self { msg: format!("InterpretError : {value}") }
}
}

View File

@ -1,7 +1,7 @@
use derive_new::new;
use log::info;
use super::{interpret::Interpret, inventory::Inventory, score::Score};
use super::{interpret::{Interpret, InterpretError, Outcome}, inventory::Inventory, score::Score};
#[derive(new)]
pub struct Maestro {
@ -28,11 +28,12 @@ impl Maestro {
todo!()
}
pub fn interpret<S: Score>(&self, score: S) {
pub async fn interpret<S: Score>(&self, score: S) -> Result<Outcome, InterpretError> {
info!("Running score {score:?}");
let interpret: S::InterpretType = score.create_interpret();
info!("Launching interpret {interpret:?}");
let result = interpret.execute(&self.inventory);
let result = interpret.execute(&self.inventory).await;
info!("Got result {result:?}");
result
}
}

View File

@ -1,6 +1,7 @@
use std::sync::Arc;
use async_trait::async_trait;
use log::info;
use russh::{client, keys::key};
use crate::domain::executors::{ExecutorError, SshClient};
@ -9,11 +10,14 @@ pub struct RusshClient;
#[async_trait]
impl SshClient for RusshClient {
async fn test_connection(&self, _username: String, _password: String) -> Result<(), crate::domain::executors::ExecutorError> {
async fn test_connection(&self, _username: &str, _password: &str) -> Result<(), crate::domain::executors::ExecutorError> {
let config = client::Config::default();
let c = Client{};
let _client = client::connect(Arc::new(config), ("192.168.12.1", 22), c).await?;
Ok(())
let mut client = client::connect(Arc::new(config), ("192.168.1.1", 22), c).await?;
match client.authenticate_password("nationtech", "opnsense").await? {
true => Ok(()),
false => Err(ExecutorError::AuthenticationError("ssh authentication failed".to_string())),
}
}
}
@ -36,6 +40,7 @@ impl client::Handler for Client {
impl From<russh::Error> for ExecutorError {
fn from(_value: russh::Error) -> Self {
todo!()
// TODO handle various russh errors properly
ExecutorError::NetworkError("Russh client error".to_string())
}
}

View File

@ -1,19 +0,0 @@
use harmony::{
domain::{
inventory::{Inventory, InventoryFilter},
maestro::Maestro,
},
modules::opnsense_dhcp::OPNSenseDhcpScore,
};
pub fn main() {
env_logger::init();
let maestro = Maestro::new(get_inventory());
let score = OPNSenseDhcpScore::new(InventoryFilter::new(vec![]));
maestro.interpret(score);
}
fn get_inventory() -> Inventory {
todo!()
}

View File

@ -1,8 +1,9 @@
use async_trait::async_trait;
use derive_new::new;
use log::info;
use crate::{domain::{
data::{Id, Version}, hardware::NetworkInterface, interpret::{InterpretError, InterpretStatus, Outcome}, inventory::Inventory, topology::IpAddress
data::{Id, Version}, executors::SshClient, hardware::NetworkInterface, interpret::{InterpretError, InterpretStatus, Outcome}, inventory::Inventory, topology::IpAddress
}, infra::executors::russh::RusshClient};
use crate::domain::{
@ -54,6 +55,7 @@ impl OPNSenseDhcpInterpret {
}
}
#[async_trait]
impl Interpret for OPNSenseDhcpInterpret {
fn get_name(&self) -> InterpretName {
InterpretName::OPNSenseDHCP
@ -71,11 +73,15 @@ impl Interpret for OPNSenseDhcpInterpret {
todo!()
}
fn execute(&self, _inventory: &Inventory) -> Result<Outcome, InterpretError> {
async fn execute(&self, _inventory: &Inventory) -> Result<Outcome, InterpretError> {
info!("Executing {} on inventory {_inventory:?}", self.get_name());
let _ssh_client = RusshClient{};
// ssh_client.test_connection("username", "password");
todo!()
let ssh_client = RusshClient{};
info!("RusshClient initiated");
ssh_client.test_connection("paul", "paul").await?;
info!("Connection test complete");
Ok(Outcome::new(InterpretStatus::SUCCESS, "Connection test successful".to_string()))
}
}