feat: Harmony inventory agent crate that exposes an endpoint listing the host hardware. Has to be reviewed, generated 99% by GLM-4.5 #115
							
								
								
									
										412
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										412
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -2,6 +2,189 @@ | ||||
| # It is not intended for manual editing. | ||||
| version = 4 | ||||
| 
 | ||||
| [[package]] | ||||
| name = "actix-codec" | ||||
| version = "0.5.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" | ||||
| dependencies = [ | ||||
|  "bitflags 2.9.1", | ||||
|  "bytes", | ||||
|  "futures-core", | ||||
|  "futures-sink", | ||||
|  "memchr", | ||||
|  "pin-project-lite", | ||||
|  "tokio", | ||||
|  "tokio-util", | ||||
|  "tracing", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "actix-http" | ||||
| version = "3.11.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "44dfe5c9e0004c623edc65391dfd51daa201e7e30ebd9c9bedf873048ec32bc2" | ||||
| dependencies = [ | ||||
|  "actix-codec", | ||||
|  "actix-rt", | ||||
|  "actix-service", | ||||
|  "actix-utils", | ||||
|  "base64 0.22.1", | ||||
|  "bitflags 2.9.1", | ||||
|  "brotli", | ||||
|  "bytes", | ||||
|  "bytestring", | ||||
|  "derive_more", | ||||
|  "encoding_rs", | ||||
|  "flate2", | ||||
|  "foldhash", | ||||
|  "futures-core", | ||||
|  "h2 0.3.26", | ||||
|  "http 0.2.12", | ||||
|  "httparse", | ||||
|  "httpdate", | ||||
|  "itoa", | ||||
|  "language-tags", | ||||
|  "local-channel", | ||||
|  "mime", | ||||
|  "percent-encoding", | ||||
|  "pin-project-lite", | ||||
|  "rand 0.9.1", | ||||
|  "sha1", | ||||
|  "smallvec", | ||||
|  "tokio", | ||||
|  "tokio-util", | ||||
|  "tracing", | ||||
|  "zstd", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "actix-macros" | ||||
| version = "0.2.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" | ||||
| dependencies = [ | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "actix-router" | ||||
| version = "0.5.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" | ||||
| dependencies = [ | ||||
|  "bytestring", | ||||
|  "cfg-if", | ||||
|  "http 0.2.12", | ||||
|  "regex", | ||||
|  "regex-lite", | ||||
|  "serde", | ||||
|  "tracing", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "actix-rt" | ||||
| version = "2.10.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" | ||||
| dependencies = [ | ||||
|  "futures-core", | ||||
|  "tokio", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "actix-server" | ||||
| version = "2.6.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" | ||||
| dependencies = [ | ||||
|  "actix-rt", | ||||
|  "actix-service", | ||||
|  "actix-utils", | ||||
|  "futures-core", | ||||
|  "futures-util", | ||||
|  "mio 1.0.4", | ||||
|  "socket2", | ||||
|  "tokio", | ||||
|  "tracing", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "actix-service" | ||||
| version = "2.0.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" | ||||
| dependencies = [ | ||||
|  "futures-core", | ||||
|  "pin-project-lite", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "actix-utils" | ||||
| version = "3.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" | ||||
| dependencies = [ | ||||
|  "local-waker", | ||||
|  "pin-project-lite", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "actix-web" | ||||
| version = "4.11.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" | ||||
| dependencies = [ | ||||
|  "actix-codec", | ||||
|  "actix-http", | ||||
|  "actix-macros", | ||||
|  "actix-router", | ||||
|  "actix-rt", | ||||
|  "actix-server", | ||||
|  "actix-service", | ||||
|  "actix-utils", | ||||
|  "actix-web-codegen", | ||||
|  "bytes", | ||||
|  "bytestring", | ||||
|  "cfg-if", | ||||
|  "cookie", | ||||
|  "derive_more", | ||||
|  "encoding_rs", | ||||
|  "foldhash", | ||||
|  "futures-core", | ||||
|  "futures-util", | ||||
|  "impl-more", | ||||
|  "itoa", | ||||
|  "language-tags", | ||||
|  "log", | ||||
|  "mime", | ||||
|  "once_cell", | ||||
|  "pin-project-lite", | ||||
|  "regex", | ||||
|  "regex-lite", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "serde_urlencoded", | ||||
|  "smallvec", | ||||
|  "socket2", | ||||
|  "time", | ||||
|  "tracing", | ||||
|  "url", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "actix-web-codegen" | ||||
| version = "4.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" | ||||
| dependencies = [ | ||||
|  "actix-router", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "addr2line" | ||||
| version = "0.24.2" | ||||
| @ -75,6 +258,21 @@ dependencies = [ | ||||
|  "memchr", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "alloc-no-stdlib" | ||||
| version = "2.0.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "alloc-stdlib" | ||||
| version = "0.2.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" | ||||
| dependencies = [ | ||||
|  "alloc-no-stdlib", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "allocator-api2" | ||||
| version = "0.2.21" | ||||
| @ -398,6 +596,27 @@ dependencies = [ | ||||
|  "serde_with", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "brotli" | ||||
| version = "8.0.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" | ||||
| dependencies = [ | ||||
|  "alloc-no-stdlib", | ||||
|  "alloc-stdlib", | ||||
|  "brotli-decompressor", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "brotli-decompressor" | ||||
| version = "5.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" | ||||
| dependencies = [ | ||||
|  "alloc-no-stdlib", | ||||
|  "alloc-stdlib", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "bstr" | ||||
| version = "1.12.0" | ||||
| @ -427,6 +646,15 @@ version = "1.10.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "bytestring" | ||||
| version = "1.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" | ||||
| dependencies = [ | ||||
|  "bytes", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "camino" | ||||
| version = "1.1.10" | ||||
| @ -506,6 +734,8 @@ version = "1.2.27" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" | ||||
| dependencies = [ | ||||
|  "jobserver", | ||||
|  "libc", | ||||
|  "shlex", | ||||
| ] | ||||
| 
 | ||||
| @ -710,6 +940,17 @@ dependencies = [ | ||||
|  "unicode-segmentation", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "cookie" | ||||
| version = "0.16.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" | ||||
| dependencies = [ | ||||
|  "percent-encoding", | ||||
|  "time", | ||||
|  "version_check", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "core-foundation" | ||||
| version = "0.9.4" | ||||
| @ -763,6 +1004,25 @@ dependencies = [ | ||||
|  "crossbeam-utils", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "crossbeam-deque" | ||||
| version = "0.8.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" | ||||
| dependencies = [ | ||||
|  "crossbeam-epoch", | ||||
|  "crossbeam-utils", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "crossbeam-epoch" | ||||
| version = "0.9.18" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" | ||||
| dependencies = [ | ||||
|  "crossbeam-utils", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "crossbeam-utils" | ||||
| version = "0.8.21" | ||||
| @ -973,6 +1233,7 @@ dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
|  "unicode-xid", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -1907,6 +2168,19 @@ dependencies = [ | ||||
|  "tokio", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "harmony_inventory_agent" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "actix-web", | ||||
|  "env_logger", | ||||
|  "log", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "sysinfo", | ||||
|  "uuid", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "harmony_macros" | ||||
| version = "0.1.0" | ||||
| @ -2346,7 +2620,7 @@ dependencies = [ | ||||
|  "js-sys", | ||||
|  "log", | ||||
|  "wasm-bindgen", | ||||
|  "windows-core", | ||||
|  "windows-core 0.61.2", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -2471,6 +2745,12 @@ dependencies = [ | ||||
|  "icu_properties", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "impl-more" | ||||
| version = "0.1.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "indenter" | ||||
| version = "0.3.3" | ||||
| @ -2655,6 +2935,16 @@ dependencies = [ | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "jobserver" | ||||
| version = "0.1.33" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" | ||||
| dependencies = [ | ||||
|  "getrandom 0.3.3", | ||||
|  "libc", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "js-sys" | ||||
| version = "0.3.77" | ||||
| @ -2857,6 +3147,12 @@ dependencies = [ | ||||
|  "tracing", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "language-tags" | ||||
| version = "0.3.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "lazy_static" | ||||
| version = "1.5.0" | ||||
| @ -2920,6 +3216,23 @@ version = "0.8.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "local-channel" | ||||
| version = "0.1.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" | ||||
| dependencies = [ | ||||
|  "futures-core", | ||||
|  "futures-sink", | ||||
|  "local-waker", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "local-waker" | ||||
| version = "0.1.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "lock_api" | ||||
| version = "0.4.13" | ||||
| @ -3055,6 +3368,15 @@ dependencies = [ | ||||
|  "serde", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "ntapi" | ||||
| version = "0.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" | ||||
| dependencies = [ | ||||
|  "winapi", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "num-bigint" | ||||
| version = "0.4.6" | ||||
| @ -3839,6 +4161,26 @@ dependencies = [ | ||||
|  "unicode-width 0.2.0", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rayon" | ||||
| version = "1.11.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" | ||||
| dependencies = [ | ||||
|  "either", | ||||
|  "rayon-core", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rayon-core" | ||||
| version = "1.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" | ||||
| dependencies = [ | ||||
|  "crossbeam-deque", | ||||
|  "crossbeam-utils", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "redox_syscall" | ||||
| version = "0.5.13" | ||||
| @ -3902,6 +4244,12 @@ dependencies = [ | ||||
|  "regex-syntax", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "regex-lite" | ||||
| version = "0.1.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "regex-syntax" | ||||
| version = "0.8.5" | ||||
| @ -4956,6 +5304,21 @@ dependencies = [ | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "sysinfo" | ||||
| version = "0.30.13" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "core-foundation-sys", | ||||
|  "libc", | ||||
|  "ntapi", | ||||
|  "once_cell", | ||||
|  "rayon", | ||||
|  "windows", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "system-configuration" | ||||
| version = "0.5.1" | ||||
| @ -5759,6 +6122,25 @@ version = "0.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows" | ||||
| version = "0.52.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" | ||||
| dependencies = [ | ||||
|  "windows-core 0.52.0", | ||||
|  "windows-targets 0.52.6", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows-core" | ||||
| version = "0.52.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" | ||||
| dependencies = [ | ||||
|  "windows-targets 0.52.6", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows-core" | ||||
| version = "0.61.2" | ||||
| @ -6242,3 +6624,31 @@ dependencies = [ | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "zstd" | ||||
| version = "0.13.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" | ||||
| dependencies = [ | ||||
|  "zstd-safe", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "zstd-safe" | ||||
| version = "7.2.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" | ||||
| dependencies = [ | ||||
|  "zstd-sys", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "zstd-sys" | ||||
| version = "2.0.15+zstd.1.5.7" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" | ||||
| dependencies = [ | ||||
|  "cc", | ||||
|  "pkg-config", | ||||
| ] | ||||
|  | ||||
| @ -12,6 +12,7 @@ members = [ | ||||
|   "harmony_cli", | ||||
|   "k3d", | ||||
|   "harmony_composer", | ||||
|   "harmony_inventory_agent", | ||||
|   "harmony_secret_derive", | ||||
|   "harmony_secret", | ||||
| ] | ||||
| @ -62,3 +63,5 @@ tar = "0.4.44" | ||||
| lazy_static = "1.5.0" | ||||
| directories = "6.0.0" | ||||
| thiserror = "2.0.14" | ||||
| serde = { version = "1.0.209", features = ["derive", "rc"] } | ||||
| serde_json = "1.0.127" | ||||
|  | ||||
| @ -16,8 +16,8 @@ reqwest = { version = "0.11", features = ["blocking", "json"] } | ||||
| russh = "0.45.0" | ||||
| rust-ipmi = "0.1.1" | ||||
| semver = "1.0.23" | ||||
| serde = { version = "1.0.209", features = ["derive", "rc"] } | ||||
| serde_json = "1.0.127" | ||||
| serde.workspace = true | ||||
| serde_json.workspace = true | ||||
| tokio.workspace = true | ||||
| derive-new.workspace = true | ||||
| log.workspace = true | ||||
|  | ||||
							
								
								
									
										13
									
								
								harmony_inventory_agent/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								harmony_inventory_agent/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| [package] | ||||
| name = "harmony_inventory_agent" | ||||
| version = "0.1.0" | ||||
| edition = "2024" | ||||
| 
 | ||||
| [dependencies] | ||||
| actix-web = "4.4" | ||||
| sysinfo = "0.30" | ||||
| serde.workspace = true | ||||
| serde_json.workspace = true | ||||
| log.workspace = true | ||||
| env_logger.workspace = true | ||||
| uuid.workspace = true | ||||
							
								
								
									
										571
									
								
								harmony_inventory_agent/src/hwinfo.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										571
									
								
								harmony_inventory_agent/src/hwinfo.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,571 @@ | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use serde_json::Value; | ||||
| use std::fs; | ||||
| use std::path::Path; | ||||
| use std::process::Command; | ||||
| use sysinfo::System; | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct PhysicalHost { | ||||
|     pub storage_drives: Vec<StorageDrive>, | ||||
|     pub storage_controller: StorageController, | ||||
|     pub memory_modules: Vec<MemoryModule>, | ||||
|     pub cpus: Vec<CPU>, | ||||
|     pub chipset: Chipset, | ||||
|     pub network_interfaces: Vec<NetworkInterface>, | ||||
|     pub management_interface: Option<ManagementInterface>, | ||||
|     pub host_uuid: String, | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct StorageDrive { | ||||
|     pub name: String, | ||||
|     pub model: String, | ||||
|     pub serial: String, | ||||
|     pub size_bytes: u64, | ||||
|     pub logical_block_size: u32, | ||||
|     pub physical_block_size: u32, | ||||
|     pub rotational: bool, | ||||
|     pub wwn: Option<String>, | ||||
|     pub interface_type: String, | ||||
|     pub smart_status: Option<String>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct StorageController { | ||||
|     pub name: String, | ||||
|     pub driver: String, | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct MemoryModule { | ||||
|     pub size_bytes: u64, | ||||
|     pub speed_mhz: Option<u32>, | ||||
|     pub manufacturer: Option<String>, | ||||
|     pub part_number: Option<String>, | ||||
|     pub serial_number: Option<String>, | ||||
|     pub rank: Option<u8>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct CPU { | ||||
|     pub model: String, | ||||
|     pub vendor: String, | ||||
|     pub cores: u32, | ||||
|     pub threads: u32, | ||||
|     pub frequency_mhz: u64, | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct Chipset { | ||||
|     pub name: String, | ||||
|     pub vendor: String, | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct NetworkInterface { | ||||
|     pub name: String, | ||||
|     pub mac_address: String, | ||||
|     pub speed_mbps: Option<u32>, | ||||
|     pub is_up: bool, | ||||
|     pub mtu: u32, | ||||
|     pub ipv4_addresses: Vec<String>, | ||||
|     pub ipv6_addresses: Vec<String>, | ||||
|     pub driver: String, | ||||
|     pub firmware_version: Option<String>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub struct ManagementInterface { | ||||
|     pub kind: String, | ||||
|     pub address: Option<String>, | ||||
|     pub firmware: Option<String>, | ||||
| } | ||||
| 
 | ||||
| impl PhysicalHost { | ||||
|     pub fn gather() -> Self { | ||||
|         let mut sys = System::new_all(); | ||||
|         sys.refresh_all(); | ||||
| 
 | ||||
|         Self { | ||||
|             storage_drives: Self::gather_storage_drives(), | ||||
|             storage_controller: Self::gather_storage_controller(), | ||||
|             memory_modules: Self::gather_memory_modules(), | ||||
|             cpus: Self::gather_cpus(&sys), | ||||
|             chipset: Self::gather_chipset(), | ||||
|             network_interfaces: Self::gather_network_interfaces(), | ||||
|             management_interface: Self::gather_management_interface(), | ||||
|             host_uuid: Self::get_host_uuid(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn gather_storage_drives() -> Vec<StorageDrive> { | ||||
|         let mut drives = Vec::new(); | ||||
| 
 | ||||
|         // Use lsblk with JSON output for robust parsing
 | ||||
|         if let Ok(output) = Command::new("lsblk") | ||||
|             .args([ | ||||
|                 "-d", | ||||
|                 "-o", | ||||
|                 "NAME,MODEL,SERIAL,SIZE,ROTA,WWN", | ||||
|                 "-n", | ||||
|                 "-e", | ||||
|                 "7", | ||||
|                 "--json", | ||||
|             ]) | ||||
|             .output() | ||||
|             && output.status.success() | ||||
|             && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout) | ||||
|             && let Some(blockdevices) = json.get("blockdevices").and_then(|v| v.as_array()) | ||||
|         { | ||||
|             for device in blockdevices { | ||||
|                 let name = device | ||||
|                     .get("name") | ||||
|                     .and_then(|v| v.as_str()) | ||||
|                     .unwrap_or("") | ||||
|                     .to_string(); | ||||
|                 if name.is_empty() { | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 let model = device | ||||
|                     .get("model") | ||||
|                     .and_then(|v| v.as_str()) | ||||
|                     .map(|s| s.trim().to_string()) | ||||
|                     .unwrap_or_default(); | ||||
| 
 | ||||
|                 let serial = device | ||||
|                     .get("serial") | ||||
|                     .and_then(|v| v.as_str()) | ||||
|                     .map(|s| s.trim().to_string()) | ||||
|                     .unwrap_or_default(); | ||||
| 
 | ||||
|                 let size_str = device.get("size").and_then(|v| v.as_str()).unwrap_or("0"); | ||||
|                 let size_bytes = Self::parse_size(size_str).unwrap_or(0); | ||||
| 
 | ||||
|                 let rotational = device | ||||
|                     .get("rota") | ||||
|                     .and_then(|v| v.as_bool()) | ||||
|                     .unwrap_or(false); | ||||
| 
 | ||||
|                 let wwn = device | ||||
|                     .get("wwn") | ||||
|                     .and_then(|v| v.as_str()) | ||||
|                     .map(|s| s.trim().to_string()) | ||||
|                     .filter(|s| !s.is_empty() && s != "null"); | ||||
| 
 | ||||
|                 let device_path = Path::new("/sys/block").join(&name); | ||||
| 
 | ||||
|                 let mut drive = StorageDrive { | ||||
|                     name: name.clone(), | ||||
|                     model, | ||||
|                     serial, | ||||
|                     size_bytes, | ||||
|                     logical_block_size: Self::read_sysfs_u32( | ||||
|                         &device_path.join("queue/logical_block_size"), | ||||
|                     ) | ||||
|                     .unwrap_or(512), | ||||
|                     physical_block_size: Self::read_sysfs_u32( | ||||
|                         &device_path.join("queue/physical_block_size"), | ||||
|                     ) | ||||
|                     .unwrap_or(512), | ||||
|                     rotational, | ||||
|                     wwn, | ||||
|                     interface_type: Self::get_interface_type(&name, &device_path), | ||||
|                     smart_status: Self::get_smart_status(&name), | ||||
|                 }; | ||||
| 
 | ||||
|                 // Enhance with additional sysfs info if available
 | ||||
|                 if device_path.exists() { | ||||
|                     if drive.model.is_empty() { | ||||
|                         drive.model = Self::read_sysfs_string(&device_path.join("device/model")); | ||||
|                     } | ||||
|                     if drive.serial.is_empty() { | ||||
|                         drive.serial = Self::read_sysfs_string(&device_path.join("device/serial")); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 drives.push(drive); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         drives | ||||
|     } | ||||
| 
 | ||||
|     fn gather_storage_controller() -> StorageController { | ||||
|         let mut controller = StorageController { | ||||
|             name: "Unknown".to_string(), | ||||
|             driver: "Unknown".to_string(), | ||||
|         }; | ||||
| 
 | ||||
|         // Use lspci with JSON output if available
 | ||||
|         if let Ok(output) = Command::new("lspci") | ||||
|             .args(["-nn", "-d", "::0100", "-J"]) // Storage controllers class with JSON
 | ||||
|             .output() | ||||
|             && output.status.success() | ||||
|             && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout) | ||||
|             && let Some(devices) = json.as_array() | ||||
|         { | ||||
|             for device in devices { | ||||
|                 if let Some(device_info) = device.as_object() | ||||
|                     && let Some(name) = device_info | ||||
|                         .get("device") | ||||
|                         .and_then(|v| v.as_object()) | ||||
|                         .and_then(|v| v.get("name")) | ||||
|                         .and_then(|v| v.as_str()) | ||||
|                 { | ||||
|                     controller.name = name.to_string(); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Fallback to text output if JSON fails
 | ||||
|         if controller.name == "Unknown" | ||||
|             && let Ok(output) = Command::new("lspci") | ||||
|                 .args(["-nn", "-d", "::0100"]) // Storage controllers class
 | ||||
|                 .output() | ||||
|             && output.status.success() | ||||
|         { | ||||
|             let output_str = String::from_utf8_lossy(&output.stdout); | ||||
|             if let Some(line) = output_str.lines().next() { | ||||
|                 let parts: Vec<&str> = line.split(':').collect(); | ||||
|                 if parts.len() > 2 { | ||||
|                     controller.name = parts[2].trim().to_string(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Try to get driver info from lsmod
 | ||||
|         if let Ok(output) = Command::new("lsmod").output() | ||||
|             && output.status.success() | ||||
|         { | ||||
|             let output_str = String::from_utf8_lossy(&output.stdout); | ||||
|             for line in output_str.lines() { | ||||
|                 if line.contains("ahci") | ||||
|                     || line.contains("nvme") | ||||
|                     || line.contains("megaraid") | ||||
|                     || line.contains("mpt3sas") | ||||
|                 { | ||||
|                     let parts: Vec<&str> = line.split_whitespace().collect(); | ||||
|                     if !parts.is_empty() { | ||||
|                         controller.driver = parts[0].to_string(); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         controller | ||||
|     } | ||||
| 
 | ||||
|     fn gather_memory_modules() -> Vec<MemoryModule> { | ||||
|         let mut modules = Vec::new(); | ||||
| 
 | ||||
|         if let Ok(output) = Command::new("dmidecode").arg("--type").arg("17").output() | ||||
|             && output.status.success() | ||||
|         { | ||||
|             let output_str = String::from_utf8_lossy(&output.stdout); | ||||
|             let sections: Vec<&str> = output_str.split("Memory Device").collect(); | ||||
| 
 | ||||
|             for section in sections.into_iter().skip(1) { | ||||
|                 let mut module = MemoryModule { | ||||
|                     size_bytes: 0, | ||||
|                     speed_mhz: None, | ||||
|                     manufacturer: None, | ||||
|                     part_number: None, | ||||
|                     serial_number: None, | ||||
|                     rank: None, | ||||
|                 }; | ||||
| 
 | ||||
|                 for line in section.lines() { | ||||
|                     let line = line.trim(); | ||||
|                     if let Some(size_str) = line.strip_prefix("Size: ") { | ||||
|                         if size_str != "No Module Installed" | ||||
|                             && let Some((num, unit)) = size_str.split_once(' ') | ||||
|                             && let Ok(num) = num.parse::<u64>() | ||||
|                         { | ||||
|                             module.size_bytes = match unit { | ||||
|                                 "MB" => num * 1024 * 1024, | ||||
|                                 "GB" => num * 1024 * 1024 * 1024, | ||||
|                                 "KB" => num * 1024, | ||||
|                                 _ => 0, | ||||
|                             }; | ||||
|                         } | ||||
|                     } else if let Some(speed_str) = line.strip_prefix("Speed: ") { | ||||
|                         if let Some((num, _unit)) = speed_str.split_once(' ') { | ||||
|                             module.speed_mhz = num.parse().ok(); | ||||
|                         } | ||||
|                     } else if let Some(man) = line.strip_prefix("Manufacturer: ") { | ||||
|                         module.manufacturer = Some(man.to_string()); | ||||
|                     } else if let Some(part) = line.strip_prefix("Part Number: ") { | ||||
|                         module.part_number = Some(part.to_string()); | ||||
|                     } else if let Some(serial) = line.strip_prefix("Serial Number: ") { | ||||
|                         module.serial_number = Some(serial.to_string()); | ||||
|                     } else if let Some(rank) = line.strip_prefix("Rank: ") { | ||||
|                         module.rank = rank.parse().ok(); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if module.size_bytes > 0 { | ||||
|                     modules.push(module); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         modules | ||||
|     } | ||||
| 
 | ||||
|     fn gather_cpus(sys: &System) -> Vec<CPU> { | ||||
|         let mut cpus = Vec::new(); | ||||
|         let global_cpu = sys.global_cpu_info(); | ||||
| 
 | ||||
|         cpus.push(CPU { | ||||
|             model: global_cpu.brand().to_string(), | ||||
|             vendor: global_cpu.vendor_id().to_string(), | ||||
|             cores: sys.physical_core_count().unwrap_or(1) as u32, | ||||
|             threads: sys.cpus().len() as u32, | ||||
|             frequency_mhz: global_cpu.frequency(), | ||||
|         }); | ||||
| 
 | ||||
|         cpus | ||||
|     } | ||||
| 
 | ||||
|     fn gather_chipset() -> Chipset { | ||||
|         Chipset { | ||||
|             name: Self::read_dmi("board-product-name").unwrap_or_else(|| "Unknown".to_string()), | ||||
|             vendor: Self::read_dmi("board-manufacturer").unwrap_or_else(|| "Unknown".to_string()), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn gather_network_interfaces() -> Vec<NetworkInterface> { | ||||
|         let mut interfaces = Vec::new(); | ||||
|         let sys_net_path = Path::new("/sys/class/net"); | ||||
| 
 | ||||
|         if let Ok(entries) = fs::read_dir(sys_net_path) { | ||||
|             for entry in entries.flatten() { | ||||
|                 let iface_name = entry.file_name().into_string().unwrap_or_default(); | ||||
|                 let iface_path = entry.path(); | ||||
| 
 | ||||
|                 // Skip virtual interfaces
 | ||||
|                 if iface_name.starts_with("lo") | ||||
|                     || iface_name.starts_with("docker") | ||||
|                     || iface_name.starts_with("virbr") | ||||
|                     || iface_name.starts_with("veth") | ||||
|                     || iface_name.starts_with("br-") | ||||
|                     || iface_name.starts_with("tun") | ||||
|                     || iface_name.starts_with("wg") | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 // Check if it's a physical interface by looking for device directory
 | ||||
|                 if !iface_path.join("device").exists() { | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 let mac_address = Self::read_sysfs_string(&iface_path.join("address")); | ||||
|                 let speed_mbps = Self::read_sysfs_u32(&iface_path.join("speed")); | ||||
|                 let operstate = Self::read_sysfs_string(&iface_path.join("operstate")); | ||||
|                 let mtu = Self::read_sysfs_u32(&iface_path.join("mtu")).unwrap_or(1500); | ||||
|                 let driver = Self::read_sysfs_string(&iface_path.join("device/driver/module")); | ||||
|                 let firmware_version = | ||||
|                     Self::read_sysfs_opt_string(&iface_path.join("device/firmware_version")); | ||||
| 
 | ||||
|                 // Get IP addresses using ip command with JSON output
 | ||||
|                 let (ipv4_addresses, ipv6_addresses) = Self::get_interface_ips_json(&iface_name); | ||||
| 
 | ||||
|                 interfaces.push(NetworkInterface { | ||||
|                     name: iface_name, | ||||
|                     mac_address, | ||||
|                     speed_mbps, | ||||
|                     is_up: operstate == "up", | ||||
|                     mtu, | ||||
|                     ipv4_addresses, | ||||
|                     ipv6_addresses, | ||||
|                     driver, | ||||
|                     firmware_version, | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         interfaces | ||||
|     } | ||||
| 
 | ||||
|     fn gather_management_interface() -> Option<ManagementInterface> { | ||||
|         // Try to detect common management interfaces
 | ||||
|         if Path::new("/dev/ipmi0").exists() { | ||||
|             Some(ManagementInterface { | ||||
|                 kind: "IPMI".to_string(), | ||||
|                 address: None, | ||||
|                 firmware: Self::read_dmi("bios-version"), | ||||
|             }) | ||||
|         } else if Path::new("/sys/class/misc/mei").exists() { | ||||
|             Some(ManagementInterface { | ||||
|                 kind: "Intel ME".to_string(), | ||||
|                 address: None, | ||||
|                 firmware: None, | ||||
|             }) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn get_host_uuid() -> String { | ||||
|         Self::read_dmi("system-uuid").unwrap() | ||||
|     } | ||||
| 
 | ||||
|     // Helper methods
 | ||||
|     fn read_sysfs_string(path: &Path) -> String { | ||||
|         fs::read_to_string(path) | ||||
|             .unwrap_or_default() | ||||
|             .trim() | ||||
|             .to_string() | ||||
|     } | ||||
| 
 | ||||
|     fn read_sysfs_opt_string(path: &Path) -> Option<String> { | ||||
|         fs::read_to_string(path) | ||||
|             .ok() | ||||
|             .map(|s| s.trim().to_string()) | ||||
|             .filter(|s| !s.is_empty()) | ||||
|     } | ||||
| 
 | ||||
|     fn read_sysfs_u32(path: &Path) -> Option<u32> { | ||||
|         fs::read_to_string(path) | ||||
|             .ok() | ||||
|             .and_then(|s| s.trim().parse().ok()) | ||||
|     } | ||||
| 
 | ||||
|     fn read_dmi(field: &str) -> Option<String> { | ||||
|         Command::new("dmidecode") | ||||
|             .arg("-s") | ||||
|             .arg(field) | ||||
|             .output() | ||||
|             .ok() | ||||
|             .filter(|output| output.status.success()) | ||||
|             .and_then(|output| String::from_utf8(output.stdout).ok()) | ||||
|             .map(|s| s.trim().to_string()) | ||||
|             .filter(|s| !s.is_empty()) | ||||
|     } | ||||
| 
 | ||||
|     fn get_interface_type(device_name: &str, device_path: &Path) -> String { | ||||
|         if device_name.starts_with("nvme") { | ||||
|             "NVMe".to_string() | ||||
|         } else if device_name.starts_with("sd") { | ||||
|             "SATA".to_string() | ||||
|         } else if device_name.starts_with("hd") { | ||||
|             "IDE".to_string() | ||||
|         } else if device_name.starts_with("vd") { | ||||
|             "VirtIO".to_string() | ||||
|         } else { | ||||
|             // Try to determine from device path
 | ||||
|             Self::read_sysfs_string(&device_path.join("device/subsystem")) | ||||
|                 .split('/') | ||||
|                 .next_back() | ||||
|                 .unwrap_or("Unknown") | ||||
|                 .to_string() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn get_smart_status(device_name: &str) -> Option<String> { | ||||
|         Command::new("smartctl") | ||||
|             .arg("-H") | ||||
|             .arg(format!("/dev/{}", device_name)) | ||||
|             .output() | ||||
|             .ok() | ||||
|             .filter(|output| output.status.success()) | ||||
|             .and_then(|output| String::from_utf8(output.stdout).ok()) | ||||
|             .and_then(|s| { | ||||
|                 s.lines() | ||||
|                     .find(|line| line.contains("SMART overall-health self-assessment")) | ||||
|                     .and_then(|line| line.split(':').nth(1)) | ||||
|                     .map(|s| s.trim().to_string()) | ||||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     fn parse_size(size_str: &str) -> Option<u64> { | ||||
|         if size_str.ends_with('T') { | ||||
|             size_str[..size_str.len() - 1] | ||||
|                 .parse::<u64>() | ||||
|                 .ok() | ||||
|                 .map(|t| t * 1024 * 1024 * 1024 * 1024) | ||||
|         } else if size_str.ends_with('G') { | ||||
|             size_str[..size_str.len() - 1] | ||||
|                 .parse::<u64>() | ||||
|                 .ok() | ||||
|                 .map(|g| g * 1024 * 1024 * 1024) | ||||
|         } else if size_str.ends_with('M') { | ||||
|             size_str[..size_str.len() - 1] | ||||
|                 .parse::<u64>() | ||||
|                 .ok() | ||||
|                 .map(|m| m * 1024 * 1024) | ||||
|         } else if size_str.ends_with('K') { | ||||
|             size_str[..size_str.len() - 1] | ||||
|                 .parse::<u64>() | ||||
|                 .ok() | ||||
|                 .map(|k| k * 1024) | ||||
|         } else if size_str.ends_with('B') { | ||||
|             size_str[..size_str.len() - 1].parse::<u64>().ok() | ||||
|         } else { | ||||
|             size_str.parse::<u64>().ok() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn get_interface_ips_json(iface_name: &str) -> (Vec<String>, Vec<String>) { | ||||
|         let mut ipv4 = Vec::new(); | ||||
|         let mut ipv6 = Vec::new(); | ||||
| 
 | ||||
|         // Get IPv4 addresses using JSON output
 | ||||
|         if let Ok(output) = Command::new("ip") | ||||
|             .args(["-j", "-4", "addr", "show", iface_name]) | ||||
|             .output() | ||||
|             && output.status.success() | ||||
|             && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout) | ||||
|             && let Some(addrs) = json.as_array() | ||||
|         { | ||||
|             for addr_info in addrs { | ||||
|                 if let Some(addr_info_obj) = addr_info.as_object() | ||||
|                     && let Some(addr_info) = | ||||
|                         addr_info_obj.get("addr_info").and_then(|v| v.as_array()) | ||||
|                 { | ||||
|                     for addr in addr_info { | ||||
|                         if let Some(addr_obj) = addr.as_object() | ||||
|                             && let Some(ip) = addr_obj.get("local").and_then(|v| v.as_str()) | ||||
|                         { | ||||
|                             ipv4.push(ip.to_string()); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Get IPv6 addresses using JSON output
 | ||||
|         if let Ok(output) = Command::new("ip") | ||||
|             .args(["-j", "-6", "addr", "show", iface_name]) | ||||
|             .output() | ||||
|             && output.status.success() | ||||
|             && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout) | ||||
|             && let Some(addrs) = json.as_array() | ||||
|         { | ||||
|             for addr_info in addrs { | ||||
|                 if let Some(addr_info_obj) = addr_info.as_object() | ||||
|                     && let Some(addr_info) = | ||||
|                         addr_info_obj.get("addr_info").and_then(|v| v.as_array()) | ||||
|                 { | ||||
|                     for addr in addr_info { | ||||
|                         if let Some(addr_obj) = addr.as_object() | ||||
|                             && let Some(ip) = addr_obj.get("local").and_then(|v| v.as_str()) | ||||
|                         { | ||||
|                             // Skip link-local addresses
 | ||||
|                             if !ip.starts_with("fe80::") { | ||||
|                                 ipv6.push(ip.to_string()); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         (ipv4, ipv6) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										29
									
								
								harmony_inventory_agent/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								harmony_inventory_agent/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| // src/main.rs
 | ||||
| use actix_web::{App, HttpServer, Responder, get}; | ||||
| use hwinfo::PhysicalHost; | ||||
| use std::env; | ||||
| 
 | ||||
| mod hwinfo; | ||||
| 
 | ||||
| #[get("/inventory")] | ||||
| async fn inventory() -> impl Responder { | ||||
|     log::info!("Received inventory request"); | ||||
|     let host = PhysicalHost::gather(); | ||||
|     log::info!("Inventory data gathered successfully"); | ||||
|     actix_web::HttpResponse::Ok().json(host) | ||||
| } | ||||
| 
 | ||||
| #[actix_web::main] | ||||
| async fn main() -> std::io::Result<()> { | ||||
|     env_logger::init(); | ||||
| 
 | ||||
|     let port = env::var("HARMONY_INVENTORY_AGENT_PORT").unwrap_or_else(|_| "8080".to_string()); | ||||
|     let bind_addr = format!("0.0.0.0:{}", port); | ||||
| 
 | ||||
|     log::info!("Starting inventory agent on {}", bind_addr); | ||||
| 
 | ||||
|     HttpServer::new(|| App::new().service(inventory)) | ||||
|         .bind(&bind_addr)? | ||||
|         .run() | ||||
|         .await | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user