From 47d138a9c6be0e4526518a24dde281f548fee750 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Thu, 11 Jan 2024 13:36:39 -0600 Subject: [PATCH] Render templates to file and set permissions --- Cargo.lock | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/main.rs | 121 ++++++++++++++++++++++++++++++++--- 3 files changed, 287 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bf4fcf..1beca79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,6 +168,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" @@ -184,6 +211,12 @@ dependencies = [ "crypto-common", ] +[[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 +284,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 +348,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 +372,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 +460,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 +503,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -504,6 +567,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 +586,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 +681,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 +719,7 @@ checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -666,6 +757,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" @@ -697,6 +798,39 @@ 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 = "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 +881,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -766,6 +900,7 @@ version = "0.1.0" dependencies = [ "argparse", "file-mode", + "pwd-grp", "serde", "serde_yaml", "tera", @@ -774,6 +909,23 @@ dependencies = [ "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 +946,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -922,6 +1074,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 +1117,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -981,7 +1139,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1145,3 +1303,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", +] diff --git a/Cargo.toml b/Cargo.toml index 5918155..53841ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] argparse = "0.2.2" 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" tera = "1.19.1" diff --git a/src/main.rs b/src/main.rs index fedfcc0..17d1a16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ mod model; -use std::path::PathBuf; -use std::io::Read; +use std::io::{Read, Seek, Write}; +use std::path::{Path, PathBuf}; use argparse::{ArgumentParser, Store, StoreOption}; use serde::de::DeserializeOwned; use serde_yaml::Value; use tera::{Context, Tera}; -use tracing::error; +use tracing::{debug, error, info}; use model::Instructions; @@ -19,6 +19,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}")] @@ -82,7 +94,10 @@ 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) + { error!("Failed to process instructions: {}", e); std::process::exit(1); } @@ -101,20 +116,110 @@ fn load_yaml(path: &str) -> Result { fn process_instructions( templates: &str, + destdir: impl AsRef, instructions: Instructions, values: Value, ) -> Result<(), ProcessInstructionsError> { let tera = Tera::new(&format!("{}/**", templates))?; let ctx = Context::from_serialize(&values)?; 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); + continue; + } + }; + let mut dest = PathBuf::from(destdir.as_ref()); + dest.push(i.dest.strip_prefix("/").unwrap_or(i.dest.as_path())); + if let Err(e) = write_file(&dest, out.as_bytes()) { + error!("Failed to write output file {}: {}", dest.display(), e); + continue; + } + 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); } } } Ok(()) } + +fn write_file( + dest: impl AsRef, + 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()?; + info!("Wrote output: {} ({} bytes)", dest.as_ref().display(), size); + Ok(()) +} + +fn chown( + path: impl AsRef, + 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, + 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(()) +}