feat: Inventory PhysicalHost persistence with sqlx and local sqlite db
Some checks failed
Run Check Script / check (pull_request) Failing after 34s

This commit is contained in:
Jean-Gabriel Gill-Couture 2025-08-30 16:02:49 -04:00
parent 1eca2cc1a9
commit cb4382fbb5
13 changed files with 560 additions and 4 deletions

View File

@ -0,0 +1,32 @@
{
"db_name": "SQLite",
"query": "SELECT id, version_id, data as \"data: Json<PhysicalHost>\" 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<PhysicalHost>",
"ordinal": 2,
"type_info": "Null"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false
]
},
"hash": "934035c7ca6e064815393e4e049a7934b0a7fac04a4fe4b2a354f0443d630990"
}

View File

@ -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"
}

343
Cargo.lock generated
View File

@ -474,6 +474,15 @@ dependencies = [
"syn 2.0.105", "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]] [[package]]
name = "atomic-waker" name = "atomic-waker"
version = "1.1.2" version = "1.1.2"
@ -1052,6 +1061,21 @@ dependencies = [
"libc", "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]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.2" version = "1.4.2"
@ -1089,6 +1113,15 @@ dependencies = [
"crossbeam-utils", "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]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.21" version = "0.8.21"
@ -1409,6 +1442,12 @@ dependencies = [
"syn 2.0.105", "syn 2.0.105",
] ]
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.19" version = "1.0.19"
@ -1471,6 +1510,9 @@ name = "either"
version = "1.15.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "elliptic-curve" name = "elliptic-curve"
@ -1586,6 +1628,17 @@ dependencies = [
"windows-sys 0.60.2", "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]] [[package]]
name = "event-listener" name = "event-listener"
version = "5.4.0" version = "5.4.0"
@ -1984,6 +2037,17 @@ dependencies = [
"futures-util", "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]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.31" version = "0.3.31"
@ -2241,11 +2305,13 @@ dependencies = [
"serde_with", "serde_with",
"serde_yaml", "serde_yaml",
"similar", "similar",
"sqlx",
"strum 0.27.1", "strum 0.27.1",
"tar", "tar",
"temp-dir", "temp-dir",
"temp-file", "temp-file",
"tempfile", "tempfile",
"thiserror 2.0.14",
"tokio", "tokio",
"tokio-util", "tokio-util",
"url", "url",
@ -2391,6 +2457,15 @@ dependencies = [
"foldhash", "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]] [[package]]
name = "headers" name = "headers"
version = "0.4.1" version = "0.4.1"
@ -2960,7 +3035,7 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
[[package]] [[package]]
name = "infisical" name = "infisical"
version = "0.0.2" 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 = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"reqwest 0.12.20", "reqwest 0.12.20",
@ -3333,6 +3408,17 @@ dependencies = [
"redox_syscall", "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]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.15" version = "0.4.15"
@ -3429,6 +3515,16 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 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]] [[package]]
name = "md5" name = "md5"
version = "0.7.0" version = "0.7.0"
@ -5280,6 +5376,9 @@ name = "smallvec"
version = "1.15.1" version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "snafu" name = "snafu"
@ -5352,6 +5451,194 @@ dependencies = [
"der", "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]] [[package]]
name = "ssh-cipher" name = "ssh-cipher"
version = "0.2.0" version = "0.2.0"
@ -5415,6 +5702,17 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 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]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"
@ -6018,12 +6316,33 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "unicode-bidi"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.18" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 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]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.12.0" version = "1.12.0"
@ -6147,6 +6466,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
@ -6186,6 +6511,12 @@ dependencies = [
"wit-bindgen-rt", "wit-bindgen-rt",
] ]
[[package]]
name = "wasite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.100" version = "0.2.100"
@ -6306,6 +6637,16 @@ dependencies = [
"rustls-pki-types", "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]] [[package]]
name = "winapi" name = "winapi"
version = "0.2.8" version = "0.2.8"

View File

@ -65,3 +65,5 @@ directories = "6.0.0"
thiserror = "2.0.14" thiserror = "2.0.14"
serde = { version = "1.0.209", features = ["derive", "rc"] } serde = { version = "1.0.209", features = ["derive", "rc"] }
serde_json = "1.0.127" serde_json = "1.0.127"
askama = "0.14"
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite" ] }

View File

@ -65,10 +65,12 @@ kube-derive = "1.1.0"
bollard.workspace = true bollard.workspace = true
tar.workspace = true tar.workspace = true
base64.workspace = true base64.workspace = true
thiserror.workspace = true
once_cell = "1.21.3" once_cell = "1.21.3"
harmony_inventory_agent = { path = "../harmony_inventory_agent" } harmony_inventory_agent = { path = "../harmony_inventory_agent" }
harmony_secret_derive = { version = "0.1.0", path = "../harmony_secret_derive" } harmony_secret_derive = { version = "0.1.0", path = "../harmony_secret_derive" }
askama = "0.14.0" askama.workspace = true
sqlx.workspace = true
[dev-dependencies] [dev-dependencies]
pretty_assertions.workspace = true pretty_assertions.workspace = true

View File

@ -24,6 +24,14 @@ pub struct Id {
value: String, value: String,
} }
impl Id {
pub fn empty() -> Self {
Id {
value: String::new(),
}
}
}
impl FromStr for Id { impl FromStr for Id {
type Err = (); type Err = ();
@ -34,6 +42,12 @@ impl FromStr for Id {
} }
} }
impl From<String> for Id {
fn from(value: String) -> Self {
Self { value }
}
}
impl std::fmt::Display for Id { impl std::fmt::Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.value) f.write_str(&self.value)

View File

@ -1,8 +1,8 @@
use std::sync::Arc; use std::{str::FromStr, sync::Arc};
use derive_new::new; use derive_new::new;
use harmony_types::net::MacAddress; use harmony_types::net::MacAddress;
use serde::{Serialize, Serializer, ser::SerializeStruct}; use serde::{Deserialize, Serialize, Serializer, ser::SerializeStruct};
use serde_value::Value; use serde_value::Value;
pub type HostGroup = Vec<PhysicalHost>; pub type HostGroup = Vec<PhysicalHost>;
@ -11,6 +11,7 @@ pub type FirewallGroup = Vec<PhysicalHost>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PhysicalHost { pub struct PhysicalHost {
pub id: Id,
pub category: HostCategory, pub category: HostCategory,
pub network: Vec<NetworkInterface>, pub network: Vec<NetworkInterface>,
pub management: Arc<dyn ManagementInterface>, pub management: Arc<dyn ManagementInterface>,
@ -23,6 +24,7 @@ pub struct PhysicalHost {
impl PhysicalHost { impl PhysicalHost {
pub fn empty(category: HostCategory) -> Self { pub fn empty(category: HostCategory) -> Self {
Self { Self {
id: Id::empty(),
category, category,
network: vec![], network: vec![],
storage: vec![], storage: vec![],
@ -128,6 +130,15 @@ impl Serialize for PhysicalHost {
} }
} }
impl<'de> Deserialize<'de> for PhysicalHost {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
todo!()
}
}
#[derive(new, Serialize)] #[derive(new, Serialize)]
pub struct ManualManagementInterface; pub struct ManualManagementInterface;
@ -187,6 +198,8 @@ pub struct NetworkInterface {
#[cfg(test)] #[cfg(test)]
use harmony_macros::mac_address; use harmony_macros::mac_address;
use crate::data::Id;
#[cfg(test)] #[cfg(test)]
impl NetworkInterface { impl NetworkInterface {
pub fn dummy() -> Self { pub fn dummy() -> Self {
@ -304,6 +317,7 @@ mod tests {
fn test_serialize_physical_host_with_hp_ilo() { fn test_serialize_physical_host_with_hp_ilo() {
// Create a PhysicalHost with HP iLO management // Create a PhysicalHost with HP iLO management
let host = PhysicalHost { let host = PhysicalHost {
id: Id::empty(),
category: HostCategory::Server, category: HostCategory::Server,
network: vec![NetworkInterface::dummy()], network: vec![NetworkInterface::dummy()],
management: Arc::new(MockHPIlo { management: Arc::new(MockHPIlo {
@ -341,6 +355,7 @@ mod tests {
fn test_serialize_physical_host_with_dell_idrac() { fn test_serialize_physical_host_with_dell_idrac() {
// Create a PhysicalHost with Dell iDRAC management // Create a PhysicalHost with Dell iDRAC management
let host = PhysicalHost { let host = PhysicalHost {
id: Id::empty(),
category: HostCategory::Server, category: HostCategory::Server,
network: vec![NetworkInterface::dummy()], network: vec![NetworkInterface::dummy()],
management: Arc::new(MockDellIdrac { management: Arc::new(MockDellIdrac {
@ -375,6 +390,7 @@ mod tests {
fn test_different_management_implementations_produce_valid_json() { fn test_different_management_implementations_produce_valid_json() {
// Create hosts with different management implementations // Create hosts with different management implementations
let host1 = PhysicalHost { let host1 = PhysicalHost {
id: Id::empty(),
category: HostCategory::Server, category: HostCategory::Server,
network: vec![], network: vec![],
management: Arc::new(MockHPIlo { management: Arc::new(MockHPIlo {
@ -390,6 +406,7 @@ mod tests {
}; };
let host2 = PhysicalHost { let host2 = PhysicalHost {
id: Id::empty(),
category: HostCategory::Server, category: HostCategory::Server,
network: vec![], network: vec![],
management: Arc::new(MockDellIdrac { management: Arc::new(MockDellIdrac {

View File

@ -1,3 +1,6 @@
mod repository;
pub use repository::*;
#[derive(Debug, new, Clone)] #[derive(Debug, new, Clone)]
pub struct InventoryFilter { pub struct InventoryFilter {
target: Vec<Filter>, target: Vec<Filter>,

View File

@ -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<Option<PhysicalHost>, RepoError>;
}

View File

@ -0,0 +1 @@
mod sqlite;

View File

@ -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<Sqlite>,
}
impl SqliteInventoryRepository {
pub async fn new(database_url: &str) -> Result<Self, RepoError> {
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<Option<PhysicalHost>, RepoError> {
let row = sqlx::query_as!(
DbHost,
r#"SELECT id, version_id, data as "data: Json<PhysicalHost>" 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<PhysicalHost>,
id: Id,
version_id: Id,
}

View File

@ -1,4 +1,6 @@
pub mod executors; pub mod executors;
pub mod hp_ilo; pub mod hp_ilo;
pub mod intel_amt; pub mod intel_amt;
pub mod inventory;
pub mod opnsense; pub mod opnsense;
mod sqlx;

36
harmony/src/infra/sqlx.rs Normal file
View File

@ -0,0 +1,36 @@
use crate::inventory::RepoError;
impl From<sqlx::Error> 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<serde_json::Error> for RepoError {
fn from(value: serde_json::Error) -> Self {
RepoError::Serialization(value.to_string())
}
}