forked from DGNum/colmena
Initial commit
This commit is contained in:
commit
e092ba5bb1
14 changed files with 1869 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
760
Cargo.lock
generated
Normal file
760
Cargo.lock
generated
Normal file
|
@ -0,0 +1,760 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "ansi_term"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "2.33.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||||
|
dependencies = [
|
||||||
|
"ansi_term",
|
||||||
|
"atty",
|
||||||
|
"bitflags",
|
||||||
|
"strsim",
|
||||||
|
"textwrap",
|
||||||
|
"unicode-width",
|
||||||
|
"vec_map",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colmena"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"clap",
|
||||||
|
"console",
|
||||||
|
"futures",
|
||||||
|
"glob",
|
||||||
|
"indicatif",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"snafu",
|
||||||
|
"tempfile",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a50aab2529019abfabfa93f1e6c41ef392f91fbf179b347a7e96abb524884a08"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"regex",
|
||||||
|
"terminal_size",
|
||||||
|
"unicode-width",
|
||||||
|
"winapi",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "doc-comment"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-hack",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
|
"pin-project",
|
||||||
|
"pin-utils",
|
||||||
|
"proc-macro-hack",
|
||||||
|
"proc-macro-nested",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.1.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indicatif"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"lazy_static",
|
||||||
|
"number_prefix",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instant"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.81"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f33bc887064ef1fd66020c9adfc45bb9f33d75a42096c81e7c56c65b75dd1a8b"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"miow",
|
||||||
|
"ntapi",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miow"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
|
||||||
|
dependencies = [
|
||||||
|
"socket2",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ntapi"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "number_prefix"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
||||||
|
dependencies = [
|
||||||
|
"instant",
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7c6d9b8427445284a09c55be860a15855ab580a417ccad9da88f5a06787ced0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"instant",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-internal",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-internal"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-hack"
|
||||||
|
version = "0.5.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-nested"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
"rand_hc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_hc"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.1.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
|
||||||
|
dependencies = [
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "remove_dir_all"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.118"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.118"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "snafu"
|
||||||
|
version = "0.6.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
|
||||||
|
dependencies = [
|
||||||
|
"doc-comment",
|
||||||
|
"snafu-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "snafu-derive"
|
||||||
|
version = "0.6.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.54"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
"libc",
|
||||||
|
"rand",
|
||||||
|
"redox_syscall",
|
||||||
|
"remove_dir_all",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal_size"
|
||||||
|
version = "0.1.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4bd2d183bd3fac5f5fe38ddbeb4dc9aec4a39a9d7d59e7491d900302da01cbe1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "720ba21c25078711bf456d607987d95bce90f7c3bea5abe1db587862e7a1e87c"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"libc",
|
||||||
|
"memchr",
|
||||||
|
"mio",
|
||||||
|
"num_cpus",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"slab",
|
||||||
|
"tokio-macros",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21d30fdbb5dc2d8f91049691aa1a9d4d4ae422a21c334ce8936e5886d30c5c45"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vec_map"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.9.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "colmena"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Zhaofeng Li <hello@zhaofeng.li>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-trait = "0.1.42"
|
||||||
|
clap = "2.33.3"
|
||||||
|
console = "0.13.0"
|
||||||
|
futures = "0.3.8"
|
||||||
|
glob = "0.3.0"
|
||||||
|
indicatif = "0.15.0"
|
||||||
|
log = "0.4.11"
|
||||||
|
serde = { version = "1.0.118", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
snafu = "0.6.10"
|
||||||
|
tempfile = "3.1.0"
|
||||||
|
tokio = { version = "0.3.6", features = ["full"] }
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Zhaofeng Li
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Colmena
|
||||||
|
|
||||||
|
Colmena is a simple, stateless NixOS deployment tool modeled after NixOps and Morph, written in Rust.
|
||||||
|
It's a thin wrapper over Nix commands like `nix-instantiate` and `nix-copy-closure`, and supports parallel deployment.
|
||||||
|
|
||||||
|
Colmena is still an early prototype.
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
Colmena is available under the MIT License.
|
89
src/command/apply.rs
Normal file
89
src/command/apply.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use clap::{Arg, App, SubCommand, ArgMatches};
|
||||||
|
|
||||||
|
use crate::nix::{Hive, DeploymentTask, DeploymentGoal};
|
||||||
|
use crate::deployment::deploy;
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
|
pub fn subcommand() -> App<'static, 'static> {
|
||||||
|
let command = SubCommand::with_name("apply")
|
||||||
|
.about("Apply the configuration")
|
||||||
|
.arg(Arg::with_name("goal")
|
||||||
|
.help("Deployment goal")
|
||||||
|
.long_help("Same as the targets for switch-to-configuration.\n\"push\" means only copying the closures to remote nodes.")
|
||||||
|
.default_value("switch")
|
||||||
|
.index(1)
|
||||||
|
.possible_values(&["push", "switch", "boot", "test", "dry-activate"]))
|
||||||
|
.arg(Arg::with_name("parallel")
|
||||||
|
.short("p")
|
||||||
|
.long("parallel")
|
||||||
|
.help("Parallelism limit")
|
||||||
|
.long_help("Set to 0 to disable parallemism limit.")
|
||||||
|
.default_value("10")
|
||||||
|
.takes_value(true)
|
||||||
|
.validator(|s| {
|
||||||
|
match s.parse::<usize>() {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(_) => Err(String::from("The value must be a valid number")),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.arg(Arg::with_name("verbose")
|
||||||
|
.short("v")
|
||||||
|
.long("verbose")
|
||||||
|
.help("Be verbose")
|
||||||
|
.long_help("Deactivates the progress spinner and prints every line of output.")
|
||||||
|
.takes_value(false));
|
||||||
|
|
||||||
|
util::register_common_args(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
||||||
|
let hive = Hive::from_config_arg(local_args).unwrap();
|
||||||
|
|
||||||
|
println!("Enumerating nodes...");
|
||||||
|
let deployment_info = hive.deployment_info().await.unwrap();
|
||||||
|
let all_nodes: Vec<String> = deployment_info.keys().cloned().collect();
|
||||||
|
|
||||||
|
let selected_nodes = match local_args.value_of("on") {
|
||||||
|
Some(filter) => {
|
||||||
|
util::filter_nodes(&all_nodes, filter)
|
||||||
|
}
|
||||||
|
None => all_nodes.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if selected_nodes.len() == 0 {
|
||||||
|
println!("No hosts matched. Exiting...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if selected_nodes.len() == all_nodes.len() {
|
||||||
|
println!("Building all node configurations...");
|
||||||
|
} else {
|
||||||
|
println!("Selected {} out of {} hosts. Building node configurations...", selected_nodes.len(), deployment_info.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some ugly argument mangling :/
|
||||||
|
let profiles = hive.build_selected(selected_nodes).await.unwrap();
|
||||||
|
let goal = DeploymentGoal::from_str(local_args.value_of("goal").unwrap()).unwrap();
|
||||||
|
let verbose = local_args.is_present("verbose");
|
||||||
|
|
||||||
|
let max_parallelism = local_args.value_of("parallel").unwrap().parse::<usize>().unwrap();
|
||||||
|
let max_parallelism = match max_parallelism {
|
||||||
|
0 => None,
|
||||||
|
_ => Some(max_parallelism),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut task_list: Vec<DeploymentTask> = Vec::new();
|
||||||
|
for (name, profile) in profiles.iter() {
|
||||||
|
let task = DeploymentTask::new(
|
||||||
|
name.clone(),
|
||||||
|
deployment_info.get(name).unwrap().clone(),
|
||||||
|
profile.clone(),
|
||||||
|
goal,
|
||||||
|
);
|
||||||
|
task_list.push(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Applying configurations...");
|
||||||
|
|
||||||
|
deploy(task_list, max_parallelism, !verbose).await;
|
||||||
|
}
|
47
src/command/build.rs
Normal file
47
src/command/build.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
use clap::{Arg, App, SubCommand, ArgMatches};
|
||||||
|
|
||||||
|
use crate::nix::Hive;
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
|
pub fn subcommand() -> App<'static, 'static> {
|
||||||
|
let command = SubCommand::with_name("build")
|
||||||
|
.about("Build the configuration")
|
||||||
|
.arg(Arg::with_name("verbose")
|
||||||
|
.short("v")
|
||||||
|
.long("verbose")
|
||||||
|
.help("Be verbose")
|
||||||
|
.long_help("Deactivates the progress spinner and prints every line of output.")
|
||||||
|
.takes_value(false));
|
||||||
|
|
||||||
|
util::register_common_args(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
|
||||||
|
let hive = Hive::from_config_arg(local_args).unwrap();
|
||||||
|
|
||||||
|
println!("Enumerating nodes...");
|
||||||
|
let deployment_info = hive.deployment_info().await.unwrap();
|
||||||
|
let all_nodes: Vec<String> = deployment_info.keys().cloned().collect();
|
||||||
|
|
||||||
|
let selected_nodes = match local_args.value_of("on") {
|
||||||
|
Some(filter) => {
|
||||||
|
util::filter_nodes(&all_nodes, filter)
|
||||||
|
}
|
||||||
|
None => all_nodes.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if selected_nodes.len() == 0 {
|
||||||
|
println!("No hosts matched. Exiting...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if selected_nodes.len() == all_nodes.len() {
|
||||||
|
println!("Building all node configurations...");
|
||||||
|
} else {
|
||||||
|
println!("Selected {} out of {} hosts. Building node configurations...", selected_nodes.len(), deployment_info.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
hive.build_selected(selected_nodes).await.unwrap();
|
||||||
|
|
||||||
|
println!("Success!");
|
||||||
|
}
|
2
src/command/mod.rs
Normal file
2
src/command/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod build;
|
||||||
|
pub mod apply;
|
118
src/deployment.rs
Normal file
118
src/deployment.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
use std::cmp::min;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget};
|
||||||
|
use futures::future::join_all;
|
||||||
|
|
||||||
|
use crate::nix::{DeploymentTask, DeploymentResult};
|
||||||
|
use crate::progress::get_spinner_styles;
|
||||||
|
|
||||||
|
/// User-facing deploy routine
|
||||||
|
pub async fn deploy(tasks: Vec<DeploymentTask<'static>>, max_parallelism: Option<usize>, progress_bar: bool) {
|
||||||
|
let parallelism = match max_parallelism {
|
||||||
|
Some(limit) => {
|
||||||
|
min(limit, tasks.len())
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
tasks.len()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let node_name_alignment = tasks.iter().map(|task| task.name().len()).max().unwrap();
|
||||||
|
|
||||||
|
let multi = Arc::new(MultiProgress::new());
|
||||||
|
let root_bar = Arc::new(multi.add(ProgressBar::new(tasks.len() as u64)));
|
||||||
|
multi.set_draw_target(ProgressDrawTarget::stderr_nohz());
|
||||||
|
|
||||||
|
{
|
||||||
|
let (spinner_style, _) = get_spinner_styles(node_name_alignment);
|
||||||
|
root_bar.set_message("Running...");
|
||||||
|
root_bar.set_style(spinner_style);
|
||||||
|
root_bar.inc(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tasks = Arc::new(Mutex::new(tasks));
|
||||||
|
let result_list: Arc<Mutex<Vec<DeploymentResult>>> = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
|
let mut futures = Vec::new();
|
||||||
|
|
||||||
|
for _ in 0..parallelism {
|
||||||
|
let tasks = tasks.clone();
|
||||||
|
let result_list = result_list.clone();
|
||||||
|
let multi = multi.clone();
|
||||||
|
let (spinner_style, failing_spinner_style) = get_spinner_styles(node_name_alignment);
|
||||||
|
|
||||||
|
let root_bar = root_bar.clone();
|
||||||
|
|
||||||
|
let future = tokio::spawn(async move {
|
||||||
|
// Perform tasks until there's none
|
||||||
|
loop {
|
||||||
|
let (task, remaining) = {
|
||||||
|
let mut tasks = tasks.lock().unwrap();
|
||||||
|
let task = tasks.pop();
|
||||||
|
let remaining = tasks.len();
|
||||||
|
(task, remaining)
|
||||||
|
};
|
||||||
|
|
||||||
|
if task.is_none() {
|
||||||
|
// We are donzo!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut task = task.unwrap();
|
||||||
|
|
||||||
|
let bar = multi.add(ProgressBar::new(100));
|
||||||
|
bar.set_style(spinner_style.clone());
|
||||||
|
bar.set_prefix(task.name());
|
||||||
|
bar.set_message("Starting...");
|
||||||
|
bar.inc(0);
|
||||||
|
|
||||||
|
if progress_bar {
|
||||||
|
task.set_progress_bar(&bar);
|
||||||
|
task.set_failing_spinner_style(failing_spinner_style.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
match task.execute().await {
|
||||||
|
Ok(result) => {
|
||||||
|
if !result.success() {
|
||||||
|
bar.abandon_with_message("Failed")
|
||||||
|
} else {
|
||||||
|
bar.finish_with_message(task.goal().success_str().unwrap());
|
||||||
|
}
|
||||||
|
bar.inc(0);
|
||||||
|
let mut result_list = result_list.lock().unwrap();
|
||||||
|
result_list.push(result);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("An error occurred while pushing to {}: {:?}", task.name(), e);
|
||||||
|
bar.set_style(failing_spinner_style.clone());
|
||||||
|
bar.abandon_with_message("Internal error");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
root_bar.inc(1);
|
||||||
|
|
||||||
|
if remaining == 0 {
|
||||||
|
root_bar.finish_with_message("Finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
futures.push(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
if progress_bar {
|
||||||
|
futures.push(tokio::task::spawn_blocking(move || {
|
||||||
|
multi.join().unwrap();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
join_all(futures).await;
|
||||||
|
|
||||||
|
let result_list = result_list.lock().unwrap();
|
||||||
|
for result in result_list.iter() {
|
||||||
|
if !result.success() {
|
||||||
|
println!("{}", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
src/eval.nix
Normal file
131
src/eval.nix
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
{ rawHive }:
|
||||||
|
with builtins;
|
||||||
|
let
|
||||||
|
defaultHive = {
|
||||||
|
# Will be set in defaultHiveMeta
|
||||||
|
network = {};
|
||||||
|
|
||||||
|
# Like in NixOps, there is a special host named `defaults`
|
||||||
|
# containing configurations that will be applied to all
|
||||||
|
# hosts.
|
||||||
|
defaults = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultHiveMeta = {
|
||||||
|
name = "hive";
|
||||||
|
description = "A Colmena Hive";
|
||||||
|
|
||||||
|
# Can be a path, a lambda, or an initialized Nixpkgs attrset
|
||||||
|
nixpkgs = <nixpkgs>;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Colmena-specific options
|
||||||
|
#
|
||||||
|
# Largely compatible with NixOps/Morph.
|
||||||
|
deploymentOptions = { name, lib, ... }:
|
||||||
|
let
|
||||||
|
types = lib.types;
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
deployment = {
|
||||||
|
targetHost = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
The target SSH node for deployment.
|
||||||
|
|
||||||
|
If not specified, the node's attribute name will be used.
|
||||||
|
'';
|
||||||
|
type = types.str;
|
||||||
|
default = name;
|
||||||
|
};
|
||||||
|
targetUser = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
The user to use to log into the remote node.
|
||||||
|
'';
|
||||||
|
type = types.str;
|
||||||
|
default = "root";
|
||||||
|
};
|
||||||
|
tags = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
A list of tags for the node.
|
||||||
|
|
||||||
|
Can be used to select a group of nodes for deployment.
|
||||||
|
'';
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
hiveMeta = {
|
||||||
|
network = defaultHiveMeta // (if rawHive ? network then rawHive.network else {});
|
||||||
|
};
|
||||||
|
hive = defaultHive // rawHive // hiveMeta;
|
||||||
|
|
||||||
|
pkgs = let
|
||||||
|
pkgConf = hive.network.nixpkgs;
|
||||||
|
in if typeOf pkgConf == "path" then
|
||||||
|
import pkgConf {}
|
||||||
|
else if typeOf pkgConf == "lambda" then
|
||||||
|
pkgConf {}
|
||||||
|
else if typeOf pkgConf == "set" then
|
||||||
|
pkgConf
|
||||||
|
else throw ''
|
||||||
|
network.nixpkgs must be one of:
|
||||||
|
|
||||||
|
- A path to Nixpkgs (e.g., <nixpkgs>)
|
||||||
|
- A Nixpkgs lambda (e.g., import <nixpkgs>)
|
||||||
|
- A Nixpkgs attribute set
|
||||||
|
'';
|
||||||
|
|
||||||
|
lib = pkgs.lib;
|
||||||
|
reservedNames = [ "defaults" "network" "meta" ];
|
||||||
|
|
||||||
|
evalNode = name: config: let
|
||||||
|
evalConfig = import (pkgs.path + "/nixos/lib/eval-config.nix");
|
||||||
|
in evalConfig {
|
||||||
|
system = currentSystem;
|
||||||
|
modules = [
|
||||||
|
deploymentOptions
|
||||||
|
hive.defaults
|
||||||
|
config
|
||||||
|
] ++ (import (pkgs.path + "/nixos/modules/module-list.nix"));
|
||||||
|
specialArgs = {
|
||||||
|
inherit name nodes;
|
||||||
|
modulesPath = pkgs.path + "/nixos/modules";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeNames = filter (name: ! elem name reservedNames) (attrNames hive);
|
||||||
|
|
||||||
|
# Exported attributes
|
||||||
|
#
|
||||||
|
# Functions are intended to be called with `nix-instantiate --eval --json`
|
||||||
|
|
||||||
|
nodes = listToAttrs (map (name: {
|
||||||
|
inherit name;
|
||||||
|
value = evalNode name hive.${name};
|
||||||
|
}) nodeNames);
|
||||||
|
|
||||||
|
deploymentInfoJson = toJSON (lib.attrsets.mapAttrs (name: eval: eval.config.deployment) nodes);
|
||||||
|
|
||||||
|
toplevel = lib.attrsets.mapAttrs (name: eval: eval.config.system.build.toplevel) nodes;
|
||||||
|
|
||||||
|
buildAll = buildSelected {
|
||||||
|
names = nodeNames;
|
||||||
|
};
|
||||||
|
buildSelected = { names ? null }: let
|
||||||
|
# Change in the order of the names should not cause a derivation to be created
|
||||||
|
selected = lib.attrsets.filterAttrs (name: _: elem name names) toplevel;
|
||||||
|
in derivation rec {
|
||||||
|
name = "colmena-${hive.network.name}";
|
||||||
|
system = currentSystem;
|
||||||
|
json = toJSON (lib.attrsets.mapAttrs (k: v: toString v) selected);
|
||||||
|
builder = pkgs.writeScript "${name}.sh" ''
|
||||||
|
#!/bin/sh
|
||||||
|
echo "$json" > $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
inherit nodes deploymentInfoJson toplevel buildAll buildSelected;
|
||||||
|
}
|
31
src/main.rs
Normal file
31
src/main.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use clap::{App, AppSettings};
|
||||||
|
|
||||||
|
mod nix;
|
||||||
|
mod command;
|
||||||
|
mod progress;
|
||||||
|
mod deployment;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>>{
|
||||||
|
let matches = App::new("Colmena")
|
||||||
|
.version("0.1.0")
|
||||||
|
.author("Zhaofeng Li <hello@zhaofeng.li>")
|
||||||
|
.about("NixOS deployment tool")
|
||||||
|
.global_setting(AppSettings::ColoredHelp)
|
||||||
|
.setting(AppSettings::ArgRequiredElseHelp)
|
||||||
|
.subcommand(command::apply::subcommand())
|
||||||
|
.subcommand(command::build::subcommand())
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
if let Some(sub_matches) = matches.subcommand_matches("build") {
|
||||||
|
command::build::run(&matches, &sub_matches).await;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if let Some(sub_matches) = matches.subcommand_matches("apply") {
|
||||||
|
command::apply::run(&matches, &sub_matches).await;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
583
src/nix.rs
Normal file
583
src/nix.rs
Normal file
|
@ -0,0 +1,583 @@
|
||||||
|
//! A Colmena Hive.
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::convert::AsRef;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{ExitStatus, Stdio};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use console::style;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use clap::ArgMatches;
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use snafu::Snafu;
|
||||||
|
use tempfile::{NamedTempFile, TempPath};
|
||||||
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
const HIVE_EVAL: &'static [u8] = include_bytes!("eval.nix");
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct DeploymentInfo {
|
||||||
|
#[serde(rename = "targetHost")]
|
||||||
|
target_host: String,
|
||||||
|
|
||||||
|
#[serde(rename = "targetUser")]
|
||||||
|
target_user: String,
|
||||||
|
tags: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DeploymentTask<'task> {
|
||||||
|
/// Name of the target.
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
/// The target to deploy to.
|
||||||
|
target: DeploymentInfo,
|
||||||
|
|
||||||
|
/// Nix store path to the system profile to deploy.
|
||||||
|
profile: PathBuf,
|
||||||
|
|
||||||
|
/// The goal of this deployment.
|
||||||
|
goal: DeploymentGoal,
|
||||||
|
|
||||||
|
/// A ProgressBar to show the deployment progress to the user.
|
||||||
|
progress: Option<&'task ProgressBar>,
|
||||||
|
|
||||||
|
/// The ProgressStyle to set when the deployment is failing.
|
||||||
|
failing_spinner_style: Option<ProgressStyle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum DeploymentGoal {
|
||||||
|
/// Push the closures only.
|
||||||
|
Push,
|
||||||
|
|
||||||
|
/// Make the configuration the boot default and activate now.
|
||||||
|
Switch,
|
||||||
|
|
||||||
|
/// Make the configuration the boot default.
|
||||||
|
Boot,
|
||||||
|
|
||||||
|
/// Activate the configuration, but don't make it the boot default.
|
||||||
|
Test,
|
||||||
|
|
||||||
|
/// Show what would be done if this configuration were activated.
|
||||||
|
DryActivate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeploymentGoal {
|
||||||
|
pub fn from_str(s: &str) -> Option<Self> {
|
||||||
|
match s {
|
||||||
|
"push" => Some(Self::Push),
|
||||||
|
"switch" => Some(Self::Switch),
|
||||||
|
"boot" => Some(Self::Boot),
|
||||||
|
"test" => Some(Self::Test),
|
||||||
|
"dry-activate" => Some(Self::DryActivate),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> Option<&'static str> {
|
||||||
|
use DeploymentGoal::*;
|
||||||
|
match self {
|
||||||
|
Push => None,
|
||||||
|
Switch => Some("switch"),
|
||||||
|
Boot => Some("boot"),
|
||||||
|
Test => Some("test"),
|
||||||
|
DryActivate => Some("dry-activate"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn success_str(&self) -> Option<&'static str> {
|
||||||
|
use DeploymentGoal::*;
|
||||||
|
match self {
|
||||||
|
Push => Some("Pushed"),
|
||||||
|
Switch => Some("Activation successful"),
|
||||||
|
Boot => Some("Will be activated next boot"),
|
||||||
|
Test => Some("Activation successful (test)"),
|
||||||
|
DryActivate => Some("Dry activation successful"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Results of a DeploymentTask to show to the user.
|
||||||
|
pub struct DeploymentResult {
|
||||||
|
name: String,
|
||||||
|
push_output: Option<String>,
|
||||||
|
push_successful: Option<bool>,
|
||||||
|
activate_output: Option<String>,
|
||||||
|
activate_successful: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeploymentResult {
|
||||||
|
fn new(name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
push_output: None,
|
||||||
|
push_successful: None,
|
||||||
|
activate_output: None,
|
||||||
|
activate_successful: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the deployment was successful overall.
|
||||||
|
pub fn success(&self) -> bool {
|
||||||
|
if let Some(push_successful) = self.push_successful {
|
||||||
|
if !push_successful {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(activate_successful) = self.activate_successful {
|
||||||
|
if !activate_successful {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_log(f: &mut fmt::Formatter<'_>, output: Option<&String>) -> fmt::Result {
|
||||||
|
if let Some(output) = output {
|
||||||
|
writeln!(f, "Last 10 lines of log:")?;
|
||||||
|
writeln!(f, "~~~~~~~~~~")?;
|
||||||
|
let lines: Vec<&str> = output.split("\n").collect();
|
||||||
|
|
||||||
|
let start = if lines.len() < 10 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
lines.len() - 10
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in start..lines.len() {
|
||||||
|
writeln!(f, "{}", lines[i])?;
|
||||||
|
}
|
||||||
|
writeln!(f, "~~~~~~~~~~")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DeploymentResult {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if let Some(push_successful) = self.push_successful {
|
||||||
|
if push_successful {
|
||||||
|
writeln!(f, "Deployment on node {} succeeded.", self.name)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "Deployment on node {} failed. ", self.name)?;
|
||||||
|
Self::dump_log(f, self.push_output.as_ref())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(activate_successful) = self.activate_successful {
|
||||||
|
if activate_successful {
|
||||||
|
writeln!(f, "Activation on node {} succeeded.", self.name)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "Activation on node {} failed.", self.name)?;
|
||||||
|
Self::dump_log(f, self.activate_output.as_ref())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Snafu)]
|
||||||
|
pub enum NixError {
|
||||||
|
#[snafu(display("I/O Error: {}", error))]
|
||||||
|
IoError { error: std::io::Error },
|
||||||
|
|
||||||
|
#[snafu(display("Nix returned invalid response: {}", output))]
|
||||||
|
BadOutput { output: String },
|
||||||
|
|
||||||
|
#[snafu(display("Nix exited with error code: {}", exit_code))]
|
||||||
|
NixFailure { exit_code: i32 },
|
||||||
|
|
||||||
|
#[snafu(display("Nix was interrupted"))]
|
||||||
|
NixKilled,
|
||||||
|
|
||||||
|
#[snafu(display("Nix Error: {}", message))]
|
||||||
|
Unknown { message: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type NixResult<T> = Result<T, NixError>;
|
||||||
|
|
||||||
|
pub struct Hive {
|
||||||
|
hive: PathBuf,
|
||||||
|
eval_nix: TempPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NixInstantiate<'hive> {
|
||||||
|
eval_nix: &'hive Path,
|
||||||
|
hive: &'hive Path,
|
||||||
|
expression: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'hive> NixInstantiate<'hive> {
|
||||||
|
fn new(eval_nix: &'hive Path, hive: &'hive Path, expression: String) -> Self {
|
||||||
|
Self {
|
||||||
|
eval_nix,
|
||||||
|
expression,
|
||||||
|
hive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instantiate(self) -> Command {
|
||||||
|
// FIXME: unwrap
|
||||||
|
// Technically filenames can be arbitrary byte strings (OsStr),
|
||||||
|
// but Nix may not like it...
|
||||||
|
|
||||||
|
let mut command = Command::new("nix-instantiate");
|
||||||
|
command
|
||||||
|
.arg("-E")
|
||||||
|
.arg(format!(
|
||||||
|
"with builtins; let eval = import {}; hive = eval {{ rawHive = import {}; }}; in {}",
|
||||||
|
self.eval_nix.to_str().unwrap(),
|
||||||
|
self.hive.to_str().unwrap(),
|
||||||
|
self.expression,
|
||||||
|
));
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval(self) -> Command {
|
||||||
|
let mut command = self.instantiate();
|
||||||
|
command.arg("--eval").arg("--json");
|
||||||
|
command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
trait NixCommand {
|
||||||
|
async fn passthrough(&mut self) -> NixResult<()>;
|
||||||
|
async fn capture_output(&mut self) -> NixResult<String>;
|
||||||
|
async fn capture_json<T>(&mut self) -> NixResult<T> where T: DeserializeOwned;
|
||||||
|
async fn capture_store_path(&mut self) -> NixResult<StorePath>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl NixCommand for Command {
|
||||||
|
/// Runs the command with stdout and stderr passed through to the user.
|
||||||
|
async fn passthrough(&mut self) -> NixResult<()> {
|
||||||
|
let exit = self
|
||||||
|
.spawn()
|
||||||
|
.map_err(map_io_error)?
|
||||||
|
.wait()
|
||||||
|
.await
|
||||||
|
.map_err(map_io_error)?;
|
||||||
|
|
||||||
|
if exit.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(match exit.code() {
|
||||||
|
Some(exit_code) => NixError::NixFailure { exit_code },
|
||||||
|
None => NixError::NixKilled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Captures output as a String.
|
||||||
|
async fn capture_output(&mut self) -> NixResult<String> {
|
||||||
|
// We want the user to see the raw errors
|
||||||
|
let output = self
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.map_err(map_io_error)?
|
||||||
|
.wait_with_output()
|
||||||
|
.await
|
||||||
|
.map_err(map_io_error)?;
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
// FIXME: unwrap
|
||||||
|
Ok(String::from_utf8(output.stdout).unwrap())
|
||||||
|
} else {
|
||||||
|
Err(match output.status.code() {
|
||||||
|
Some(exit_code) => NixError::NixFailure { exit_code },
|
||||||
|
None => NixError::NixKilled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Captures deserialized output from JSON.
|
||||||
|
async fn capture_json<T>(&mut self) -> NixResult<T> where T: DeserializeOwned {
|
||||||
|
let output = self.capture_output().await?;
|
||||||
|
serde_json::from_str(&output).map_err(|_| NixError::BadOutput {
|
||||||
|
output: output.clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Captures a single store path.
|
||||||
|
async fn capture_store_path(&mut self) -> NixResult<StorePath> {
|
||||||
|
let output = self.capture_output().await?;
|
||||||
|
Ok(StorePath(output.trim_end().into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Nix store path.
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct StorePath(PathBuf);
|
||||||
|
|
||||||
|
impl StorePath {
|
||||||
|
/// Builds the store path.
|
||||||
|
pub async fn realise(&self) -> NixResult<Vec<PathBuf>> {
|
||||||
|
Command::new("nix-store")
|
||||||
|
.arg("--realise")
|
||||||
|
.arg(&self.0)
|
||||||
|
.capture_output()
|
||||||
|
.await
|
||||||
|
.map(|paths| {
|
||||||
|
paths.lines().map(|p| p.into()).collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A serialized Nix expression.
|
||||||
|
///
|
||||||
|
/// Very hacky and involves an Import From Derivation, so should be
|
||||||
|
/// avoided as much as possible. But I suppose it's more robust than attempting
|
||||||
|
/// to generate Nix expressions directly or escaping a JSON string to strip
|
||||||
|
/// off Nix interpolation.
|
||||||
|
struct SerializedNixExpresssion {
|
||||||
|
json_file: TempPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerializedNixExpresssion {
|
||||||
|
pub fn new<'de, T>(data: T) -> NixResult<Self> where T: Serialize {
|
||||||
|
let mut tmp = NamedTempFile::new().map_err(map_io_error)?;
|
||||||
|
let json = serde_json::to_vec(&data).expect("Could not serialize data");
|
||||||
|
tmp.write_all(&json).map_err(map_io_error)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
json_file: tmp.into_temp_path(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expression(&self) -> String {
|
||||||
|
format!("(builtins.fromJSON (builtins.readFile {}))", self.json_file.to_str().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hive {
|
||||||
|
pub fn new<P: AsRef<Path>>(hive: P) -> NixResult<Self> {
|
||||||
|
let mut eval_nix = NamedTempFile::new().map_err(map_io_error)?;
|
||||||
|
eval_nix.write_all(HIVE_EVAL).map_err(map_io_error)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
hive: hive.as_ref().to_owned(),
|
||||||
|
eval_nix: eval_nix.into_temp_path(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_config_arg(args: &ArgMatches<'_>) -> NixResult<Self> {
|
||||||
|
let path = args.value_of("config").expect("The config arg should exist").to_owned();
|
||||||
|
let path = canonicalize_path(path);
|
||||||
|
|
||||||
|
Self::new(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a list of nodes in the hive
|
||||||
|
pub async fn nodes(&self) -> NixResult<Vec<String>> {
|
||||||
|
self.nix_instantiate("attrNames hive.nodes").eval()
|
||||||
|
.capture_json().await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve deployment info for all nodes
|
||||||
|
pub async fn deployment_info(&self) -> NixResult<HashMap<String, DeploymentInfo>> {
|
||||||
|
// FIXME: Really ugly :(
|
||||||
|
let s: String = self.nix_instantiate("hive.deploymentInfoJson").eval()
|
||||||
|
.capture_json().await?;
|
||||||
|
|
||||||
|
Ok(serde_json::from_str(&s).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds selected nodes
|
||||||
|
pub async fn build_selected(&self, nodes: Vec<String>) -> NixResult<HashMap<String, PathBuf>> {
|
||||||
|
let nodes_expr = SerializedNixExpresssion::new(&nodes)?;
|
||||||
|
let expr = format!("hive.buildSelected {{ names = {}; }}", nodes_expr.expression());
|
||||||
|
|
||||||
|
self.build_common(&expr).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds all node configurations
|
||||||
|
pub async fn build_all(&self) -> NixResult<HashMap<String, PathBuf>> {
|
||||||
|
self.build_common("hive.buildAll").await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds node configurations
|
||||||
|
///
|
||||||
|
/// Expects the resulting store path to point to a JSON file containing
|
||||||
|
/// a map of node name -> store path.
|
||||||
|
async fn build_common(&self, expression: &str) -> NixResult<HashMap<String, PathBuf>> {
|
||||||
|
let build: StorePath = self.nix_instantiate(expression).instantiate()
|
||||||
|
.capture_store_path().await?;
|
||||||
|
|
||||||
|
let realization = build.realise().await?;
|
||||||
|
assert!(realization.len() == 1);
|
||||||
|
|
||||||
|
let json = fs::read_to_string(&realization[0]).map_err(map_io_error)?;
|
||||||
|
let result_map: HashMap<String, PathBuf> = serde_json::from_str(&json)
|
||||||
|
.expect("Bad result from our own build routine");
|
||||||
|
|
||||||
|
Ok(result_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nix_instantiate(&self, expression: &str) -> NixInstantiate {
|
||||||
|
NixInstantiate::new(&self.eval_nix, &self.hive, expression.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'task> DeploymentTask<'task> {
|
||||||
|
pub fn new(name: String, target: DeploymentInfo, profile: PathBuf, goal: DeploymentGoal) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
target,
|
||||||
|
profile,
|
||||||
|
goal,
|
||||||
|
progress: None,
|
||||||
|
failing_spinner_style: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str { &self.name }
|
||||||
|
pub fn goal(&self) -> DeploymentGoal { self.goal }
|
||||||
|
|
||||||
|
/// Set the progress bar used during deployment.
|
||||||
|
pub fn set_progress_bar(&mut self, progress: &'task ProgressBar) {
|
||||||
|
self.progress = Some(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a spinner style to switch to when the deployment is failing.
|
||||||
|
pub fn set_failing_spinner_style(&mut self, style: ProgressStyle) {
|
||||||
|
self.failing_spinner_style = Some(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&mut self) -> NixResult<DeploymentResult> {
|
||||||
|
match self.goal {
|
||||||
|
DeploymentGoal::Push => {
|
||||||
|
self.push().await
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.push_and_activate().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn push(&mut self) -> NixResult<DeploymentResult> {
|
||||||
|
let mut result = DeploymentResult::new(self.name.clone());
|
||||||
|
|
||||||
|
// Issue of interest:
|
||||||
|
// https://github.com/NixOS/nix/issues?q=ipv6
|
||||||
|
let target = format!("{}@{}", self.target.target_user, self.target.target_host);
|
||||||
|
let mut command = Command::new("nix-copy-closure");
|
||||||
|
command
|
||||||
|
.arg("--to")
|
||||||
|
.arg("--gzip")
|
||||||
|
.arg("--include-outputs")
|
||||||
|
.arg("--use-substitutes")
|
||||||
|
.arg(&target)
|
||||||
|
.arg(&self.profile);
|
||||||
|
|
||||||
|
let (exit, output) = self.run_command(&mut command, false).await?;
|
||||||
|
|
||||||
|
if let Some(progress) = self.progress.as_mut() {
|
||||||
|
if !exit.success() {
|
||||||
|
if self.failing_spinner_style.is_some() {
|
||||||
|
let style = self.failing_spinner_style.as_ref().unwrap().clone();
|
||||||
|
progress.set_style(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push_successful = Some(exit.success());
|
||||||
|
result.push_output = output;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn push_and_activate(&mut self) -> NixResult<DeploymentResult> {
|
||||||
|
let mut result = self.push().await?;
|
||||||
|
|
||||||
|
if !result.success() {
|
||||||
|
// Don't go any further
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = format!("{}@{}", self.target.target_user, self.target.target_host);
|
||||||
|
let activation_command = format!("{}/bin/switch-to-configuration", self.profile.to_str().unwrap());
|
||||||
|
let mut command = Command::new("ssh");
|
||||||
|
command
|
||||||
|
.arg("-o")
|
||||||
|
.arg("StrictHostKeyChecking=accept-new")
|
||||||
|
.arg(&target)
|
||||||
|
.arg("--")
|
||||||
|
.arg(activation_command)
|
||||||
|
.arg(self.goal.as_str().unwrap());
|
||||||
|
|
||||||
|
let (exit, output) = self.run_command(&mut command, true).await?;
|
||||||
|
|
||||||
|
if let Some(progress) = self.progress.as_mut() {
|
||||||
|
if !exit.success() {
|
||||||
|
if self.failing_spinner_style.is_some() {
|
||||||
|
let style = self.failing_spinner_style.as_ref().unwrap().clone();
|
||||||
|
progress.set_style(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.activate_successful = Some(exit.success());
|
||||||
|
result.activate_output = output;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_command(&mut self, command: &mut Command, capture_stdout: bool) -> NixResult<(ExitStatus, Option<String>)> {
|
||||||
|
command.stdin(Stdio::null());
|
||||||
|
command.stderr(Stdio::piped());
|
||||||
|
|
||||||
|
if capture_stdout {
|
||||||
|
command.stdout(Stdio::piped());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut child = command.spawn().map_err(map_io_error)?;
|
||||||
|
|
||||||
|
let mut stderr = BufReader::new(child.stderr.as_mut().unwrap());
|
||||||
|
let mut output = String::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut line = String::new();
|
||||||
|
let len = stderr.read_line(&mut line).await.unwrap();
|
||||||
|
|
||||||
|
if len == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let trimmed = line.trim_end();
|
||||||
|
if let Some(progress) = self.progress.as_mut() {
|
||||||
|
progress.set_message(trimmed);
|
||||||
|
progress.inc(0);
|
||||||
|
} else {
|
||||||
|
println!("{} | {}", style(&self.name).cyan(), trimmed);
|
||||||
|
}
|
||||||
|
output += &line;
|
||||||
|
}
|
||||||
|
let exit = child.wait().await.map_err(map_io_error)?;
|
||||||
|
Ok((exit, Some(output)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_io_error(error: std::io::Error) -> NixError {
|
||||||
|
NixError::IoError { error }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canonicalize_path(path: String) -> PathBuf {
|
||||||
|
if !path.starts_with("/") {
|
||||||
|
format!("./{}", path).into()
|
||||||
|
} else {
|
||||||
|
path.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
15
src/progress.rs
Normal file
15
src/progress.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use indicatif::ProgressStyle;
|
||||||
|
|
||||||
|
pub fn get_spinner_styles(node_name_alignment: usize) -> (ProgressStyle, ProgressStyle) {
|
||||||
|
let template = format!("{{prefix:>{}.bold.dim}} {{spinner}} {{elapsed}} {{wide_msg}}", node_name_alignment);
|
||||||
|
|
||||||
|
(
|
||||||
|
ProgressStyle::default_spinner()
|
||||||
|
.tick_chars("🕛🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚✅")
|
||||||
|
.template(&template),
|
||||||
|
|
||||||
|
ProgressStyle::default_spinner()
|
||||||
|
.tick_chars("❌❌")
|
||||||
|
.template(&template),
|
||||||
|
)
|
||||||
|
}
|
40
src/util.rs
Normal file
40
src/util.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use clap::{Arg, App};
|
||||||
|
use glob::Pattern as GlobPattern;
|
||||||
|
|
||||||
|
pub fn filter_nodes(nodes: &Vec<String>, filter: &str) -> Vec<String> {
|
||||||
|
let filters: Vec<GlobPattern> = filter.split(",").map(|pattern| GlobPattern::new(pattern).unwrap()).collect();
|
||||||
|
|
||||||
|
if filters.len() > 0 {
|
||||||
|
nodes.iter().filter(|name| {
|
||||||
|
for filter in filters.iter() {
|
||||||
|
if filter.matches(name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}).cloned().collect()
|
||||||
|
} else {
|
||||||
|
nodes.to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_common_args<'a, 'b>(command: App<'a, 'b>) -> App<'a, 'b> {
|
||||||
|
command
|
||||||
|
.arg(Arg::with_name("config")
|
||||||
|
.short("f")
|
||||||
|
.long("config")
|
||||||
|
.help("Path to a Hive expression")
|
||||||
|
.default_value("hive.nix")
|
||||||
|
.required(true))
|
||||||
|
.arg(Arg::with_name("on")
|
||||||
|
.long("on")
|
||||||
|
.help("Select a list of machines")
|
||||||
|
.long_help(r#"The list is comma-separated and globs are supported.
|
||||||
|
Valid examples:
|
||||||
|
|
||||||
|
- host1,host2,host3
|
||||||
|
- edge-*
|
||||||
|
- edge-*,core-*"#)
|
||||||
|
.takes_value(true))
|
||||||
|
}
|
Loading…
Reference in a new issue