feat(modules/opnsense): refactor and add upgrade functionality

Refactor OPNSense module to use a mod.rs structure and add an OPNsenseLaunchUpgrade score for launching firmware upgrades.
This commit is contained in:
Jean-Gabriel Gill-Couture 2025-02-21 11:23:45 -05:00
parent 0eb9e02b99
commit 62a554fac7
11 changed files with 111 additions and 14 deletions

15
Cargo.lock generated
View File

@ -815,6 +815,21 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "example-nanodc"
version = "0.1.0"
dependencies = [
"cidr",
"env_logger",
"harmony",
"harmony_macros",
"harmony_tui",
"harmony_types",
"log",
"tokio",
"url",
]
[[package]] [[package]]
name = "example-opnsense" name = "example-opnsense"
version = "0.1.0" version = "0.1.0"

View File

@ -1,3 +1,5 @@
### Watch the whole repo on every change
Due to the current setup being a mix of separate repositories with gitignore and rust workspace, a few options are required for cargo-watch to have the desired behavior : Due to the current setup being a mix of separate repositories with gitignore and rust workspace, a few options are required for cargo-watch to have the desired behavior :
```sh ```sh

View File

@ -17,9 +17,9 @@ We have decided to adopt Hexagonal Architecture (also known as Ports and Adapter
2. Flexibility: The ports and adapters model allows us to easily swap or add new implementations for different hardware or protocols without affecting the core logic. 2. Flexibility: The ports and adapters model allows us to easily swap or add new implementations for different hardware or protocols without affecting the core logic.
3. Testability: The architecture facilitates easier testing by allowing us to mock external dependencies through port interfaces. 3. Testability: The architecture facilitates easier testing by allowing us to mock external dependencies through port interfaces or virtualization.
4. Alignment with Project Requirements: The structure aligns well with our need to interact with various external systems (IPMI, Redfish, AMT, etc.) and our plan to use event-driven patterns with NATS. 4. Alignment with Project Requirements: The structure aligns well with our need to interact with various external systems (IPMI, Redfish, AMT, etc.) and our plan to use event-driven patterns with eg. NATS.
5. Future-proofing: As we plan to expand into day 2 operations and policy management, Hexagonal Architecture provides a clear path for growth without compromising existing structure. 5. Future-proofing: As we plan to expand into day 2 operations and policy management, Hexagonal Architecture provides a clear path for growth without compromising existing structure.

View File

@ -1,5 +1,7 @@
**Architecture Decision Record: Harmony Infrastructure Abstractions** **Architecture Decision Record: Harmony Infrastructure Abstractions**
**Status**: Proposed
**Context**: Harmony is an infrastructure orchestrator written in pure Rust, aiming to provide real portability of automation across different cloud providers and infrastructure setups. To achieve this, we need to define infrastructure abstractions that are provider-agnostic and flexible enough to accommodate various use cases. **Context**: Harmony is an infrastructure orchestrator written in pure Rust, aiming to provide real portability of automation across different cloud providers and infrastructure setups. To achieve this, we need to define infrastructure abstractions that are provider-agnostic and flexible enough to accommodate various use cases.
**Decision**: We will define our infrastructure abstractions using a domain-driven approach, focusing on the core logic of Harmony. These abstractions will only include the absolutely required elements for a specific resource, without referencing specific providers or implementations. **Decision**: We will define our infrastructure abstractions using a domain-driven approach, focusing on the core logic of Harmony. These abstractions will only include the absolutely required elements for a specific resource, without referencing specific providers or implementations.
@ -50,6 +52,5 @@ impl Database for AmazonRDS {
``` ```
By defining our infrastructure abstractions in this way, we ensure that Harmony remains provider-agnostic and flexible enough to accommodate various use cases. This approach enables real portability of automation across different cloud providers and infrastructure setups. By defining our infrastructure abstractions in this way, we ensure that Harmony remains provider-agnostic and flexible enough to accommodate various use cases. This approach enables real portability of automation across different cloud providers and infrastructure setups.
**Status**: Accepted
**Consequences**: This decision will lead to a more modular and flexible architecture for Harmony, allowing users to easily adopt harmony on their own infrastructure and eventually switch between different infrastructure providers and reuse their existing automation scripts. It will also simplify the development process for new features and use cases, as we can focus on implementing the core domain logic without worrying about provider-specific details. **Consequences**: This decision will lead to a more modular and flexible architecture for Harmony, allowing users to easily adopt harmony on their own infrastructure and eventually switch between different infrastructure providers and reuse their existing automation scripts. It will also simplify the development process for new features and use cases, as we can focus on implementing the core domain logic without worrying about provider-specific details.

View File

@ -0,0 +1,18 @@
[package]
name = "example-nanodc"
edition = "2024"
version.workspace = true
readme.workspace = true
license.workspace = true
publish = false
[dependencies]
harmony = { path = "../../harmony" }
harmony_tui = { path = "../../harmony_tui" }
harmony_types = { path = "../../harmony_types" }
cidr = { workspace = true }
tokio = { workspace = true }
harmony_macros = { path = "../../harmony_macros" }
log = { workspace = true }
env_logger = { workspace = true }
url = { workspace = true }

View File

@ -0,0 +1,20 @@
use harmony::{
inventory::Inventory,
maestro::Maestro,
modules::{dummy::{ErrorScore, PanicScore, SuccessScore}, k8s::deployment::K8sDeploymentScore},
topology::HAClusterTopology,
};
#[tokio::main]
async fn main() {
let inventory = Inventory::autoload();
let topology = HAClusterTopology::autoload();
let mut maestro = Maestro::new(inventory, topology);
maestro.register_all(vec![
Box::new(SuccessScore {}),
Box::new(ErrorScore {}),
Box::new(PanicScore {}),
]);
harmony_tui::init(maestro).await.unwrap();
}

View File

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

View File

@ -1,2 +0,0 @@
target
private_repos

View File

@ -0,0 +1,6 @@
mod shell;
mod upgrade;
pub use shell::*;
pub use upgrade::*;

View File

@ -12,14 +12,14 @@ use crate::{
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct OPNSenseShellCommandScore { pub struct OPNsenseShellCommandScore {
pub opnsense: Arc<RwLock<opnsense_config::Config>>, pub opnsense: Arc<RwLock<opnsense_config::Config>>,
pub command: String, pub command: String,
} }
impl Score for OPNSenseShellCommandScore { impl Score for OPNsenseShellCommandScore {
fn create_interpret(&self) -> Box<dyn Interpret> { fn create_interpret(&self) -> Box<dyn Interpret> {
Box::new(OPNsenseInterpret { Box::new(OPNsenseShellInterpret {
status: InterpretStatus::QUEUED, status: InterpretStatus::QUEUED,
score: self.clone(), score: self.clone(),
}) })
@ -35,13 +35,13 @@ impl Score for OPNSenseShellCommandScore {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct OPNsenseInterpret { pub struct OPNsenseShellInterpret {
status: InterpretStatus, pub status: InterpretStatus,
score: OPNSenseShellCommandScore, pub score: OPNsenseShellCommandScore,
} }
#[async_trait] #[async_trait]
impl Interpret for OPNsenseInterpret { impl Interpret for OPNsenseShellInterpret {
async fn execute( async fn execute(
&self, &self,
_inventory: &Inventory, _inventory: &Inventory,

View File

@ -0,0 +1,37 @@
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::{
interpret::{Interpret, InterpretStatus},
score::Score,
};
use super::{OPNsenseShellCommandScore, OPNsenseShellInterpret};
#[derive(Debug, Clone)]
pub struct OPNSenseLaunchUpgrade {
pub opnsense: Arc<RwLock<opnsense_config::Config>>,
}
impl Score for OPNSenseLaunchUpgrade {
fn create_interpret(&self) -> Box<dyn Interpret> {
let score = OPNsenseShellCommandScore {
opnsense: self.opnsense.clone(),
command: "/usr/local/opnsense/scripts/firmware/update.sh".to_string(),
};
Box::new(OPNsenseShellInterpret {
status: InterpretStatus::QUEUED,
score,
})
}
fn name(&self) -> String {
"OPNSenseLaunchUpgrade".to_string()
}
fn clone_box(&self) -> Box<dyn Score> {
Box::new(self.clone())
}
}