feat: add serde derive to Score types

This commit adds `serde` dependency and derives `Serialize` trait for `Score` types. This is necessary for serialization and deserialization of these types, which is required to display Scores to various user interfaces

- Added `serde` dependency to `harmony_types/Cargo.toml`.
- Added `serde::Serialize` derive macro to `MacAddress` in `harmony_types/src/lib.rs`.
- Added `serde::Serialize` derive macro to `Config` in `opnsense-config/src/config/config.rs`.
- Added `serde::Serialize` derive macro to `Score` in `harmony_types/src/lib.rs`.
- Added `serde::Serialize` derive macro to `Config` and `Score` in relevant modules.
- Added placeholder `todo!()` implementations for `serialize` methods. These will be implemented in future commits.
This commit is contained in:
Jean-Gabriel Gill-Couture 2025-04-05 14:33:39 -04:00
parent ab9b7476a4
commit b4cc5cff4f
38 changed files with 450 additions and 97 deletions

4
Cargo.lock generated
View File

@ -1158,6 +1158,7 @@ dependencies = [
"rust-ipmi",
"semver",
"serde",
"serde-value",
"serde_json",
"serde_yaml",
"tokio",
@ -1195,6 +1196,9 @@ dependencies = [
[[package]]
name = "harmony_types"
version = "0.1.0"
dependencies = [
"serde",
]
[[package]]
name = "hashbrown"

View File

@ -30,6 +30,7 @@ url = "2.5.4"
kube = "0.98.0"
k8s-openapi = { version = "0.24.0", features = [ "v1_30" ] }
serde_yaml = "0.9.34"
serde-value = "0.7.0"
http = "1.2.0"
[workspace.dependencies.uuid]

View File

@ -2,8 +2,7 @@ use harmony::{
data::Version,
maestro::Maestro,
modules::lamp::{LAMPConfig, LAMPScore},
score::Score,
topology::{HAClusterTopology, Topology, Url},
topology::{HAClusterTopology, Url},
};
#[tokio::main]
@ -23,7 +22,3 @@ async fn main() {
.await
.unwrap();
}
fn clone_score<T: Topology, S: Score<T> + Clone + 'static>(score: S) -> Box<S> {
Box::new(score.clone())
}

View File

@ -1,7 +1,10 @@
use harmony::{
inventory::Inventory,
maestro::Maestro,
modules::{dummy::{ErrorScore, PanicScore, SuccessScore}, k8s::deployment::K8sDeploymentScore},
modules::{
dummy::{ErrorScore, PanicScore, SuccessScore},
k8s::deployment::K8sDeploymentScore,
},
topology::HAClusterTopology,
};

View File

@ -29,3 +29,4 @@ kube = { workspace = true }
k8s-openapi = { workspace = true }
serde_yaml = { workspace = true }
http = { workspace = true }
serde-value = { workspace = true }

View File

@ -2,6 +2,8 @@ use std::sync::Arc;
use derive_new::new;
use harmony_types::net::MacAddress;
use serde::{Serialize, Serializer, ser::SerializeStruct};
use serde_value::Value;
pub type HostGroup = Vec<PhysicalHost>;
pub type SwitchGroup = Vec<Switch>;
@ -75,10 +77,7 @@ impl PhysicalHost {
}
pub fn label(mut self, name: String, value: String) -> Self {
self.labels.push(Label {
_name: name,
_value: value,
});
self.labels.push(Label { name, value });
self
}
@ -88,7 +87,49 @@ impl PhysicalHost {
}
}
#[derive(new)]
// Custom Serialize implementation for PhysicalHost
impl Serialize for PhysicalHost {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Determine the number of fields
let mut num_fields = 5; // category, network, storage, labels, management
if self.memory_size.is_some() {
num_fields += 1;
}
if self.cpu_count.is_some() {
num_fields += 1;
}
// Create a serialization structure
let mut state = serializer.serialize_struct("PhysicalHost", num_fields)?;
// Serialize the standard fields
state.serialize_field("category", &self.category)?;
state.serialize_field("network", &self.network)?;
state.serialize_field("storage", &self.storage)?;
state.serialize_field("labels", &self.labels)?;
// Serialize optional fields
if let Some(memory) = self.memory_size {
state.serialize_field("memory_size", &memory)?;
}
if let Some(cpu) = self.cpu_count {
state.serialize_field("cpu_count", &cpu)?;
}
let mgmt_data = self.management.serialize_management();
// pub management: Arc<dyn ManagementInterface>,
// Handle management interface - either as a field or flattened
state.serialize_field("management", &mgmt_data)?;
state.end()
}
}
#[derive(new, Serialize)]
pub struct ManualManagementInterface;
impl ManagementInterface for ManualManagementInterface {
@ -101,7 +142,7 @@ impl ManagementInterface for ManualManagementInterface {
}
}
pub trait ManagementInterface: Send + Sync {
pub trait ManagementInterface: Send + Sync + SerializableManagement {
fn boot_to_pxe(&self);
fn get_supported_protocol_names(&self) -> String;
}
@ -115,21 +156,49 @@ impl std::fmt::Debug for dyn ManagementInterface {
}
}
#[derive(Debug, Clone)]
// Define a trait for serializing management interfaces
pub trait SerializableManagement {
fn serialize_management(&self) -> Value;
}
// Provide a blanket implementation for all types that implement both ManagementInterface and Serialize
impl<T> SerializableManagement for T
where
T: ManagementInterface + Serialize,
{
fn serialize_management(&self) -> Value {
serde_value::to_value(self).expect("ManagementInterface should serialize successfully")
}
}
#[derive(Debug, Clone, Serialize)]
pub enum HostCategory {
Server,
Firewall,
Switch,
}
#[derive(Debug, new, Clone)]
#[derive(Debug, new, Clone, Serialize)]
pub struct NetworkInterface {
pub name: Option<String>,
pub mac_address: MacAddress,
pub speed: Option<u64>,
}
#[derive(Debug, new, Clone)]
#[cfg(test)]
use harmony_macros::mac_address;
#[cfg(test)]
impl NetworkInterface {
pub fn dummy() -> Self {
Self {
name: Some(String::new()),
mac_address: mac_address!("00:00:00:00:00:00"),
speed: Some(0),
}
}
}
#[derive(Debug, new, Clone, Serialize)]
pub enum StorageConnectionType {
Sata3g,
Sata6g,
@ -137,13 +206,13 @@ pub enum StorageConnectionType {
Sas12g,
PCIE,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub enum StorageKind {
SSD,
NVME,
HDD,
}
#[derive(Debug, new, Clone)]
#[derive(Debug, new, Clone, Serialize)]
pub struct Storage {
pub connection: StorageConnectionType,
pub kind: StorageKind,
@ -151,20 +220,33 @@ pub struct Storage {
pub serial: String,
}
#[derive(Debug, Clone)]
#[cfg(test)]
impl Storage {
pub fn dummy() -> Self {
Self {
connection: StorageConnectionType::Sata3g,
kind: StorageKind::SSD,
size: 0,
serial: String::new(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Switch {
_interface: Vec<NetworkInterface>,
_management_interface: NetworkInterface,
}
#[derive(Debug, new, Clone)]
#[derive(Debug, new, Clone, Serialize)]
pub struct Label {
_name: String,
_value: String,
pub name: String,
pub value: String,
}
pub type Address = String;
#[derive(new, Debug)]
#[derive(new, Debug, Serialize)]
pub struct Location {
pub address: Address,
pub name: String,
@ -178,3 +260,158 @@ impl Location {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
// Mock implementation of ManagementInterface
#[derive(Debug, Clone, Serialize, Deserialize)]
struct MockHPIlo {
ip: String,
username: String,
password: String,
firmware_version: String,
}
impl ManagementInterface for MockHPIlo {
fn boot_to_pxe(&self) {}
fn get_supported_protocol_names(&self) -> String {
String::new()
}
}
// Another mock implementation
#[derive(Debug, Clone, Serialize, Deserialize)]
struct MockDellIdrac {
hostname: String,
port: u16,
api_token: String,
}
impl ManagementInterface for MockDellIdrac {
fn boot_to_pxe(&self) {}
fn get_supported_protocol_names(&self) -> String {
String::new()
}
}
#[test]
fn test_serialize_physical_host_with_hp_ilo() {
// Create a PhysicalHost with HP iLO management
let host = PhysicalHost {
category: HostCategory::Server,
network: vec![NetworkInterface::dummy()],
management: Arc::new(MockHPIlo {
ip: "192.168.1.100".to_string(),
username: "admin".to_string(),
password: "password123".to_string(),
firmware_version: "2.5.0".to_string(),
}),
storage: vec![Storage::dummy()],
labels: vec![Label::new("datacenter".to_string(), "us-east".to_string())],
memory_size: Some(64_000_000),
cpu_count: Some(16),
};
// Serialize to JSON
let json = serde_json::to_string(&host).expect("Failed to serialize host");
// Check that the serialized JSON contains the HP iLO details
assert!(json.contains("192.168.1.100"));
assert!(json.contains("admin"));
assert!(json.contains("password123"));
assert!(json.contains("firmware_version"));
assert!(json.contains("2.5.0"));
// Parse back to verify structure (not the exact management interface)
let parsed: serde_json::Value = serde_json::from_str(&json).expect("Failed to parse JSON");
// Verify basic structure
assert_eq!(parsed["cpu_count"], 16);
assert_eq!(parsed["memory_size"], 64_000_000);
assert_eq!(parsed["network"][0]["name"], "");
}
#[test]
fn test_serialize_physical_host_with_dell_idrac() {
// Create a PhysicalHost with Dell iDRAC management
let host = PhysicalHost {
category: HostCategory::Server,
network: vec![NetworkInterface::dummy()],
management: Arc::new(MockDellIdrac {
hostname: "idrac-server01".to_string(),
port: 443,
api_token: "abcdef123456".to_string(),
}),
storage: vec![Storage::dummy()],
labels: vec![Label::new("env".to_string(), "production".to_string())],
memory_size: Some(128_000_000),
cpu_count: Some(32),
};
// Serialize to JSON
let json = serde_json::to_string(&host).expect("Failed to serialize host");
// Check that the serialized JSON contains the Dell iDRAC details
assert!(json.contains("idrac-server01"));
assert!(json.contains("443"));
assert!(json.contains("abcdef123456"));
// Parse back to verify structure
let parsed: serde_json::Value = serde_json::from_str(&json).expect("Failed to parse JSON");
// Verify basic structure
assert_eq!(parsed["cpu_count"], 32);
assert_eq!(parsed["memory_size"], 128_000_000);
assert_eq!(parsed["storage"][0]["path"], serde_json::Value::Null);
}
#[test]
fn test_different_management_implementations_produce_valid_json() {
// Create hosts with different management implementations
let host1 = PhysicalHost {
category: HostCategory::Server,
network: vec![],
management: Arc::new(MockHPIlo {
ip: "10.0.0.1".to_string(),
username: "root".to_string(),
password: "secret".to_string(),
firmware_version: "3.0.0".to_string(),
}),
storage: vec![],
labels: vec![],
memory_size: None,
cpu_count: None,
};
let host2 = PhysicalHost {
category: HostCategory::Server,
network: vec![],
management: Arc::new(MockDellIdrac {
hostname: "server02-idrac".to_string(),
port: 8443,
api_token: "token123".to_string(),
}),
storage: vec![],
labels: vec![],
memory_size: None,
cpu_count: None,
};
// Both should serialize successfully
let json1 = serde_json::to_string(&host1).expect("Failed to serialize host1");
let json2 = serde_json::to_string(&host2).expect("Failed to serialize host2");
// Both JSONs should be valid and parseable
let _: serde_json::Value = serde_json::from_str(&json1).expect("Invalid JSON for host1");
let _: serde_json::Value = serde_json::from_str(&json2).expect("Invalid JSON for host2");
// The JSONs should be different because they contain different management interfaces
assert_ne!(json1, json2);
}
}

View File

@ -37,7 +37,7 @@ impl std::fmt::Display for InterpretName {
}
#[async_trait]
pub trait Interpret<T: Topology>: std::fmt::Debug + Send {
pub trait Interpret<T>: std::fmt::Debug + Send {
async fn execute(&self, inventory: &Inventory, topology: &T)
-> Result<Outcome, InterpretError>;
fn get_name(&self) -> InterpretName;

View File

@ -1,15 +1,35 @@
use serde::Serialize;
use serde_value::Value;
use super::{interpret::Interpret, topology::Topology};
pub trait Score<T: Topology>: std::fmt::Debug + Send + Sync + CloneBoxScore<T> {
pub trait Score<T: Topology>:
std::fmt::Debug + Send + Sync + CloneBoxScore<T> + SerializeScore<T>
{
fn create_interpret(&self) -> Box<dyn Interpret<T>>;
fn name(&self) -> String;
}
pub trait SerializeScore<T: Topology> {
fn serialize(&self) -> Value;
}
impl<'de, S, T> SerializeScore<T> for S
where
T: Topology,
S: Score<T> + Serialize,
{
fn serialize(&self) -> Value {
// TODO not sure if this is the right place to handle the error or it should bubble
// up?
serde_value::to_value(&self).expect("Score should serialize successfully")
}
}
pub trait CloneBoxScore<T: Topology> {
fn clone_box(&self) -> Box<dyn Score<T>>;
}
impl<S, T> CloneBoxScore<T> for S
where
T: Topology,
@ -19,5 +39,3 @@ where
Box::new(self.clone())
}
}
pub trait FrontendScore<T: Topology>: Score<T> + std::fmt::Display {}

View File

@ -334,7 +334,6 @@ impl TftpServer for DummyInfra {
#[async_trait]
impl HttpServer for DummyInfra {
async fn serve_files(&self, _url: &Url) -> Result<(), ExecutorError> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
}
fn get_ip(&self) -> IpAddress {

View File

@ -1,4 +1,5 @@
use derive_new::new;
use serde::Serialize;
use crate::hardware::PhysicalHost;
@ -8,7 +9,7 @@ use super::LogicalHost;
///
/// This is the only construct that directly maps a logical host to a physical host.
/// It serves as a bridge between the logical cluster structure and the physical infrastructure.
#[derive(Debug, new, Clone)]
#[derive(Debug, new, Clone, Serialize)]
pub struct HostBinding {
/// Reference to the LogicalHost
pub logical_host: LogicalHost,

View File

@ -2,6 +2,7 @@ use std::{net::SocketAddr, str::FromStr};
use async_trait::async_trait;
use log::debug;
use serde::Serialize;
use super::{IpAddress, LogicalHost};
use crate::executors::ExecutorError;
@ -36,20 +37,20 @@ impl std::fmt::Debug for dyn LoadBalancer {
f.write_fmt(format_args!("LoadBalancer {}", self.get_ip()))
}
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct LoadBalancerService {
pub backend_servers: Vec<BackendServer>,
pub listening_port: SocketAddr,
pub health_check: Option<HealthCheck>,
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct BackendServer {
pub address: String,
pub port: u16,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum HttpMethod {
GET,
POST,
@ -91,14 +92,14 @@ impl std::fmt::Display for HttpMethod {
}
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum HttpStatusCode {
Success2xx,
UserError4xx,
ServerError5xx,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum HealthCheck {
HTTP(String, HttpMethod, HttpStatusCode),
TCP(Option<u16>),

View File

@ -12,6 +12,7 @@ mod network;
pub use host_binding::*;
pub use http::*;
pub use network::*;
use serde::Serialize;
pub use tftp::*;
use std::net::IpAddr;
@ -20,8 +21,6 @@ pub trait Topology {
fn name(&self) -> &str;
}
pub trait Capability {}
pub type IpAddress = IpAddr;
#[derive(Debug, Clone)]
@ -30,6 +29,18 @@ pub enum Url {
Url(url::Url),
}
impl Serialize for Url {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Url::LocalFolder(path) => serializer.serialize_str(path),
Url::Url(url) => serializer.serialize_str(&url.as_str()),
}
}
}
impl std::fmt::Display for Url {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -48,7 +59,7 @@ impl std::fmt::Display for Url {
/// - A control plane node
///
/// This abstraction focuses on the logical role and services, independent of the physical hardware.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct LogicalHost {
/// The IP address of this logical host.
pub ip: IpAddress,
@ -130,3 +141,23 @@ fn increment_ip(ip: IpAddress, increment: u32) -> Option<IpAddress> {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[test]
fn test_serialize_local_folder() {
let url = Url::LocalFolder("path/to/folder".to_string());
let serialized = serde_json::to_string(&url).unwrap();
assert_eq!(serialized, "\"path/to/folder\"");
}
#[test]
fn test_serialize_url() {
let url = Url::Url(url::Url::parse("https://example.com").unwrap());
let serialized = serde_json::to_string(&url).unwrap();
assert_eq!(serialized, "\"https://example.com/\"");
}
}

View File

@ -2,10 +2,11 @@ use std::{net::Ipv4Addr, str::FromStr, sync::Arc};
use async_trait::async_trait;
use harmony_types::net::MacAddress;
use serde::Serialize;
use crate::executors::ExecutorError;
use super::{openshift::OpenshiftClient, IpAddress, LogicalHost};
use super::{IpAddress, LogicalHost, openshift::OpenshiftClient};
#[derive(Debug)]
pub struct DHCPStaticEntry {
@ -45,7 +46,6 @@ pub trait OcK8sclient: Send + Sync + std::fmt::Debug {
async fn oc_client(&self) -> Result<Arc<OpenshiftClient>, kube::Error>;
}
#[async_trait]
pub trait DhcpServer: Send + Sync + std::fmt::Debug {
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>;
@ -62,11 +62,7 @@ pub trait DhcpServer: Send + Sync + std::fmt::Debug {
pub trait DnsServer: Send + Sync {
async fn register_dhcp_leases(&self, register: bool) -> Result<(), ExecutorError>;
async fn register_hosts(&self, hosts: Vec<DnsRecord>) -> Result<(), ExecutorError>;
fn remove_record(
&self,
name: &str,
record_type: DnsRecordType,
) -> Result<(), ExecutorError>;
fn remove_record(&self, name: &str, record_type: DnsRecordType) -> Result<(), ExecutorError>;
async fn list_records(&self) -> Vec<DnsRecord>;
fn get_ip(&self) -> IpAddress;
fn get_host(&self) -> LogicalHost;
@ -117,7 +113,7 @@ pub enum Action {
Deny,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub enum DnsRecordType {
A,
AAAA,
@ -138,7 +134,7 @@ impl std::fmt::Display for DnsRecordType {
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct DnsRecord {
pub host: String,
pub domain: String,

View File

@ -3,8 +3,9 @@ use crate::topology::IpAddress;
use derive_new::new;
use harmony_types::net::MacAddress;
use log::info;
use serde::Serialize;
#[derive(new)]
#[derive(new, Serialize)]
pub struct HPIlo {
ip_address: Option<IpAddress>,
mac_address: Option<MacAddress>,

View File

@ -2,8 +2,9 @@ use crate::hardware::ManagementInterface;
use derive_new::new;
use harmony_types::net::MacAddress;
use log::info;
use serde::Serialize;
#[derive(new)]
#[derive(new, Serialize)]
pub struct IntelAmtManagement {
mac_address: MacAddress,
}

View File

@ -1,7 +1,8 @@
use crate::hardware::ManagementInterface;
use derive_new::new;
use serde::Serialize;
#[derive(new)]
#[derive(new, Serialize)]
pub struct OPNSenseManagementInterface {}
impl ManagementInterface for OPNSenseManagementInterface {

View File

@ -1,7 +1,7 @@
use async_trait::async_trait;
use derive_new::new;
use log::info;
use serde::Serialize;
use crate::{
domain::{data::Version, interpret::InterpretStatus},
@ -12,7 +12,7 @@ use crate::{
use crate::domain::score::Score;
#[derive(Debug, new, Clone)]
#[derive(Debug, new, Clone, Serialize)]
pub struct DhcpScore {
pub host_binding: Vec<HostBinding>,
pub next_server: Option<IpAddress>,
@ -134,7 +134,7 @@ impl DhcpInterpret {
}
#[async_trait]
impl<T: Topology + DhcpServer> Interpret<T> for DhcpInterpret {
impl<T: DhcpServer> Interpret<T> for DhcpInterpret {
fn get_name(&self) -> InterpretName {
InterpretName::OPNSenseDHCP
}

View File

@ -1,6 +1,7 @@
use async_trait::async_trait;
use derive_new::new;
use log::info;
use serde::Serialize;
use crate::{
data::Version,
@ -10,7 +11,7 @@ use crate::{
topology::{DnsRecord, DnsServer, Topology},
};
#[derive(Debug, new, Clone)]
#[derive(Debug, new, Clone, Serialize)]
pub struct DnsScore {
dns_entries: Vec<DnsRecord>,
register_dhcp_leases: Option<bool>,

View File

@ -1,4 +1,5 @@
use async_trait::async_trait;
use serde::Serialize;
use crate::{
data::Version,
@ -10,7 +11,7 @@ use crate::{
/// Score that always errors. This is only useful for development/testing purposes. It does nothing
/// except returning Err(InterpretError) when interpreted.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct ErrorScore;
impl<T: Topology> Score<T> for ErrorScore {
@ -28,7 +29,7 @@ impl<T: Topology> Score<T> for ErrorScore {
/// Score that always succeeds. This is only useful for development/testing purposes. It does nothing
/// except returning Ok(Outcome::success) when interpreted.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct SuccessScore;
impl<T: Topology> Score<T> for SuccessScore {
@ -81,7 +82,7 @@ impl<T: Topology> Interpret<T> for DummyInterpret {
/// Score that always panics. This is only useful for development/testing purposes. It does nothing
/// except panic! with an error message when interpreted
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct PanicScore;
impl<T: Topology> Score<T> for PanicScore {

View File

@ -1,5 +1,6 @@
use async_trait::async_trait;
use derive_new::new;
use serde::Serialize;
use crate::{
data::{Id, Version},
@ -9,7 +10,7 @@ use crate::{
topology::{HttpServer, Topology, Url},
};
#[derive(Debug, new, Clone)]
#[derive(Debug, new, Clone, Serialize)]
pub struct HttpScore {
files_to_serve: Url,
}

View File

@ -1,17 +1,22 @@
use k8s_openapi::api::apps::v1::Deployment;
use serde::Serialize;
use serde_json::json;
use crate::{interpret::Interpret, score::Score, topology::{OcK8sclient, Topology}};
use crate::{
interpret::Interpret,
score::Score,
topology::{OcK8sclient, Topology},
};
use super::resource::{K8sResourceInterpret, K8sResourceScore};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct K8sDeploymentScore {
pub name: String,
pub image: String,
}
impl <T:Topology + OcK8sclient> Score<T> for K8sDeploymentScore {
impl<T: Topology + OcK8sclient> Score<T> for K8sDeploymentScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
let deployment: Deployment = serde_json::from_value(json!(
{

View File

@ -1,7 +1,7 @@
use async_trait::async_trait;
use k8s_openapi::NamespaceResourceScope;
use kube::Resource;
use serde::de::DeserializeOwned;
use serde::{Serialize, de::DeserializeOwned};
use crate::{
data::{Id, Version},
@ -11,7 +11,7 @@ use crate::{
topology::{OcK8sclient, Topology},
};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct K8sResourceScore<K: Resource + std::fmt::Debug> {
pub resource: Vec<K>,
}

View File

@ -1,6 +1,7 @@
use std::path::{Path, PathBuf};
use async_trait::async_trait;
use serde::Serialize;
use crate::{
data::{Id, Version},
@ -11,7 +12,7 @@ use crate::{
topology::{OcK8sclient, Topology, Url},
};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct LAMPScore {
pub name: String,
pub domain: Url,
@ -19,7 +20,7 @@ pub struct LAMPScore {
pub php_version: Version,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct LAMPConfig {
pub project_root: PathBuf,
pub ssl_enabled: bool,
@ -34,7 +35,7 @@ impl Default for LAMPConfig {
}
}
impl <T:Topology> Score<T> for LAMPScore {
impl<T: Topology> Score<T> for LAMPScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
todo!()
}
@ -50,7 +51,7 @@ pub struct LAMPInterpret {
}
#[async_trait]
impl <T:Topology + OcK8sclient> Interpret<T> for LAMPInterpret {
impl<T: Topology + OcK8sclient> Interpret<T> for LAMPInterpret {
async fn execute(
&self,
inventory: &Inventory,

View File

@ -1,15 +1,16 @@
use async_trait::async_trait;
use log::info;
use serde::Serialize;
use crate::{
data::{Id, Version},
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::Inventory,
score::{FrontendScore, Score},
score::Score,
topology::{LoadBalancer, LoadBalancerService, Topology},
};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct LoadBalancerScore {
pub public_services: Vec<LoadBalancerService>,
pub private_services: Vec<LoadBalancerService>,
@ -19,8 +20,6 @@ pub struct LoadBalancerScore {
// uuid?
}
impl <T: Topology + LoadBalancer> FrontendScore<T> for LoadBalancerScore {}
impl<T: Topology + LoadBalancer> Score<T> for LoadBalancerScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
Box::new(LoadBalancerInterpret::new(self.clone()))

View File

@ -3,8 +3,8 @@ pub mod dns;
pub mod dummy;
pub mod http;
pub mod k8s;
pub mod lamp;
pub mod load_balancer;
pub mod okd;
pub mod opnsense;
pub mod tftp;
pub mod lamp;

View File

@ -1,3 +1,5 @@
use serde::Serialize;
use crate::{
interpret::Interpret,
inventory::Inventory,
@ -6,7 +8,7 @@ use crate::{
topology::{DhcpServer, HAClusterTopology, HostBinding, Topology},
};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct OKDBootstrapDhcpScore {
dhcp_score: DhcpScore,
}

View File

@ -1,15 +1,18 @@
use std::net::SocketAddr;
use serde::Serialize;
use crate::{
interpret::Interpret,
modules::load_balancer::LoadBalancerScore,
score::Score,
topology::{
BackendServer, HAClusterTopology, HealthCheck, HttpMethod, HttpStatusCode, LoadBalancer, LoadBalancerService, Topology
BackendServer, HAClusterTopology, HealthCheck, HttpMethod, HttpStatusCode, LoadBalancer,
LoadBalancerService, Topology,
},
};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct OKDBootstrapLoadBalancerScore {
load_balancer_score: LoadBalancerScore,
}

View File

@ -1,3 +1,5 @@
use serde::Serialize;
use crate::{
interpret::Interpret,
inventory::Inventory,
@ -6,7 +8,7 @@ use crate::{
topology::{DhcpServer, HAClusterTopology, HostBinding, Topology},
};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct OKDDhcpScore {
dhcp_score: DhcpScore,
}

View File

@ -1,3 +1,5 @@
use serde::Serialize;
use crate::{
interpret::Interpret,
modules::dns::DnsScore,
@ -5,7 +7,7 @@ use crate::{
topology::{DnsRecord, DnsRecordType, DnsServer, HAClusterTopology, Topology},
};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct OKDDnsScore {
dns_score: DnsScore,
}

View File

@ -1,11 +1,14 @@
use std::net::SocketAddr;
use serde::Serialize;
use crate::{
interpret::Interpret,
modules::load_balancer::LoadBalancerScore,
score::{FrontendScore, Score},
score::Score,
topology::{
BackendServer, HAClusterTopology, HealthCheck, HttpMethod, HttpStatusCode, LoadBalancer, LoadBalancerService, Topology
BackendServer, HAClusterTopology, HealthCheck, HttpMethod, HttpStatusCode, LoadBalancer,
LoadBalancerService, Topology,
},
};
@ -15,9 +18,7 @@ impl std::fmt::Display for OKDLoadBalancerScore {
}
}
impl <T: Topology + LoadBalancer> FrontendScore<T> for OKDLoadBalancerScore {}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct OKDLoadBalancerScore {
load_balancer_score: LoadBalancerScore,
}

View File

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

View File

@ -1,6 +1,7 @@
use std::sync::Arc;
use async_trait::async_trait;
use serde::Serialize;
use tokio::sync::RwLock;
use crate::{
@ -13,10 +14,27 @@ use crate::{
#[derive(Debug, Clone)]
pub struct OPNsenseShellCommandScore {
// TODO I am pretty sure we should not hold a direct reference to the
// opnsense_config::Config here.
// This causes a problem with serialization but also could cause many more problems as this
// is mixing concerns of configuration (which is the Responsibility of Scores to define)
// and state/execution which is the responsibility of interprets via topologies to manage
//
// I feel like a better solution would be for this Score/Interpret to require
// Topology + OPNSenseShell trait bindings
pub opnsense: Arc<RwLock<opnsense_config::Config>>,
pub command: String,
}
impl Serialize for OPNsenseShellCommandScore {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
todo!("See comment about moving opnsense_config::Config outside the score")
}
}
impl<T: Topology> Score<T> for OPNsenseShellCommandScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
Box::new(OPNsenseShellInterpret {
@ -37,7 +55,7 @@ pub struct OPNsenseShellInterpret {
}
#[async_trait]
impl <T:Topology> Interpret<T> for OPNsenseShellInterpret {
impl<T: Topology> Interpret<T> for OPNsenseShellInterpret {
async fn execute(
&self,
_inventory: &Inventory,

View File

@ -1,5 +1,6 @@
use std::sync::Arc;
use serde::Serialize;
use tokio::sync::RwLock;
use crate::{
@ -15,6 +16,15 @@ pub struct OPNSenseLaunchUpgrade {
pub opnsense: Arc<RwLock<opnsense_config::Config>>,
}
impl Serialize for OPNSenseLaunchUpgrade {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
todo!("See comment in OPNSenseShellCommandScore and apply the same idea here")
}
}
impl<T: Topology> Score<T> for OPNSenseLaunchUpgrade {
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
let score = OPNsenseShellCommandScore {

View File

@ -1,5 +1,6 @@
use async_trait::async_trait;
use derive_new::new;
use serde::Serialize;
use crate::{
data::{Id, Version},
@ -9,12 +10,12 @@ use crate::{
topology::{Router, TftpServer, Topology, Url},
};
#[derive(Debug, new, Clone)]
#[derive(Debug, new, Clone, Serialize)]
pub struct TftpScore {
files_to_serve: Url,
}
impl <T:Topology + TftpServer + Router> Score<T> for TftpScore {
impl<T: Topology + TftpServer + Router> Score<T> for TftpScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
Box::new(TftpInterpret::new(self.clone()))
}
@ -30,7 +31,7 @@ pub struct TftpInterpret {
}
#[async_trait]
impl <T:Topology + TftpServer + Router> Interpret<T> for TftpInterpret {
impl<T: Topology + TftpServer + Router> Interpret<T> for TftpInterpret {
async fn execute(
&self,
_inventory: &Inventory,

View File

@ -1,10 +1,13 @@
use std::sync::{Arc, RwLock};
use crossterm::event::{Event, KeyCode, KeyEventKind};
use harmony::{modules::okd::load_balancer::OKDLoadBalancerScore, score::Score, topology::{LoadBalancer, Topology}};
use harmony::{score::Score, topology::Topology};
use log::{info, warn};
use ratatui::{
layout::Rect, style::{Style, Stylize}, widgets::{List, ListItem, ListState, StatefulWidget, Widget}, Frame
Frame,
layout::Rect,
style::{Style, Stylize},
widgets::{List, ListItem, ListState, StatefulWidget, Widget},
};
use tokio::sync::mpsc;
@ -23,19 +26,20 @@ struct Execution<T: Topology> {
score: Box<dyn Score<T>>,
}
impl <T: Topology + LoadBalancer> FrontendScore<T> for OKDLoadBalancerScore {}
#[derive(Debug)]
pub(crate) struct ScoreListWidget<T: Topology> {
list_state: Arc<RwLock<ListState>>,
scores: Vec<Box<dyn FrontendScore<T>>>,
scores: Vec<Box<dyn Score<T>>>,
execution: Option<Execution<T>>,
execution_history: Vec<Execution<T>>,
sender: mpsc::Sender<HarmonyTuiEvent<T>>,
}
impl<T: Topology + std::fmt::Debug> ScoreListWidget<T> {
pub(crate) fn new(scores: Vec<Box<dyn Score<T>>>, sender: mpsc::Sender<HarmonyTuiEvent<T>>) -> Self {
pub(crate) fn new(
scores: Vec<Box<dyn Score<T>>>,
sender: mpsc::Sender<HarmonyTuiEvent<T>>,
) -> Self {
let mut list_state = ListState::default();
list_state.select_first();
let list_state = Arc::new(RwLock::new(list_state));
@ -141,4 +145,3 @@ impl<T: Topology> Widget for &ScoreListWidget<T> {
fn score_to_list_item<'a, T: Topology>(score: &'a Box<dyn Score<T>>) -> ListItem<'a> {
ListItem::new(score.name())
}

View File

@ -4,3 +4,6 @@ edition = "2024"
version.workspace = true
readme.workspace = true
license.workspace = true
[dependencies]
serde = { version = "1.0.209", features = ["derive"] }

View File

@ -1,5 +1,7 @@
pub mod net {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
use serde::Serialize;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize)]
pub struct MacAddress(pub [u8; 6]);
impl MacAddress {

View File

@ -11,6 +11,7 @@ use crate::{
use log::{debug, info, trace, warn};
use opnsense_config_xml::OPNsense;
use russh::client;
use serde::Serialize;
use super::{ConfigManager, OPNsenseShell};
@ -21,6 +22,15 @@ pub struct Config {
shell: Arc<dyn OPNsenseShell>,
}
impl Serialize for Config {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
todo!()
}
}
impl Config {
pub async fn new(
repository: Arc<dyn ConfigManager>,