feat: implementation for opnsense os-node_exporter
Some checks are pending
Run Check Script / check (pull_request) Waiting to run

This commit is contained in:
Willem 2025-10-22 11:27:28 -04:00
parent 7536f4ec4b
commit 8126b233d8
9 changed files with 198 additions and 2 deletions

View File

@ -1,5 +1,6 @@
mod ha_cluster;
pub mod ingress;
pub mod node_exporter;
use harmony_types::net::IpAddress;
mod host_binding;
mod http;

View File

@ -4,6 +4,7 @@ mod firewall;
mod http;
mod load_balancer;
mod management;
pub mod node_exporter;
mod tftp;
use std::sync::Arc;

View File

@ -0,0 +1,44 @@
use async_trait::async_trait;
use log::debug;
use crate::{
executors::ExecutorError, infra::opnsense::OPNSenseFirewall,
topology::node_exporter::NodeExporter,
};
#[async_trait]
impl NodeExporter for OPNSenseFirewall {
async fn ensure_initialized(&self) -> Result<(), ExecutorError> {
let mut config = self.opnsense_config.write().await;
let node_exporter = config.node_exporter();
if let Some(config) = node_exporter.get_full_config() {
debug!(
"Node exporter available in opnsense config, assuming it is already installed. {config:?}"
);
} else {
config
.install_package("os-node_exporter")
.await
.map_err(|e| {
ExecutorError::UnexpectedError(format!("Executor failed when trying to install os-node_exporter package with error {e:?}"
))
})?;
}
config.node_exporter().enable(true);
Ok(())
}
async fn commit_config(&self) -> Result<(), ExecutorError> {
OPNSenseFirewall::commit_config(self).await
}
async fn reload_restart(&self) -> Result<(), ExecutorError> {
self.opnsense_config
.write()
.await
.node_exporter()
.reload_restart()
.await
.map_err(|e| ExecutorError::UnexpectedError(e.to_string()))
}
}

View File

@ -1,3 +1,4 @@
pub mod node_exporter;
mod shell;
mod upgrade;
pub use shell::*;

View File

@ -0,0 +1,70 @@
use async_trait::async_trait;
use harmony_types::id::Id;
use log::info;
use serde::Serialize;
use crate::{
data::Version,
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::Inventory,
score::Score,
topology::{Topology, node_exporter::NodeExporter},
};
#[derive(Debug, Clone, Serialize)]
pub struct NodeExporterScore {}
impl<T: Topology + NodeExporter> Score<T> for NodeExporterScore {
fn name(&self) -> String {
"NodeExporterScore".to_string()
}
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
Box::new(NodeExporterInterpret {})
}
}
#[derive(Debug)]
pub struct NodeExporterInterpret {}
#[async_trait]
impl<T: Topology + NodeExporter> Interpret<T> for NodeExporterInterpret {
async fn execute(
&self,
_inventory: &Inventory,
node_exporter: &T,
) -> Result<Outcome, InterpretError> {
info!(
"Making sure node exporter is initiailized: {:?}",
node_exporter.ensure_initialized().await?
);
info!("Applying Node Exporter configuration");
node_exporter.commit_config().await?;
info!("Reloading and restarting Node Exporter");
node_exporter.reload_restart().await?;
Ok(Outcome::success(format!(
"NodeExporter successfully configured"
)))
}
fn get_name(&self) -> InterpretName {
InterpretName::Custom("NodeExporter")
}
fn get_version(&self) -> Version {
todo!()
}
fn get_status(&self) -> InterpretStatus {
todo!()
}
fn get_children(&self) -> Vec<Id> {
todo!()
}
}

View File

@ -433,7 +433,7 @@ pub struct OPNsenseXmlSection {
#[yaserde(rename = "Interfaces")]
pub interfaces: Option<ConfigInterfaces>,
#[yaserde(rename = "NodeExporter")]
pub node_exporter: Option<RawXml>,
pub node_exporter: Option<NodeExporter>,
#[yaserde(rename = "Kea")]
pub kea: Option<RawXml>,
pub monit: Option<Monit>,
@ -1595,3 +1595,21 @@ pub struct Ifgroups {
#[yaserde(attribute = true)]
pub version: String,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct NodeExporter {
pub enabled: u8,
pub listenaddress: Option<MaybeString>,
pub listenport: u16,
pub cpu: u8,
pub exec: u8,
pub filesystem: u8,
pub loadavg: u8,
pub meminfo: u8,
pub netdev: u8,
pub time: u8,
pub devstat: u8,
pub interrupts: u8,
pub ntp: u8,
pub zfs: u8,
}

View File

@ -5,7 +5,8 @@ use crate::{
error::Error,
modules::{
caddy::CaddyConfig, dhcp_legacy::DhcpConfigLegacyISC, dns::UnboundDnsConfig,
dnsmasq::DhcpConfigDnsMasq, load_balancer::LoadBalancerConfig, tftp::TftpConfig,
dnsmasq::DhcpConfigDnsMasq, load_balancer::LoadBalancerConfig,
node_exporter::NodeExporterConfig, tftp::TftpConfig,
},
};
use log::{debug, info, trace, warn};
@ -71,6 +72,10 @@ impl Config {
LoadBalancerConfig::new(&mut self.opnsense, self.shell.clone())
}
pub fn node_exporter(&mut self) -> NodeExporterConfig<'_> {
NodeExporterConfig::new(&mut self.opnsense, self.shell.clone())
}
pub async fn upload_files(&self, source: &str, destination: &str) -> Result<String, Error> {
self.shell.upload_folder(source, destination).await
}

View File

@ -4,4 +4,5 @@ pub mod dhcp_legacy;
pub mod dns;
pub mod dnsmasq;
pub mod load_balancer;
pub mod node_exporter;
pub mod tftp;

View File

@ -0,0 +1,55 @@
use std::sync::Arc;
use opnsense_config_xml::{NodeExporter, OPNsense};
use crate::{config::OPNsenseShell, Error};
pub struct NodeExporterConfig<'a> {
opnsense: &'a mut OPNsense,
opnsense_shell: Arc<dyn OPNsenseShell>,
}
impl<'a> NodeExporterConfig<'a> {
pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self {
Self {
opnsense,
opnsense_shell,
}
}
pub fn get_full_config(&self) -> &Option<NodeExporter> {
&self.opnsense.opnsense.node_exporter
}
fn with_node_exporter<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut NodeExporter) -> R,
{
match &mut self.opnsense.opnsense.node_exporter.as_mut() {
Some(node_exporter) => f(node_exporter),
None => unimplemented!(
"
node exporter is not yet installed"
),
}
}
pub fn enable(&mut self, enabled: bool) {
self.with_node_exporter(|node_exporter| node_exporter.enabled = enabled as u8)
}
pub async fn reload_restart(&self) -> Result<(), Error> {
self.opnsense_shell
.exec("configctl node_exporter stop")
.await?;
self.opnsense_shell
.exec("configctl template reload OPNsense/NodeExporter")
.await?;
self.opnsense_shell
.exec("configctl node_exporter configtest")
.await?;
self.opnsense_shell
.exec("configctl node_exporter start")
.await?;
Ok(())
}
}