Files
rsnmpagent/src/rsnmplib/config.rs
2025-12-23 11:11:26 +01:00

271 lines
8.0 KiB
Rust

use clap::Parser;
use figment::{
Figment,
providers::{Format, Serialized, Yaml},
};
use log::warn;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::path::PathBuf;
#[allow(unused)]
use flexi_logger::{
FileSpec, LogSpecification, Logger, LoggerHandle, colored_with_thread, with_thread,
writers::{SyslogConnection, SyslogLineHeader, SyslogWriter},
};
#[derive(Deserialize, Serialize, Debug)]
pub(crate) struct DataFunctionsInterval {
pub(crate) log_debug_watcher: Option<u64>,
multipath: Option<u64>,
meminfo: Option<u64>,
processes: Option<u64>,
bonding: Option<u64>,
filesum: Option<u64>,
}
impl Default for DataFunctionsInterval {
fn default() -> Self {
DataFunctionsInterval {
log_debug_watcher: Some(5),
multipath: Some(60),
meminfo: Some(30),
processes: Some(30),
bonding: Some(30),
filesum: Some(60),
}
}
}
impl DataFunctionsInterval {
pub fn meminfo(&self) -> Option<u64> {
self.meminfo
}
pub fn processes(&self) -> Option<u64> {
self.processes
}
pub fn filesum(&self) -> Option<u64> {
self.filesum
}
pub fn bonding(&self) -> Option<u64> {
self.bonding
}
pub fn multipath(&self) -> Option<u64> {
self.multipath
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub(crate) struct DataFunctionsFilesum {
pub(crate) passwd: Option<String>,
pub(crate) shadow: Option<String>,
pub(crate) group: Option<String>,
pub(crate) authorized_keys: Option<String>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct DataFunctionsExtra {
multipath: Option<String>,
bonding: Option<String>,
filesum: DataFunctionsFilesum,
}
impl Default for DataFunctionsExtra {
fn default() -> Self {
DataFunctionsExtra {
multipath: None,
bonding: None,
filesum: DataFunctionsFilesum {
passwd: None,
shadow: None,
group: None,
authorized_keys: None,
},
}
}
}
impl DataFunctionsExtra {
pub(crate) fn filesum(&self) -> DataFunctionsFilesum {
self.filesum.clone()
}
pub(crate) fn multipath(&self) -> Option<String> {
self.multipath.clone()
}
pub(crate) fn bonding(&self) -> Option<String> {
self.bonding.clone()
}
}
#[derive(Deserialize, Serialize, Debug, Clone, clap::ValueEnum)]
enum LogConfig {
None,
StdErr,
Syslog,
Logfile,
LogfileAndSyslog,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct AppConfig {
logoutput: LogConfig,
logdir: String,
logfile_basename: String,
loglevel: String,
pub(crate) base_oid: String,
pub(crate) debug_log_marker: PathBuf,
pub(crate) intervals: DataFunctionsInterval,
pub(crate) extra_config: DataFunctionsExtra,
}
impl Default for AppConfig {
fn default() -> Self {
AppConfig {
logoutput: LogConfig::Logfile,
logdir: "log".to_string(),
logfile_basename: "rsnmpagent".to_string(),
loglevel: "info".to_string(),
base_oid: ".1.3.6.1.4.1.8072.9999.9999".to_string(),
debug_log_marker: "debug.marker".to_string().into(),
intervals: DataFunctionsInterval::default(),
extra_config: DataFunctionsExtra::default(),
}
}
}
impl AppConfig {
pub fn base_oid(&self) -> &str {
&self.base_oid
}
}
// Define the struct to hold the command-line arguments.
#[derive(Parser, Deserialize, Serialize, Debug)]
#[command(author, version, about = "rsnmpd, snmpd passpersist extension", long_about = None)]
struct Cli {
/// Optional path to a configuration file.
#[arg(short, long, value_name = "FILENAME", default_value = "config.yml")]
configfile: PathBuf,
#[arg(short, long, value_name = "LOGOUTPUT", value_enum, default_value_t = LogConfig::Logfile)]
logoutput: LogConfig,
#[arg(short, long)]
show_parsed_config: bool,
}
pub fn build_config() -> Result<AppConfig, Box<dyn Error>> {
let cli = Cli::parse();
#[cfg(debug_assertions)]
if cli.show_parsed_config {
eprintln!("Parsed config line options: {:#?}", cli);
}
let config: AppConfig = Figment::new()
.merge(Serialized::defaults(AppConfig::default()))
.merge(Yaml::file(&cli.configfile))
.merge(Serialized::defaults(&cli))
.extract()?;
if cli.show_parsed_config {
eprintln!("Loaded configuration: {:#?}", config);
}
Ok(config)
}
pub fn start_logging(config: &AppConfig) -> Result<LoggerHandle, Box<dyn Error>> {
let r = match config.logoutput {
LogConfig::None => {
let handle = Logger::with(LogSpecification::off()).do_not_log().start()?;
warn!("Starting None logging config");
handle
}
LogConfig::StdErr => {
let handle = Logger::try_with_str(&config.loglevel)?
.format(colored_with_thread)
.start()?;
warn!("Starting StdErr logging config with level: {}", config.loglevel);
handle
}
LogConfig::Syslog => {
#[cfg(target_os = "windows")]
{
Err("Syslog in windows not implemented")?
}
#[cfg(target_os = "linux")]
{
let syslog_writer = SyslogWriter::builder(
SyslogConnection::syslog_call(),
SyslogLineHeader::Rfc3164,
flexi_logger::writers::SyslogFacility::SystemDaemons,
)
.build()?;
let handle = Logger::try_with_str(&config.loglevel)?
.log_to_writer(syslog_writer)
.format(with_thread)
.start()?;
warn!("Starting Syslog logging config with level: {}", config.loglevel);
handle
}
}
LogConfig::Logfile => {
let handle = Logger::try_with_str(&config.loglevel)?
.log_to_file(
FileSpec::default()
.directory(&config.logdir)
.basename(&config.logfile_basename)
.suppress_timestamp(),
)
.append()
.format(with_thread)
.start()?;
warn!(
"Starting Logfile logging to {} with level: {}",
config.logdir, config.loglevel
);
handle
}
LogConfig::LogfileAndSyslog => {
#[cfg(target_os = "windows")]
{
eprintln!(
"Starting LogfileAndSyslog logging to {} with level: {}",
config.logdir, config.loglevel
);
Err("Syslog in windows not implemented")?
}
#[cfg(target_os = "linux")]
{
let handle = Logger::try_with_str(&config.loglevel)
.expect("Log start failed")
.log_to_file(
FileSpec::default()
.directory(&config.logdir)
.basename(&config.logfile_basename)
.suppress_timestamp(),
)
.append()
.format(with_thread)
.add_writer(
"syslog",
SyslogWriter::builder(
SyslogConnection::syslog_call(),
SyslogLineHeader::Rfc3164,
flexi_logger::writers::SyslogFacility::SystemDaemons,
)
.build()?,
)
.start()?;
warn!(
"Starting LogfileAndSyslog logging to {} with level: {}",
config.logdir, config.loglevel
);
handle
}
}
};
Ok(r)
}