Compare commits
No commits in common. "b625498c54a61b242d7a7ac3fec44fc66d20b722" and "6d0bfeedaf08c2747944bb7c065e37c14ca5d78a" have entirely different histories.
b625498c54
...
6d0bfeedaf
|
@ -0,0 +1 @@
|
||||||
|
Cargo.lock -diff
|
|
@ -44,6 +44,15 @@ version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
|
@ -168,6 +177,33 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive-adhoc"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5283ac2881753c76c0892406705553f0d9ab30649f81e18964d3408f4501edb8"
|
||||||
|
dependencies = [
|
||||||
|
"derive-adhoc-macros",
|
||||||
|
"heck",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive-adhoc-macros"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c21b673a9b8c78c34908e6fcb42b922e11c4df2de5237f1c3f58d3285904a84b"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"itertools",
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"sha3",
|
||||||
|
"strum",
|
||||||
|
"syn 1.0.109",
|
||||||
|
"void",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deunicode"
|
name = "deunicode"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
|
@ -182,8 +218,15 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -251,6 +294,12 @@ version = "0.14.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humansize"
|
name = "humansize"
|
||||||
version = "2.1.3"
|
version = "2.1.3"
|
||||||
|
@ -309,6 +358,15 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.10"
|
version = "1.0.10"
|
||||||
|
@ -324,6 +382,15 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "keccak"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940"
|
||||||
|
dependencies = [
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -403,6 +470,12 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
|
@ -440,7 +513,7 @@ dependencies = [
|
||||||
"pest_meta",
|
"pest_meta",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -504,6 +577,16 @@ version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-crate"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.76"
|
version = "1.0.76"
|
||||||
|
@ -513,6 +596,18 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pwd-grp"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6955c41fd7e4283bdf6ff3e7218b7e3f8ef24c4236b31d22be050f4cfd5e2a2c"
|
||||||
|
dependencies = [
|
||||||
|
"derive-adhoc",
|
||||||
|
"libc",
|
||||||
|
"paste",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.35"
|
version = "1.0.35"
|
||||||
|
@ -596,6 +691,12 @@ version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.16"
|
version = "1.0.16"
|
||||||
|
@ -628,7 +729,7 @@ checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -666,6 +767,16 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha3"
|
||||||
|
version = "0.10.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
"keccak",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
@ -675,6 +786,12 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "siphasher"
|
name = "siphasher"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
|
@ -697,6 +814,45 @@ version = "1.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.25.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.48"
|
version = "2.0.48"
|
||||||
|
@ -747,7 +903,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -765,15 +921,35 @@ name = "tmpl"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argparse",
|
"argparse",
|
||||||
|
"blake2",
|
||||||
"file-mode",
|
"file-mode",
|
||||||
|
"pwd-grp",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
|
"shlex",
|
||||||
"tera",
|
"tera",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.19.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.40"
|
version = "0.1.40"
|
||||||
|
@ -794,7 +970,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -922,6 +1098,12 @@ version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "void"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.4.0"
|
version = "2.4.0"
|
||||||
|
@ -959,7 +1141,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.48",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -981,7 +1163,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.48",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
@ -1145,3 +1327,12 @@ name = "windows_x86_64_msvc"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.5.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
|
@ -7,9 +7,12 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
argparse = "0.2.2"
|
argparse = "0.2.2"
|
||||||
|
blake2 = "0.10.6"
|
||||||
file-mode = { version = "0.1.2", features = ["serde"] }
|
file-mode = { version = "0.1.2", features = ["serde"] }
|
||||||
|
pwd-grp = "0.1.1"
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_yaml = "0.9.30"
|
serde_yaml = "0.9.30"
|
||||||
|
shlex = "1.2.0"
|
||||||
tera = "1.19.1"
|
tera = "1.19.1"
|
||||||
thiserror = "1.0.56"
|
thiserror = "1.0.56"
|
||||||
tracing = { version = "0.1.40", features = ["log"] }
|
tracing = { version = "0.1.40", features = ["log"] }
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
max_width = 79
|
261
src/main.rs
261
src/main.rs
|
@ -1,16 +1,27 @@
|
||||||
mod model;
|
mod model;
|
||||||
|
mod templating;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::collections::HashSet;
|
||||||
use std::io::Read;
|
use std::io::{Read, Seek, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
use argparse::{ArgumentParser, Store, StoreOption};
|
use argparse::{ArgumentParser, Store, StoreOption, StoreTrue};
|
||||||
|
use blake2::{Blake2b512, Digest};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde_yaml::Value;
|
use serde_yaml::Value;
|
||||||
|
use shlex::Shlex;
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
use tracing::error;
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use model::Instructions;
|
use model::Instructions;
|
||||||
|
|
||||||
|
macro_rules! joinargs {
|
||||||
|
($args:ident) => {
|
||||||
|
shlex::join($args.iter().map(|s| s.as_str()))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
enum LoadError {
|
enum LoadError {
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
|
@ -19,6 +30,18 @@ enum LoadError {
|
||||||
Yaml(#[from] serde_yaml::Error),
|
Yaml(#[from] serde_yaml::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
enum SetPermissionsError {
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("User not found: {0}")]
|
||||||
|
UserNotFound(String),
|
||||||
|
#[error("Group not found: {0}")]
|
||||||
|
GroupNotFound(String),
|
||||||
|
#[error("Bad mode string")]
|
||||||
|
BadMode,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
enum ProcessInstructionsError {
|
enum ProcessInstructionsError {
|
||||||
#[error("Error loading templates: {0}")]
|
#[error("Error loading templates: {0}")]
|
||||||
|
@ -31,6 +54,7 @@ struct Args {
|
||||||
values: String,
|
values: String,
|
||||||
templates: Option<String>,
|
templates: Option<String>,
|
||||||
destdir: Option<PathBuf>,
|
destdir: Option<PathBuf>,
|
||||||
|
skip_hooks: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_args(args: &mut Args) {
|
fn parse_args(args: &mut Args) {
|
||||||
|
@ -53,6 +77,11 @@ fn parse_args(args: &mut Args) {
|
||||||
StoreOption,
|
StoreOption,
|
||||||
"Destination directory",
|
"Destination directory",
|
||||||
);
|
);
|
||||||
|
parser.refer(&mut args.skip_hooks).add_option(
|
||||||
|
&["--skip-hooks", "-S"],
|
||||||
|
StoreTrue,
|
||||||
|
"Skip running hooks after rendering templates",
|
||||||
|
);
|
||||||
parser.parse_args_or_exit();
|
parser.parse_args_or_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +111,14 @@ fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let templates = args.templates.as_deref().unwrap_or("templates");
|
let templates = args.templates.as_deref().unwrap_or("templates");
|
||||||
if let Err(e) = process_instructions(templates, instructions, values) {
|
let destdir = args.destdir.as_deref().unwrap_or(Path::new("/"));
|
||||||
|
if let Err(e) = process_instructions(
|
||||||
|
templates,
|
||||||
|
destdir,
|
||||||
|
instructions,
|
||||||
|
values,
|
||||||
|
args.skip_hooks,
|
||||||
|
) {
|
||||||
error!("Failed to process instructions: {}", e);
|
error!("Failed to process instructions: {}", e);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
@ -101,20 +137,223 @@ fn load_yaml<T: DeserializeOwned>(path: &str) -> Result<T, LoadError> {
|
||||||
|
|
||||||
fn process_instructions(
|
fn process_instructions(
|
||||||
templates: &str,
|
templates: &str,
|
||||||
|
destdir: impl AsRef<Path>,
|
||||||
instructions: Instructions,
|
instructions: Instructions,
|
||||||
values: Value,
|
values: Value,
|
||||||
|
skip_hooks: bool,
|
||||||
) -> Result<(), ProcessInstructionsError> {
|
) -> Result<(), ProcessInstructionsError> {
|
||||||
let tera = Tera::new(&format!("{}/**", templates))?;
|
let mut tera = Tera::new(&format!("{}/**", templates))?;
|
||||||
|
tera.register_filter(
|
||||||
|
templating::decrypt::NAME,
|
||||||
|
templating::decrypt::DecryptFilter,
|
||||||
|
);
|
||||||
|
|
||||||
let ctx = Context::from_serialize(&values)?;
|
let ctx = Context::from_serialize(&values)?;
|
||||||
|
let mut post_hooks = HashSet::new();
|
||||||
for i in instructions.render {
|
for i in instructions.render {
|
||||||
match tera.render(&i.template, &ctx) {
|
let out = match tera.render(&i.template, &ctx) {
|
||||||
Ok(o) => {
|
Ok(o) => o,
|
||||||
print!("{}", o);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to render template {}: {}", i.template, e);
|
let msg = format_error(&e);
|
||||||
|
error!("Failed to render template {}: {}", i.template, msg);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
let mut dest = PathBuf::from(destdir.as_ref());
|
||||||
|
dest.push(i.dest.strip_prefix("/").unwrap_or(i.dest.as_path()));
|
||||||
|
let orig_cksm = checksum(&dest).ok();
|
||||||
|
if let Err(e) = write_file(&dest, out.as_bytes()) {
|
||||||
|
error!("Failed to write output file {}: {}", dest.display(), e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let new_cksm = checksum(&dest).ok();
|
||||||
|
if orig_cksm != new_cksm {
|
||||||
|
info!("File {} was changed", dest.display());
|
||||||
|
if let Some(hooks) = i.hooks {
|
||||||
|
if let Some(changed) = hooks.changed {
|
||||||
|
for hook in changed {
|
||||||
|
if let Some(args) = parse_hook(&hook.run, &dest) {
|
||||||
|
if hook.immediate {
|
||||||
|
if !skip_hooks {
|
||||||
|
run_hook(args);
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
"Skipping hook: {}",
|
||||||
|
joinargs!(args)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
post_hooks.insert(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug!("File {} was NOT changed", dest.display());
|
||||||
|
}
|
||||||
|
if i.owner.is_some() || i.group.is_some() {
|
||||||
|
if let Err(e) =
|
||||||
|
chown(&dest, i.owner.as_deref(), i.group.as_deref())
|
||||||
|
{
|
||||||
|
error!("Failed to set ownership of {}: {}", dest.display(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(mode) = i.mode {
|
||||||
|
if let Err(e) = chmod(&dest, &mode) {
|
||||||
|
error!("Failed to set mode of {}: {}", dest.display(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for args in post_hooks {
|
||||||
|
if !skip_hooks {
|
||||||
|
run_hook(args);
|
||||||
|
} else {
|
||||||
|
info!("Skipping hook: {}", joinargs!(args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_file(
|
||||||
|
dest: impl AsRef<Path>,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
if let Some(p) = dest.as_ref().parent() {
|
||||||
|
if !p.exists() {
|
||||||
|
info!("Creating directory {}", p.display());
|
||||||
|
std::fs::create_dir_all(p)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!("Writing output: {}", dest.as_ref().display());
|
||||||
|
let mut f = std::fs::File::create(&dest)?;
|
||||||
|
f.write_all(data)?;
|
||||||
|
let size = f.stream_position()?;
|
||||||
|
debug!("Wrote output: {} ({} bytes)", dest.as_ref().display(), size);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chown(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
owner: Option<&str>,
|
||||||
|
group: Option<&str>,
|
||||||
|
) -> Result<(), SetPermissionsError> {
|
||||||
|
let uid = if let Some(owner) = owner {
|
||||||
|
debug!("Looking up UID for user {}", owner);
|
||||||
|
if let Some(pw) = pwd_grp::getpwnam(owner)? {
|
||||||
|
debug!("Found UID {} for user {}", pw.uid, owner);
|
||||||
|
Some(pw.uid)
|
||||||
|
} else {
|
||||||
|
return Err(SetPermissionsError::UserNotFound(owner.into()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let gid = if let Some(group) = group {
|
||||||
|
debug!("Looking up GID for group {}", group);
|
||||||
|
if let Some(gr) = pwd_grp::getgrnam(group)? {
|
||||||
|
debug!("Found GID {} for group {}", gr.gid, group);
|
||||||
|
Some(gr.gid)
|
||||||
|
} else {
|
||||||
|
return Err(SetPermissionsError::GroupNotFound(group.into()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
debug!(
|
||||||
|
"Setting ownership of {} to {:?} / {:?}",
|
||||||
|
path.as_ref().display(),
|
||||||
|
uid,
|
||||||
|
gid
|
||||||
|
);
|
||||||
|
Ok(std::os::unix::fs::chown(path, uid, gid)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chmod(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
mode: &str,
|
||||||
|
) -> Result<(), SetPermissionsError> {
|
||||||
|
let mut filemode = file_mode::Mode::empty();
|
||||||
|
filemode
|
||||||
|
.set_str(mode)
|
||||||
|
.map_err(|_| SetPermissionsError::BadMode)?;
|
||||||
|
debug!(
|
||||||
|
"Changing mode of {} to {:o}",
|
||||||
|
path.as_ref().display(),
|
||||||
|
filemode.mode()
|
||||||
|
);
|
||||||
|
let newmode = filemode.set_mode_path(&path)?;
|
||||||
|
info!("Set mode of {} to {:o}", path.as_ref().display(), newmode);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_error(e: &dyn std::error::Error) -> String {
|
||||||
|
// TODO replace this with std::error::Error::sources when it is stablized.
|
||||||
|
// https://github.com/rust-lang/rust/issues/58520
|
||||||
|
let mut msg = e.to_string();
|
||||||
|
if let Some(e) = e.source() {
|
||||||
|
msg.push_str(&format!(": {}", format_error(e)));
|
||||||
|
}
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checksum(path: impl AsRef<Path>) -> std::io::Result<Vec<u8>> {
|
||||||
|
let mut f = std::fs::File::open(path)?;
|
||||||
|
let mut blake = Blake2b512::new();
|
||||||
|
loop {
|
||||||
|
let mut buf = vec![0u8; 16384];
|
||||||
|
match f.read_exact(&mut buf) {
|
||||||
|
Ok(_) => blake.update(buf),
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||||
|
blake.update(buf);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(blake.finalize().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_hook(command: &str, path: &Path) -> Option<Vec<String>> {
|
||||||
|
let mut bad_path = false;
|
||||||
|
let args: Vec<_> = Shlex::new(command)
|
||||||
|
.map(|a| {
|
||||||
|
if a == "%s" {
|
||||||
|
if let Some(p) = path.as_os_str().to_str() {
|
||||||
|
p.into()
|
||||||
|
} else {
|
||||||
|
bad_path = true;
|
||||||
|
a
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if bad_path {
|
||||||
|
warn!("Cannot run hook: path is not valid UTF-8");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if args.is_empty() {
|
||||||
|
warn!(
|
||||||
|
"Invalid hook for {} ({}): empty argument list",
|
||||||
|
path.display(),
|
||||||
|
command
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_hook(args: Vec<String>) {
|
||||||
|
info!("Running hook: {}", joinargs!(args));
|
||||||
|
if let Err(e) = _run_hook(args) {
|
||||||
|
error!("Error running hook: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _run_hook(args: Vec<String>) -> std::io::Result<()> {
|
||||||
|
Command::new(&args[0]).args(&args[1..]).spawn()?.wait()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
13
src/model.rs
13
src/model.rs
|
@ -2,6 +2,18 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Hook {
|
||||||
|
pub run: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub immediate: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Hooks {
|
||||||
|
pub changed: Option<Vec<Hook>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct RenderInstruction {
|
pub struct RenderInstruction {
|
||||||
pub template: String,
|
pub template: String,
|
||||||
|
@ -9,6 +21,7 @@ pub struct RenderInstruction {
|
||||||
pub owner: Option<String>,
|
pub owner: Option<String>,
|
||||||
pub group: Option<String>,
|
pub group: Option<String>,
|
||||||
pub mode: Option<String>,
|
pub mode: Option<String>,
|
||||||
|
pub hooks: Option<Hooks>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
use tera::Value;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub(crate) enum DecryptError {
|
||||||
|
#[error("Decryption failed: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("Decryption failed: {0}")]
|
||||||
|
DecryptFailed(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) static NAME: &str = "decrypt";
|
||||||
|
|
||||||
|
pub(crate) struct DecryptFilter;
|
||||||
|
|
||||||
|
impl tera::Filter for DecryptFilter {
|
||||||
|
fn filter(
|
||||||
|
&self,
|
||||||
|
value: &Value,
|
||||||
|
args: &HashMap<String, Value>,
|
||||||
|
) -> tera::Result<Value> {
|
||||||
|
let data = match value {
|
||||||
|
Value::String(s) => s.clone().into_bytes(),
|
||||||
|
_ => {
|
||||||
|
return Err(tera::Error::msg(
|
||||||
|
"Can only decrypt string values",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let identity = match args.get("identity") {
|
||||||
|
Some(Value::String(s)) => s,
|
||||||
|
Some(_) => {
|
||||||
|
return Err(tera::Error::msg("identity must be string"));
|
||||||
|
}
|
||||||
|
None => "keys.txt",
|
||||||
|
};
|
||||||
|
let decrypted = Self::decrypt(&data, identity)
|
||||||
|
.map_err(|e| tera::Error::chain("Decryption failed", e))?;
|
||||||
|
Ok(Value::String(
|
||||||
|
String::from_utf8(decrypted)
|
||||||
|
.map_err(|_| tera::Error::msg("Invalid UTF-8 string"))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_safe(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecryptFilter {
|
||||||
|
fn decrypt(
|
||||||
|
data: &[u8],
|
||||||
|
identity: impl AsRef<Path>,
|
||||||
|
) -> Result<Vec<u8>, DecryptError> {
|
||||||
|
let mut cmd = Command::new("age")
|
||||||
|
.arg("-d")
|
||||||
|
.arg("-i")
|
||||||
|
.arg(identity.as_ref())
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
if let Some(mut stdin) = cmd.stdin.take() {
|
||||||
|
stdin.write_all(data)?;
|
||||||
|
drop(stdin);
|
||||||
|
}
|
||||||
|
let mut decrypted = vec![];
|
||||||
|
if let Some(stdout) = cmd.stdout.as_mut() {
|
||||||
|
stdout.read_to_end(&mut decrypted)?;
|
||||||
|
}
|
||||||
|
let status = cmd.wait()?;
|
||||||
|
if status.success() {
|
||||||
|
Ok(decrypted)
|
||||||
|
} else {
|
||||||
|
let mut error = vec![];
|
||||||
|
if let Some(stderr) = cmd.stderr.as_mut() {
|
||||||
|
let _ = stderr.read_to_end(&mut error);
|
||||||
|
}
|
||||||
|
let msg = String::from_utf8_lossy(&error[..]).trim_end().into();
|
||||||
|
Err(DecryptError::DecryptFailed(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod decrypt;
|
Loading…
Reference in New Issue