Compare commits
7 Commits
feat/ceph-
...
bc6f7336d2
| Author | SHA1 | Date | |
|---|---|---|---|
| bc6f7336d2 | |||
| 01da8631da | |||
| 19cb7f73bc | |||
| 84f38974b1 | |||
| 7d027bcfc4 | |||
|
|
610ce84280 | ||
|
|
8bb4a9d3f6 |
414
Cargo.lock
generated
414
Cargo.lock
generated
@@ -2,6 +2,189 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
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]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
version = "0.24.2"
|
version = "0.24.2"
|
||||||
@@ -75,6 +258,21 @@ dependencies = [
|
|||||||
"memchr",
|
"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]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "allocator-api2"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -398,6 +596,27 @@ dependencies = [
|
|||||||
"serde_with",
|
"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]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
@@ -427,6 +646,15 @@ version = "1.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytestring"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "camino"
|
name = "camino"
|
||||||
version = "1.1.10"
|
version = "1.1.10"
|
||||||
@@ -506,6 +734,8 @@ version = "1.2.27"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
|
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"jobserver",
|
||||||
|
"libc",
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -704,6 +934,17 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"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]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@@ -757,6 +998,25 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"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]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.21"
|
version = "0.8.21"
|
||||||
@@ -967,6 +1227,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1834,6 +2095,7 @@ name = "harmony_cli"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"console",
|
"console",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@@ -1868,6 +2130,19 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "harmony_inventory_agent"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"actix-web",
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sysinfo",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "harmony_macros"
|
name = "harmony_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2306,7 +2581,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2431,6 +2706,12 @@ dependencies = [
|
|||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "impl-more"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indenter"
|
name = "indenter"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -2589,6 +2870,16 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.77"
|
version = "0.3.77"
|
||||||
@@ -2791,6 +3082,12 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "language-tags"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -2854,6 +3151,23 @@ version = "0.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
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]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
@@ -2983,6 +3297,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ntapi"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@@ -3703,6 +4026,26 @@ dependencies = [
|
|||||||
"unicode-width 0.2.0",
|
"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]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.13"
|
version = "0.5.13"
|
||||||
@@ -3766,6 +4109,12 @@ dependencies = [
|
|||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-lite"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
@@ -4798,6 +5147,21 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "system-configuration"
|
name = "system-configuration"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -4997,6 +5361,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio 1.0.4",
|
"mio 1.0.4",
|
||||||
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
@@ -5574,6 +5939,25 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
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]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@@ -6057,3 +6441,31 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"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",
|
"harmony_cli",
|
||||||
"k3d",
|
"k3d",
|
||||||
"harmony_composer",
|
"harmony_composer",
|
||||||
|
"harmony_inventory_agent",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
@@ -20,7 +21,7 @@ readme = "README.md"
|
|||||||
license = "GNU AGPL v3"
|
license = "GNU AGPL v3"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
log = "0.4"
|
log = { version = "0.4", features = ["kv"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
derive-new = "0.7"
|
derive-new = "0.7"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
@@ -56,3 +57,8 @@ pretty_assertions = "1.4.1"
|
|||||||
bollard = "0.19.1"
|
bollard = "0.19.1"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
tar = "0.4.44"
|
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"
|
||||||
|
|||||||
BIN
examples/application_monitoring_with_tenant/harmony
Executable file
BIN
examples/application_monitoring_with_tenant/harmony
Executable file
Binary file not shown.
@@ -8,7 +8,6 @@ use harmony::{
|
|||||||
hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup},
|
hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup},
|
||||||
infra::opnsense::OPNSenseManagementInterface,
|
infra::opnsense::OPNSenseManagementInterface,
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
maestro::Maestro,
|
|
||||||
modules::{
|
modules::{
|
||||||
http::StaticFilesHttpScore,
|
http::StaticFilesHttpScore,
|
||||||
ipxe::IpxeScore,
|
ipxe::IpxeScore,
|
||||||
@@ -130,16 +129,21 @@ async fn main() {
|
|||||||
"./data/watchguard/pxe-http-files".to_string(),
|
"./data/watchguard/pxe-http-files".to_string(),
|
||||||
));
|
));
|
||||||
let ipxe_score = IpxeScore::new();
|
let ipxe_score = IpxeScore::new();
|
||||||
let mut maestro = Maestro::initialize(inventory, topology).await.unwrap();
|
|
||||||
maestro.register_all(vec![
|
harmony_tui::run(
|
||||||
Box::new(dns_score),
|
inventory,
|
||||||
Box::new(bootstrap_dhcp_score),
|
topology,
|
||||||
Box::new(bootstrap_load_balancer_score),
|
vec![
|
||||||
Box::new(load_balancer_score),
|
Box::new(dns_score),
|
||||||
Box::new(tftp_score),
|
Box::new(bootstrap_dhcp_score),
|
||||||
Box::new(http_score),
|
Box::new(bootstrap_load_balancer_score),
|
||||||
Box::new(ipxe_score),
|
Box::new(load_balancer_score),
|
||||||
Box::new(dhcp_score),
|
Box::new(tftp_score),
|
||||||
]);
|
Box::new(http_score),
|
||||||
harmony_tui::init(maestro).await.unwrap();
|
Box::new(ipxe_score),
|
||||||
|
Box::new(dhcp_score),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use harmony::{
|
|||||||
hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup},
|
hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup},
|
||||||
infra::opnsense::OPNSenseManagementInterface,
|
infra::opnsense::OPNSenseManagementInterface,
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
maestro::Maestro,
|
|
||||||
modules::{
|
modules::{
|
||||||
dummy::{ErrorScore, PanicScore, SuccessScore},
|
dummy::{ErrorScore, PanicScore, SuccessScore},
|
||||||
http::StaticFilesHttpScore,
|
http::StaticFilesHttpScore,
|
||||||
@@ -84,20 +83,25 @@ async fn main() {
|
|||||||
let http_score = StaticFilesHttpScore::new(Url::LocalFolder(
|
let http_score = StaticFilesHttpScore::new(Url::LocalFolder(
|
||||||
"./data/watchguard/pxe-http-files".to_string(),
|
"./data/watchguard/pxe-http-files".to_string(),
|
||||||
));
|
));
|
||||||
let mut maestro = Maestro::initialize(inventory, topology).await.unwrap();
|
|
||||||
maestro.register_all(vec![
|
harmony_tui::run(
|
||||||
Box::new(dns_score),
|
inventory,
|
||||||
Box::new(dhcp_score),
|
topology,
|
||||||
Box::new(load_balancer_score),
|
vec![
|
||||||
Box::new(tftp_score),
|
Box::new(dns_score),
|
||||||
Box::new(http_score),
|
Box::new(dhcp_score),
|
||||||
Box::new(OPNsenseShellCommandScore {
|
Box::new(load_balancer_score),
|
||||||
opnsense: opnsense.get_opnsense_config(),
|
Box::new(tftp_score),
|
||||||
command: "touch /tmp/helloharmonytouching".to_string(),
|
Box::new(http_score),
|
||||||
}),
|
Box::new(OPNsenseShellCommandScore {
|
||||||
Box::new(SuccessScore {}),
|
opnsense: opnsense.get_opnsense_config(),
|
||||||
Box::new(ErrorScore {}),
|
command: "touch /tmp/helloharmonytouching".to_string(),
|
||||||
Box::new(PanicScore {}),
|
}),
|
||||||
]);
|
Box::new(SuccessScore {}),
|
||||||
harmony_tui::init(maestro).await.unwrap();
|
Box::new(ErrorScore {}),
|
||||||
|
Box::new(PanicScore {}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use std::net::{SocketAddr, SocketAddrV4};
|
|||||||
|
|
||||||
use harmony::{
|
use harmony::{
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
maestro::Maestro,
|
|
||||||
modules::{
|
modules::{
|
||||||
dns::DnsScore,
|
dns::DnsScore,
|
||||||
dummy::{ErrorScore, PanicScore, SuccessScore},
|
dummy::{ErrorScore, PanicScore, SuccessScore},
|
||||||
@@ -16,18 +15,19 @@ use harmony_macros::ipv4;
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let inventory = Inventory::autoload();
|
harmony_tui::run(
|
||||||
let topology = DummyInfra {};
|
Inventory::autoload(),
|
||||||
let mut maestro = Maestro::initialize(inventory, topology).await.unwrap();
|
DummyInfra {},
|
||||||
|
vec![
|
||||||
maestro.register_all(vec![
|
Box::new(SuccessScore {}),
|
||||||
Box::new(SuccessScore {}),
|
Box::new(ErrorScore {}),
|
||||||
Box::new(ErrorScore {}),
|
Box::new(PanicScore {}),
|
||||||
Box::new(PanicScore {}),
|
Box::new(DnsScore::new(vec![], None)),
|
||||||
Box::new(DnsScore::new(vec![], None)),
|
Box::new(build_large_score()),
|
||||||
Box::new(build_large_score()),
|
],
|
||||||
]);
|
)
|
||||||
harmony_tui::init(maestro).await.unwrap();
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_large_score() -> LoadBalancerScore {
|
fn build_large_score() -> LoadBalancerScore {
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ reqwest = { version = "0.11", features = ["blocking", "json"] }
|
|||||||
russh = "0.45.0"
|
russh = "0.45.0"
|
||||||
rust-ipmi = "0.1.1"
|
rust-ipmi = "0.1.1"
|
||||||
semver = "1.0.23"
|
semver = "1.0.23"
|
||||||
serde = { version = "1.0.209", features = ["derive", "rc"] }
|
serde.workspace = true
|
||||||
serde_json = "1.0.127"
|
serde_json.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
derive-new.workspace = true
|
derive-new.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ pub struct DummyInfra;
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Topology for DummyInfra {
|
impl Topology for DummyInfra {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
todo!()
|
"DummyInfra"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ensure_ready(&self) -> Result<PreparationOutcome, PreparationError> {
|
async fn ensure_ready(&self) -> Result<PreparationOutcome, PreparationError> {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ indicatif = "0.18.0"
|
|||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
indicatif-log-bridge = "0.2.3"
|
indicatif-log-bridge = "0.2.3"
|
||||||
|
chrono.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
harmony = { path = "../harmony", features = ["testing"] }
|
harmony = { path = "../harmony", features = ["testing"] }
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
|
use chrono::Local;
|
||||||
|
use console::style;
|
||||||
use harmony::{
|
use harmony::{
|
||||||
instrumentation::{self, HarmonyEvent},
|
instrumentation::{self, HarmonyEvent},
|
||||||
modules::application::ApplicationFeatureStatus,
|
modules::application::ApplicationFeatureStatus,
|
||||||
topology::TopologyStatus,
|
topology::TopologyStatus,
|
||||||
};
|
};
|
||||||
use indicatif::MultiProgress;
|
use log::{error, info, log_enabled};
|
||||||
use indicatif_log_bridge::LogWrapper;
|
use std::io::Write;
|
||||||
use log::error;
|
use std::sync::{Arc, Mutex};
|
||||||
use std::{
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
thread,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::progress::{IndicatifProgressTracker, ProgressTracker};
|
|
||||||
|
|
||||||
pub fn init() -> tokio::task::JoinHandle<()> {
|
pub fn init() -> tokio::task::JoinHandle<()> {
|
||||||
let base_progress = configure_logger();
|
configure_logger();
|
||||||
let handle = tokio::spawn(handle_events(base_progress));
|
let handle = tokio::spawn(handle_events());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() {
|
if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() {
|
||||||
@@ -27,28 +22,76 @@ pub fn init() -> tokio::task::JoinHandle<()> {
|
|||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure_logger() -> MultiProgress {
|
fn configure_logger() {
|
||||||
let logger =
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build();
|
.format(|buf, record| {
|
||||||
let level = logger.filter();
|
let debug_mode = log_enabled!(log::Level::Debug);
|
||||||
let progress = MultiProgress::new();
|
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
LogWrapper::new(progress.clone(), logger)
|
let level = match record.level() {
|
||||||
.try_init()
|
log::Level::Error => style("ERROR").red(),
|
||||||
.unwrap();
|
log::Level::Warn => style("WARN").yellow(),
|
||||||
log::set_max_level(level);
|
log::Level::Info => style("INFO").green(),
|
||||||
|
log::Level::Debug => style("DEBUG").blue(),
|
||||||
progress
|
log::Level::Trace => style("TRACE").magenta(),
|
||||||
|
};
|
||||||
|
if let Some(status) = record.key_values().get(log::kv::Key::from("status")) {
|
||||||
|
let status = status.to_borrowed_str().unwrap();
|
||||||
|
let emoji = match status {
|
||||||
|
"finished" => style(crate::theme::EMOJI_SUCCESS.to_string()).green(),
|
||||||
|
"skipped" => style(crate::theme::EMOJI_SKIP.to_string()).yellow(),
|
||||||
|
"failed" => style(crate::theme::EMOJI_ERROR.to_string()).red(),
|
||||||
|
_ => style("".into()),
|
||||||
|
};
|
||||||
|
if debug_mode {
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"[{} {:<5} {}] {} {}",
|
||||||
|
timestamp,
|
||||||
|
level,
|
||||||
|
record.target(),
|
||||||
|
emoji,
|
||||||
|
record.args()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
writeln!(buf, "[{:<5}] {} {}", level, emoji, record.args())
|
||||||
|
}
|
||||||
|
} else if let Some(emoji) = record.key_values().get(log::kv::Key::from("emoji")) {
|
||||||
|
if debug_mode {
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"[{} {:<5} {}] {} {}",
|
||||||
|
timestamp,
|
||||||
|
level,
|
||||||
|
record.target(),
|
||||||
|
emoji,
|
||||||
|
record.args()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
writeln!(buf, "[{:<5}] {} {}", level, emoji, record.args())
|
||||||
|
}
|
||||||
|
} else if debug_mode {
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"[{} {:<5} {}] {}",
|
||||||
|
timestamp,
|
||||||
|
level,
|
||||||
|
record.target(),
|
||||||
|
record.args()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
writeln!(buf, "[{:<5}] {}", level, record.args())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_events(base_progress: MultiProgress) {
|
async fn handle_events() {
|
||||||
let progress_tracker = Arc::new(IndicatifProgressTracker::new(base_progress.clone()));
|
|
||||||
let preparing_topology = Arc::new(Mutex::new(false));
|
let preparing_topology = Arc::new(Mutex::new(false));
|
||||||
let current_score: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
|
let current_score: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
|
||||||
|
|
||||||
instrumentation::subscribe("Harmony CLI Logger", {
|
instrumentation::subscribe("Harmony CLI Logger", {
|
||||||
move |event| {
|
move |event| {
|
||||||
let progress_tracker = Arc::clone(&progress_tracker);
|
|
||||||
let preparing_topology = Arc::clone(&preparing_topology);
|
let preparing_topology = Arc::clone(&preparing_topology);
|
||||||
let current_score = Arc::clone(¤t_score);
|
let current_score = Arc::clone(¤t_score);
|
||||||
|
|
||||||
@@ -59,90 +102,57 @@ async fn handle_events(base_progress: MultiProgress) {
|
|||||||
match event {
|
match event {
|
||||||
HarmonyEvent::HarmonyStarted => {}
|
HarmonyEvent::HarmonyStarted => {}
|
||||||
HarmonyEvent::HarmonyFinished => {
|
HarmonyEvent::HarmonyFinished => {
|
||||||
progress_tracker.add_section(
|
let emoji = crate::theme::EMOJI_HARMONY.to_string();
|
||||||
"harmony-summary",
|
info!(emoji = emoji.as_str(); "Harmony completed");
|
||||||
&format!("\n{} Harmony completed\n\n", crate::theme::EMOJI_HARMONY),
|
|
||||||
);
|
|
||||||
progress_tracker.add_section("harmony-finished", "\n\n");
|
|
||||||
thread::sleep(Duration::from_millis(200));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
HarmonyEvent::TopologyStateChanged {
|
HarmonyEvent::TopologyStateChanged {
|
||||||
topology,
|
topology,
|
||||||
status,
|
status,
|
||||||
message,
|
message,
|
||||||
} => {
|
} => match status {
|
||||||
let section_key = topology_key(&topology);
|
TopologyStatus::Queued => {}
|
||||||
|
TopologyStatus::Preparing => {
|
||||||
match status {
|
let emoji = format!("{}", style(crate::theme::EMOJI_TOPOLOGY.to_string()).yellow());
|
||||||
TopologyStatus::Queued => {}
|
info!(emoji = emoji.as_str(); "Preparing environment: {topology}...");
|
||||||
TopologyStatus::Preparing => {
|
(*preparing_topology) = true;
|
||||||
progress_tracker.add_section(
|
}
|
||||||
§ion_key,
|
TopologyStatus::Success => {
|
||||||
&format!(
|
(*preparing_topology) = false;
|
||||||
"\n{} Preparing environment: {topology}...",
|
if let Some(message) = message {
|
||||||
crate::theme::EMOJI_TOPOLOGY
|
info!(status = "finished"; "{message}");
|
||||||
),
|
|
||||||
);
|
|
||||||
(*preparing_topology) = true;
|
|
||||||
}
|
|
||||||
TopologyStatus::Success => {
|
|
||||||
(*preparing_topology) = false;
|
|
||||||
progress_tracker.add_task(§ion_key, "topology-success", "");
|
|
||||||
progress_tracker
|
|
||||||
.finish_task("topology-success", &message.unwrap_or("".into()));
|
|
||||||
}
|
|
||||||
TopologyStatus::Noop => {
|
|
||||||
(*preparing_topology) = false;
|
|
||||||
progress_tracker.add_task(§ion_key, "topology-skip", "");
|
|
||||||
progress_tracker
|
|
||||||
.skip_task("topology-skip", &message.unwrap_or("".into()));
|
|
||||||
}
|
|
||||||
TopologyStatus::Error => {
|
|
||||||
progress_tracker.add_task(§ion_key, "topology-error", "");
|
|
||||||
(*preparing_topology) = false;
|
|
||||||
progress_tracker
|
|
||||||
.fail_task("topology-error", &message.unwrap_or("".into()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
TopologyStatus::Noop => {
|
||||||
|
(*preparing_topology) = false;
|
||||||
|
if let Some(message) = message {
|
||||||
|
info!(status = "skipped"; "{message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TopologyStatus::Error => {
|
||||||
|
(*preparing_topology) = false;
|
||||||
|
if let Some(message) = message {
|
||||||
|
error!(status = "failed"; "{message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
HarmonyEvent::InterpretExecutionStarted {
|
HarmonyEvent::InterpretExecutionStarted {
|
||||||
execution_id: task_key,
|
execution_id: _,
|
||||||
topology,
|
topology: _,
|
||||||
interpret: _,
|
interpret: _,
|
||||||
score,
|
score,
|
||||||
message,
|
message,
|
||||||
} => {
|
} => {
|
||||||
let is_key_topology = (*preparing_topology)
|
if *preparing_topology || current_score.is_some() {
|
||||||
&& progress_tracker.contains_section(&topology_key(&topology));
|
info!("{message}");
|
||||||
let is_key_current_score = current_score.is_some()
|
|
||||||
&& progress_tracker
|
|
||||||
.contains_section(&score_key(¤t_score.clone().unwrap()));
|
|
||||||
let is_key_score = progress_tracker.contains_section(&score_key(&score));
|
|
||||||
|
|
||||||
let section_key = if is_key_topology {
|
|
||||||
topology_key(&topology)
|
|
||||||
} else if is_key_current_score {
|
|
||||||
score_key(¤t_score.clone().unwrap())
|
|
||||||
} else if is_key_score {
|
|
||||||
score_key(&score)
|
|
||||||
} else {
|
} else {
|
||||||
(*current_score) = Some(score.clone());
|
(*current_score) = Some(score.clone());
|
||||||
let key = score_key(&score);
|
let emoji = format!("{}", style(crate::theme::EMOJI_SCORE).blue());
|
||||||
progress_tracker.add_section(
|
info!(emoji = emoji.as_str(); "Interpreting score: {score}...");
|
||||||
&key,
|
}
|
||||||
&format!(
|
|
||||||
"{} Interpreting score: {score}...",
|
|
||||||
crate::theme::EMOJI_SCORE
|
|
||||||
),
|
|
||||||
);
|
|
||||||
key
|
|
||||||
};
|
|
||||||
|
|
||||||
progress_tracker.add_task(§ion_key, &task_key, &message);
|
|
||||||
}
|
}
|
||||||
HarmonyEvent::InterpretExecutionFinished {
|
HarmonyEvent::InterpretExecutionFinished {
|
||||||
execution_id: task_key,
|
execution_id: _,
|
||||||
topology: _,
|
topology: _,
|
||||||
interpret: _,
|
interpret: _,
|
||||||
score,
|
score,
|
||||||
@@ -155,16 +165,17 @@ async fn handle_events(base_progress: MultiProgress) {
|
|||||||
match outcome {
|
match outcome {
|
||||||
Ok(outcome) => match outcome.status {
|
Ok(outcome) => match outcome.status {
|
||||||
harmony::interpret::InterpretStatus::SUCCESS => {
|
harmony::interpret::InterpretStatus::SUCCESS => {
|
||||||
progress_tracker.finish_task(&task_key, &outcome.message);
|
info!(status = "finished"; "{}", outcome.message);
|
||||||
}
|
}
|
||||||
harmony::interpret::InterpretStatus::NOOP => {
|
harmony::interpret::InterpretStatus::NOOP => {
|
||||||
progress_tracker.skip_task(&task_key, &outcome.message);
|
info!(status = "skipped"; "{}", outcome.message);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!(status = "failed"; "{}", outcome.message);
|
||||||
}
|
}
|
||||||
_ => progress_tracker.fail_task(&task_key, &outcome.message),
|
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Interpret error: {err}");
|
error!(status = "failed"; "{}", err);
|
||||||
progress_tracker.fail_task(&task_key, &err.to_string());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,30 +184,17 @@ async fn handle_events(base_progress: MultiProgress) {
|
|||||||
application,
|
application,
|
||||||
feature,
|
feature,
|
||||||
status,
|
status,
|
||||||
} => {
|
} => match status {
|
||||||
if let Some(score) = &(*current_score) {
|
ApplicationFeatureStatus::Installing => {
|
||||||
let section_key = score_key(score);
|
info!("Installing feature '{}' for '{}'...", feature, application);
|
||||||
let task_key = app_feature_key(&application, &feature);
|
|
||||||
|
|
||||||
match status {
|
|
||||||
ApplicationFeatureStatus::Installing => {
|
|
||||||
let message = format!("Feature '{}' installing...", feature);
|
|
||||||
progress_tracker.add_task(§ion_key, &task_key, &message);
|
|
||||||
}
|
|
||||||
ApplicationFeatureStatus::Installed => {
|
|
||||||
let message = format!("Feature '{}' installed", feature);
|
|
||||||
progress_tracker.finish_task(&task_key, &message);
|
|
||||||
}
|
|
||||||
ApplicationFeatureStatus::Failed { details } => {
|
|
||||||
let message = format!(
|
|
||||||
"Feature '{}' installation failed: {}",
|
|
||||||
feature, details
|
|
||||||
);
|
|
||||||
progress_tracker.fail_task(&task_key, &message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
ApplicationFeatureStatus::Installed => {
|
||||||
|
info!(status = "finished"; "Feature '{}' installed", feature);
|
||||||
|
}
|
||||||
|
ApplicationFeatureStatus::Failed { details } => {
|
||||||
|
error!(status = "failed"; "Feature '{}' installation failed: {}", feature, details);
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -204,15 +202,3 @@ async fn handle_events(base_progress: MultiProgress) {
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn topology_key(topology: &str) -> String {
|
|
||||||
format!("topology-{topology}")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn score_key(score: &str) -> String {
|
|
||||||
format!("score-{score}")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn app_feature_key(application: &str, feature: &str) -> String {
|
|
||||||
format!("app-{application}-{feature}")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -90,13 +90,37 @@ pub async fn run<T: Topology + Send + Sync + 'static>(
|
|||||||
topology: T,
|
topology: T,
|
||||||
scores: Vec<Box<dyn Score<T>>>,
|
scores: Vec<Box<dyn Score<T>>>,
|
||||||
args_struct: Option<Args>,
|
args_struct: Option<Args>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let args = match args_struct {
|
||||||
|
Some(args) => args,
|
||||||
|
None => Args::parse(),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tui"))]
|
||||||
|
if args.interactive {
|
||||||
|
return Err("Not compiled with interactive support".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tui")]
|
||||||
|
if args.interactive {
|
||||||
|
return harmony_tui::run(inventory, topology, scores).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
run_cli(inventory, topology, scores, args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_cli<T: Topology + Send + Sync + 'static>(
|
||||||
|
inventory: Inventory,
|
||||||
|
topology: T,
|
||||||
|
scores: Vec<Box<dyn Score<T>>>,
|
||||||
|
args: Args,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let cli_logger_handle = cli_logger::init();
|
let cli_logger_handle = cli_logger::init();
|
||||||
|
|
||||||
let mut maestro = Maestro::initialize(inventory, topology).await.unwrap();
|
let mut maestro = Maestro::initialize(inventory, topology).await.unwrap();
|
||||||
maestro.register_all(scores);
|
maestro.register_all(scores);
|
||||||
|
|
||||||
let result = init(maestro, args_struct).await;
|
let result = init(maestro, args).await;
|
||||||
|
|
||||||
instrumentation::instrument(instrumentation::HarmonyEvent::HarmonyFinished).unwrap();
|
instrumentation::instrument(instrumentation::HarmonyEvent::HarmonyFinished).unwrap();
|
||||||
let _ = tokio::try_join!(cli_logger_handle);
|
let _ = tokio::try_join!(cli_logger_handle);
|
||||||
@@ -105,23 +129,8 @@ pub async fn run<T: Topology + Send + Sync + 'static>(
|
|||||||
|
|
||||||
async fn init<T: Topology + Send + Sync + 'static>(
|
async fn init<T: Topology + Send + Sync + 'static>(
|
||||||
maestro: harmony::maestro::Maestro<T>,
|
maestro: harmony::maestro::Maestro<T>,
|
||||||
args_struct: Option<Args>,
|
args: Args,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let args = match args_struct {
|
|
||||||
Some(args) => args,
|
|
||||||
None => Args::parse(),
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
if args.interactive {
|
|
||||||
return harmony_tui::init(maestro).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "tui"))]
|
|
||||||
if args.interactive {
|
|
||||||
return Err("Not compiled with interactive support".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = env_logger::builder().try_init();
|
let _ = env_logger::builder().try_init();
|
||||||
|
|
||||||
let scores_vec = maestro_scores_filter(&maestro, args.all, args.filter, args.number);
|
let scores_vec = maestro_scores_filter(&maestro, args.all, args.filter, args.number);
|
||||||
@@ -193,14 +202,14 @@ mod tests {
|
|||||||
let maestro = init_test_maestro();
|
let maestro = init_test_maestro();
|
||||||
let res = crate::init(
|
let res = crate::init(
|
||||||
maestro,
|
maestro,
|
||||||
Some(crate::Args {
|
crate::Args {
|
||||||
yes: true,
|
yes: true,
|
||||||
filter: Some("SuccessScore".to_owned()),
|
filter: Some("SuccessScore".to_owned()),
|
||||||
interactive: false,
|
interactive: false,
|
||||||
all: true,
|
all: true,
|
||||||
number: 0,
|
number: 0,
|
||||||
list: false,
|
list: false,
|
||||||
}),
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -213,14 +222,14 @@ mod tests {
|
|||||||
|
|
||||||
let res = crate::init(
|
let res = crate::init(
|
||||||
maestro,
|
maestro,
|
||||||
Some(crate::Args {
|
crate::Args {
|
||||||
yes: true,
|
yes: true,
|
||||||
filter: Some("ErrorScore".to_owned()),
|
filter: Some("ErrorScore".to_owned()),
|
||||||
interactive: false,
|
interactive: false,
|
||||||
all: true,
|
all: true,
|
||||||
number: 0,
|
number: 0,
|
||||||
list: false,
|
list: false,
|
||||||
}),
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -233,14 +242,14 @@ mod tests {
|
|||||||
|
|
||||||
let res = crate::init(
|
let res = crate::init(
|
||||||
maestro,
|
maestro,
|
||||||
Some(crate::Args {
|
crate::Args {
|
||||||
yes: true,
|
yes: true,
|
||||||
filter: None,
|
filter: None,
|
||||||
interactive: false,
|
interactive: false,
|
||||||
all: false,
|
all: false,
|
||||||
number: 0,
|
number: 0,
|
||||||
list: false,
|
list: false,
|
||||||
}),
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
@@ -9,7 +9,13 @@ use widget::{help::HelpWidget, score::ScoreListWidget};
|
|||||||
use std::{panic, sync::Arc, time::Duration};
|
use std::{panic, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use crossterm::event::{Event, EventStream, KeyCode, KeyEventKind};
|
use crossterm::event::{Event, EventStream, KeyCode, KeyEventKind};
|
||||||
use harmony::{maestro::Maestro, score::Score, topology::Topology};
|
use harmony::{
|
||||||
|
instrumentation::{self, HarmonyEvent},
|
||||||
|
inventory::Inventory,
|
||||||
|
maestro::Maestro,
|
||||||
|
score::Score,
|
||||||
|
topology::Topology,
|
||||||
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
self, Frame,
|
self, Frame,
|
||||||
layout::{Constraint, Layout, Position},
|
layout::{Constraint, Layout, Position},
|
||||||
@@ -39,22 +45,62 @@ pub mod tui {
|
|||||||
///
|
///
|
||||||
/// #[tokio::main]
|
/// #[tokio::main]
|
||||||
/// async fn main() {
|
/// async fn main() {
|
||||||
/// let inventory = Inventory::autoload();
|
/// harmony_tui::run(
|
||||||
/// let topology = HAClusterTopology::autoload();
|
/// Inventory::autoload(),
|
||||||
/// let mut maestro = Maestro::new_without_initialization(inventory, topology);
|
/// HAClusterTopology::autoload(),
|
||||||
///
|
/// vec![
|
||||||
/// maestro.register_all(vec![
|
/// Box::new(SuccessScore {}),
|
||||||
/// Box::new(SuccessScore {}),
|
/// Box::new(ErrorScore {}),
|
||||||
/// Box::new(ErrorScore {}),
|
/// Box::new(PanicScore {}),
|
||||||
/// Box::new(PanicScore {}),
|
/// ]
|
||||||
/// ]);
|
/// ).await.unwrap();
|
||||||
/// harmony_tui::init(maestro).await.unwrap();
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn init<T: Topology + Send + Sync + 'static>(
|
pub async fn run<T: Topology + Send + Sync + 'static>(
|
||||||
|
inventory: Inventory,
|
||||||
|
topology: T,
|
||||||
|
scores: Vec<Box<dyn Score<T>>>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let handle = init_instrumentation().await;
|
||||||
|
|
||||||
|
let mut maestro = Maestro::initialize(inventory, topology).await.unwrap();
|
||||||
|
maestro.register_all(scores);
|
||||||
|
|
||||||
|
let result = init(maestro).await;
|
||||||
|
|
||||||
|
let _ = tokio::try_join!(handle);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init<T: Topology + Send + Sync + 'static>(
|
||||||
maestro: Maestro<T>,
|
maestro: Maestro<T>,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
HarmonyTUI::new(maestro).init().await
|
let result = HarmonyTUI::new(maestro).init().await;
|
||||||
|
|
||||||
|
instrumentation::instrument(HarmonyEvent::HarmonyFinished).unwrap();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init_instrumentation() -> tokio::task::JoinHandle<()> {
|
||||||
|
let handle = tokio::spawn(handle_harmony_events());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_harmony_events() {
|
||||||
|
instrumentation::subscribe("Harmony TUI Logger", async |event| {
|
||||||
|
if let HarmonyEvent::HarmonyFinished = event {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HarmonyTUI<T: Topology> {
|
pub struct HarmonyTUI<T: Topology> {
|
||||||
|
|||||||
Reference in New Issue
Block a user