Compare commits

...

7 Commits

9 changed files with 430 additions and 197 deletions

541
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@ codegen-units = 1
[dependencies] [dependencies]
async-compression = { version = "0.4.27", features = ["tokio", "zstd"] } async-compression = { version = "0.4.27", features = ["tokio", "zstd"] }
base32 = "0.5.1" base32 = "0.5.1"
base64 = "0.22.1"
bytes = "1.10.1" bytes = "1.10.1"
chrono = { version = "0.4.41", default-features = false, features = ["clock", "now"] } chrono = { version = "0.4.41", default-features = false, features = ["clock", "now"] }
clap = { version = "4.5.40", features = ["derive", "env"] } clap = { version = "4.5.40", features = ["derive", "env"] }

View File

@ -3,21 +3,61 @@ use log::info;
use std::path::Path; use std::path::Path;
use tokio::fs; use tokio::fs;
pub async fn files(files: &[crate::File], root: &str) -> Result<()> { use crate::base64_decode;
pub async fn files(files: &[crate::File], root: &str, dry_run: bool) -> Result<()> {
for file in files { for file in files {
let path = chroot(root, &file.path); let path = chroot(root, &file.path);
let path = Path::new(&path); let path = Path::new(&path);
if let Some(parent) = path.parent() { if !dry_run && let Some(parent) = path.parent() {
fs::create_dir_all(parent).await?; fs::create_dir_all(parent).await?;
} }
use crate::FileKind as K; use crate::FileKind as K;
match &file.kind { match &file.kind {
K::Content(content) => fs::write(path, content.as_bytes()).await?, K::Content(content) => {
K::Dir(true) => fs::create_dir(path).await?, if dry_run {
info!(
"would create {} ({} bytes from content)",
file.path,
content.len()
);
} else {
fs::write(path, content.as_bytes()).await?;
}
}
K::Content64(content) => {
let content = base64_decode(content)?;
if dry_run {
info!(
"would create {} ({} bytes from content64)",
file.path,
content.len()
);
} else {
fs::write(path, content).await?
}
}
K::Dir(true) => {
if dry_run {
info!("would create {} (directory)", file.path);
} else {
fs::create_dir(path).await?;
}
}
K::Dir(false) => {} // shouldn't happen, but semantic is to ignore K::Dir(false) => {} // shouldn't happen, but semantic is to ignore
K::Symlink(tgt) => fs::symlink(tgt, path).await?, K::Symlink(tgt) => {
if dry_run {
info!("would create {} (symlink to {})", file.path, tgt);
} else {
fs::symlink(tgt, path).await?;
}
}
}
if dry_run {
continue;
} }
match file.kind { match file.kind {

View File

@ -24,6 +24,9 @@ enum Command {
/// path prefix (aka chroot) /// path prefix (aka chroot)
#[arg(short = 'P', long, default_value = "/")] #[arg(short = 'P', long, default_value = "/")]
prefix: String, prefix: String,
/// don't really write files
#[arg(long)]
dry_run: bool,
}, },
Logger { Logger {
/// Path where the logs are stored /// Path where the logs are stored
@ -97,9 +100,10 @@ async fn main() -> Result<()> {
config, config,
filters, filters,
prefix, prefix,
dry_run,
} => { } => {
let filters = parse_globs(&filters)?; let filters = parse_globs(&filters)?;
apply_config(&config, &filters, &prefix).await apply_config(&config, &filters, &prefix, dry_run).await
} }
C::Logger { C::Logger {
ref log_path, ref log_path,
@ -156,7 +160,12 @@ async fn main() -> Result<()> {
} }
} }
async fn apply_config(config_file: &str, filters: &[glob::Pattern], chroot: &str) -> Result<()> { async fn apply_config(
config_file: &str,
filters: &[glob::Pattern],
chroot: &str,
dry_run: bool,
) -> Result<()> {
let config = fs::read_to_string(config_file).await?; let config = fs::read_to_string(config_file).await?;
let config: dkl::Config = serde_yaml::from_str(&config)?; let config: dkl::Config = serde_yaml::from_str(&config)?;
@ -168,7 +177,7 @@ async fn apply_config(config_file: &str, filters: &[glob::Pattern], chroot: &str
.collect() .collect()
}; };
dkl::apply::files(&files, chroot).await dkl::apply::files(&files, chroot, dry_run).await
} }
#[derive(Subcommand)] #[derive(Subcommand)]

View File

@ -105,7 +105,7 @@ impl Default for SSHServer {
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct LvmVG { pub struct LvmVG {
#[serde(alias = "vg")] #[serde(rename = "vg", alias = "name")]
pub name: String, pub name: String,
pub pvs: LvmPV, pub pvs: LvmPV,

View File

@ -170,6 +170,8 @@ pub struct Config {
pub host_templates: Vec<HostConfig>, pub host_templates: Vec<HostConfig>,
#[serde(default, rename = "SSLConfig")] #[serde(default, rename = "SSLConfig")]
pub ssl_config: String, pub ssl_config: String,
#[serde(default, deserialize_with = "deserialize_null_as_default")]
pub extra_ca_certs: Map<String, String>,
} }
// compensate for go's encoder pitfalls // compensate for go's encoder pitfalls

View File

@ -1,4 +1,4 @@
use eyre::{format_err, Result}; use eyre::{Result, format_err};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use std::path::PathBuf; use std::path::PathBuf;
use tokio::{fs, io::AsyncWriteExt, process::Command}; use tokio::{fs, io::AsyncWriteExt, process::Command};

View File

@ -1,10 +1,10 @@
pub mod apply; pub mod apply;
pub mod proxy;
pub mod bootstrap; pub mod bootstrap;
pub mod dls; pub mod dls;
pub mod dynlay; pub mod dynlay;
pub mod fs; pub mod fs;
pub mod logger; pub mod logger;
pub mod proxy;
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)] #[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct Config { pub struct Config {
@ -63,9 +63,10 @@ pub struct File {
} }
#[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "lowercase")]
pub enum FileKind { pub enum FileKind {
Content(String), Content(String),
Content64(String),
Symlink(String), Symlink(String),
Dir(bool), Dir(bool),
} }
@ -80,3 +81,8 @@ impl Config {
self.files.iter().find(|f| f.path == path) self.files.iter().find(|f| f.path == path)
} }
} }
pub fn base64_decode(s: &str) -> Result<Vec<u8>, base64::DecodeError> {
use base64::{Engine, prelude::BASE64_STANDARD_NO_PAD as B64};
B64.decode(s.trim_end_matches('='))
}

View File

@ -1,6 +1,6 @@
use async_compression::tokio::write::{ZstdDecoder, ZstdEncoder}; use async_compression::tokio::write::{ZstdDecoder, ZstdEncoder};
use chrono::{DurationRound, TimeDelta, Utc}; use chrono::{DurationRound, TimeDelta, Utc};
use eyre::{format_err, Result}; use eyre::{Result, format_err};
use log::{debug, error, warn}; use log::{debug, error, warn};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Stdio; use std::process::Stdio;
@ -9,7 +9,7 @@ use tokio::{
io::{self, AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter}, io::{self, AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter},
process, process,
sync::mpsc, sync::mpsc,
time::{sleep, Duration}, time::{Duration, sleep},
}; };
pub type Timestamp = chrono::DateTime<Utc>; pub type Timestamp = chrono::DateTime<Utc>;