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"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
|
@ -168,6 +177,33 @@ dependencies = [
|
|||
"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]]
|
||||
name = "deunicode"
|
||||
version = "1.4.2"
|
||||
|
@ -182,8 +218,15 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
|
@ -251,6 +294,12 @@ version = "0.14.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "2.1.3"
|
||||
|
@ -309,6 +358,15 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.10"
|
||||
|
@ -324,6 +382,15 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
@ -403,6 +470,12 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
|
@ -440,7 +513,7 @@ dependencies = [
|
|||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -504,6 +577,16 @@ version = "0.2.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.76"
|
||||
|
@ -513,6 +596,18 @@ dependencies = [
|
|||
"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]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
|
@ -596,6 +691,12 @@ version = "0.8.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.16"
|
||||
|
@ -628,7 +729,7 @@ checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -666,6 +767,16 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha3"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
|
@ -675,6 +786,12 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
|
@ -697,6 +814,45 @@ version = "1.11.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
|
@ -747,7 +903,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -765,15 +921,35 @@ name = "tmpl"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"argparse",
|
||||
"blake2",
|
||||
"file-mode",
|
||||
"pwd-grp",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"shlex",
|
||||
"tera",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"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]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
|
@ -794,7 +970,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -922,6 +1098,12 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
|
@ -959,7 +1141,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.48",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
@ -981,7 +1163,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.48",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
@ -1145,3 +1327,12 @@ name = "windows_x86_64_msvc"
|
|||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]
|
||||
argparse = "0.2.2"
|
||||
blake2 = "0.10.6"
|
||||
file-mode = { version = "0.1.2", features = ["serde"] }
|
||||
pwd-grp = "0.1.1"
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
serde_yaml = "0.9.30"
|
||||
shlex = "1.2.0"
|
||||
tera = "1.19.1"
|
||||
thiserror = "1.0.56"
|
||||
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 templating;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::io::Read;
|
||||
use std::collections::HashSet;
|
||||
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_yaml::Value;
|
||||
use shlex::Shlex;
|
||||
use tera::{Context, Tera};
|
||||
use tracing::error;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use model::Instructions;
|
||||
|
||||
macro_rules! joinargs {
|
||||
($args:ident) => {
|
||||
shlex::join($args.iter().map(|s| s.as_str()))
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum LoadError {
|
||||
#[error("{0}")]
|
||||
|
@ -19,6 +30,18 @@ enum LoadError {
|
|||
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)]
|
||||
enum ProcessInstructionsError {
|
||||
#[error("Error loading templates: {0}")]
|
||||
|
@ -31,6 +54,7 @@ struct Args {
|
|||
values: String,
|
||||
templates: Option<String>,
|
||||
destdir: Option<PathBuf>,
|
||||
skip_hooks: bool,
|
||||
}
|
||||
|
||||
fn parse_args(args: &mut Args) {
|
||||
|
@ -53,6 +77,11 @@ fn parse_args(args: &mut Args) {
|
|||
StoreOption,
|
||||
"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();
|
||||
}
|
||||
|
||||
|
@ -82,7 +111,14 @@ fn main() {
|
|||
}
|
||||
};
|
||||
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);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
@ -101,20 +137,223 @@ fn load_yaml<T: DeserializeOwned>(path: &str) -> Result<T, LoadError> {
|
|||
|
||||
fn process_instructions(
|
||||
templates: &str,
|
||||
destdir: impl AsRef<Path>,
|
||||
instructions: Instructions,
|
||||
values: Value,
|
||||
skip_hooks: bool,
|
||||
) -> 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 mut post_hooks = HashSet::new();
|
||||
for i in instructions.render {
|
||||
match tera.render(&i.template, &ctx) {
|
||||
Ok(o) => {
|
||||
print!("{}", o);
|
||||
}
|
||||
let out = match tera.render(&i.template, &ctx) {
|
||||
Ok(o) => o,
|
||||
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(())
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
#[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)]
|
||||
pub struct RenderInstruction {
|
||||
pub template: String,
|
||||
|
@ -9,6 +21,7 @@ pub struct RenderInstruction {
|
|||
pub owner: Option<String>,
|
||||
pub group: Option<String>,
|
||||
pub mode: Option<String>,
|
||||
pub hooks: Option<Hooks>,
|
||||
}
|
||||
|
||||
#[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