add xml parse
This commit is contained in:
851
Cargo.lock
generated
851
Cargo.lock
generated
@@ -2,6 +2,24 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
@@ -58,6 +76,64 @@ version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.60"
|
||||
@@ -104,24 +180,301 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
|
||||
dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-slash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
@@ -140,6 +493,243 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
|
||||
dependencies = [
|
||||
"ref-cast-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast-impl"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"ref-cast",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"ref-cast",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.13.0",
|
||||
"schemars 0.9.0",
|
||||
"schemars 1.2.1",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -152,6 +742,18 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"lazy_static",
|
||||
"path-slash",
|
||||
"regex",
|
||||
"roxmltree",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"toml",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -165,24 +767,252 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.0.7+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.0.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.10+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.7+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
@@ -191,3 +1021,24 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -5,4 +5,16 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.102"
|
||||
clap = { version = "4.5.60", features = ["derive"] }
|
||||
clap = { version = "4.5.60", features = ["derive", "env"] }
|
||||
lazy_static = "1.5.0"
|
||||
path-slash = "0.2.1"
|
||||
regex = "1.12.3"
|
||||
roxmltree = "0.21.1"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
serde_yaml = "0.9.34"
|
||||
serde_with = "*"
|
||||
thiserror = "2.0.18"
|
||||
tokio = { version = "1.50.0", features = ["full"] }
|
||||
toml = "1.0.7"
|
||||
yaml-rust = "0.4.5"
|
||||
0
config/groups/common.toml
Normal file
0
config/groups/common.toml
Normal file
11
config/groups/compute.toml
Normal file
11
config/groups/compute.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
# 计算节点专属配置
|
||||
linux_distro = "centos"
|
||||
linux_version = "8"
|
||||
|
||||
[nodes.compute]
|
||||
variables = {"cpu_cores" = "48","memory" = "192G","swap_size" = "32G"}
|
||||
|
||||
# 追加XML路径
|
||||
xml_paths = [
|
||||
"xml/compute"
|
||||
]
|
||||
5
config/groups/gpu.toml
Normal file
5
config/groups/gpu.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
extends = "compute"
|
||||
|
||||
[vars]
|
||||
os = "rocky9"
|
||||
has_gpu = true
|
||||
0
config/groups/storage.toml
Normal file
0
config/groups/storage.toml
Normal file
38
config/main.toml
Normal file
38
config/main.toml
Normal file
@@ -0,0 +1,38 @@
|
||||
# 全局配置
|
||||
linux_distro = "centos"
|
||||
linux_version = "8"
|
||||
|
||||
# XML配置路径(相对路径,基于main.toml所在目录)
|
||||
xml_paths = [
|
||||
"xml/kickstart",
|
||||
"xml/packages"
|
||||
]
|
||||
|
||||
# 全局变量
|
||||
[variables]
|
||||
domain = "sunhpc.local"
|
||||
ntp_server = "ntp.sunhpc.local"
|
||||
dns_server = "192.168.1.1"
|
||||
root_password = "111111"
|
||||
|
||||
# 节点配置
|
||||
[nodes]
|
||||
[nodes.common]
|
||||
arch = "x86_64"
|
||||
linux_distro = "centos"
|
||||
linux_version = "8"
|
||||
[nodes.common.variables]
|
||||
network_interface = "eth0"
|
||||
ip_prefix = "192.168.1."
|
||||
|
||||
[nodes.compute]
|
||||
inherit = "common"
|
||||
[nodes.compute.variables]
|
||||
ip_suffix = "100"
|
||||
hostname = "compute-01"
|
||||
|
||||
[nodes.storage]
|
||||
inherit = "common"
|
||||
[nodes.storage.variables]
|
||||
ip_suffix = "200"
|
||||
hostname = "storage-01"
|
||||
0
output/compute-0-0.ks
Normal file
0
output/compute-0-0.ks
Normal file
0
output/compute-0-1.ks
Normal file
0
output/compute-0-1.ks
Normal file
0
output/frontend-0-0.ks
Normal file
0
output/frontend-0-0.ks
Normal file
0
scripts/common/base_config.sh
Normal file
0
scripts/common/base_config.sh
Normal file
0
scripts/common/check_hardware.sh
Normal file
0
scripts/common/check_hardware.sh
Normal file
0
scripts/common/register_dns.sh
Normal file
0
scripts/common/register_dns.sh
Normal file
0
scripts/compute/install_docker.sh
Normal file
0
scripts/compute/install_docker.sh
Normal file
0
scripts/compute/join_slurm.sh
Normal file
0
scripts/compute/join_slurm.sh
Normal file
0
scripts/gpu/install_cuda.sh
Normal file
0
scripts/gpu/install_cuda.sh
Normal file
0
scripts/gpu/install_nvidia_driver.sh
Normal file
0
scripts/gpu/install_nvidia_driver.sh
Normal file
0
scripts/storage/install_ceph.sh
Normal file
0
scripts/storage/install_ceph.sh
Normal file
0
scripts/storage/prepare_osd_disks.sh
Normal file
0
scripts/storage/prepare_osd_disks.sh
Normal file
133
src/commands/config/check.rs
Normal file
133
src/commands/config/check.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
//! sunhpc config check 命令实现
|
||||
//! 功能:加载配置文件 → 解析XML → 生成Linux Kickstart脚本
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Args;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// 导入内部模块
|
||||
use crate::internal::config::ksgen::KickstartGenerator;
|
||||
use crate::internal::config::loader::ConfigLoader;
|
||||
|
||||
/// 配置检查命令参数
|
||||
#[derive(Args, Debug)]
|
||||
#[command(about = "检查配置文件并生成Kickstart脚本", long_about = None)]
|
||||
pub struct CheckArgs {
|
||||
/// 配置文件路径(默认:config/main.toml)
|
||||
#[arg(
|
||||
short = 'c',
|
||||
long = "config",
|
||||
default_value = "config/main.toml",
|
||||
help = "指定主配置文件路径(TOML/YAML格式)"
|
||||
)]
|
||||
pub config: PathBuf,
|
||||
|
||||
/// 输出格式(text/json,默认:text)
|
||||
#[arg(
|
||||
short = 'f',
|
||||
long = "format",
|
||||
default_value = "text",
|
||||
help = "Kickstart输出格式:text(纯文本)/json(调试用)"
|
||||
)]
|
||||
pub format: String,
|
||||
|
||||
/// 详细输出模式
|
||||
#[arg(
|
||||
long = "verbose",
|
||||
help = "显示更多调试信息(配置加载过程、节点变量等)"
|
||||
)]
|
||||
pub verbose: bool,
|
||||
|
||||
/// 目标节点名称(默认:compute)
|
||||
#[arg(
|
||||
short = 'n',
|
||||
long = "node",
|
||||
default_value = "compute",
|
||||
help = "指定要生成Kickstart的节点名称(对应配置中的nodes字段)"
|
||||
)]
|
||||
pub node: String,
|
||||
|
||||
/// Linux发行版过滤(可选,覆盖配置中的值)
|
||||
#[arg(
|
||||
long = "distro",
|
||||
help = "指定Linux发行版(如centos/ubuntu/rhel),优先级高于配置文件"
|
||||
)]
|
||||
pub distro: Option<String>,
|
||||
|
||||
/// Linux版本过滤(可选,覆盖配置中的值)
|
||||
#[arg(
|
||||
long = "version",
|
||||
help = "指定Linux版本(如8/9/22.04),优先级高于配置文件"
|
||||
)]
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
/// 执行check命令的核心逻辑
|
||||
pub fn run(args: CheckArgs) -> Result<()> {
|
||||
|
||||
let cwd = get_current_working_dir()?;
|
||||
// 1. 打印基础信息
|
||||
println!("========================================");
|
||||
println!("🔍 SUNHPC 配置检查工具(Linux专属)");
|
||||
println!("========================================");
|
||||
println!("配置文件路径: {}", args.config.display());
|
||||
println!("当前路径: {}", cwd.display());
|
||||
println!("目标节点: {}", args.node);
|
||||
println!("输出格式: {}", args.format);
|
||||
println!("详细模式: {}", if args.verbose { "开启" } else { "关闭" });
|
||||
if let Some(distro) = &args.distro {
|
||||
println!("指定发行版: {}", distro);
|
||||
}
|
||||
if let Some(version) = &args.version {
|
||||
println!("指定版本: {}", version);
|
||||
}
|
||||
println!("----------------------------------------");
|
||||
|
||||
// 2. 加载配置文件(TOML/YAML)
|
||||
let mut config_loader = ConfigLoader::new(&args.config)
|
||||
.with_context(|| format!("加载配置文件失败: {}", args.config.display()))?;
|
||||
|
||||
// 覆盖发行版/版本配置(如果命令行指定)
|
||||
if let Some(distro) = &args.distro {
|
||||
config_loader.global.linux_distro = distro.clone();
|
||||
}
|
||||
if let Some(version) = &args.version {
|
||||
config_loader.global.linux_version = version.clone();
|
||||
}
|
||||
|
||||
// 详细模式:打印加载的配置信息
|
||||
if args.verbose {
|
||||
println!("📄 配置加载成功!");
|
||||
println!(" - 全局变量数: {}", config_loader.global.variables.len());
|
||||
println!(" - 节点数: {}", config_loader.global.nodes.len());
|
||||
println!(" - XML路径数: {}", config_loader.global.xml_paths.len());
|
||||
println!(" - Linux发行版: {}", config_loader.global.linux_distro);
|
||||
println!(" - Linux版本: {}", config_loader.global.linux_version);
|
||||
println!("----------------------------------------");
|
||||
}
|
||||
|
||||
// 3. 初始化Kickstart生成器
|
||||
let mut generator = KickstartGenerator::new(config_loader, &args.node)
|
||||
.with_context(|| format!("初始化Kickstart生成器失败(节点: {})", args.node))?;
|
||||
|
||||
// 4. 解析XML配置并构建Kickstart上下文
|
||||
generator.parse()
|
||||
.with_context(|| "解析XML配置文件失败")?;
|
||||
|
||||
// 5. 生成Kickstart脚本内容
|
||||
let kickstart_content = generator.generate(&args.format)
|
||||
.with_context(|| format!("生成Kickstart失败(格式: {})", args.format))?;
|
||||
|
||||
// 6. 输出结果
|
||||
println!("✅ Kickstart脚本生成成功!");
|
||||
println!("----------------------------------------");
|
||||
println!("{}", kickstart_content);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取当前路径
|
||||
fn get_current_working_dir() -> Result<PathBuf, std::io::Error> {
|
||||
env::current_dir()
|
||||
}
|
||||
25
src/commands/config/mod.rs
Normal file
25
src/commands/config/mod.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use clap::Subcommand;
|
||||
use anyhow::Result;
|
||||
|
||||
// 引入具体的命令逻辑文件
|
||||
mod check;
|
||||
mod sync;
|
||||
|
||||
/// Server 子命令集的具体命令
|
||||
#[derive(Subcommand)]
|
||||
pub enum ConfigCommands {
|
||||
/// 检查配置文件语法正确性
|
||||
Check(check::CheckArgs),
|
||||
|
||||
/// 分发配置文件到所有节点
|
||||
Sync(sync::StopArgs),
|
||||
|
||||
}
|
||||
|
||||
/// 执行 server 命令集的入口函数
|
||||
pub fn execute(cmd: ConfigCommands) -> Result<(), anyhow::Error> {
|
||||
match cmd {
|
||||
ConfigCommands::Check(args) => check::run(args),
|
||||
ConfigCommands::Sync(args) => sync::run(args),
|
||||
}
|
||||
}
|
||||
13
src/commands/config/sync.rs
Normal file
13
src/commands/config/sync.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub struct StopArgs {
|
||||
/// 强制停止
|
||||
#[arg(short, long)]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
pub fn run(args: StopArgs) -> Result<()> {
|
||||
println!("🛑 正在停止服务器... (force: {})", args.force);
|
||||
Ok(())
|
||||
}
|
||||
49
src/commands/db/init.rs
Normal file
49
src/commands/db/init.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use clap::Args;
|
||||
use anyhow::Result;
|
||||
// 引入核心逻辑
|
||||
use crate::internal::database::DbConfig;
|
||||
use crate::internal::database::init_db;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct InitArgs {
|
||||
/// 数据库连接 URL
|
||||
#[arg(short, long, env = "DATABASE_URL", default_value = "postgres://localhost/mydb")]
|
||||
pub url: String,
|
||||
|
||||
/// 最大连接池大小
|
||||
#[arg(long, default_value_t = 10)]
|
||||
pub max_connections: u32,
|
||||
|
||||
/// 是否跳过确认提示
|
||||
#[arg(long, short)]
|
||||
pub yes: bool,
|
||||
}
|
||||
|
||||
/// CLI 层的执行函数
|
||||
pub async fn run(args: InitArgs) -> Result<()> {
|
||||
if !args.yes {
|
||||
print!("⚠️ 即将初始化数据库 '{}',数据可能会被清空。继续吗?(y/N): ", args.url);
|
||||
use std::io::{self, Write};
|
||||
io::stdout().flush()?;
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
|
||||
if !input.trim().eq_ignore_ascii_case("y") {
|
||||
println!("操作已取消。");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 将 CLI 参数转换为 核心逻辑需要的结构体
|
||||
let config = DbConfig {
|
||||
url: args.url,
|
||||
max_connections: args.max_connections,
|
||||
};
|
||||
|
||||
// 2. 调用 internal 层的纯逻辑
|
||||
// 注意:这里调用的是 async 函数
|
||||
init_db(&config).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,15 +2,20 @@ use clap::Subcommand;
|
||||
use anyhow::Result;
|
||||
|
||||
mod migrate;
|
||||
mod init;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum DbCommands {
|
||||
/// 运行数据库迁移
|
||||
Migrate(migrate::MigrateArgs),
|
||||
|
||||
/// 数据库初始化
|
||||
Init(init::InitArgs),
|
||||
}
|
||||
|
||||
pub fn execute(cmd: DbCommands) -> Result<()> {
|
||||
pub async fn execute(cmd: DbCommands) -> Result<()> {
|
||||
match cmd {
|
||||
DbCommands::Migrate(args) => migrate::run(args),
|
||||
DbCommands::Init(args) => init::run(args).await,
|
||||
}
|
||||
}
|
||||
27
src/commands/db/status.rs
Normal file
27
src/commands/db/status.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
// src/commands/db/status.rs
|
||||
use clap::Args;
|
||||
use anyhow::Result;
|
||||
use crate::internal::database::check_connection;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct StatusArgs {
|
||||
/// 数据库 URL (默认从环境变量读取)
|
||||
#[arg(short, long, env = "DATABASE_URL", default_value = "postgres://localhost/mydb")]
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
pub async fn run(args: StatusArgs) -> Result<()> {
|
||||
println!("🏓 正在检查数据库状态...");
|
||||
|
||||
match check_connection(&args.url).await? {
|
||||
true => {
|
||||
println!("✅ 数据库连接正常: {}", args.url);
|
||||
Ok(())
|
||||
}
|
||||
false => {
|
||||
eprintln!("❌ 无法连接到数据库: {}", args.url);
|
||||
// 返回错误码
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
use clap::Subcommand;
|
||||
|
||||
// 声明子模块,对应 src/commands/ 下的目录
|
||||
pub mod config;
|
||||
pub mod report;
|
||||
pub mod server;
|
||||
pub mod db;
|
||||
// 未来扩展:pub mod new_feature;
|
||||
@@ -13,7 +15,15 @@ pub enum CliCommands {
|
||||
#[command(subcommand)]
|
||||
Server(server::ServerCommands),
|
||||
|
||||
/// 数据库管理相关命令 (db migrate, db seed)
|
||||
/// 服务器配置文件解析相关命令
|
||||
#[command(subcommand)]
|
||||
Config(config::ConfigCommands),
|
||||
|
||||
/// 打印集群配置信息
|
||||
#[command(subcommand)]
|
||||
Report(report::ReportCommands),
|
||||
|
||||
/// 数据库管理相关命令 (db init, db migrate)
|
||||
#[command(subcommand)]
|
||||
Db(db::DbCommands),
|
||||
|
||||
|
||||
21
src/commands/report/mod.rs
Normal file
21
src/commands/report/mod.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use clap::Subcommand;
|
||||
use anyhow::Result;
|
||||
|
||||
// 引入具体的命令逻辑文件
|
||||
mod nextip;
|
||||
|
||||
/// Server 子命令集的具体命令
|
||||
#[derive(Subcommand)]
|
||||
pub enum ReportCommands {
|
||||
/// 生成下一个可用 IP 地址
|
||||
#[command(name = "nextip")] // 强制子命令名为 nextip
|
||||
NextIP(nextip::NextIPArgs), // 默认字母有大小写,自动转换成驼峰命令法next-ip.
|
||||
|
||||
}
|
||||
|
||||
/// 执行 report nextip 命令集的入口函数
|
||||
pub fn execute(cmd: ReportCommands) -> Result<(), anyhow::Error> {
|
||||
match cmd {
|
||||
ReportCommands::NextIP(args) => nextip::run(args),
|
||||
}
|
||||
}
|
||||
71
src/commands/report/nextip.rs
Normal file
71
src/commands/report/nextip.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
#[warn(unused_imports)]
|
||||
use crate::internal::network::IPGenerator;
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(clap::Args, Debug)]
|
||||
pub struct NextIPArgs {
|
||||
/// 网络地址(如 10.1.1.0)
|
||||
#[arg(short = 'n', long = "network", default_value = "10.1.1.0")]
|
||||
network: String,
|
||||
|
||||
/// 子网掩码(如 255.255.255.128,不指定则自动推断)
|
||||
#[arg(short = 'm', long = "netmask", default_value = "255.255.255.0")]
|
||||
netmask: Option<String>,
|
||||
|
||||
/// IP 偏移步长(仅允许正数,从第一个可以用 IP 向后偏移)
|
||||
#[arg(short = 's', long = "step", default_value = "1")]
|
||||
step: u32,
|
||||
}
|
||||
|
||||
pub fn run(args: NextIPArgs) -> Result<()> {
|
||||
|
||||
// 调用 IP 生成逻辑
|
||||
generate_next_ip(args)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// 核心逻辑:生成指定偏移的 IP 地址
|
||||
fn generate_next_ip(opts: NextIPArgs) -> Result<()> {
|
||||
// 创建 IP 生成器实例(用 anyhow 包装错误)
|
||||
let mut generator = IPGenerator::new(&opts.network, opts.netmask.as_deref())
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create IP generator: {}", e))?;
|
||||
|
||||
// 获取网段最大可用步长,提前校验
|
||||
let max_step = generator.get_max_available_step();
|
||||
if opts.step > max_step {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Step {} exceeds maximum available step {} for this network (mask: {})",
|
||||
opts.step,
|
||||
max_step,
|
||||
generator.get_netmask_str()
|
||||
));
|
||||
}
|
||||
|
||||
// 获取初始 IP、新 IP、网络地址等信息
|
||||
let initial_ip = generator.curr()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get initial IP: {}", e))?;
|
||||
let new_ip = generator.next(opts.step as i32)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to move IP address: {}", e))?;
|
||||
let network_addr = generator.get_network();
|
||||
let netmask_str = generator.get_netmask_str();
|
||||
|
||||
// ========== 核心:格式化对齐输出 ==========
|
||||
// 定义左侧字段的宽度(根据最长字段调整,这里选 25 足够覆盖所有字段名)
|
||||
const FIELD_WIDTH: usize = 30;
|
||||
println!("{:>FIELD_WIDTH$}: {}", "Initial IP (first available)", initial_ip);
|
||||
println!("{:>FIELD_WIDTH$}: {}", "IP after step", format!("{} (step: {})", new_ip, opts.step));
|
||||
println!("{:>FIELD_WIDTH$}: {}", "Network address", network_addr);
|
||||
println!("{:>FIELD_WIDTH$}: {}", "Subnet mask", netmask_str);
|
||||
println!("{:>FIELD_WIDTH$}: {}", "Max available step", max_step);
|
||||
|
||||
println!("\nIP generation completed successfully");
|
||||
|
||||
|
||||
// 扩展点:写入配置/同步到集群的业务逻辑
|
||||
// 示例:write_ip_to_config(&new_ip)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
595
src/internal/config/ksgen.rs
Normal file
595
src/internal/config/ksgen.rs
Normal file
@@ -0,0 +1,595 @@
|
||||
//! Kickstart生成器模块
|
||||
//! 功能:
|
||||
//! 1. 基于配置和XML解析结果生成Linux Kickstart脚本
|
||||
//! 2. 支持不同Linux发行版/版本的适配
|
||||
//! 3. 输出纯文本或JSON格式的Kickstart内容
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Serialize;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::hash_map::RandomState;
|
||||
use std::path::PathBuf;
|
||||
use roxmltree::Node;
|
||||
|
||||
// 导入内部模块
|
||||
use super::loader::ConfigLoader;
|
||||
use super::xml_parser::XmlLoader;
|
||||
|
||||
/// Kickstart上下文(存储生成脚本所需的所有数据)
|
||||
/// 对应gen.py的Generator_linux的ks字段
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct KickstartContext {
|
||||
/// 节点遍历顺序(file, roll)
|
||||
pub order: Vec<(String, String)>,
|
||||
/// debug信息列表
|
||||
pub debug: Vec<String>,
|
||||
/// main部分内容(Kickstart核心配置)
|
||||
pub main: Vec<String>,
|
||||
/// 启用的RPM包列表
|
||||
pub rpms_on: Vec<String>,
|
||||
/// 禁用的RPM包列表
|
||||
pub rpms_off: Vec<String>,
|
||||
/// pre脚本([参数, 内容])
|
||||
pub pre: Vec<Vec<String>>,
|
||||
/// post脚本([参数, 解释器, 内容])
|
||||
pub post: Vec<Vec<String>>,
|
||||
/// boot pre脚本
|
||||
pub boot_pre: Vec<String>,
|
||||
/// boot post脚本
|
||||
pub boot_post: Vec<String>,
|
||||
/// RCS文件配置(路径: (所有者, 权限))
|
||||
pub rcs_files: HashMap<PathBuf, (String, String)>,
|
||||
}
|
||||
|
||||
/// 节点数据结构(用于避免借用冲突)
|
||||
#[derive(Debug)]
|
||||
struct NodeData {
|
||||
/// 节点名称
|
||||
pub node_name: String,
|
||||
/// 文件属性
|
||||
pub file: String,
|
||||
/// roll属性
|
||||
pub roll: String,
|
||||
/// 节点文本内容
|
||||
pub text: String,
|
||||
/// 是否禁用(package节点)
|
||||
pub disable: bool,
|
||||
/// 是否元包(package节点)
|
||||
pub meta_type: bool,
|
||||
/// 解释器(pre/post节点)
|
||||
pub interpreter: String,
|
||||
/// 参数(pre/post节点)
|
||||
pub arg: String,
|
||||
/// 顺序(boot节点)
|
||||
pub order: String,
|
||||
/// 文件名(file节点)
|
||||
pub file_name: String,
|
||||
/// 所有者(file节点)
|
||||
pub owner: String,
|
||||
/// 权限(file节点)
|
||||
pub perms: String,
|
||||
/// main节点的子节点数据
|
||||
pub children_data: Vec<MainChildData>,
|
||||
}
|
||||
|
||||
/// main节点的子节点数据
|
||||
#[derive(Debug)]
|
||||
struct MainChildData {
|
||||
/// 子节点名称
|
||||
pub name: String,
|
||||
/// 子节点文本
|
||||
pub text: String,
|
||||
/// partition属性(clearpart节点)
|
||||
pub partition: String,
|
||||
}
|
||||
|
||||
/// Linux Kickstart生成器
|
||||
pub struct KickstartGenerator {
|
||||
/// 配置加载器
|
||||
config_loader: ConfigLoader,
|
||||
/// XML加载器
|
||||
xml_loader: XmlLoader,
|
||||
/// Kickstart上下文
|
||||
context: KickstartContext,
|
||||
/// 目标节点名称
|
||||
node_name: String,
|
||||
}
|
||||
|
||||
impl KickstartGenerator {
|
||||
/// 创建Kickstart生成器
|
||||
/// 参数:
|
||||
/// - config_loader: 配置加载器实例
|
||||
/// - node_name: 目标节点名称
|
||||
pub fn new(config_loader: ConfigLoader, node_name: &str) -> Result<Self> {
|
||||
// 1. 获取节点变量(用于XML过滤)
|
||||
let node_vars = config_loader.get_node_vars(node_name)
|
||||
.with_context(|| format!("获取节点{}的变量失败", node_name))?;
|
||||
|
||||
// 2. 创建XML加载器(使用节点变量作为过滤条件)
|
||||
let xml_loader = XmlLoader::new(node_vars, &config_loader.global.xml_paths)
|
||||
.with_context(|| "初始化XML加载器失败")?;
|
||||
|
||||
// 3. 初始化上下文
|
||||
let context = KickstartContext::default();
|
||||
|
||||
Ok(Self {
|
||||
config_loader,
|
||||
xml_loader,
|
||||
context,
|
||||
node_name: node_name.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// 解析XML并构建Kickstart上下文
|
||||
pub fn parse(&mut self) -> Result<()> {
|
||||
println!("📝 开始解析XML配置生成Kickstart...");
|
||||
|
||||
// 提取节点数据(避免借用冲突)
|
||||
let node_data: Vec<NodeData> = self.xml_loader.iter_linux_nodes()
|
||||
.map(|node| {
|
||||
let node_name = node.tag_name().name().to_string();
|
||||
let file = node.attribute("file").unwrap_or_default().to_string();
|
||||
let roll = node.attribute("roll").unwrap_or_default().to_string();
|
||||
let text = self.xml_loader.get_node_text(&node);
|
||||
|
||||
// 提取常用属性
|
||||
let disable = node.attribute("disable").is_some();
|
||||
let meta_type = node.attribute("type").map(|s| s == "meta").unwrap_or(false);
|
||||
let interpreter = node.attribute("interpreter").unwrap_or("/bin/bash").to_string();
|
||||
let arg = node.attribute("arg").unwrap_or_default().to_string();
|
||||
let order = node.attribute("order").unwrap_or("pre").to_string();
|
||||
let file_name = node.attribute("name").unwrap_or_default().to_string();
|
||||
let owner = node.attribute("owner").unwrap_or("root").to_string();
|
||||
let perms = node.attribute("perms").unwrap_or("0644").to_string();
|
||||
|
||||
// 对于main节点,提取子节点数据
|
||||
let children_data = if node_name == "main" {
|
||||
Self::extract_main_children(&node, &self.xml_loader)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
NodeData {
|
||||
node_name,
|
||||
file,
|
||||
roll,
|
||||
text,
|
||||
disable,
|
||||
meta_type,
|
||||
interpreter,
|
||||
arg,
|
||||
order,
|
||||
file_name,
|
||||
owner,
|
||||
perms,
|
||||
children_data,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 遍历节点数据(此时不再持有xml_loader的借用)
|
||||
for data in node_data {
|
||||
// 记录遍历顺序
|
||||
self.context.order.push((data.file.clone(), data.roll.clone()));
|
||||
|
||||
// 根据节点类型处理
|
||||
match data.node_name.as_str() {
|
||||
"main" => self.handle_main_node_data(&data)?,
|
||||
"package" => self.handle_package_node_data(&data)?,
|
||||
"pre" => self.handle_pre_node_data(&data)?,
|
||||
"post" => self.handle_post_node_data(&data)?,
|
||||
"configure" => self.handle_configure_node_data(&data)?,
|
||||
"boot" => self.handle_boot_node_data(&data)?,
|
||||
"debug" => self.context.debug.push(data.text),
|
||||
"file" => self.handle_file_node_data(&data)?,
|
||||
_ => {
|
||||
// 其他节点:记录到debug
|
||||
self.context.debug.push(format!(
|
||||
"未处理的节点类型: {} (文本: {})",
|
||||
data.node_name, data.text
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("✅ XML解析完成,共处理{}个节点", self.context.order.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 提取main节点的子节点数据
|
||||
fn extract_main_children<'a, 'b>(node: &Node<'a, 'b>, xml_loader: &XmlLoader) -> Vec<MainChildData> {
|
||||
let mut children_data = Vec::new();
|
||||
|
||||
for child in node.children() {
|
||||
if child.node_type() != roxmltree::NodeType::Element {
|
||||
continue;
|
||||
}
|
||||
|
||||
let child_name = child.tag_name().name().to_string();
|
||||
let child_text = xml_loader.get_node_text(&child);
|
||||
let partition = child.attribute("partition").unwrap_or_default().to_string();
|
||||
|
||||
children_data.push(MainChildData {
|
||||
name: child_name,
|
||||
text: child_text,
|
||||
partition,
|
||||
});
|
||||
}
|
||||
|
||||
children_data
|
||||
}
|
||||
|
||||
/// 生成Kickstart脚本内容
|
||||
/// 参数:format - 输出格式(text/json)
|
||||
pub fn generate(&self, format: &str) -> Result<String> {
|
||||
match format.to_lowercase().as_str() {
|
||||
"text" => self.generate_text(),
|
||||
"json" => self.generate_json(),
|
||||
_ => Err(anyhow::anyhow!("不支持的输出格式: {},仅支持text/json", format)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成纯文本格式的Kickstart脚本
|
||||
fn generate_text(&self) -> Result<String> {
|
||||
let mut content = String::new();
|
||||
|
||||
// 1. 生成头部注释
|
||||
content.push_str(&format!(
|
||||
"# SUNHPC Kickstart脚本(自动生成)
|
||||
# 节点: {}
|
||||
# Linux发行版: {}
|
||||
# Linux版本: {}
|
||||
# 生成时间: autoGen
|
||||
\n",
|
||||
self.node_name,
|
||||
self.config_loader.global.linux_distro,
|
||||
self.config_loader.global.linux_version
|
||||
));
|
||||
|
||||
// 2. 生成main部分(Kickstart核心配置)
|
||||
content.push_str("# ========== 核心配置 ==========\n");
|
||||
for line in &self.context.main {
|
||||
content.push_str(line);
|
||||
content.push('\n');
|
||||
}
|
||||
content.push('\n');
|
||||
|
||||
// 3. 生成包管理部分
|
||||
content.push_str("# ========== 包配置 ==========\n");
|
||||
if !self.context.rpms_on.is_empty() || !self.context.rpms_off.is_empty() {
|
||||
content.push_str("%packages --ignoremissing\n");
|
||||
// 启用的包
|
||||
for rpm in &self.context.rpms_on {
|
||||
content.push_str(rpm);
|
||||
content.push('\n');
|
||||
}
|
||||
// 禁用的包(前缀-)
|
||||
for rpm in &self.context.rpms_off {
|
||||
content.push_str(&format!("-{}\n", rpm));
|
||||
}
|
||||
content.push_str("%end\n\n");
|
||||
}
|
||||
|
||||
// 4. 生成pre脚本部分
|
||||
content.push_str("# ========== Pre安装脚本 ==========\n");
|
||||
for pre in &self.context.pre {
|
||||
content.push_str(&format!("%pre --log=/tmp/ks-pre.log {}\n", pre[0]));
|
||||
content.push_str(&pre[1]);
|
||||
content.push_str("\n%end\n\n");
|
||||
}
|
||||
|
||||
// 5. 生成post脚本部分
|
||||
content.push_str("# ========== Post安装脚本 ==========\n");
|
||||
let log_path = "/mnt/sysimage/var/log/sunhpc-install.log";
|
||||
for post in &self.context.post {
|
||||
content.push_str(&format!("%post --log={} {}\n", log_path, post[0]));
|
||||
content.push_str(&post[1]); // 解释器(#!/bin/bash)
|
||||
content.push_str("\n");
|
||||
content.push_str(&post[2]); // 脚本内容
|
||||
content.push_str("\n%end\n\n");
|
||||
}
|
||||
|
||||
// 6. 生成boot脚本部分
|
||||
content.push_str("# ========== Boot配置脚本 ==========\n");
|
||||
content.push_str(&format!("%post --log={}\n", log_path));
|
||||
content.push_str("cat >> /etc/sysconfig/sunhpc-pre << EOF\n");
|
||||
for line in &self.context.boot_pre {
|
||||
content.push_str(line);
|
||||
content.push('\n');
|
||||
}
|
||||
content.push_str("EOF\n\n");
|
||||
|
||||
content.push_str("cat >> /etc/sysconfig/sunhpc-post << EOF\n");
|
||||
for line in &self.context.boot_post {
|
||||
content.push_str(line);
|
||||
content.push('\n');
|
||||
}
|
||||
content.push_str("EOF\n%end\n");
|
||||
|
||||
// 7. 生成debug信息(如果有)
|
||||
if !self.context.debug.is_empty() {
|
||||
content.push_str("\n# ========== Debug信息 ==========\n");
|
||||
content.push_str("# 以下为调试信息,实际部署时可删除\n");
|
||||
for debug in &self.context.debug {
|
||||
content.push_str(&format!("# {}\n", debug));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
/// 生成JSON格式的Kickstart上下文(调试用)
|
||||
fn generate_json(&self) -> Result<String> {
|
||||
let json = serde_json::to_string_pretty(&self.context)
|
||||
.with_context(|| "将Kickstart上下文序列化为JSON失败")?;
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// 以下为节点处理方法(按XML节点类型)
|
||||
// ------------------------------
|
||||
|
||||
/// 处理<main>节点(Kickstart核心配置)
|
||||
fn handle_main_node_data(&mut self, data: &NodeData) -> Result<()> {
|
||||
for child in &data.children_data {
|
||||
// 根据不同子节点类型生成配置
|
||||
match child.name.as_str() {
|
||||
"clearpart" => {
|
||||
// clearpart配置(示例:clearpart --all --initlabel)
|
||||
let mut clearpart_line = "clearpart".to_string();
|
||||
if !child.partition.is_empty() {
|
||||
clearpart_line.push_str(&format!(" --partition={}", child.partition));
|
||||
}
|
||||
if !child.text.is_empty() {
|
||||
clearpart_line.push_str(&format!(" {}", child.text));
|
||||
}
|
||||
self.context.main.push(clearpart_line);
|
||||
}
|
||||
|
||||
"bootloader" | "lilo" => {
|
||||
// 引导加载器配置
|
||||
self.context.main.push(format!("bootloader {}", child.text));
|
||||
}
|
||||
|
||||
"lang" => {
|
||||
// 语言配置
|
||||
self.context.main.push(format!("lang {}", child.text));
|
||||
}
|
||||
|
||||
"keyboard" => {
|
||||
// 键盘配置
|
||||
self.context.main.push(format!("keyboard {}", child.text));
|
||||
}
|
||||
|
||||
"timezone" => {
|
||||
// 时区配置
|
||||
self.context.main.push(format!("timezone {}", child.text));
|
||||
}
|
||||
|
||||
"rootpw" => {
|
||||
// root密码配置
|
||||
self.context.main.push(format!("rootpw {}", child.text));
|
||||
}
|
||||
|
||||
"network" => {
|
||||
// 网络配置
|
||||
self.context.main.push(format!("network {}", child.text));
|
||||
}
|
||||
|
||||
"part" | "volgroup" | "logvol" | "raid" => {
|
||||
// 分区配置
|
||||
self.context.main.push(format!("{} {}", child.name, child.text));
|
||||
}
|
||||
|
||||
"reboot" | "poweroff" => {
|
||||
// 安装后动作
|
||||
self.context.main.push(child.name.clone());
|
||||
}
|
||||
|
||||
_ => {
|
||||
// 其他主节点:直接拼接
|
||||
self.context.main.push(format!("{} {}", child.name, child.text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理<package>节点(RPM包配置)
|
||||
fn handle_package_node_data(&mut self, data: &NodeData) -> Result<()> {
|
||||
let package_name = data.text.trim();
|
||||
if package_name.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 构建包名(元包前缀@)
|
||||
let rpm_name = if data.meta_type {
|
||||
format!("@{}", package_name)
|
||||
} else {
|
||||
package_name.to_string()
|
||||
};
|
||||
|
||||
// 去重集合(避免重复添加)
|
||||
let mut rpms_on_set: HashSet<String, RandomState> = self.context.rpms_on.clone()
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut rpms_off_set: HashSet<String, RandomState> = self.context.rpms_off.clone()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
if data.disable {
|
||||
// 禁用的包:不在启用列表才添加到禁用列表
|
||||
if !rpms_on_set.contains(&rpm_name) {
|
||||
rpms_off_set.insert(rpm_name.clone());
|
||||
}
|
||||
rpms_on_set.remove(&rpm_name);
|
||||
} else {
|
||||
// 启用的包:添加到启用列表,移除禁用列表
|
||||
rpms_on_set.insert(rpm_name.clone());
|
||||
rpms_off_set.remove(&rpm_name);
|
||||
}
|
||||
|
||||
// 将临时HashSet转回Vec,覆盖原数据(可变借用)
|
||||
self.context.rpms_on = rpms_on_set.into_iter().collect();
|
||||
self.context.rpms_off = rpms_off_set.into_iter().collect();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理<pre>节点(安装前脚本)
|
||||
fn handle_pre_node_data(&mut self, data: &NodeData) -> Result<()> {
|
||||
// 构建pre脚本参数
|
||||
let pre_args = format!(
|
||||
"--interpreter {} {}",
|
||||
data.interpreter,
|
||||
data.arg
|
||||
).trim().to_string();
|
||||
|
||||
// 添加到pre列表
|
||||
self.context.pre.push(vec![pre_args, data.text.clone()]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理<post>节点(安装后脚本)
|
||||
fn handle_post_node_data(&mut self, data: &NodeData) -> Result<()> {
|
||||
// 构建post脚本项(参数、解释器、内容)
|
||||
let post_item = vec![
|
||||
data.arg.clone(),
|
||||
format!("#!{}", data.interpreter),
|
||||
data.text.clone(),
|
||||
];
|
||||
|
||||
// 添加到post列表
|
||||
self.context.post.push(post_item);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理<configure>节点(配置脚本,转为post脚本)
|
||||
fn handle_configure_node_data(&mut self, data: &NodeData) -> Result<()> {
|
||||
// configure节点等价于post节点
|
||||
self.handle_post_node_data(data)
|
||||
}
|
||||
|
||||
/// 处理<configure>节点(配置脚本,转为post脚本)- 保留以兼容
|
||||
#[allow(dead_code)]
|
||||
fn handle_configure_node<'a, 'b>(&mut self, node: &Node<'a, 'b>) -> Result<()> {
|
||||
// configure节点等价于post节点(保留这个方法以兼容)
|
||||
let interpreter = node.attribute("interpreter").unwrap_or("/bin/bash");
|
||||
let arg = node.attribute("arg").unwrap_or_default();
|
||||
let content = self.xml_loader.get_node_text(node);
|
||||
|
||||
// 构建post脚本项(参数、解释器、内容)
|
||||
let post_item = vec![
|
||||
arg.to_string(),
|
||||
format!("#!{}", interpreter),
|
||||
content,
|
||||
];
|
||||
|
||||
// 添加到post列表
|
||||
self.context.post.push(post_item);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理<boot>节点(启动脚本)
|
||||
fn handle_boot_node_data(&mut self, data: &NodeData) -> Result<()> {
|
||||
match data.order.as_str() {
|
||||
"pre" => self.context.boot_pre.push(data.text.clone()),
|
||||
"post" => self.context.boot_post.push(data.text.clone()),
|
||||
_ => {
|
||||
// 未知顺序:同时添加到pre和post
|
||||
self.context.boot_pre.push(data.text.clone());
|
||||
self.context.boot_post.push(data.text.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理<debug>节点(调试信息)
|
||||
#[allow(dead_code)]
|
||||
fn handle_debug_node<'a, 'b>(&mut self, node: &Node<'a, 'b>) -> Result<()> {
|
||||
// 保留这个方法以兼容
|
||||
let content = self.xml_loader.get_node_text(node);
|
||||
self.context.debug.push(content);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理<file>节点(文件配置)
|
||||
fn handle_file_node_data(&mut self, data: &NodeData) -> Result<()> {
|
||||
if data.file_name.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 记录RCS文件配置
|
||||
let file_path = PathBuf::from(&data.file_name);
|
||||
self.context.rcs_files.insert(file_path, (data.owner.clone(), data.perms.clone()));
|
||||
|
||||
// 生成文件创建脚本(添加到post)
|
||||
let post_content = format!(
|
||||
r#"
|
||||
# 创建文件: {}
|
||||
mkdir -p $(dirname {})
|
||||
cat > {} << EOF
|
||||
->{}
|
||||
EOF
|
||||
chown {} {}
|
||||
chmod {} {}
|
||||
"#,
|
||||
data.file_name, data.file_name, data.file_name, data.text, data.owner, data.file_name, data.perms, data.file_name
|
||||
);
|
||||
|
||||
// 添加到post脚本
|
||||
self.context.post.push(vec![
|
||||
"".to_string(),
|
||||
"#!/bin/bash".to_string(),
|
||||
post_content,
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理<file>节点(文件配置)
|
||||
#[allow(dead_code)]
|
||||
fn handle_file_node<'a, 'b>(&mut self, node: &Node<'a, 'b>) -> Result<()> {
|
||||
// 提取文件属性
|
||||
let file_name = node.attribute("name").unwrap_or_default();
|
||||
let owner = node.attribute("owner").unwrap_or("root");
|
||||
let perms = node.attribute("perms").unwrap_or("0644");
|
||||
let content = self.xml_loader.get_node_text(node);
|
||||
|
||||
if file_name.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 记录RCS文件配置
|
||||
let file_path = PathBuf::from(file_name);
|
||||
self.context.rcs_files.insert(file_path, (owner.to_string(), perms.to_string()));
|
||||
|
||||
// 生成文件创建脚本(添加到post)
|
||||
let post_content = format!(
|
||||
r#"
|
||||
# 创建文件: {}
|
||||
mkdir -p $(dirname {})
|
||||
cat > {} << EOF
|
||||
{}
|
||||
EOF
|
||||
chown {} {}
|
||||
chmod {} {}
|
||||
"#,
|
||||
file_name, file_name, file_name, content, owner, file_name, perms, file_name
|
||||
);
|
||||
|
||||
// 添加到post脚本
|
||||
self.context.post.push(vec![
|
||||
"".to_string(),
|
||||
"#!/bin/bash".to_string(),
|
||||
post_content,
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
322
src/internal/config/loader.rs
Normal file
322
src/internal/config/loader.rs
Normal file
@@ -0,0 +1,322 @@
|
||||
//! 配置加载器模块
|
||||
//! 功能:
|
||||
//! 1. 解析TOML/YAML格式的主配置+分组配置
|
||||
//! 2. 处理节点继承关系(子节点合并父节点变量)
|
||||
//! 3. 管理Linux发行版/版本配置
|
||||
//! 4. 收集XML文件路径(转为绝对路径)
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use yaml_rust::{Yaml, YamlLoader};
|
||||
|
||||
/// 全局配置结构体(对应main.toml的根节点)
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct GlobalConfig {
|
||||
/// 全局通用变量(所有节点共享)
|
||||
#[serde(default)]
|
||||
pub variables: HashMap<String, String>,
|
||||
|
||||
/// 节点配置(key=节点名,value=节点具体配置)
|
||||
#[serde(default)]
|
||||
pub nodes: HashMap<String, NodeConfig>,
|
||||
|
||||
/// XML配置文件/目录路径列表
|
||||
#[serde(default)]
|
||||
pub xml_paths: Vec<PathBuf>,
|
||||
|
||||
/// Linux发行版(如centos/rhel/ubuntu)
|
||||
#[serde(default = "default_linux_distro")]
|
||||
pub linux_distro: String,
|
||||
|
||||
/// Linux版本(如8/9/22.04)
|
||||
#[serde(default = "default_linux_version")]
|
||||
pub linux_version: String,
|
||||
}
|
||||
|
||||
/// 节点配置结构体(支持继承)
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct NodeConfig {
|
||||
/// 继承的父节点名称(可选)
|
||||
#[serde(default)]
|
||||
pub inherit: Option<String>,
|
||||
|
||||
/// 节点架构(x86_64/aarch64等)
|
||||
#[serde(default = "default_arch")]
|
||||
pub arch: String,
|
||||
|
||||
/// Linux发行版(覆盖全局配置)
|
||||
#[serde(default)]
|
||||
pub linux_distro: String,
|
||||
|
||||
/// Linux版本(覆盖全局配置)
|
||||
#[serde(default)]
|
||||
pub linux_version: String,
|
||||
|
||||
/// 节点私有变量(覆盖全局变量)
|
||||
#[serde(default)]
|
||||
pub variables: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// 配置加载器核心结构体
|
||||
pub struct ConfigLoader {
|
||||
/// 加载完成的全局配置
|
||||
pub global: GlobalConfig,
|
||||
|
||||
/// 原始配置文件路径
|
||||
#[allow(dead_code)]
|
||||
config_path: PathBuf,
|
||||
}
|
||||
|
||||
// 默认值函数
|
||||
fn default_arch() -> String {
|
||||
"x86_64".to_string()
|
||||
}
|
||||
|
||||
fn default_linux_distro() -> String {
|
||||
"centos".to_string()
|
||||
}
|
||||
|
||||
fn default_linux_version() -> String {
|
||||
"8".to_string()
|
||||
}
|
||||
|
||||
impl ConfigLoader {
|
||||
/// 创建配置加载器并加载配置文件
|
||||
/// 参数:config_path - 主配置文件路径(TOML/YAML)
|
||||
pub fn new(config_path: &Path) -> Result<Self> {
|
||||
// 1. 读取配置文件内容
|
||||
let config_str = fs::read_to_string(config_path)
|
||||
.with_context(|| format!("无法读取配置文件: {}", config_path.display()))?;
|
||||
|
||||
// 2. 解析配置(优先TOML,兼容YAML)
|
||||
let mut global: GlobalConfig = match toml::from_str(&config_str) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(toml_err) => {
|
||||
eprintln!("TOML解析失败: {}", toml_err);
|
||||
// TOML解析失败,尝试YAML
|
||||
let yaml_docs = YamlLoader::load_from_str(&config_str)
|
||||
.with_context(|| "配置文件不是有效的TOML或YAML格式")?;
|
||||
|
||||
if yaml_docs.is_empty() {
|
||||
return Err(anyhow::anyhow!("YAML配置文件为空"))
|
||||
.context("解析Yaml后未获取到任何信息");
|
||||
}
|
||||
Self::yaml_to_global(&yaml_docs[0])?
|
||||
}
|
||||
};
|
||||
|
||||
// 3. 合并分组配置(groups目录下的compute/storage等)
|
||||
Self::merge_group_configs(&mut global, config_path.parent().unwrap())?;
|
||||
|
||||
// 4. 处理节点继承关系(子节点合并父节点变量)
|
||||
Self::process_node_inheritance(&mut global)?;
|
||||
|
||||
// 5. 标准化XML路径(转为绝对路径)
|
||||
global.xml_paths = global.xml_paths
|
||||
.iter()
|
||||
.map(|p| {
|
||||
if p.is_absolute() {
|
||||
p.clone()
|
||||
} else {
|
||||
config_path.parent().unwrap().join(p)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
global,
|
||||
config_path: config_path.to_path_buf(),
|
||||
})
|
||||
}
|
||||
|
||||
/// 将YAML对象转换为GlobalConfig(兼容逻辑)
|
||||
fn yaml_to_global(yaml: &Yaml) -> Result<GlobalConfig> {
|
||||
let mut global = GlobalConfig {
|
||||
variables: HashMap::new(),
|
||||
nodes: HashMap::new(),
|
||||
xml_paths: Vec::new(),
|
||||
linux_distro: default_linux_distro(),
|
||||
linux_version: default_linux_version(),
|
||||
};
|
||||
|
||||
// 解析全局变量
|
||||
if let Yaml::Hash(vars_hash) = &yaml["variables"] {
|
||||
for (k, v) in vars_hash {
|
||||
if let (Yaml::String(k_str), Yaml::String(v_str)) = (k, v) {
|
||||
global.variables.insert(k_str.clone(), v_str.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析节点配置
|
||||
if let Yaml::Hash(nodes_hash) = &yaml["nodes"] {
|
||||
for (node_name, node_yaml) in nodes_hash {
|
||||
let node_name_str = node_name
|
||||
.as_str()
|
||||
.with_context(|| "节点名称必须是字符串")?
|
||||
.to_string();
|
||||
|
||||
// 解析单个节点配置
|
||||
let inherit = node_yaml["inherit"].as_str().map(|s| s.to_string());
|
||||
let arch = node_yaml["arch"].as_str().unwrap_or(&default_arch()).to_string();
|
||||
let linux_distro = node_yaml["linux_distro"]
|
||||
.as_str()
|
||||
.unwrap_or(&default_linux_distro())
|
||||
.to_string();
|
||||
let linux_version = node_yaml["linux_version"]
|
||||
.as_str()
|
||||
.unwrap_or(&default_linux_version())
|
||||
.to_string();
|
||||
|
||||
// 解析节点私有变量
|
||||
let mut node_vars = HashMap::new();
|
||||
if let Yaml::Hash(vars_hash) = &node_yaml["variables"] {
|
||||
for (k, v) in vars_hash {
|
||||
if let (Yaml::String(k_str), Yaml::String(v_str)) = (k, v) {
|
||||
node_vars.insert(k_str.clone(), v_str.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到节点列表
|
||||
global.nodes.insert(
|
||||
node_name_str,
|
||||
NodeConfig {
|
||||
inherit,
|
||||
arch,
|
||||
linux_distro,
|
||||
linux_version,
|
||||
variables: node_vars,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 解析XML路径
|
||||
if let Yaml::Array(xml_paths_arr) = &yaml["xml_paths"] {
|
||||
for p in xml_paths_arr {
|
||||
if let Yaml::String(p_str) = p {
|
||||
global.xml_paths.push(PathBuf::from(p_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析Linux发行版/版本
|
||||
if let Yaml::String(distro) = &yaml["linux_distro"] {
|
||||
global.linux_distro = distro.clone();
|
||||
}
|
||||
if let Yaml::String(version) = &yaml["linux_version"] {
|
||||
global.linux_version = version.clone();
|
||||
}
|
||||
|
||||
Ok(global)
|
||||
}
|
||||
|
||||
/// 合并groups目录下的分组配置(compute/storage/gpu/common)
|
||||
fn merge_group_configs(global: &mut GlobalConfig, parent_dir: &Path) -> Result<()> {
|
||||
let groups_dir = parent_dir.join("groups");
|
||||
if !groups_dir.exists() || !groups_dir.is_dir() {
|
||||
// 没有groups目录,直接返回
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 遍历groups目录下的所有TOML/YAML文件
|
||||
for entry in fs::read_dir(groups_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
// 只处理文件,且后缀为toml/yaml/yml
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
|
||||
if !["toml", "yaml", "yml"].contains(&ext) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 读取并解析分组配置
|
||||
let group_str = fs::read_to_string(&path)?;
|
||||
let group_config: GlobalConfig = if ext == "toml" {
|
||||
toml::from_str(&group_str)?
|
||||
} else {
|
||||
let yaml_docs = YamlLoader::load_from_str(&group_str)?;
|
||||
Self::yaml_to_global(&yaml_docs[0])?
|
||||
};
|
||||
|
||||
// 合并配置(分组配置覆盖全局配置)
|
||||
global.variables.extend(group_config.variables);
|
||||
global.nodes.extend(group_config.nodes);
|
||||
global.xml_paths.extend(group_config.xml_paths);
|
||||
|
||||
// 合并Linux发行版/版本(如果分组配置有值)
|
||||
if !group_config.linux_distro.is_empty() {
|
||||
global.linux_distro = group_config.linux_distro;
|
||||
}
|
||||
if !group_config.linux_version.is_empty() {
|
||||
global.linux_version = group_config.linux_version;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理节点继承关系(子节点合并父节点的变量/配置)
|
||||
fn process_node_inheritance(global: &mut GlobalConfig) -> Result<()> {
|
||||
let nodes_clone = global.nodes.clone(); // 克隆一份用于读取父节点
|
||||
|
||||
for (node_name, node) in global.nodes.iter_mut() {
|
||||
// 如果节点有继承的父节点
|
||||
if let Some(parent_name) = &node.inherit {
|
||||
let parent_node = nodes_clone
|
||||
.get(parent_name)
|
||||
.with_context(|| format!("节点{}继承的父节点{}不存在", node_name, parent_name))?;
|
||||
|
||||
// 1. 合并变量:父节点变量 → 子节点变量(子节点覆盖父节点)
|
||||
let mut parent_vars = parent_node.variables.clone();
|
||||
parent_vars.extend(node.variables.clone());
|
||||
node.variables = parent_vars;
|
||||
|
||||
// 2. 继承架构(子节点未设置则使用父节点)
|
||||
if node.arch == default_arch() && parent_node.arch != default_arch() {
|
||||
node.arch = parent_node.arch.clone();
|
||||
}
|
||||
|
||||
// 3. 继承Linux发行版(子节点未设置则使用父节点)
|
||||
if node.linux_distro == default_linux_distro() && parent_node.linux_distro != default_linux_distro() {
|
||||
node.linux_distro = parent_node.linux_distro.clone();
|
||||
}
|
||||
|
||||
// 4. 继承Linux版本(子节点未设置则使用父节点)
|
||||
if node.linux_version == default_linux_version() && parent_node.linux_version != default_linux_version() {
|
||||
node.linux_version = parent_node.linux_version.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取指定节点的完整变量(全局变量 + 节点私有变量)
|
||||
/// 参数:node_name - 节点名称
|
||||
pub fn get_node_vars(&self, node_name: &str) -> Result<HashMap<String, String>> {
|
||||
let node = self.global.nodes.get(node_name)
|
||||
.with_context(|| format!("节点{}不存在于配置文件中", node_name))?;
|
||||
|
||||
// 1. 复制全局变量
|
||||
let mut vars = self.global.variables.clone();
|
||||
|
||||
// 2. 合并节点私有变量(覆盖全局变量)
|
||||
vars.extend(node.variables.clone());
|
||||
|
||||
// 3. 注入节点内置变量
|
||||
vars.insert("arch".to_string(), node.arch.clone());
|
||||
vars.insert("linux_distro".to_string(), node.linux_distro.clone());
|
||||
vars.insert("linux_version".to_string(), node.linux_version.clone());
|
||||
vars.insert("node_name".to_string(), node_name.to_string());
|
||||
|
||||
Ok(vars)
|
||||
}
|
||||
}
|
||||
5
src/internal/config/mod.rs
Normal file
5
src/internal/config/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
// 配置相关模块:加载、解析、合并 TOML 配置文件
|
||||
pub mod loader;
|
||||
pub mod xml_parser;
|
||||
pub mod ksgen;
|
||||
226
src/internal/config/xml_parser.rs
Normal file
226
src/internal/config/xml_parser.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
//! XML解析器模块
|
||||
//! 功能:
|
||||
//! 1. 加载指定目录/文件下的所有XML文件
|
||||
//! 2. 实现NodeFilter逻辑(过滤符合Linux发行版/版本/架构的节点)
|
||||
//! 3. 提供XML节点遍历和文本提取功能
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use roxmltree::{Document, Node, NodeType};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// XML节点过滤器(复刻gen.py的NodeFilter)
|
||||
/// 用于过滤符合条件的XML节点(架构/发行版/版本/阶段)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct XmlNodeFilter {
|
||||
/// 过滤条件(arch/linux_distro/linux_version等)
|
||||
filter_attrs: HashMap<String, String>,
|
||||
/// 阶段过滤(pre/post,默认全部)
|
||||
phases: HashSet<String>,
|
||||
}
|
||||
|
||||
impl XmlNodeFilter {
|
||||
/// 创建过滤器
|
||||
/// 参数:filter_attrs - 过滤属性(arch/linux_distro/linux_version)
|
||||
pub fn new(filter_attrs: HashMap<String, String>) -> Self {
|
||||
// 默认包含pre和post阶段
|
||||
let mut phases = HashSet::new();
|
||||
phases.insert("pre".to_string());
|
||||
phases.insert("post".to_string());
|
||||
|
||||
Self {
|
||||
filter_attrs,
|
||||
phases,
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置阶段过滤(仅保留指定阶段)
|
||||
/// 参数:phases - 阶段列表(如["pre"]或["post"])
|
||||
#[allow(dead_code)]
|
||||
pub fn set_phases(&mut self, phases: &[&str]) {
|
||||
self.phases = phases.iter().map(|s| s.to_string()).collect();
|
||||
}
|
||||
|
||||
/// 检查节点是否符合过滤条件(核心逻辑)
|
||||
/// 对应gen.py的isCorrectCond
|
||||
pub fn is_node_match(&self, node: &Node) -> bool {
|
||||
// 非元素节点直接过滤
|
||||
if node.node_type() != NodeType::Element {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. 提取节点属性
|
||||
let node_arch = node.attribute("arch").unwrap_or_default();
|
||||
let node_distro = node.attribute("linux_distro").unwrap_or_default();
|
||||
let node_version = node.attribute("linux_version").unwrap_or_default();
|
||||
let node_phase = node.attribute("phase").unwrap_or_default();
|
||||
|
||||
// 2. 阶段过滤(支持逗号分隔的多个阶段)
|
||||
let node_phases: HashSet<String> = node_phase
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_lowercase())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
// 如果节点指定了阶段,但与过滤器阶段无交集 → 过滤
|
||||
if !node_phases.is_empty() && node_phases.intersection(&self.phases).next().is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 架构过滤(节点指定了arch但不匹配 → 过滤)
|
||||
if !node_arch.is_empty() && node_arch != self.filter_attrs.get("arch").map(|s| s.as_str()).unwrap_or("") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 发行版过滤(节点指定了linux_distro但不匹配 → 过滤)
|
||||
if !node_distro.is_empty() && node_distro != self.filter_attrs.get("linux_distro").map(|s|s.as_str()).unwrap_or("") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. 版本过滤(节点指定了linux_version但不匹配 → 过滤)
|
||||
if !node_version.is_empty() && node_version != self.filter_attrs.get("linux_version").map(|s|s.as_str()).unwrap_or("") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 所有条件都满足
|
||||
true
|
||||
}
|
||||
|
||||
/// 检查节点是否是Linux Kickstart允许的主节点
|
||||
/// 对应gen.py的MainNodeFilter_linux
|
||||
pub fn is_linux_main_node(&self, node: &Node) -> bool {
|
||||
if !self.is_node_match(node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 允许的主节点列表(Kickstart核心节点)
|
||||
let allowed_main_nodes = [
|
||||
"kickstart", "include", "main", "auth", "clearpart", "device", "driverdisk",
|
||||
"install", "nfs", "cdrom", "interactive", "harddrive", "url", "keyboard",
|
||||
"lang", "langsupport", "lilo", "bootloader", "mouse", "network", "part",
|
||||
"volgroup", "logvol", "raid", "reboot", "rootpw", "skipx", "text",
|
||||
"timezone", "upgrade", "xconfig", "zerombr"
|
||||
];
|
||||
|
||||
allowed_main_nodes.contains(&node.tag_name().name())
|
||||
}
|
||||
|
||||
/// 检查节点是否是Linux Kickstart允许的其他节点
|
||||
/// 对应gen.py的OtherNodeFilter_linux
|
||||
pub fn is_linux_other_node(&self, node: &Node) -> bool {
|
||||
if !self.is_node_match(node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 允许的其他节点列表(包/脚本/配置相关)
|
||||
let allowed_other_nodes = [
|
||||
"attributes", "debug", "description", "package", "pre", "post",
|
||||
"boot", "configure", "file"
|
||||
];
|
||||
|
||||
allowed_other_nodes.contains(&node.tag_name().name())
|
||||
}
|
||||
}
|
||||
|
||||
/// XML加载器(加载所有XML文件并提供遍历接口)
|
||||
#[derive(Debug)]
|
||||
pub struct XmlLoader{
|
||||
/// 加载的所有XML文档
|
||||
pub docs: Vec<Document<'static>>,
|
||||
/// 节点过滤器
|
||||
pub filter: XmlNodeFilter,
|
||||
}
|
||||
|
||||
impl XmlLoader {
|
||||
/// 创建XML加载器
|
||||
/// 参数:
|
||||
/// - filter_attrs: 过滤属性(arch/linux_distro/linux_version)
|
||||
/// - xml_paths: XML文件/目录路径列表
|
||||
pub fn new(filter_attrs: HashMap<String, String>, xml_paths: &[PathBuf]) -> Result<Self> {
|
||||
let mut docs = Vec::new();
|
||||
|
||||
// 遍历所有XML路径(文件/目录)
|
||||
for path in xml_paths {
|
||||
if path.is_dir() {
|
||||
// 目录:遍历所有.xml文件
|
||||
docs.extend(Self::load_xml_dir(path)?);
|
||||
} else if path.is_file() && path.extension().and_then(|e| e.to_str()) == Some("xml") {
|
||||
// 文件:直接加载
|
||||
let doc = Self::load_xml_file(path)?;
|
||||
docs.push(doc);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建过滤器
|
||||
let filter = XmlNodeFilter::new(filter_attrs);
|
||||
|
||||
Ok(Self { docs, filter})
|
||||
}
|
||||
|
||||
/// 加载目录下的所有XML文件
|
||||
fn load_xml_dir(dir: &Path) -> Result<Vec<Document<'static>>> {
|
||||
let mut docs = Vec::new();
|
||||
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
// 只处理.xml文件
|
||||
if path.is_file() && path.extension().and_then(|e| e.to_str()) == Some("xml") {
|
||||
let doc = Self::load_xml_file(&path)?;
|
||||
docs.push(doc);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(docs)
|
||||
}
|
||||
|
||||
/// 加载单个XML文件
|
||||
fn load_xml_file(path: &Path) -> Result<Document<'static>> {
|
||||
// 读取文件内容
|
||||
let xml_str = fs::read_to_string(path)
|
||||
.with_context(|| format!("读取XML文件失败: {}", path.display()))?;
|
||||
|
||||
// 解析XML为DOM文档
|
||||
let xml_static: &'static str = Box::leak(xml_str.into_boxed_str());
|
||||
let doc = Document::parse(xml_static)
|
||||
.with_context(|| format!("解析XML文件失败: {}", path.display()))?;
|
||||
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
/// 遍历所有符合条件的Linux节点
|
||||
pub fn iter_linux_nodes(&self) -> impl Iterator<Item = Node<'_, '_>> {
|
||||
self.docs.iter()
|
||||
.flat_map(|doc| doc.descendants()) // 遍历所有后代节点
|
||||
.filter(|node| {
|
||||
// 过滤出主节点或其他节点
|
||||
self.filter.is_linux_main_node(node) || self.filter.is_linux_other_node(node)
|
||||
})
|
||||
}
|
||||
|
||||
/// 获取节点的所有文本内容(包括子节点)
|
||||
pub fn get_node_text<'a, 'b>(&self, node: &Node<'a, 'b>) -> String {
|
||||
let mut text = String::new();
|
||||
|
||||
for child in node.children() {
|
||||
match child.node_type() {
|
||||
// 文本节点:直接追加
|
||||
NodeType::Text => {
|
||||
if let Some(t) = child.text() {
|
||||
text.push_str(t);
|
||||
}
|
||||
}
|
||||
// 元素节点:递归获取文本
|
||||
NodeType::Element => {
|
||||
text.push_str(&self.get_node_text(&child));
|
||||
}
|
||||
// 其他节点:忽略
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// 去除首尾空白,替换多空格为单空格
|
||||
text.trim().replace(&['\n', '\r', '\t'][..], " ").replace(" ", " ")
|
||||
}
|
||||
}
|
||||
52
src/internal/database/database.rs
Normal file
52
src/internal/database/database.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use anyhow::{Result};
|
||||
use std::time::Duration;
|
||||
|
||||
/// 模拟数据库配置结构
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DbConfig {
|
||||
pub url: String,
|
||||
pub max_connections: u32,
|
||||
}
|
||||
|
||||
/// 核心逻辑:初始化数据库
|
||||
/// 这个函数不关心参数从哪里来(CLI、配置文件、环境变量)
|
||||
pub async fn init_db(config: &DbConfig) -> Result<()> {
|
||||
println!("🔌 正在连接数据库: {}", config.url);
|
||||
|
||||
// 模拟网络延迟
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// 模拟连接逻辑
|
||||
if config.url.contains("error") {
|
||||
anyhow::bail!("无法连接到数据库,URL 无效");
|
||||
}
|
||||
|
||||
println!("✅ 连接成功!最大连接数设置为: {}", config.max_connections);
|
||||
|
||||
// 模拟执行初始化脚本
|
||||
run_migrations().await?;
|
||||
|
||||
println!("🎉 数据库初始化完成!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 核心逻辑:检查连接状态
|
||||
#[allow(dead_code)]
|
||||
pub async fn check_connection(url: &str) -> Result<bool> {
|
||||
println!("🏓 正在 Ping 数据库: {}", url);
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
if url.contains("error") {
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// 内部辅助函数:运行迁移
|
||||
async fn run_migrations() -> Result<()> {
|
||||
println!("🚀 正在运行数据迁移脚本...");
|
||||
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
println!(" - 创建表 users");
|
||||
println!(" - 创建表 logs");
|
||||
Ok(())
|
||||
}
|
||||
8
src/internal/database/mod.rs
Normal file
8
src/internal/database/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
// 公开具体的实现文件
|
||||
pub mod database;
|
||||
|
||||
// 重新导出常用函数,方便 commands 层调用
|
||||
// 例如:use crate::internal::database::init_db;
|
||||
pub use database::init_db;
|
||||
//pub use database::check_connection;
|
||||
pub use database::DbConfig;
|
||||
5
src/internal/mod.rs
Normal file
5
src/internal/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
// 公开 database 模块,供外部(commands)使用
|
||||
|
||||
pub mod database;
|
||||
pub mod config;
|
||||
pub mod network;
|
||||
238
src/internal/network/ip.rs
Normal file
238
src/internal/network/ip.rs
Normal file
@@ -0,0 +1,238 @@
|
||||
use std::fmt;
|
||||
use std::error::Error;
|
||||
|
||||
// 自定义错误类型(实现 Error trait 方便上层处理)
|
||||
#[derive(Debug)]
|
||||
pub enum IPError {
|
||||
InvalidAddressFormat,
|
||||
OutOfRange(String),
|
||||
NonUnicastAddress,
|
||||
ParseError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for IPError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
IPError::InvalidAddressFormat => write!(f, "Invalid IP address format (must be x.x.x.x)"),
|
||||
IPError::OutOfRange(msg) => write!(f, "IP address out of range: {}", msg),
|
||||
IPError::NonUnicastAddress => write!(f, "Not a unicast IP address (invalid network type)"),
|
||||
IPError::ParseError(msg) => write!(f, "IP parse error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for IPError {}
|
||||
|
||||
// IP 地址核心结构体(IPv4)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct IPAddr(u32);
|
||||
|
||||
impl IPAddr {
|
||||
/// 从字符串创建 IP 地址(如 "192.168.1.1")
|
||||
pub fn from_str(s: &str) -> Result<Self, IPError> {
|
||||
let parts: Vec<&str> = s.split('.').collect();
|
||||
if parts.len() != 4 {
|
||||
return Err(IPError::InvalidAddressFormat);
|
||||
}
|
||||
|
||||
let mut octets = [0u8; 4];
|
||||
for (i, part) in parts.iter().enumerate() {
|
||||
octets[i] = part.parse().map_err(|e| {
|
||||
IPError::ParseError(format!("Failed to parse octet {}: {}", part, e))
|
||||
})?;
|
||||
}
|
||||
|
||||
// 转换为 u32(大端序:第一个 octet 是最高位)
|
||||
let addr = ((octets[0] as u32) << 24)
|
||||
| ((octets[1] as u32) << 16)
|
||||
| ((octets[2] as u32) << 8)
|
||||
| (octets[3] as u32);
|
||||
|
||||
Ok(Self(addr))
|
||||
}
|
||||
|
||||
/// 从 u32 数值创建 IP 地址
|
||||
#[allow(dead_code)]
|
||||
pub fn from_u32(addr: u32) -> Self {
|
||||
Self(addr)
|
||||
}
|
||||
|
||||
/// 获取 u32 格式的地址
|
||||
pub fn to_u32(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// 位运算:与
|
||||
pub fn bitwise_and(&self, other: &Self) -> Self {
|
||||
Self(self.0 & other.0)
|
||||
}
|
||||
|
||||
/// 位运算:或
|
||||
#[allow(dead_code)]
|
||||
pub fn bitwise_or(&self, other: &Self) -> Self {
|
||||
Self(self.0 | other.0)
|
||||
}
|
||||
|
||||
/// 位运算:非
|
||||
pub fn bitwise_not(&self) -> Self {
|
||||
Self(!self.0)
|
||||
}
|
||||
|
||||
/// 地址加法(支持负数)
|
||||
pub fn add(&self, n: i32) -> Self {
|
||||
if n >= 0 {
|
||||
Self(self.0 + n as u32)
|
||||
} else {
|
||||
Self(self.0 - n.abs() as u32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 实现 Display 以便格式化输出
|
||||
impl fmt::Display for IPAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}.{}.{}.{}",
|
||||
(self.0 >> 24) & 0xFF,
|
||||
(self.0 >> 16) & 0xFF,
|
||||
(self.0 >> 8) & 0xFF,
|
||||
self.0 & 0xFF
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// IP 生成器核心结构体
|
||||
#[derive(Debug)]
|
||||
pub struct IPGenerator {
|
||||
network: IPAddr,
|
||||
netmask: IPAddr,
|
||||
addr: IPAddr,
|
||||
}
|
||||
|
||||
impl IPGenerator {
|
||||
/// 创建 IP 生成器实例
|
||||
/// - network: 网络地址(如 "10.1.1.0")
|
||||
/// - netmask: 子网掩码(None 则自动推断 A/B/C 类)
|
||||
pub fn new(network: &str, netmask: Option<&str>) -> Result<Self, IPError> {
|
||||
let network_addr = IPAddr::from_str(network)?;
|
||||
let netmask_addr = match netmask {
|
||||
Some(addr_str) => IPAddr::from_str(addr_str)?,
|
||||
None => Self::infer_netmask(&network_addr)?,
|
||||
};
|
||||
|
||||
// 初始IP = 网络地址 + 1(第一个可用地址)
|
||||
let initial_addr = network_addr.add(1);
|
||||
|
||||
// 效验初始 IP 是否在合法范围
|
||||
Self::validate_ip_in_range(&initial_addr, &network_addr, &netmask_addr)?;
|
||||
|
||||
Ok(Self {
|
||||
network: network_addr,
|
||||
netmask: netmask_addr,
|
||||
addr: initial_addr,
|
||||
})
|
||||
}
|
||||
|
||||
/// 自动推断子网掩码(A/B/C 类)
|
||||
fn infer_netmask(addr: &IPAddr) -> Result<IPAddr, IPError> {
|
||||
let first_octet = (addr.0 >> 24) & 0xFF;
|
||||
match first_octet {
|
||||
// A 类: 0-127
|
||||
0..=127 => Ok(IPAddr::from_str("255.0.0.0")?),
|
||||
// B 类: 128-191
|
||||
128..=191 => Ok(IPAddr::from_str("255.255.0.0")?),
|
||||
// C 类: 192-223
|
||||
192..=223 => Ok(IPAddr::from_str("255.255.255.0")?),
|
||||
// 非单播地址
|
||||
_ => Err(IPError::NonUnicastAddress),
|
||||
}
|
||||
}
|
||||
|
||||
// 校验 IP 是否在网段合法范围(非网络/广播地址)
|
||||
fn validate_ip_in_range(ip: &IPAddr, network: &IPAddr, netmask: &IPAddr) -> Result<(), IPError> {
|
||||
let inverted_netmask = netmask.bitwise_not();
|
||||
let net_addr = network.bitwise_and(netmask).to_u32();
|
||||
let ip_addr = ip.to_u32();
|
||||
let broadcast_addr = net_addr | inverted_netmask.to_u32();
|
||||
|
||||
// 检查是否是网络地址
|
||||
if ip_addr == net_addr {
|
||||
return Err(IPError::OutOfRange("IP is network address".to_string()));
|
||||
}
|
||||
// 检查是否是广播地址
|
||||
if ip_addr == broadcast_addr {
|
||||
return Err(IPError::OutOfRange("IP is broadcast address".to_string()));
|
||||
}
|
||||
// 检查是否在网段内
|
||||
if (ip_addr & netmask.to_u32()) != net_addr {
|
||||
return Err(IPError::OutOfRange("IP is not in network range".to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 获取当前 IP(带校验)
|
||||
pub fn curr(&self) -> Result<IPAddr, IPError> {
|
||||
Self::validate_ip_in_range(&self.addr, &self.network, &self.netmask)?;
|
||||
Ok(self.addr)
|
||||
}
|
||||
|
||||
// 动态计算最大可用步长(根据子网掩码)
|
||||
pub fn get_max_available_step(&self) -> u32 {
|
||||
let inverted_netmask = self.netmask.bitwise_not().to_u32();
|
||||
// 可用 IP 数量 = 主机位总数 - 2(排除网络/广播地址)
|
||||
let total_available = inverted_netmask - 1; // 减 1 是因为初始 IP 已经是 +1 了
|
||||
total_available as u32
|
||||
}
|
||||
|
||||
// 获取子网掩码的字符串形式
|
||||
pub fn get_netmask_str(&self) -> String {
|
||||
format!("{}", self.netmask)
|
||||
}
|
||||
|
||||
/// 获取网络地址(如 10.1.1.0/25 对应 10.1.1.0)
|
||||
pub fn get_network(&self) -> String {
|
||||
format!("{}", self.addr.bitwise_and(&self.netmask))
|
||||
}
|
||||
|
||||
// 偏移 IP(仅支持正数步长)
|
||||
pub fn next(&mut self, n: i32) -> Result<IPAddr, IPError> {
|
||||
// 强制转为正数(禁用负数偏移)
|
||||
let step = if n < 0 { 0 } else { n as u32 };
|
||||
let new_addr = self.addr.add(step as i32);
|
||||
|
||||
// 校验新 IP 是否合法
|
||||
Self::validate_ip_in_range(&new_addr, &self.network, &self.netmask)?;
|
||||
|
||||
self.addr = new_addr;
|
||||
Ok(self.addr)
|
||||
}
|
||||
|
||||
/// 地址递减(等价于 next(-1))
|
||||
#[allow(dead_code)]
|
||||
pub fn dec(&mut self) -> Result<IPAddr, IPError> {
|
||||
self.next(-1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 测试用例(可选)
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ip_from_str() {
|
||||
let ip = IPAddr::from_str("10.1.1.0").unwrap();
|
||||
assert_eq!(ip.to_u32(), 0x0a010100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ip_generator() {
|
||||
let mut generator = IPGenerator::new("10.1.1.0", Some("255.255.255.128")).unwrap();
|
||||
assert_eq!(generator.curr().unwrap().to_string(), "10.1.1.127");
|
||||
generator.next(-126).unwrap();
|
||||
assert_eq!(generator.curr().unwrap().to_string(), "10.1.1.1");
|
||||
}
|
||||
}
|
||||
3
src/internal/network/mod.rs
Normal file
3
src/internal/network/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod ip;
|
||||
|
||||
pub use ip::IPGenerator;
|
||||
14
src/main.rs
14
src/main.rs
@@ -1,13 +1,12 @@
|
||||
use clap::Parser;
|
||||
use anyhow::Result;
|
||||
|
||||
// 引入 commands 模块
|
||||
mod commands;
|
||||
mod utils; // 假设有通用工具
|
||||
mod commands; // 引入 commands 模块
|
||||
mod internal; // 引入 internal 模块
|
||||
mod utils; // 引入 utils 通用工具模块
|
||||
|
||||
use commands::CliCommands;
|
||||
|
||||
/// 我的超级 CLI 工具
|
||||
#[derive(Parser)]
|
||||
#[command(name = "sunhpc")]
|
||||
#[command(author = "Qichao.Sun")]
|
||||
@@ -23,7 +22,8 @@ struct Cli {
|
||||
command: CliCommands,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
if cli.debug {
|
||||
@@ -33,7 +33,9 @@ fn main() -> Result<()> {
|
||||
// 根据子命令分发逻辑
|
||||
match cli.command {
|
||||
CliCommands::Server(args) => commands::server::execute(args)?,
|
||||
CliCommands::Db(args) => commands::db::execute(args)?,
|
||||
CliCommands::Db(args) => commands::db::execute(args).await?,
|
||||
CliCommands::Config(args) => commands::config::execute(args)?,
|
||||
CliCommands::Report(args) => commands::report::execute(args)?,
|
||||
// 未来扩展新命令时,只需在这里添加新的匹配臂
|
||||
// CliCommands::NewFeature(args) => commands::new_feature::execute(args)?,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user