163 lines
4.1 KiB
Rust
163 lines
4.1 KiB
Rust
![]() |
use clap::{CommandFactory, Parser, Subcommand};
|
||
|
use eyre::{format_err, Result};
|
||
|
use log::{debug, error};
|
||
|
use tokio::fs;
|
||
|
|
||
|
#[derive(Parser)]
|
||
|
#[command()]
|
||
|
struct Cli {
|
||
|
#[command(subcommand)]
|
||
|
command: Command,
|
||
|
}
|
||
|
|
||
|
#[derive(Subcommand)]
|
||
|
enum Command {
|
||
|
ApplyConfig {
|
||
|
/// config file to use
|
||
|
#[arg(default_value = "config.yaml")]
|
||
|
config: String,
|
||
|
/// glob filters to select files to apply
|
||
|
#[arg(short = 'F', long)]
|
||
|
filters: Vec<String>,
|
||
|
/// path prefix (aka chroot)
|
||
|
#[arg(short = 'P', long, default_value = "/")]
|
||
|
prefix: String,
|
||
|
},
|
||
|
Logger {
|
||
|
#[arg(long, short = 'p', default_value = "/var/log")]
|
||
|
log_path: String,
|
||
|
#[arg(long, short = 'n')]
|
||
|
log_name: Option<String>,
|
||
|
#[arg(long)]
|
||
|
with_prefix: bool,
|
||
|
command: String,
|
||
|
args: Vec<String>,
|
||
|
},
|
||
|
Log {
|
||
|
#[arg(long, short = 'p', default_value = "/var/log")]
|
||
|
log_path: String,
|
||
|
log_name: String,
|
||
|
since: Option<String>,
|
||
|
until: Option<String>,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
#[tokio::main(flavor = "current_thread")]
|
||
|
async fn main() -> Result<()> {
|
||
|
clap_complete::CompleteEnv::with_factory(Cli::command).complete();
|
||
|
|
||
|
let cli = Cli::parse();
|
||
|
|
||
|
env_logger::builder()
|
||
|
.parse_filters("info")
|
||
|
.parse_default_env()
|
||
|
.init();
|
||
|
|
||
|
use Command as C;
|
||
|
match cli.command {
|
||
|
C::ApplyConfig {
|
||
|
config,
|
||
|
filters,
|
||
|
prefix,
|
||
|
} => {
|
||
|
let filters = parse_globs(&filters)?;
|
||
|
apply_config(&config, &filters, &prefix).await
|
||
|
}
|
||
|
C::Logger {
|
||
|
ref log_path,
|
||
|
ref log_name,
|
||
|
with_prefix,
|
||
|
command,
|
||
|
args,
|
||
|
} => {
|
||
|
let command = command.as_str();
|
||
|
let log_name = log_name.as_deref().unwrap_or_else(|| basename(command));
|
||
|
|
||
|
dkl::logger::Logger {
|
||
|
log_path,
|
||
|
log_name,
|
||
|
with_prefix,
|
||
|
}
|
||
|
.run(command, &args)
|
||
|
.await
|
||
|
}
|
||
|
C::Log {
|
||
|
log_path,
|
||
|
log_name,
|
||
|
since,
|
||
|
until,
|
||
|
} => {
|
||
|
let since = parse_ts_arg(since)?;
|
||
|
let until = parse_ts_arg(until)?;
|
||
|
|
||
|
let mut files = dkl::logger::log_files(&log_path, &log_name).await?;
|
||
|
files.sort();
|
||
|
|
||
|
let mut out = tokio::io::stdout();
|
||
|
|
||
|
for f in files {
|
||
|
if !since.is_none_or(|since| f.timestamp >= since) {
|
||
|
continue;
|
||
|
}
|
||
|
if !until.is_none_or(|until| f.timestamp <= until) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
debug!("{f:?}");
|
||
|
f.copy_to(&mut out).await?;
|
||
|
}
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn parse_ts_arg(ts: Option<String>) -> Result<Option<dkl::logger::Timestamp>> {
|
||
|
match ts {
|
||
|
None => Ok(None),
|
||
|
Some(ts) => {
|
||
|
let ts = dkl::logger::parse_ts(&ts)
|
||
|
.map_err(|e| format_err!("invalid timestamp: {ts}: {e}"))?;
|
||
|
Ok(Some(ts))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn basename(path: &str) -> &str {
|
||
|
path.rsplit_once('/').map_or(path, |split| split.1)
|
||
|
}
|
||
|
|
||
|
async fn apply_config(config_file: &str, filters: &[glob::Pattern], chroot: &str) -> Result<()> {
|
||
|
let config = fs::read_to_string(config_file).await?;
|
||
|
let config: dkl::Config = serde_yaml::from_str(&config)?;
|
||
|
|
||
|
let files = if filters.is_empty() {
|
||
|
config.files
|
||
|
} else {
|
||
|
(config.files.into_iter())
|
||
|
.filter(|f| filters.iter().any(|filter| filter.matches(&f.path)))
|
||
|
.collect()
|
||
|
};
|
||
|
|
||
|
dkl::apply::files(&files, chroot).await
|
||
|
}
|
||
|
|
||
|
fn parse_globs(filters: &[String]) -> Result<Vec<glob::Pattern>> {
|
||
|
let mut errors = false;
|
||
|
let filters = (filters.iter())
|
||
|
.filter_map(|s| {
|
||
|
glob::Pattern::new(s)
|
||
|
.inspect_err(|e| {
|
||
|
error!("invalid filter: {s:?}: {e}");
|
||
|
errors = true;
|
||
|
})
|
||
|
.ok()
|
||
|
})
|
||
|
.collect();
|
||
|
|
||
|
if errors {
|
||
|
return Err(format_err!("invalid filters"));
|
||
|
}
|
||
|
|
||
|
Ok(filters)
|
||
|
}
|