feat(harmony): add OPNSense shell command execution score

Introduces a new `OPNSenseShellCommandScore` to execute shell commands on an OPNSense device within the Harmony framework. This allows for custom command execution as part of the scoring and interpretation process, enhancing the flexibility and functionality of the system.
This commit is contained in:
Jean-Gabriel Gill-Couture 2025-02-04 16:39:49 -05:00
parent 697c669d05
commit 0b30d82793
6 changed files with 102 additions and 1 deletions

View File

@ -13,6 +13,7 @@ use harmony::{
dummy::{ErrorScore, PanicScore, SuccessScore},
http::HttpScore,
okd::{dhcp::OKDDhcpScore, dns::OKDDnsScore},
opnsense::OPNSenseShellCommandScore,
tftp::TftpScore,
},
topology::{LogicalHost, UnmanagedRouter, Url},
@ -91,6 +92,10 @@ async fn main() {
Box::new(load_balancer_score),
Box::new(tftp_score),
Box::new(http_score),
Box::new(OPNSenseShellCommandScore {
opnsense: opnsense.get_opnsense_config(),
command: "touch /tmp/helloharmonytouching".to_string(),
}),
Box::new(SuccessScore {}),
Box::new(ErrorScore {}),
Box::new(PanicScore {}),

View File

@ -18,6 +18,7 @@ pub enum InterpretName {
Http,
Dummy,
Panic,
OPNSense,
}
impl std::fmt::Display for InterpretName {
@ -30,6 +31,7 @@ impl std::fmt::Display for InterpretName {
InterpretName::Http => f.write_str("Http"),
InterpretName::Dummy => f.write_str("Dummy"),
InterpretName::Panic => f.write_str("Panic"),
InterpretName::OPNSense => f.write_str("OPNSense"),
}
}
}

View File

@ -16,7 +16,7 @@ use crate::{
topology::{IpAddress, LogicalHost},
};
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct OPNSenseFirewall {
opnsense_config: Arc<RwLock<opnsense_config::Config>>,
host: LogicalHost,
@ -36,6 +36,10 @@ impl OPNSenseFirewall {
}
}
pub fn get_opnsense_config(&self) -> Arc<RwLock<opnsense_config::Config>> {
self.opnsense_config.clone()
}
async fn commit_config(&self) -> Result<(), ExecutorError> {
self.opnsense_config
.read()

View File

@ -5,4 +5,5 @@ pub mod http;
pub mod k8s;
pub mod load_balancer;
pub mod okd;
pub mod opnsense;
pub mod tftp;

View File

@ -0,0 +1,85 @@
use std::sync::Arc;
use async_trait::async_trait;
use tokio::sync::RwLock;
use crate::{
data::{Id, Version},
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::Inventory,
score::Score,
topology::HAClusterTopology,
};
#[derive(Debug, Clone)]
pub struct OPNSenseShellCommandScore {
pub opnsense: Arc<RwLock<opnsense_config::Config>>,
pub command: String,
}
impl Score for OPNSenseShellCommandScore {
fn create_interpret(&self) -> Box<dyn Interpret> {
Box::new(OPNsenseInterpret {
status: InterpretStatus::QUEUED,
score: self.clone(),
})
}
fn name(&self) -> String {
"OPNSenseShellCommandScore".to_string()
}
fn clone_box(&self) -> Box<dyn Score> {
Box::new(self.clone())
}
}
#[derive(Debug)]
pub struct OPNsenseInterpret {
status: InterpretStatus,
score: OPNSenseShellCommandScore,
}
#[async_trait]
impl Interpret for OPNsenseInterpret {
async fn execute(
&self,
_inventory: &Inventory,
_topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> {
let output = self
.score
.opnsense
.read()
.await
.run_command(&self.score.command)
.await?;
Ok(Outcome::success(format!(
"Command execution successful : {}\n\n{output}",
self.score.command
)))
}
fn get_name(&self) -> InterpretName {
InterpretName::OPNSense
}
fn get_version(&self) -> Version {
Version::from("1.0.0").unwrap()
}
fn get_status(&self) -> InterpretStatus {
self.status.clone()
}
fn get_children(&self) -> Vec<Id> {
todo!()
}
}
impl From<opnsense_config::Error> for InterpretError {
fn from(value: opnsense_config::Error) -> Self {
Self::new(format!("opnsense_config::Error {value:?}"))
}
}

View File

@ -181,6 +181,10 @@ impl Config {
Ok(OPNsense::from(xml))
}
pub async fn run_command(&self, command: &str) -> Result<String, Error> {
self.shell.exec(command).await
}
}
#[cfg(test)]