From cb4382fbb510d495c6cfe08d134e39abe6003a0d Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Sat, 30 Aug 2025 16:02:49 -0400 Subject: [PATCH 1/3] feat: Inventory PhysicalHost persistence with sqlx and local sqlite db --- ...a7934b0a7fac04a4fe4b2a354f0443d630990.json | 32 ++ ...93d65a237d31d24b74a29c6a8d8420d255ab8.json | 12 + Cargo.lock | 343 +++++++++++++++++- Cargo.toml | 2 + harmony/Cargo.toml | 4 +- harmony/src/domain/data/id.rs | 14 + harmony/src/domain/hardware/mod.rs | 21 +- harmony/src/domain/inventory/mod.rs | 3 + harmony/src/domain/inventory/repository.rs | 25 ++ harmony/src/infra/inventory/mod.rs | 1 + harmony/src/infra/inventory/sqlite.rs | 69 ++++ harmony/src/infra/mod.rs | 2 + harmony/src/infra/sqlx.rs | 36 ++ 13 files changed, 560 insertions(+), 4 deletions(-) create mode 100644 .sqlx/query-934035c7ca6e064815393e4e049a7934b0a7fac04a4fe4b2a354f0443d630990.json create mode 100644 .sqlx/query-f10f615ee42129ffa293e46f2f893d65a237d31d24b74a29c6a8d8420d255ab8.json create mode 100644 harmony/src/domain/inventory/repository.rs create mode 100644 harmony/src/infra/inventory/mod.rs create mode 100644 harmony/src/infra/inventory/sqlite.rs create mode 100644 harmony/src/infra/sqlx.rs diff --git a/.sqlx/query-934035c7ca6e064815393e4e049a7934b0a7fac04a4fe4b2a354f0443d630990.json b/.sqlx/query-934035c7ca6e064815393e4e049a7934b0a7fac04a4fe4b2a354f0443d630990.json new file mode 100644 index 0000000..2d1f1ba --- /dev/null +++ b/.sqlx/query-934035c7ca6e064815393e4e049a7934b0a7fac04a4fe4b2a354f0443d630990.json @@ -0,0 +1,32 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, version_id, data as \"data: Json\" FROM physical_hosts WHERE id = ? ORDER BY version_id DESC LIMIT 1", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "version_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "data: Json", + "ordinal": 2, + "type_info": "Null" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "934035c7ca6e064815393e4e049a7934b0a7fac04a4fe4b2a354f0443d630990" +} diff --git a/.sqlx/query-f10f615ee42129ffa293e46f2f893d65a237d31d24b74a29c6a8d8420d255ab8.json b/.sqlx/query-f10f615ee42129ffa293e46f2f893d65a237d31d24b74a29c6a8d8420d255ab8.json new file mode 100644 index 0000000..7df288a --- /dev/null +++ b/.sqlx/query-f10f615ee42129ffa293e46f2f893d65a237d31d24b74a29c6a8d8420d255ab8.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO physical_hosts (id, version_id, data) VALUES (?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "f10f615ee42129ffa293e46f2f893d65a237d31d24b74a29c6a8d8420d255ab8" +} diff --git a/Cargo.lock b/Cargo.lock index 9120acb..a33b177 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -474,6 +474,15 @@ dependencies = [ "syn 2.0.105", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -1052,6 +1061,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -1089,6 +1113,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -1409,6 +1442,12 @@ dependencies = [ "syn 2.0.105", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "dyn-clone" version = "1.0.19" @@ -1471,6 +1510,9 @@ name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] [[package]] name = "elliptic-curve" @@ -1586,6 +1628,17 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + [[package]] name = "event-listener" version = "5.4.0" @@ -1984,6 +2037,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -2241,11 +2305,13 @@ dependencies = [ "serde_with", "serde_yaml", "similar", + "sqlx", "strum 0.27.1", "tar", "temp-dir", "temp-file", "tempfile", + "thiserror 2.0.14", "tokio", "tokio-util", "url", @@ -2391,6 +2457,15 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.4", +] + [[package]] name = "headers" version = "0.4.1" @@ -2960,7 +3035,7 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "infisical" version = "0.0.2" -source = "git+https://github.com/jggc/rust-sdk.git?branch=patch-1#5a8509ef5483a5798c5d1a7f7ebeb5ba5b783253" +source = "git+https://github.com/jggc/rust-sdk.git?branch=patch-1#30d820194d29491411bd14f6c2e18ec500bb0b14" dependencies = [ "base64 0.22.1", "reqwest 0.12.20", @@ -3333,6 +3408,17 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -3429,6 +3515,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "md5" version = "0.7.0" @@ -5280,6 +5376,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "snafu" @@ -5352,6 +5451,194 @@ dependencies = [ "der", ] +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.4", + "hashlink", + "indexmap 2.10.0", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.14", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.105", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.105", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.9.1", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.14", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.9.1", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.14", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.14", + "tracing", + "url", +] + [[package]] name = "ssh-cipher" version = "0.2.0" @@ -5415,6 +5702,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -6018,12 +6316,33 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -6147,6 +6466,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -6186,6 +6511,12 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -6306,6 +6637,16 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + [[package]] name = "winapi" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index 81de5e2..aec04b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,3 +65,5 @@ directories = "6.0.0" thiserror = "2.0.14" serde = { version = "1.0.209", features = ["derive", "rc"] } serde_json = "1.0.127" +askama = "0.14" +sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite" ] } diff --git a/harmony/Cargo.toml b/harmony/Cargo.toml index 56cce84..26e5f71 100644 --- a/harmony/Cargo.toml +++ b/harmony/Cargo.toml @@ -65,10 +65,12 @@ kube-derive = "1.1.0" bollard.workspace = true tar.workspace = true base64.workspace = true +thiserror.workspace = true once_cell = "1.21.3" harmony_inventory_agent = { path = "../harmony_inventory_agent" } harmony_secret_derive = { version = "0.1.0", path = "../harmony_secret_derive" } -askama = "0.14.0" +askama.workspace = true +sqlx.workspace = true [dev-dependencies] pretty_assertions.workspace = true diff --git a/harmony/src/domain/data/id.rs b/harmony/src/domain/data/id.rs index 5a17ae0..98cf1b9 100644 --- a/harmony/src/domain/data/id.rs +++ b/harmony/src/domain/data/id.rs @@ -24,6 +24,14 @@ pub struct Id { value: String, } +impl Id { + pub fn empty() -> Self { + Id { + value: String::new(), + } + } +} + impl FromStr for Id { type Err = (); @@ -34,6 +42,12 @@ impl FromStr for Id { } } +impl From for Id { + fn from(value: String) -> Self { + Self { value } + } +} + impl std::fmt::Display for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.value) diff --git a/harmony/src/domain/hardware/mod.rs b/harmony/src/domain/hardware/mod.rs index 399f313..d8d63df 100644 --- a/harmony/src/domain/hardware/mod.rs +++ b/harmony/src/domain/hardware/mod.rs @@ -1,8 +1,8 @@ -use std::sync::Arc; +use std::{str::FromStr, sync::Arc}; use derive_new::new; use harmony_types::net::MacAddress; -use serde::{Serialize, Serializer, ser::SerializeStruct}; +use serde::{Deserialize, Serialize, Serializer, ser::SerializeStruct}; use serde_value::Value; pub type HostGroup = Vec; @@ -11,6 +11,7 @@ pub type FirewallGroup = Vec; #[derive(Debug, Clone)] pub struct PhysicalHost { + pub id: Id, pub category: HostCategory, pub network: Vec, pub management: Arc, @@ -23,6 +24,7 @@ pub struct PhysicalHost { impl PhysicalHost { pub fn empty(category: HostCategory) -> Self { Self { + id: Id::empty(), category, network: vec![], storage: vec![], @@ -128,6 +130,15 @@ impl Serialize for PhysicalHost { } } +impl<'de> Deserialize<'de> for PhysicalHost { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + todo!() + } +} + #[derive(new, Serialize)] pub struct ManualManagementInterface; @@ -187,6 +198,8 @@ pub struct NetworkInterface { #[cfg(test)] use harmony_macros::mac_address; + +use crate::data::Id; #[cfg(test)] impl NetworkInterface { pub fn dummy() -> Self { @@ -304,6 +317,7 @@ mod tests { fn test_serialize_physical_host_with_hp_ilo() { // Create a PhysicalHost with HP iLO management let host = PhysicalHost { + id: Id::empty(), category: HostCategory::Server, network: vec![NetworkInterface::dummy()], management: Arc::new(MockHPIlo { @@ -341,6 +355,7 @@ mod tests { fn test_serialize_physical_host_with_dell_idrac() { // Create a PhysicalHost with Dell iDRAC management let host = PhysicalHost { + id: Id::empty(), category: HostCategory::Server, network: vec![NetworkInterface::dummy()], management: Arc::new(MockDellIdrac { @@ -375,6 +390,7 @@ mod tests { fn test_different_management_implementations_produce_valid_json() { // Create hosts with different management implementations let host1 = PhysicalHost { + id: Id::empty(), category: HostCategory::Server, network: vec![], management: Arc::new(MockHPIlo { @@ -390,6 +406,7 @@ mod tests { }; let host2 = PhysicalHost { + id: Id::empty(), category: HostCategory::Server, network: vec![], management: Arc::new(MockDellIdrac { diff --git a/harmony/src/domain/inventory/mod.rs b/harmony/src/domain/inventory/mod.rs index 851a150..2760e12 100644 --- a/harmony/src/domain/inventory/mod.rs +++ b/harmony/src/domain/inventory/mod.rs @@ -1,3 +1,6 @@ +mod repository; +pub use repository::*; + #[derive(Debug, new, Clone)] pub struct InventoryFilter { target: Vec, diff --git a/harmony/src/domain/inventory/repository.rs b/harmony/src/domain/inventory/repository.rs new file mode 100644 index 0000000..e4e02a9 --- /dev/null +++ b/harmony/src/domain/inventory/repository.rs @@ -0,0 +1,25 @@ +use async_trait::async_trait; + +use crate::hardware::PhysicalHost; + +/// Errors that can occur within the repository layer. +#[derive(thiserror::Error, Debug)] +pub enum RepoError { + #[error("Database query failed: {0}")] + QueryFailed(String), + #[error("Data serialization failed: {0}")] + Serialization(String), + #[error("Data deserialization failed: {0}")] + Deserialization(String), + #[error("Could not connect to the database: {0}")] + ConnectionFailed(String), +} + +// --- Trait and Implementation --- + +/// Defines the contract for inventory persistence. +#[async_trait] +pub trait InventoryRepository: Send + Sync + 'static { + async fn save(&self, host: &PhysicalHost) -> Result<(), RepoError>; + async fn get_latest_by_id(&self, host_id: &str) -> Result, RepoError>; +} diff --git a/harmony/src/infra/inventory/mod.rs b/harmony/src/infra/inventory/mod.rs new file mode 100644 index 0000000..8e0367d --- /dev/null +++ b/harmony/src/infra/inventory/mod.rs @@ -0,0 +1 @@ +mod sqlite; diff --git a/harmony/src/infra/inventory/sqlite.rs b/harmony/src/infra/inventory/sqlite.rs new file mode 100644 index 0000000..03fae14 --- /dev/null +++ b/harmony/src/infra/inventory/sqlite.rs @@ -0,0 +1,69 @@ +use crate::{ + data::Id, + hardware::PhysicalHost, + inventory::{InventoryRepository, RepoError}, +}; +use async_trait::async_trait; +use log::{info, warn}; +use sqlx::{Pool, Sqlite, SqlitePool}; + +/// A thread-safe, connection-pooled repository using SQLite. +#[derive(Debug)] +pub struct SqliteInventoryRepository { + pool: Pool, +} + +impl SqliteInventoryRepository { + pub async fn new(database_url: &str) -> Result { + let pool = SqlitePool::connect(database_url) + .await + .map_err(|e| RepoError::ConnectionFailed(e.to_string()))?; + + todo!("make sure migrations are up to date"); + info!( + "SQLite inventory repository initialized at '{}'", + database_url, + ); + Ok(Self { pool }) + } +} + +#[async_trait] +impl InventoryRepository for SqliteInventoryRepository { + async fn save(&self, host: &PhysicalHost) -> Result<(), RepoError> { + let data = serde_json::to_vec(host).map_err(|e| RepoError::Serialization(e.to_string()))?; + + let id = Id::default().to_string(); + let host_id = host.id.to_string(); + + sqlx::query!( + "INSERT INTO physical_hosts (id, version_id, data) VALUES (?, ?, ?)", + host_id, + id, + data, + ) + .execute(&self.pool) + .await?; + + info!("Saved new inventory version for host '{}'", host.id); + Ok(()) + } + + async fn get_latest_by_id(&self, host_id: &str) -> Result, RepoError> { + let row = sqlx::query_as!( + DbHost, + r#"SELECT id, version_id, data as "data: Json" FROM physical_hosts WHERE id = ? ORDER BY version_id DESC LIMIT 1"#, + host_id + ) + .fetch_optional(&self.pool) + .await?; + todo!() + } +} + +use sqlx::types::Json; +struct DbHost { + data: Json, + id: Id, + version_id: Id, +} diff --git a/harmony/src/infra/mod.rs b/harmony/src/infra/mod.rs index b3a94ea..c05c7b6 100644 --- a/harmony/src/infra/mod.rs +++ b/harmony/src/infra/mod.rs @@ -1,4 +1,6 @@ pub mod executors; pub mod hp_ilo; pub mod intel_amt; +pub mod inventory; pub mod opnsense; +mod sqlx; diff --git a/harmony/src/infra/sqlx.rs b/harmony/src/infra/sqlx.rs new file mode 100644 index 0000000..00472b2 --- /dev/null +++ b/harmony/src/infra/sqlx.rs @@ -0,0 +1,36 @@ +use crate::inventory::RepoError; + +impl From for RepoError { + fn from(value: sqlx::Error) -> Self { + match value { + sqlx::Error::Configuration(_) + | sqlx::Error::Io(_) + | sqlx::Error::Tls(_) + | sqlx::Error::Protocol(_) + | sqlx::Error::PoolTimedOut + | sqlx::Error::PoolClosed + | sqlx::Error::WorkerCrashed => RepoError::ConnectionFailed(value.to_string()), + sqlx::Error::InvalidArgument(_) + | sqlx::Error::Database(_) + | sqlx::Error::RowNotFound + | sqlx::Error::TypeNotFound { .. } + | sqlx::Error::ColumnIndexOutOfBounds { .. } + | sqlx::Error::ColumnNotFound(_) + | sqlx::Error::AnyDriverError(_) + | sqlx::Error::Migrate(_) + | sqlx::Error::InvalidSavePointStatement + | sqlx::Error::BeginFailed => RepoError::QueryFailed(value.to_string()), + sqlx::Error::Encode(_) => RepoError::Serialization(value.to_string()), + sqlx::Error::Decode(_) | sqlx::Error::ColumnDecode { .. } => { + RepoError::Deserialization(value.to_string()) + } + _ => RepoError::QueryFailed(value.to_string()), + } + } +} + +impl From for RepoError { + fn from(value: serde_json::Error) -> Self { + RepoError::Serialization(value.to_string()) + } +} From f9906cb419fba535563ddbd871ad67ba48436571 Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Sat, 30 Aug 2025 17:52:02 -0400 Subject: [PATCH 2/3] refact: Move basic types to harmony_types crate to avoid external dependencies. This includes Id, IpAddress, Url and some other heavily used types --- Cargo.lock | 8 ++- Cargo.toml | 3 +- .../Cargo.toml | 5 +- .../src/main.rs | 5 +- examples/lamp/src/main.rs | 3 +- examples/monitoring/Cargo.toml | 7 +- examples/monitoring/src/main.rs | 3 +- examples/monitoring_with_tenant/Cargo.toml | 5 +- examples/monitoring_with_tenant/src/main.rs | 5 +- examples/nanodc/src/main.rs | 3 +- examples/opnsense/src/main.rs | 3 +- examples/rust/src/main.rs | 3 +- examples/tenant/src/main.rs | 2 +- harmony/Cargo.toml | 1 - harmony/src/domain/data/mod.rs | 2 - harmony/src/domain/executors/mod.rs | 3 +- harmony/src/domain/hardware/mod.rs | 2 +- harmony/src/domain/interpret/mod.rs | 6 +- harmony/src/domain/score.rs | 2 +- harmony/src/domain/topology/ha_cluster.rs | 2 +- harmony/src/domain/topology/http.rs | 4 +- harmony/src/domain/topology/load_balancer.rs | 3 +- harmony/src/domain/topology/mod.rs | 36 ++-------- harmony/src/domain/topology/network.rs | 4 +- .../topology/oberservability/monitoring.rs | 3 +- harmony/src/domain/topology/tenant/mod.rs | 2 +- harmony/src/domain/topology/tftp.rs | 2 +- harmony/src/infra/executors/russh/mod.rs | 6 +- harmony/src/infra/hp_ilo/mod.rs | 2 +- harmony/src/infra/inventory/sqlite.rs | 4 +- harmony/src/infra/opnsense/dhcp.rs | 3 +- harmony/src/infra/opnsense/dns.rs | 2 +- harmony/src/infra/opnsense/firewall.rs | 3 +- harmony/src/infra/opnsense/http.rs | 8 +-- harmony/src/infra/opnsense/load_balancer.rs | 5 +- harmony/src/infra/opnsense/mod.rs | 6 +- harmony/src/infra/opnsense/tftp.rs | 7 +- .../application/features/helm_argocd_score.rs | 3 +- .../application/features/monitoring.rs | 3 +- harmony/src/modules/application/mod.rs | 3 +- harmony/src/modules/application/rust.rs | 6 +- harmony/src/modules/dhcp.rs | 6 +- harmony/src/modules/dns.rs | 3 +- harmony/src/modules/dummy.rs | 5 +- harmony/src/modules/helm/chart.rs | 3 +- harmony/src/modules/helm/command.rs | 3 +- harmony/src/modules/http.rs | 6 +- harmony/src/modules/inventory/mod.rs | 3 +- harmony/src/modules/ipxe.rs | 3 +- harmony/src/modules/k3d/install.rs | 3 +- harmony/src/modules/k8s/resource.rs | 3 +- harmony/src/modules/lamp.rs | 6 +- harmony/src/modules/load_balancer.rs | 3 +- .../alert_channel/discord_alert_channel.rs | 3 +- .../alert_channel/webhook_receiver.rs | 3 +- .../application_monitoring_score.rs | 3 +- harmony/src/modules/monitoring/ntfy/ntfy.rs | 3 +- harmony/src/modules/okd/ipxe.rs | 6 +- harmony/src/modules/opnsense/shell.rs | 3 +- .../k8s_prometheus_alerting_score.rs | 3 +- .../ceph/ceph_osd_replacement_score.rs | 3 +- .../ceph/ceph_validate_health_score.rs | 4 +- harmony/src/modules/tenant/mod.rs | 3 +- harmony/src/modules/tftp.rs | 6 +- .../src/local_presence/discover.rs | 3 +- harmony_types/Cargo.toml | 4 +- harmony_types/src/id.rs | 72 +++++++++++++++++++ harmony_types/src/lib.rs | 30 +------- harmony_types/src/net.rs | 55 ++++++++++++++ 69 files changed, 274 insertions(+), 165 deletions(-) create mode 100644 harmony_types/src/id.rs create mode 100644 harmony_types/src/net.rs diff --git a/Cargo.lock b/Cargo.lock index a33b177..a787f9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1671,6 +1671,7 @@ dependencies = [ "env_logger", "harmony", "harmony_cli", + "harmony_types", "logging", "tokio", "url", @@ -1732,6 +1733,7 @@ dependencies = [ "harmony", "harmony_cli", "harmony_macros", + "harmony_types", "tokio", "url", ] @@ -1743,6 +1745,7 @@ dependencies = [ "cidr", "harmony", "harmony_cli", + "harmony_types", "tokio", "url", ] @@ -2293,7 +2296,6 @@ dependencies = [ "opnsense-config", "opnsense-config-xml", "pretty_assertions", - "rand 0.9.1", "reqwest 0.11.27", "russh", "rust-ipmi", @@ -2437,7 +2439,9 @@ dependencies = [ name = "harmony_types" version = "0.1.0" dependencies = [ + "rand 0.9.1", "serde", + "url", ] [[package]] @@ -3849,7 +3853,7 @@ dependencies = [ "env_logger", "log", "pretty_assertions", - "rand 0.8.5", + "rand 0.9.1", "serde", "thiserror 2.0.14", "tokio", diff --git a/Cargo.toml b/Cargo.toml index aec04b8..6be0aa9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ tokio = { version = "1.40", features = [ cidr = { features = ["serde"], version = "0.2" } russh = "0.45" russh-keys = "0.45" -rand = "0.8" +rand = "0.9" url = "2.5" kube = { version = "1.1.0", features = [ "config", @@ -67,3 +67,4 @@ serde = { version = "1.0.209", features = ["derive", "rc"] } serde_json = "1.0.127" askama = "0.14" sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite" ] } +reqwest = { version = "0.12", features = ["stream", "rustls-tls", "http2"], default-features = false } diff --git a/examples/application_monitoring_with_tenant/Cargo.toml b/examples/application_monitoring_with_tenant/Cargo.toml index b9b63de..3f162d3 100644 --- a/examples/application_monitoring_with_tenant/Cargo.toml +++ b/examples/application_monitoring_with_tenant/Cargo.toml @@ -7,8 +7,9 @@ license.workspace = true [dependencies] env_logger.workspace = true -harmony = { version = "0.1.0", path = "../../harmony" } -harmony_cli = { version = "0.1.0", path = "../../harmony_cli" } +harmony = { path = "../../harmony" } +harmony_cli = { path = "../../harmony_cli" } +harmony_types = { path = "../../harmony_types" } logging = "0.1.0" tokio.workspace = true url.workspace = true diff --git a/examples/application_monitoring_with_tenant/src/main.rs b/examples/application_monitoring_with_tenant/src/main.rs index 9ab0bf6..7e60703 100644 --- a/examples/application_monitoring_with_tenant/src/main.rs +++ b/examples/application_monitoring_with_tenant/src/main.rs @@ -1,15 +1,16 @@ use std::{path::PathBuf, str::FromStr, sync::Arc}; use harmony::{ - data::Id, inventory::Inventory, modules::{ application::{ApplicationScore, RustWebFramework, RustWebapp, features::Monitoring}, monitoring::alert_channel::webhook_receiver::WebhookReceiver, tenant::TenantScore, }, - topology::{K8sAnywhereTopology, Url, tenant::TenantConfig}, + topology::{K8sAnywhereTopology, tenant::TenantConfig}, }; +use harmony_types::id::Id; +use harmony_types::net::Url; #[tokio::main] async fn main() { diff --git a/examples/lamp/src/main.rs b/examples/lamp/src/main.rs index b621156..3dc5a25 100644 --- a/examples/lamp/src/main.rs +++ b/examples/lamp/src/main.rs @@ -2,8 +2,9 @@ use harmony::{ data::Version, inventory::Inventory, modules::lamp::{LAMPConfig, LAMPScore}, - topology::{K8sAnywhereTopology, Url}, + topology::K8sAnywhereTopology, }; +use harmony_types::net::Url; #[tokio::main] async fn main() { diff --git a/examples/monitoring/Cargo.toml b/examples/monitoring/Cargo.toml index 1c35330..c9f7d80 100644 --- a/examples/monitoring/Cargo.toml +++ b/examples/monitoring/Cargo.toml @@ -6,8 +6,9 @@ readme.workspace = true license.workspace = true [dependencies] -harmony = { version = "0.1.0", path = "../../harmony" } -harmony_cli = { version = "0.1.0", path = "../../harmony_cli" } -harmony_macros = { version = "0.1.0", path = "../../harmony_macros" } +harmony = { path = "../../harmony" } +harmony_cli = { path = "../../harmony_cli" } +harmony_macros = { path = "../../harmony_macros" } +harmony_types = { path = "../../harmony_types" } tokio.workspace = true url.workspace = true diff --git a/examples/monitoring/src/main.rs b/examples/monitoring/src/main.rs index b0a3939..d06a93e 100644 --- a/examples/monitoring/src/main.rs +++ b/examples/monitoring/src/main.rs @@ -22,8 +22,9 @@ use harmony::{ k8s::pvc::high_pvc_fill_rate_over_two_days, }, }, - topology::{K8sAnywhereTopology, Url}, + topology::K8sAnywhereTopology, }; +use harmony_types::net::Url; #[tokio::main] async fn main() { diff --git a/examples/monitoring_with_tenant/Cargo.toml b/examples/monitoring_with_tenant/Cargo.toml index 27fd4dd..10049c6 100644 --- a/examples/monitoring_with_tenant/Cargo.toml +++ b/examples/monitoring_with_tenant/Cargo.toml @@ -7,7 +7,8 @@ license.workspace = true [dependencies] cidr.workspace = true -harmony = { version = "0.1.0", path = "../../harmony" } -harmony_cli = { version = "0.1.0", path = "../../harmony_cli" } +harmony = { path = "../../harmony" } +harmony_cli = { path = "../../harmony_cli" } +harmony_types = { path = "../../harmony_types" } tokio.workspace = true url.workspace = true diff --git a/examples/monitoring_with_tenant/src/main.rs b/examples/monitoring_with_tenant/src/main.rs index 2960944..5b85f78 100644 --- a/examples/monitoring_with_tenant/src/main.rs +++ b/examples/monitoring_with_tenant/src/main.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, str::FromStr}; use harmony::{ - data::Id, inventory::Inventory, modules::{ monitoring::{ @@ -19,10 +18,12 @@ use harmony::{ tenant::TenantScore, }, topology::{ - K8sAnywhereTopology, Url, + K8sAnywhereTopology, tenant::{ResourceLimits, TenantConfig, TenantNetworkPolicy}, }, }; +use harmony_types::id::Id; +use harmony_types::net::Url; #[tokio::main] async fn main() { diff --git a/examples/nanodc/src/main.rs b/examples/nanodc/src/main.rs index 10ba715..a8a17e3 100644 --- a/examples/nanodc/src/main.rs +++ b/examples/nanodc/src/main.rs @@ -18,9 +18,10 @@ use harmony::{ }, tftp::TftpScore, }, - topology::{LogicalHost, UnmanagedRouter, Url}, + topology::{LogicalHost, UnmanagedRouter}, }; use harmony_macros::{ip, mac_address}; +use harmony_types::net::Url; #[tokio::main] async fn main() { diff --git a/examples/opnsense/src/main.rs b/examples/opnsense/src/main.rs index e868829..3af30cf 100644 --- a/examples/opnsense/src/main.rs +++ b/examples/opnsense/src/main.rs @@ -15,9 +15,10 @@ use harmony::{ opnsense::OPNsenseShellCommandScore, tftp::TftpScore, }, - topology::{LogicalHost, UnmanagedRouter, Url}, + topology::{LogicalHost, UnmanagedRouter}, }; use harmony_macros::{ip, mac_address}; +use harmony_types::net::Url; #[tokio::main] async fn main() { diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index feb92ef..b361edd 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -11,8 +11,9 @@ use harmony::{ discord_alert_channel::DiscordWebhook, webhook_receiver::WebhookReceiver, }, }, - topology::{K8sAnywhereTopology, Url}, + topology::K8sAnywhereTopology, }; +use harmony_types::net::Url; #[tokio::main] async fn main() { diff --git a/examples/tenant/src/main.rs b/examples/tenant/src/main.rs index 356ad1b..1b0a3e4 100644 --- a/examples/tenant/src/main.rs +++ b/examples/tenant/src/main.rs @@ -1,11 +1,11 @@ use std::str::FromStr; use harmony::{ - data::Id, inventory::Inventory, modules::tenant::TenantScore, topology::{K8sAnywhereTopology, tenant::TenantConfig}, }; +use harmony_types::id::Id; #[tokio::main] async fn main() { diff --git a/harmony/Cargo.toml b/harmony/Cargo.toml index 26e5f71..07a2480 100644 --- a/harmony/Cargo.toml +++ b/harmony/Cargo.toml @@ -9,7 +9,6 @@ license.workspace = true testing = [] [dependencies] -rand = "0.9" hex = "0.4" reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features = false } russh = "0.45.0" diff --git a/harmony/src/domain/data/mod.rs b/harmony/src/domain/data/mod.rs index 8d90a52..10d6dc5 100644 --- a/harmony/src/domain/data/mod.rs +++ b/harmony/src/domain/data/mod.rs @@ -1,6 +1,4 @@ mod file; -mod id; mod version; pub use file::*; -pub use id::*; pub use version::*; diff --git a/harmony/src/domain/executors/mod.rs b/harmony/src/domain/executors/mod.rs index fb95701..19d0cad 100644 --- a/harmony/src/domain/executors/mod.rs +++ b/harmony/src/domain/executors/mod.rs @@ -1,8 +1,7 @@ use std::fmt; use async_trait::async_trait; - -use super::topology::IpAddress; +use harmony_types::net::IpAddress; #[derive(Debug)] pub enum ExecutorError { diff --git a/harmony/src/domain/hardware/mod.rs b/harmony/src/domain/hardware/mod.rs index d8d63df..20c3596 100644 --- a/harmony/src/domain/hardware/mod.rs +++ b/harmony/src/domain/hardware/mod.rs @@ -199,7 +199,7 @@ pub struct NetworkInterface { #[cfg(test)] use harmony_macros::mac_address; -use crate::data::Id; +use harmony_types::id::Id; #[cfg(test)] impl NetworkInterface { pub fn dummy() -> Self { diff --git a/harmony/src/domain/interpret/mod.rs b/harmony/src/domain/interpret/mod.rs index be671a2..71d2f61 100644 --- a/harmony/src/domain/interpret/mod.rs +++ b/harmony/src/domain/interpret/mod.rs @@ -1,13 +1,11 @@ +use harmony_types::id::Id; use std::error::Error; use async_trait::async_trait; use derive_new::new; use super::{ - data::{Id, Version}, - executors::ExecutorError, - inventory::Inventory, - topology::PreparationError, + data::Version, executors::ExecutorError, inventory::Inventory, topology::PreparationError, }; pub enum InterpretName { diff --git a/harmony/src/domain/score.rs b/harmony/src/domain/score.rs index d2585fe..8efe608 100644 --- a/harmony/src/domain/score.rs +++ b/harmony/src/domain/score.rs @@ -1,3 +1,4 @@ +use harmony_types::id::Id; use std::collections::BTreeMap; use async_trait::async_trait; @@ -5,7 +6,6 @@ use serde::Serialize; use serde_value::Value; use super::{ - data::Id, instrumentation::{self, HarmonyEvent}, interpret::{Interpret, InterpretError, Outcome}, inventory::Inventory, diff --git a/harmony/src/domain/topology/ha_cluster.rs b/harmony/src/domain/topology/ha_cluster.rs index 31d1a7f..707081a 100644 --- a/harmony/src/domain/topology/ha_cluster.rs +++ b/harmony/src/domain/topology/ha_cluster.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use harmony_macros::ip; use harmony_types::net::MacAddress; +use harmony_types::net::Url; use log::debug; use log::info; @@ -26,7 +27,6 @@ use super::Router; use super::TftpServer; use super::Topology; -use super::Url; use super::k8s::K8sClient; use std::sync::Arc; diff --git a/harmony/src/domain/topology/http.rs b/harmony/src/domain/topology/http.rs index 1d35621..cc6c1f0 100644 --- a/harmony/src/domain/topology/http.rs +++ b/harmony/src/domain/topology/http.rs @@ -1,8 +1,8 @@ use crate::{data::FileContent, executors::ExecutorError}; use async_trait::async_trait; -use super::{IpAddress, Url}; - +use harmony_types::net::IpAddress; +use harmony_types::net::Url; #[async_trait] pub trait HttpServer: Send + Sync { async fn serve_files(&self, url: &Url) -> Result<(), ExecutorError>; diff --git a/harmony/src/domain/topology/load_balancer.rs b/harmony/src/domain/topology/load_balancer.rs index 7cc326e..3a38103 100644 --- a/harmony/src/domain/topology/load_balancer.rs +++ b/harmony/src/domain/topology/load_balancer.rs @@ -4,8 +4,9 @@ use async_trait::async_trait; use log::debug; use serde::Serialize; -use super::{IpAddress, LogicalHost}; +use super::LogicalHost; use crate::executors::ExecutorError; +use harmony_types::net::IpAddress; impl std::fmt::Debug for dyn LoadBalancer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/harmony/src/domain/topology/mod.rs b/harmony/src/domain/topology/mod.rs index 81b3f12..a1060a5 100644 --- a/harmony/src/domain/topology/mod.rs +++ b/harmony/src/domain/topology/mod.rs @@ -1,4 +1,5 @@ mod ha_cluster; +use harmony_types::net::IpAddress; mod host_binding; mod http; pub mod installable; @@ -32,7 +33,6 @@ use super::{ instrumentation::{self, HarmonyEvent}, }; use std::error::Error; -use std::net::IpAddr; /// Represents a logical view of an infrastructure environment providing specific capabilities. /// @@ -196,35 +196,6 @@ pub trait MultiTargetTopology: Topology { fn current_target(&self) -> DeploymentTarget; } -pub type IpAddress = IpAddr; - -#[derive(Debug, Clone)] -pub enum Url { - LocalFolder(String), - Url(url::Url), -} - -impl Serialize for Url { - fn serialize(&self, serializer: S) -> Result - 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 { - Url::LocalFolder(path) => write!(f, "{}", path), - Url::Url(url) => write!(f, "{}", url), - } - } -} - /// Represents a logical member of a cluster that provides one or more services. /// /// A LogicalHost can represent various roles within the infrastructure, such as: @@ -263,7 +234,8 @@ impl LogicalHost { /// /// ``` /// use std::str::FromStr; - /// use harmony::topology::{IpAddress, LogicalHost}; + /// use harmony::topology::{LogicalHost}; + /// use harmony_types::net::IpAddress; /// /// let start_ip = IpAddress::from_str("192.168.0.20").unwrap(); /// let hosts = LogicalHost::create_hosts(3, start_ip, "worker"); @@ -319,7 +291,7 @@ fn increment_ip(ip: IpAddress, increment: u32) -> Option { #[cfg(test)] mod tests { - use super::*; + use harmony_types::net::Url; use serde_json; #[test] diff --git a/harmony/src/domain/topology/network.rs b/harmony/src/domain/topology/network.rs index f68d29c..7773ae1 100644 --- a/harmony/src/domain/topology/network.rs +++ b/harmony/src/domain/topology/network.rs @@ -1,12 +1,12 @@ use std::{net::Ipv4Addr, str::FromStr, sync::Arc}; use async_trait::async_trait; -use harmony_types::net::MacAddress; +use harmony_types::net::{IpAddress, MacAddress}; use serde::Serialize; use crate::executors::ExecutorError; -use super::{IpAddress, LogicalHost, k8s::K8sClient}; +use super::{LogicalHost, k8s::K8sClient}; #[derive(Debug)] pub struct DHCPStaticEntry { diff --git a/harmony/src/domain/topology/oberservability/monitoring.rs b/harmony/src/domain/topology/oberservability/monitoring.rs index c2e93d6..1489e83 100644 --- a/harmony/src/domain/topology/oberservability/monitoring.rs +++ b/harmony/src/domain/topology/oberservability/monitoring.rs @@ -4,11 +4,12 @@ use async_trait::async_trait; use log::debug; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, topology::{Topology, installable::Installable}, }; +use harmony_types::id::Id; #[async_trait] pub trait AlertSender: Send + Sync + std::fmt::Debug { diff --git a/harmony/src/domain/topology/tenant/mod.rs b/harmony/src/domain/topology/tenant/mod.rs index 1978563..1ce1bcb 100644 --- a/harmony/src/domain/topology/tenant/mod.rs +++ b/harmony/src/domain/topology/tenant/mod.rs @@ -2,7 +2,7 @@ pub mod k8s; mod manager; pub mod network_policy; -use crate::data::Id; +use harmony_types::id::Id; pub use manager::*; use serde::{Deserialize, Serialize}; use std::str::FromStr; diff --git a/harmony/src/domain/topology/tftp.rs b/harmony/src/domain/topology/tftp.rs index ed99bab..84c21e6 100644 --- a/harmony/src/domain/topology/tftp.rs +++ b/harmony/src/domain/topology/tftp.rs @@ -1,7 +1,7 @@ use crate::executors::ExecutorError; use async_trait::async_trait; -use super::{IpAddress, Url}; +use harmony_types::net::{IpAddress, Url}; #[async_trait] pub trait TftpServer: Send + Sync { diff --git a/harmony/src/infra/executors/russh/mod.rs b/harmony/src/infra/executors/russh/mod.rs index 4bc6e73..168fe75 100644 --- a/harmony/src/infra/executors/russh/mod.rs +++ b/harmony/src/infra/executors/russh/mod.rs @@ -3,11 +3,9 @@ use std::sync::Arc; use russh::{client, keys::key}; -use crate::{ - domain::executors::{ExecutorError, SshClient}, - topology::IpAddress, -}; +use crate::domain::executors::{ExecutorError, SshClient}; +use harmony_types::net::IpAddress; pub struct RusshClient; #[async_trait] diff --git a/harmony/src/infra/hp_ilo/mod.rs b/harmony/src/infra/hp_ilo/mod.rs index ff0e313..b5edbe6 100644 --- a/harmony/src/infra/hp_ilo/mod.rs +++ b/harmony/src/infra/hp_ilo/mod.rs @@ -1,6 +1,6 @@ use crate::hardware::ManagementInterface; -use crate::topology::IpAddress; use derive_new::new; +use harmony_types::net::IpAddress; use harmony_types::net::MacAddress; use log::info; use serde::Serialize; diff --git a/harmony/src/infra/inventory/sqlite.rs b/harmony/src/infra/inventory/sqlite.rs index 03fae14..d079996 100644 --- a/harmony/src/infra/inventory/sqlite.rs +++ b/harmony/src/infra/inventory/sqlite.rs @@ -1,10 +1,10 @@ use crate::{ - data::Id, hardware::PhysicalHost, inventory::{InventoryRepository, RepoError}, }; use async_trait::async_trait; -use log::{info, warn}; +use harmony_types::id::Id; +use log::info; use sqlx::{Pool, Sqlite, SqlitePool}; /// A thread-safe, connection-pooled repository using SQLite. diff --git a/harmony/src/infra/opnsense/dhcp.rs b/harmony/src/infra/opnsense/dhcp.rs index ee4ba6c..272ffc2 100644 --- a/harmony/src/infra/opnsense/dhcp.rs +++ b/harmony/src/infra/opnsense/dhcp.rs @@ -4,10 +4,11 @@ use log::info; use crate::{ executors::ExecutorError, - topology::{DHCPStaticEntry, DhcpServer, IpAddress, LogicalHost, PxeOptions}, + topology::{DHCPStaticEntry, DhcpServer, LogicalHost, PxeOptions}, }; use super::OPNSenseFirewall; +use harmony_types::net::IpAddress; #[async_trait] impl DhcpServer for OPNSenseFirewall { diff --git a/harmony/src/infra/opnsense/dns.rs b/harmony/src/infra/opnsense/dns.rs index d486529..7a58b64 100644 --- a/harmony/src/infra/opnsense/dns.rs +++ b/harmony/src/infra/opnsense/dns.rs @@ -1,11 +1,11 @@ use crate::infra::opnsense::Host; -use crate::infra::opnsense::IpAddress; use crate::infra::opnsense::LogicalHost; use crate::{ executors::ExecutorError, topology::{DnsRecord, DnsServer}, }; use async_trait::async_trait; +use harmony_types::net::IpAddress; use super::OPNSenseFirewall; diff --git a/harmony/src/infra/opnsense/firewall.rs b/harmony/src/infra/opnsense/firewall.rs index cc25f72..589b244 100644 --- a/harmony/src/infra/opnsense/firewall.rs +++ b/harmony/src/infra/opnsense/firewall.rs @@ -1,9 +1,10 @@ use crate::{ executors::ExecutorError, - topology::{Firewall, FirewallRule, IpAddress, LogicalHost}, + topology::{Firewall, FirewallRule, LogicalHost}, }; use super::OPNSenseFirewall; +use harmony_types::net::IpAddress; impl Firewall for OPNSenseFirewall { fn add_rule(&mut self, _rule: FirewallRule) -> Result<(), ExecutorError> { diff --git a/harmony/src/infra/opnsense/http.rs b/harmony/src/infra/opnsense/http.rs index 1d34d4c..fa6fe7d 100644 --- a/harmony/src/infra/opnsense/http.rs +++ b/harmony/src/infra/opnsense/http.rs @@ -1,13 +1,11 @@ use async_trait::async_trait; use log::info; -use crate::{ - data::FileContent, - executors::ExecutorError, - topology::{HttpServer, IpAddress, Url}, -}; +use crate::{data::FileContent, executors::ExecutorError, topology::HttpServer}; use super::OPNSenseFirewall; +use harmony_types::net::IpAddress; +use harmony_types::net::Url; const OPNSENSE_HTTP_ROOT_PATH: &str = "/usr/local/http"; #[async_trait] diff --git a/harmony/src/infra/opnsense/load_balancer.rs b/harmony/src/infra/opnsense/load_balancer.rs index 471348f..9414faf 100644 --- a/harmony/src/infra/opnsense/load_balancer.rs +++ b/harmony/src/infra/opnsense/load_balancer.rs @@ -6,10 +6,11 @@ use uuid::Uuid; use crate::{ executors::ExecutorError, topology::{ - BackendServer, HealthCheck, HttpMethod, HttpStatusCode, IpAddress, LoadBalancer, - LoadBalancerService, LogicalHost, + BackendServer, HealthCheck, HttpMethod, HttpStatusCode, LoadBalancer, LoadBalancerService, + LogicalHost, }, }; +use harmony_types::net::IpAddress; use super::OPNSenseFirewall; diff --git a/harmony/src/infra/opnsense/mod.rs b/harmony/src/infra/opnsense/mod.rs index 0aa5532..3878cfc 100644 --- a/harmony/src/infra/opnsense/mod.rs +++ b/harmony/src/infra/opnsense/mod.rs @@ -11,10 +11,8 @@ pub use management::*; use opnsense_config_xml::Host; use tokio::sync::RwLock; -use crate::{ - executors::ExecutorError, - topology::{IpAddress, LogicalHost}, -}; +use crate::{executors::ExecutorError, topology::LogicalHost}; +use harmony_types::net::IpAddress; #[derive(Debug, Clone)] pub struct OPNSenseFirewall { diff --git a/harmony/src/infra/opnsense/tftp.rs b/harmony/src/infra/opnsense/tftp.rs index 1bf7b6c..275b40d 100644 --- a/harmony/src/infra/opnsense/tftp.rs +++ b/harmony/src/infra/opnsense/tftp.rs @@ -1,10 +1,9 @@ use async_trait::async_trait; use log::info; -use crate::{ - executors::ExecutorError, - topology::{IpAddress, TftpServer, Url}, -}; +use crate::{executors::ExecutorError, topology::TftpServer}; +use harmony_types::net::IpAddress; +use harmony_types::net::Url; use super::OPNSenseFirewall; diff --git a/harmony/src/modules/application/features/helm_argocd_score.rs b/harmony/src/modules/application/features/helm_argocd_score.rs index 0532111..c439727 100644 --- a/harmony/src/modules/application/features/helm_argocd_score.rs +++ b/harmony/src/modules/application/features/helm_argocd_score.rs @@ -4,13 +4,14 @@ use serde::Serialize; use std::str::FromStr; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, modules::helm::chart::{HelmChartScore, HelmRepository}, score::Score, topology::{HelmCommand, K8sclient, Topology}, }; +use harmony_types::id::Id; use super::ArgoApplication; diff --git a/harmony/src/modules/application/features/monitoring.rs b/harmony/src/modules/application/features/monitoring.rs index e8303ce..1c1c00b 100644 --- a/harmony/src/modules/application/features/monitoring.rs +++ b/harmony/src/modules/application/features/monitoring.rs @@ -11,7 +11,7 @@ use crate::{ alert_channel::webhook_receiver::WebhookReceiver, ntfy::ntfy::NtfyScore, }, score::Score, - topology::{HelmCommand, K8sclient, Topology, Url, tenant::TenantManager}, + topology::{HelmCommand, K8sclient, Topology, tenant::TenantManager}, }; use crate::{ modules::prometheus::prometheus::PrometheusApplicationMonitoring, @@ -19,6 +19,7 @@ use crate::{ }; use async_trait::async_trait; use base64::{Engine as _, engine::general_purpose}; +use harmony_types::net::Url; use log::{debug, info}; #[derive(Debug, Clone)] diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index beb10d6..8e60984 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -13,12 +13,13 @@ use async_trait::async_trait; use serde::Serialize; use crate::{ - data::{Id, Version}, + data::Version, instrumentation::{self, HarmonyEvent}, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, topology::Topology, }; +use harmony_types::id::Id; #[derive(Clone, Debug)] pub enum ApplicationFeatureStatus { diff --git a/harmony/src/modules/application/rust.rs b/harmony/src/modules/application/rust.rs index da1e594..40c85bb 100644 --- a/harmony/src/modules/application/rust.rs +++ b/harmony/src/modules/application/rust.rs @@ -15,10 +15,8 @@ use serde::Serialize; use tar::Archive; use crate::config::{REGISTRY_PROJECT, REGISTRY_URL}; -use crate::{ - score::Score, - topology::{Topology, Url}, -}; +use crate::{score::Score, topology::Topology}; +use harmony_types::net::Url; use super::{Application, ApplicationFeature, ApplicationInterpret, HelmPackage, OCICompliant}; diff --git a/harmony/src/modules/dhcp.rs b/harmony/src/modules/dhcp.rs index 81643db..9ef45d3 100644 --- a/harmony/src/modules/dhcp.rs +++ b/harmony/src/modules/dhcp.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; use derive_new::new; +use harmony_types::id::Id; use log::info; use serde::Serialize; @@ -7,10 +8,11 @@ use crate::{ domain::{data::Version, interpret::InterpretStatus}, interpret::{Interpret, InterpretError, InterpretName, Outcome}, inventory::Inventory, - topology::{DHCPStaticEntry, DhcpServer, HostBinding, IpAddress, PxeOptions, Topology}, + topology::{DHCPStaticEntry, DhcpServer, HostBinding, PxeOptions, Topology}, }; use crate::domain::score::Score; +use harmony_types::net::IpAddress; #[derive(Debug, new, Clone, Serialize)] pub struct DhcpScore { @@ -135,7 +137,7 @@ impl Interpret for DhcpInterpret { self.status.clone() } - fn get_children(&self) -> Vec { + fn get_children(&self) -> Vec { todo!() } diff --git a/harmony/src/modules/dns.rs b/harmony/src/modules/dns.rs index 07b883d..9608fa1 100644 --- a/harmony/src/modules/dns.rs +++ b/harmony/src/modules/dns.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; use derive_new::new; +use harmony_types::id::Id; use log::info; use serde::Serialize; @@ -91,7 +92,7 @@ impl Interpret for DnsInterpret { self.status.clone() } - fn get_children(&self) -> Vec { + fn get_children(&self) -> Vec { todo!() } diff --git a/harmony/src/modules/dummy.rs b/harmony/src/modules/dummy.rs index 2e63797..784d307 100644 --- a/harmony/src/modules/dummy.rs +++ b/harmony/src/modules/dummy.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use harmony_types::id::Id; use serde::Serialize; use crate::{ @@ -67,7 +68,7 @@ impl Interpret for DummyInterpret { self.status.clone() } - fn get_children(&self) -> Vec { + fn get_children(&self) -> Vec { todo!() } @@ -113,7 +114,7 @@ impl Interpret for PanicInterpret { InterpretStatus::QUEUED } - fn get_children(&self) -> Vec { + fn get_children(&self) -> Vec { todo!() } diff --git a/harmony/src/modules/helm/chart.rs b/harmony/src/modules/helm/chart.rs index dd94678..9048ce6 100644 --- a/harmony/src/modules/helm/chart.rs +++ b/harmony/src/modules/helm/chart.rs @@ -1,9 +1,10 @@ -use crate::data::{Id, Version}; +use crate::data::Version; use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}; use crate::inventory::Inventory; use crate::score::Score; use crate::topology::{HelmCommand, Topology}; use async_trait::async_trait; +use harmony_types::id::Id; use helm_wrapper_rs; use helm_wrapper_rs::blocking::{DefaultHelmExecutor, HelmExecutor}; use log::{debug, info, warn}; diff --git a/harmony/src/modules/helm/command.rs b/harmony/src/modules/helm/command.rs index 149d6c6..c4d92c1 100644 --- a/harmony/src/modules/helm/command.rs +++ b/harmony/src/modules/helm/command.rs @@ -8,11 +8,12 @@ use std::process::{Command, Output}; use temp_dir::{self, TempDir}; use temp_file::TempFile; -use crate::data::{Id, Version}; +use crate::data::Version; use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}; use crate::inventory::Inventory; use crate::score::Score; use crate::topology::{HelmCommand, K8sclient, Topology}; +use harmony_types::id::Id; #[derive(Clone)] pub struct HelmCommandExecutor { diff --git a/harmony/src/modules/http.rs b/harmony/src/modules/http.rs index a04ea73..fd7a5c8 100644 --- a/harmony/src/modules/http.rs +++ b/harmony/src/modules/http.rs @@ -3,12 +3,14 @@ use derive_new::new; use serde::Serialize; use crate::{ - data::{FileContent, Id, Version}, + data::{FileContent, Version}, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, - topology::{HttpServer, Topology, Url}, + topology::{HttpServer, Topology}, }; +use harmony_types::id::Id; +use harmony_types::net::Url; /// Configure an HTTP server that is provided by the Topology /// diff --git a/harmony/src/modules/inventory/mod.rs b/harmony/src/modules/inventory/mod.rs index a7d168b..e8bbd71 100644 --- a/harmony/src/modules/inventory/mod.rs +++ b/harmony/src/modules/inventory/mod.rs @@ -4,12 +4,13 @@ use log::{debug, info}; use serde::{Deserialize, Serialize}; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, topology::Topology, }; +use harmony_types::id::Id; /// This launches an harmony_inventory_agent discovery process /// This will allow us to register/update hosts running harmony_inventory_agent diff --git a/harmony/src/modules/ipxe.rs b/harmony/src/modules/ipxe.rs index f9e8ed3..a7aa472 100644 --- a/harmony/src/modules/ipxe.rs +++ b/harmony/src/modules/ipxe.rs @@ -3,12 +3,13 @@ use derive_new::new; use serde::Serialize; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, topology::Topology, }; +use harmony_types::id::Id; #[derive(Debug, new, Clone, Serialize)] pub struct IpxeScore { diff --git a/harmony/src/modules/k3d/install.rs b/harmony/src/modules/k3d/install.rs index 245cf41..244dff4 100644 --- a/harmony/src/modules/k3d/install.rs +++ b/harmony/src/modules/k3d/install.rs @@ -6,12 +6,13 @@ use serde::Serialize; use crate::{ config::HARMONY_DATA_DIR, - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, topology::Topology, }; +use harmony_types::id::Id; #[derive(Debug, Clone, Serialize)] pub struct K3DInstallationScore { diff --git a/harmony/src/modules/k8s/resource.rs b/harmony/src/modules/k8s/resource.rs index b6709ea..d679326 100644 --- a/harmony/src/modules/k8s/resource.rs +++ b/harmony/src/modules/k8s/resource.rs @@ -5,12 +5,13 @@ use log::info; use serde::{Serialize, de::DeserializeOwned}; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, topology::{K8sclient, Topology}, }; +use harmony_types::id::Id; #[derive(Debug, Clone, Serialize)] pub struct K8sResourceScore { diff --git a/harmony/src/modules/lamp.rs b/harmony/src/modules/lamp.rs index 1a853ea..66ca45e 100644 --- a/harmony/src/modules/lamp.rs +++ b/harmony/src/modules/lamp.rs @@ -3,6 +3,7 @@ use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, WORKDIR use dockerfile_builder::{Dockerfile, instruction_builder::EnvBuilder}; use fqdn::fqdn; use harmony_macros::ingress_path; +use harmony_types::net::Url; use non_blank_string_rs::NonBlankString; use serde_json::json; use std::collections::HashMap; @@ -18,13 +19,14 @@ use crate::config::{REGISTRY_PROJECT, REGISTRY_URL}; use crate::modules::k8s::ingress::K8sIngressScore; use crate::topology::HelmCommand; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, modules::k8s::deployment::K8sDeploymentScore, score::Score, - topology::{K8sclient, Topology, Url}, + topology::{K8sclient, Topology}, }; +use harmony_types::id::Id; use super::helm::chart::HelmChartScore; diff --git a/harmony/src/modules/load_balancer.rs b/harmony/src/modules/load_balancer.rs index cd78f84..033ba2e 100644 --- a/harmony/src/modules/load_balancer.rs +++ b/harmony/src/modules/load_balancer.rs @@ -3,12 +3,13 @@ use log::info; use serde::Serialize; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, topology::{LoadBalancer, LoadBalancerService, Topology}, }; +use harmony_types::id::Id; #[derive(Debug, Clone, Serialize)] pub struct LoadBalancerScore { diff --git a/harmony/src/modules/monitoring/alert_channel/discord_alert_channel.rs b/harmony/src/modules/monitoring/alert_channel/discord_alert_channel.rs index 1d704a4..caab4d1 100644 --- a/harmony/src/modules/monitoring/alert_channel/discord_alert_channel.rs +++ b/harmony/src/modules/monitoring/alert_channel/discord_alert_channel.rs @@ -20,8 +20,9 @@ use crate::{ }, prometheus::prometheus::{Prometheus, PrometheusReceiver}, }, - topology::{Url, oberservability::monitoring::AlertReceiver}, + topology::oberservability::monitoring::AlertReceiver, }; +use harmony_types::net::Url; #[derive(Debug, Clone, Serialize)] pub struct DiscordWebhook { diff --git a/harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs b/harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs index 9a9d5d2..51e63b6 100644 --- a/harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs +++ b/harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs @@ -19,8 +19,9 @@ use crate::{ }, prometheus::prometheus::{Prometheus, PrometheusReceiver}, }, - topology::{Url, oberservability::monitoring::AlertReceiver}, + topology::oberservability::monitoring::AlertReceiver, }; +use harmony_types::net::Url; #[derive(Debug, Clone, Serialize)] pub struct WebhookReceiver { diff --git a/harmony/src/modules/monitoring/application_monitoring/application_monitoring_score.rs b/harmony/src/modules/monitoring/application_monitoring/application_monitoring_score.rs index 51c8ff9..f4707a8 100644 --- a/harmony/src/modules/monitoring/application_monitoring/application_monitoring_score.rs +++ b/harmony/src/modules/monitoring/application_monitoring/application_monitoring_score.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use serde::Serialize; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, modules::{ @@ -15,6 +15,7 @@ use crate::{ score::Score, topology::{PreparationOutcome, Topology, oberservability::monitoring::AlertReceiver}, }; +use harmony_types::id::Id; #[derive(Debug, Clone, Serialize)] pub struct ApplicationMonitoringScore { diff --git a/harmony/src/modules/monitoring/ntfy/ntfy.rs b/harmony/src/modules/monitoring/ntfy/ntfy.rs index 68106b7..87ed580 100644 --- a/harmony/src/modules/monitoring/ntfy/ntfy.rs +++ b/harmony/src/modules/monitoring/ntfy/ntfy.rs @@ -6,13 +6,14 @@ use serde::Serialize; use strum::{Display, EnumString}; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, modules::monitoring::ntfy::helm::ntfy_helm_chart::ntfy_helm_chart_score, score::Score, topology::{HelmCommand, K8sclient, MultiTargetTopology, Topology, k8s::K8sClient}, }; +use harmony_types::id::Id; #[derive(Debug, Clone, Serialize)] pub struct NtfyScore { diff --git a/harmony/src/modules/okd/ipxe.rs b/harmony/src/modules/okd/ipxe.rs index a0b32d4..38de035 100644 --- a/harmony/src/modules/okd/ipxe.rs +++ b/harmony/src/modules/okd/ipxe.rs @@ -1,17 +1,19 @@ use askama::Template; use async_trait::async_trait; use derive_new::new; +use harmony_types::net::Url; use serde::Serialize; use std::net::IpAddr; use crate::{ - data::{FileContent, FilePath, Id, Version}, + data::{FileContent, FilePath, Version}, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, modules::{dhcp::DhcpScore, http::StaticFilesHttpScore, tftp::TftpScore}, score::Score, - topology::{DhcpServer, HttpServer, Router, TftpServer, Topology, Url}, + topology::{DhcpServer, HttpServer, Router, TftpServer, Topology}, }; +use harmony_types::id::Id; #[derive(Debug, new, Clone, Serialize)] pub struct OkdIpxeScore { diff --git a/harmony/src/modules/opnsense/shell.rs b/harmony/src/modules/opnsense/shell.rs index 90be4e6..0b651ff 100644 --- a/harmony/src/modules/opnsense/shell.rs +++ b/harmony/src/modules/opnsense/shell.rs @@ -5,12 +5,13 @@ use serde::Serialize; use tokio::sync::RwLock; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, topology::Topology, }; +use harmony_types::id::Id; #[derive(Debug, Clone)] pub struct OPNsenseShellCommandScore { diff --git a/harmony/src/modules/prometheus/k8s_prometheus_alerting_score.rs b/harmony/src/modules/prometheus/k8s_prometheus_alerting_score.rs index 0af1063..30bc8bd 100644 --- a/harmony/src/modules/prometheus/k8s_prometheus_alerting_score.rs +++ b/harmony/src/modules/prometheus/k8s_prometheus_alerting_score.rs @@ -24,7 +24,7 @@ use crate::modules::monitoring::kube_prometheus::crd::service_monitor::{ use crate::topology::oberservability::monitoring::AlertReceiver; use crate::topology::{K8sclient, Topology, k8s::K8sClient}; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, modules::monitoring::kube_prometheus::crd::{ @@ -37,6 +37,7 @@ use crate::{ }, score::Score, }; +use harmony_types::id::Id; use super::prometheus::PrometheusApplicationMonitoring; diff --git a/harmony/src/modules/storage/ceph/ceph_osd_replacement_score.rs b/harmony/src/modules/storage/ceph/ceph_osd_replacement_score.rs index 708c501..77dd24a 100644 --- a/harmony/src/modules/storage/ceph/ceph_osd_replacement_score.rs +++ b/harmony/src/modules/storage/ceph/ceph_osd_replacement_score.rs @@ -9,12 +9,13 @@ use serde::{Deserialize, Serialize}; use tokio::time::sleep; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, topology::{K8sclient, Topology, k8s::K8sClient}, }; +use harmony_types::id::Id; #[derive(Debug, Clone, Serialize)] pub struct CephRemoveOsd { diff --git a/harmony/src/modules/storage/ceph/ceph_validate_health_score.rs b/harmony/src/modules/storage/ceph/ceph_validate_health_score.rs index 2f7f87c..f6b43ec 100644 --- a/harmony/src/modules/storage/ceph/ceph_validate_health_score.rs +++ b/harmony/src/modules/storage/ceph/ceph_validate_health_score.rs @@ -3,15 +3,15 @@ use std::{sync::Arc, time::Duration}; use async_trait::async_trait; use log::debug; use serde::Serialize; -use tokio::time::Instant; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, topology::{K8sclient, Topology, k8s::K8sClient}, }; +use harmony_types::id::Id; #[derive(Clone, Debug, Serialize)] pub struct CephVerifyClusterHealth { diff --git a/harmony/src/modules/tenant/mod.rs b/harmony/src/modules/tenant/mod.rs index b1a49c2..16386a2 100644 --- a/harmony/src/modules/tenant/mod.rs +++ b/harmony/src/modules/tenant/mod.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use serde::Serialize; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, @@ -14,6 +14,7 @@ use crate::{ tenant::{TenantConfig, TenantManager}, }, }; +use harmony_types::id::Id; #[derive(Debug, Serialize, Clone)] pub struct TenantScore { diff --git a/harmony/src/modules/tftp.rs b/harmony/src/modules/tftp.rs index 6763ec0..1cc8a8d 100644 --- a/harmony/src/modules/tftp.rs +++ b/harmony/src/modules/tftp.rs @@ -3,12 +3,14 @@ use derive_new::new; use serde::Serialize; use crate::{ - data::{Id, Version}, + data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, - topology::{Router, TftpServer, Topology, Url}, + topology::{Router, TftpServer, Topology}, }; +use harmony_types::id::Id; +use harmony_types::net::Url; #[derive(Debug, new, Clone, Serialize)] pub struct TftpScore { diff --git a/harmony_inventory_agent/src/local_presence/discover.rs b/harmony_inventory_agent/src/local_presence/discover.rs index 251db99..a2ae216 100644 --- a/harmony_inventory_agent/src/local_presence/discover.rs +++ b/harmony_inventory_agent/src/local_presence/discover.rs @@ -4,8 +4,7 @@ use crate::local_presence::SERVICE_NAME; pub type DiscoveryEvent = ServiceEvent; -pub fn discover_agents(timeout: Option, on_event: impl Fn(DiscoveryEvent) + Send + 'static) -{ +pub fn discover_agents(timeout: Option, on_event: impl Fn(DiscoveryEvent) + Send + 'static) { // Create a new mDNS daemon. let mdns = ServiceDaemon::new().expect("Failed to create mDNS daemon"); diff --git a/harmony_types/Cargo.toml b/harmony_types/Cargo.toml index 0b8c068..f02874e 100644 --- a/harmony_types/Cargo.toml +++ b/harmony_types/Cargo.toml @@ -6,4 +6,6 @@ readme.workspace = true license.workspace = true [dependencies] -serde = { version = "1.0.209", features = ["derive"] } +serde.workspace = true +url.workspace = true +rand.workspace = true diff --git a/harmony_types/src/id.rs b/harmony_types/src/id.rs new file mode 100644 index 0000000..98cf1b9 --- /dev/null +++ b/harmony_types/src/id.rs @@ -0,0 +1,72 @@ +use rand::distr::Alphanumeric; +use rand::distr::SampleString; +use std::str::FromStr; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; + +use serde::{Deserialize, Serialize}; + +/// A unique identifier designed for ease of use. +/// +/// You can pass it any String to use and Id, or you can use the default format with `Id::default()` +/// +/// The default format looks like this +/// +/// `462d4c_g2COgai` +/// +/// The first part is the unix timesamp in hexadecimal which makes Id easily sorted by creation time. +/// Second part is a serie of 7 random characters. +/// +/// **It is not meant to be very secure or unique**, it is suitable to generate up to 10 000 items per +/// second with a reasonable collision rate of 0,000014 % as calculated by this calculator : https://kevingal.com/apps/collision.html +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Id { + value: String, +} + +impl Id { + pub fn empty() -> Self { + Id { + value: String::new(), + } + } +} + +impl FromStr for Id { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(Id { + value: s.to_string(), + }) + } +} + +impl From for Id { + fn from(value: String) -> Self { + Self { value } + } +} + +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.value) + } +} + +impl Default for Id { + fn default() -> Self { + let start = SystemTime::now(); + let since_the_epoch = start + .duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + let timestamp = since_the_epoch.as_secs(); + + let hex_timestamp = format!("{:x}", timestamp & 0xffffff); + + let random_part: String = Alphanumeric.sample_string(&mut rand::rng(), 7); + + let value = format!("{}_{}", hex_timestamp, random_part); + Self { value } + } +} diff --git a/harmony_types/src/lib.rs b/harmony_types/src/lib.rs index 9f4930d..7bb1abd 100644 --- a/harmony_types/src/lib.rs +++ b/harmony_types/src/lib.rs @@ -1,28 +1,2 @@ -pub mod net { - use serde::Serialize; - - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize)] - pub struct MacAddress(pub [u8; 6]); - - impl MacAddress { - #[cfg(test)] - pub fn dummy() -> Self { - Self([0, 0, 0, 0, 0, 0]) - } - } - - impl From<&MacAddress> for String { - fn from(value: &MacAddress) -> Self { - format!( - "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", - value.0[0], value.0[1], value.0[2], value.0[3], value.0[4], value.0[5] - ) - } - } - - impl std::fmt::Display for MacAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("MacAddress {}", String::from(self))) - } - } -} +pub mod id; +pub mod net; diff --git a/harmony_types/src/net.rs b/harmony_types/src/net.rs new file mode 100644 index 0000000..e2905a1 --- /dev/null +++ b/harmony_types/src/net.rs @@ -0,0 +1,55 @@ +use serde::Serialize; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize)] +pub struct MacAddress(pub [u8; 6]); + +impl MacAddress { + #[cfg(test)] + pub fn dummy() -> Self { + Self([0, 0, 0, 0, 0, 0]) + } +} + +impl From<&MacAddress> for String { + fn from(value: &MacAddress) -> Self { + format!( + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + value.0[0], value.0[1], value.0[2], value.0[3], value.0[4], value.0[5] + ) + } +} + +impl std::fmt::Display for MacAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("MacAddress {}", String::from(self))) + } +} + +pub type IpAddress = std::net::IpAddr; + +#[derive(Debug, Clone)] +pub enum Url { + LocalFolder(String), + Url(url::Url), +} + +impl Serialize for Url { + fn serialize(&self, serializer: S) -> Result + 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 { + Url::LocalFolder(path) => write!(f, "{}", path), + Url::Url(url) => write!(f, "{}", url), + } + } +} From 701d8cfab9986b85b374879a993c6cfc8d64ffcb Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Sun, 31 Aug 2025 22:45:07 +0000 Subject: [PATCH 3/3] feat: automatically discover inventory (#127) ## Fully automated inventory gathering now works! Boot up harmony_inventory_agent with `cargo run -p harmony_inventory_agent` Launch the DiscoverInventoryAgentScore , currently available this way : `RUST_LOG=info cargo run -p example-cli -- -f Discover -y` And you will have automatically all hosts saved to the database. Run `cargo sqlx setup` if you have not done it yet. Co-authored-by: Ian Letourneau Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/127 Co-authored-by: Jean-Gabriel Gill-Couture Co-committed-by: Jean-Gabriel Gill-Couture --- Cargo.lock | 3 + Cargo.toml | 2 +- .../okd/http_files/harmony_inventory_agent | 4 +- examples/nanodc/src/main.rs | 3 +- examples/okd_pxe/src/topology.rs | 3 +- examples/opnsense/src/main.rs | 3 +- harmony/src/domain/config.rs | 8 + harmony/src/domain/hardware/mod.rs | 474 ++++++++---------- harmony/src/domain/inventory/mod.rs | 8 +- harmony/src/infra/inventory/mod.rs | 18 +- harmony/src/infra/inventory/sqlite.rs | 8 +- harmony/src/modules/inventory/mod.rs | 89 +++- harmony_inventory_agent/Cargo.toml | 3 + harmony_inventory_agent/src/client.rs | 15 + harmony_inventory_agent/src/hwinfo.rs | 47 +- harmony_inventory_agent/src/lib.rs | 3 +- .../src/local_presence/discover.rs | 19 +- harmony_types/src/net.rs | 28 +- migrations/20250830163356_Physical_hosts.sql | 8 + 19 files changed, 442 insertions(+), 304 deletions(-) create mode 100644 harmony_inventory_agent/src/client.rs create mode 100644 migrations/20250830163356_Physical_hosts.sql diff --git a/Cargo.lock b/Cargo.lock index a787f9e..62d8aee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2366,9 +2366,12 @@ version = "0.1.0" dependencies = [ "actix-web", "env_logger", + "harmony_macros", + "harmony_types", "local-ip-address", "log", "mdns-sd 0.14.1 (git+https://github.com/jggc/mdns-sd.git?branch=patch-1)", + "reqwest 0.12.20", "serde", "serde_json", "sysinfo", diff --git a/Cargo.toml b/Cargo.toml index 6be0aa9..d92c0e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,4 +67,4 @@ serde = { version = "1.0.209", features = ["derive", "rc"] } serde_json = "1.0.127" askama = "0.14" sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite" ] } -reqwest = { version = "0.12", features = ["stream", "rustls-tls", "http2"], default-features = false } +reqwest = { version = "0.12", features = ["blocking", "stream", "rustls-tls", "http2", "json"], default-features = false } diff --git a/data/pxe/okd/http_files/harmony_inventory_agent b/data/pxe/okd/http_files/harmony_inventory_agent index ada5282..1d802f7 100755 --- a/data/pxe/okd/http_files/harmony_inventory_agent +++ b/data/pxe/okd/http_files/harmony_inventory_agent @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aed14f47246bc20c5ada082f782da77da90ef9f78ef18fbf9f160f2101d9c92a -size 8129096 +oid sha256:5244fa8968fe15c2415de6cc487e6112f8aedd9989951e018f9bdb536b1016d2 +size 8139216 diff --git a/examples/nanodc/src/main.rs b/examples/nanodc/src/main.rs index a8a17e3..a6bb8e4 100644 --- a/examples/nanodc/src/main.rs +++ b/examples/nanodc/src/main.rs @@ -87,8 +87,7 @@ async fn main() { let inventory = Inventory { location: Location::new("I am mobile".to_string(), "earth".to_string()), switch: SwitchGroup::from([]), - firewall: FirewallGroup::from([PhysicalHost::empty(HostCategory::Firewall) - .management(Arc::new(OPNSenseManagementInterface::new()))]), + firewall_mgmt: Box::new(OPNSenseManagementInterface::new()), storage_host: vec![], worker_host: vec![ PhysicalHost::empty(HostCategory::Server) diff --git a/examples/okd_pxe/src/topology.rs b/examples/okd_pxe/src/topology.rs index eb23908..27eb8c0 100644 --- a/examples/okd_pxe/src/topology.rs +++ b/examples/okd_pxe/src/topology.rs @@ -69,8 +69,7 @@ pub fn get_inventory() -> Inventory { "testopnsense".to_string(), ), switch: SwitchGroup::from([]), - firewall: FirewallGroup::from([PhysicalHost::empty(HostCategory::Firewall) - .management(Arc::new(OPNSenseManagementInterface::new()))]), + firewall_mgmt: Box::new(OPNSenseManagementInterface::new()), storage_host: vec![], worker_host: vec![], control_plane_host: vec![], diff --git a/examples/opnsense/src/main.rs b/examples/opnsense/src/main.rs index 3af30cf..465b0fa 100644 --- a/examples/opnsense/src/main.rs +++ b/examples/opnsense/src/main.rs @@ -63,8 +63,7 @@ async fn main() { "wk".to_string(), ), switch: SwitchGroup::from([]), - firewall: FirewallGroup::from([PhysicalHost::empty(HostCategory::Firewall) - .management(Arc::new(OPNSenseManagementInterface::new()))]), + firewall_mgmt: Box::new(OPNSenseManagementInterface::new()), storage_host: vec![], worker_host: vec![], control_plane_host: vec![ diff --git a/harmony/src/domain/config.rs b/harmony/src/domain/config.rs index 62f612f..1a91684 100644 --- a/harmony/src/domain/config.rs +++ b/harmony/src/domain/config.rs @@ -12,4 +12,12 @@ lazy_static! { std::env::var("HARMONY_REGISTRY_PROJECT").unwrap_or_else(|_| "harmony".to_string()); pub static ref DRY_RUN: bool = std::env::var("HARMONY_DRY_RUN").is_ok_and(|value| value.parse().unwrap_or(false)); + pub static ref DEFAULT_DATABASE_URL: String = "sqlite://harmony.sqlite".to_string(); + pub static ref DATABASE_URL: String = std::env::var("HARMONY_DATABASE_URL") + .map(|value| if value.is_empty() { + (*DEFAULT_DATABASE_URL).clone() + } else { + value + }) + .unwrap_or((*DEFAULT_DATABASE_URL).clone()); } diff --git a/harmony/src/domain/hardware/mod.rs b/harmony/src/domain/hardware/mod.rs index 20c3596..3a14e1a 100644 --- a/harmony/src/domain/hardware/mod.rs +++ b/harmony/src/domain/hardware/mod.rs @@ -1,24 +1,24 @@ -use std::{str::FromStr, sync::Arc}; +use std::sync::Arc; use derive_new::new; +use harmony_inventory_agent::hwinfo::{CPU, MemoryModule, NetworkInterface, StorageDrive}; use harmony_types::net::MacAddress; -use serde::{Deserialize, Serialize, Serializer, ser::SerializeStruct}; +use serde::{Deserialize, Serialize}; use serde_value::Value; pub type HostGroup = Vec; pub type SwitchGroup = Vec; pub type FirewallGroup = Vec; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct PhysicalHost { pub id: Id, pub category: HostCategory, pub network: Vec, - pub management: Arc, - pub storage: Vec, + pub storage: Vec, pub labels: Vec