fix(generic-snmp): unwrap from main() removed and errors better handled

This commit is contained in:
David Boucher 2025-06-29 17:25:52 +02:00
parent 2736a097ce
commit d18a18a20d
3 changed files with 92 additions and 31 deletions

View File

@ -1,5 +1,5 @@
use snafu::prelude::Snafu; use snafu::prelude::Snafu;
use std::path::PathBuf; use std::{io, path::PathBuf};
#[derive(Debug, Snafu)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub))] #[snafu(visibility(pub))]
@ -21,14 +21,22 @@ pub enum Error {
#[snafu(display("Threshold: The threshold syntax must follow '[@]start:end'"))] #[snafu(display("Threshold: The threshold syntax must follow '[@]start:end'"))]
BadThreshold, BadThreshold,
#[snafu(display("Json: Failed to parse JSON: {}", message))] #[snafu(transparent)]
JsonParse { message: String }, Io { source: io::Error },
#[snafu(transparent)]
Lexopt { source: lexopt::Error },
#[snafu(display("Json: Unable to read the JSON file '{}'", path.display()))] #[snafu(transparent)]
JsonRead { SerdeJson { source: serde_json::Error },
source: std::io::Error, }
path: PathBuf,
}, impl From<std::ffi::OsString> for Error {
fn from(value: std::ffi::OsString) -> Self {
//let val = value.into_string().unwrap_or_else(|_| "Invalid UTF-8".to_string());
Error::Lexopt {
source: lexopt::Error::NonUnicodeValue(value),
}
}
} }
pub type Result<T, E = Error> = std::result::Result<T, E>; pub type Result<T, E = Error> = std::result::Result<T, E>;

View File

@ -1,3 +1,4 @@
extern crate regex;
extern crate serde; extern crate serde;
extern crate serde_json; extern crate serde_json;
@ -8,6 +9,7 @@ use crate::compute::{Compute, Parser, ast::ExprResult, threshold::Threshold};
use crate::output::{Output, OutputFormatter}; use crate::output::{Output, OutputFormatter};
use crate::snmp::{snmp_bulk_get, snmp_bulk_walk, snmp_bulk_walk_with_labels}; use crate::snmp::{snmp_bulk_get, snmp_bulk_walk, snmp_bulk_walk_with_labels};
use log::{debug, trace}; use log::{debug, trace};
use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
@ -207,13 +209,33 @@ impl Command {
collect collect
} }
pub fn execute(&self, target: &str, version: &str, community: &str) -> Result<CmdResult> { pub fn execute(
&self,
target: &str,
version: &str,
community: &str,
filter_in: &Option<String>,
filter_out: &Option<String>,
) -> Result<CmdResult> {
let mut collect = self.execute_snmp_collect(target, version, community); let mut collect = self.execute_snmp_collect(target, version, community);
let mut idx: u32 = 0; let mut idx: u32 = 0;
let mut metrics = vec![]; let mut metrics = vec![];
let mut my_res = SnmpResult::new(HashMap::new()); let mut my_res = SnmpResult::new(HashMap::new());
let mut status = Status::Ok; let mut status = Status::Ok;
// Prepare filters
let re_in = if let Some(filter_in) = filter_in {
Some(Regex::new(filter_in).unwrap())
} else {
None
};
let re_out: Option<Regex> = if let Some(filter_out) = filter_out {
Some(Regex::new(filter_out).unwrap())
} else {
None
};
for metric in self.compute.metrics.iter() { for metric in self.compute.metrics.iter() {
let value = &metric.value; let value = &metric.value;
let parser = Parser::new(&collect); let parser = Parser::new(&collect);
@ -256,6 +278,16 @@ impl Command {
panic!("A label must be a string"); panic!("A label must be a string");
} }
}; };
if let Some(ref re) = re_in {
if !re.is_match(&name) {
continue;
}
}
if let Some(ref re) = re_out {
if re.is_match(&name) {
continue;
}
}
let current_status = let current_status =
compute_status(item, &metric.warning, &metric.critical)?; compute_status(item, &metric.warning, &metric.critical)?;
status = worst(status, current_status); status = worst(status, current_status);
@ -291,6 +323,16 @@ impl Command {
res res
} }
}; };
if let Some(ref re) = re_in {
if !re.is_match(&name) {
continue;
}
}
if let Some(ref re) = re_out {
if re.is_match(&name) {
continue;
}
}
let current_status = compute_status(s, &metric.warning, &metric.critical)?; let current_status = compute_status(s, &metric.warning, &metric.critical)?;
status = worst(status, current_status); status = worst(status, current_status);
let w = match metric.warning { let w = match metric.warning {

View File

@ -21,26 +21,18 @@ use generic::error::*;
use lalrpop_util::lalrpop_mod; use lalrpop_util::lalrpop_mod;
use lexopt::Arg; use lexopt::Arg;
use log::trace; use log::trace;
use snafu::ResultExt;
use std::fs; use std::fs;
lalrpop_mod!(grammar); lalrpop_mod!(grammar);
fn json_to_command(file_name: &str) -> Result<Command, Error> { fn json_to_command(file_name: &str) -> Result<Command, Error> {
// Transform content of the file into a string // Transform content of the file into a string
let contents = fs::read_to_string(file_name).context(JsonReadSnafu { path: file_name })?; let configuration = fs::read_to_string(file_name)?;
let command = serde_json::from_str(&configuration)?;
let content = contents.as_str(); Ok(command)
let module: serde_json::Result<Command> = serde_json::from_str(content);
match module {
Ok(c) => Ok(c),
Err(err) => Err(Error::JsonParse {
message: err.to_string(),
}),
}
} }
#[snafu::report]
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
env_logger::Builder::from_env( env_logger::Builder::from_env(
Env::default() Env::default()
@ -57,6 +49,8 @@ fn main() -> Result<(), Error> {
let mut port = 161; let mut port = 161;
let mut snmp_version = "2c".to_string(); let mut snmp_version = "2c".to_string();
let mut snmp_community = "public".to_string(); let mut snmp_community = "public".to_string();
let mut filter_in = None;
let mut filter_out = None;
let mut cmd: Option<Command> = None; let mut cmd: Option<Command> = None;
loop { loop {
let arg = parser.next(); let arg = parser.next();
@ -64,32 +58,43 @@ fn main() -> Result<(), Error> {
Ok(arg) => match arg { Ok(arg) => match arg {
Some(arg) => match arg { Some(arg) => match arg {
Short('H') | Long("hostname") => { Short('H') | Long("hostname") => {
hostname = parser.value().unwrap().into_string().unwrap(); hostname = parser.value()?.into_string()?;
trace!("hostname: {:}", hostname); trace!("hostname: {:}", hostname);
} }
Short('p') | Long("port") => { Short('p') | Long("port") => {
port = parser.value().unwrap().parse::<u16>().unwrap(); port = parser.value()?.parse::<u16>()?;
trace!("port: {}", port); trace!("port: {}", port);
} }
Short('j') | Long("json") => { Short('j') | Long("json") => {
let json = Some(parser.value().unwrap().into_string().unwrap()); let json = parser.value()?.into_string()?;
let json = json.unwrap();
trace!("json: {:?}", json); trace!("json: {:?}", json);
cmd = Some(json_to_command(&json)?); cmd = Some(json_to_command(&json)?);
} }
Short('v') | Long("snmp-version") => { Short('v') | Long("snmp-version") => {
snmp_version = parser.value().unwrap().into_string().unwrap(); snmp_version = parser.value()?.into_string()?;
trace!("snmp_version: {}", snmp_version); trace!("snmp_version: {}", snmp_version);
} }
Short('c') | Long("snmp-community") => { Short('c') | Long("snmp-community") => {
snmp_community = parser.value().unwrap().into_string().unwrap(); snmp_community = parser.value()?.into_string()?;
trace!("snmp_community: {}", snmp_community); trace!("snmp_community: {}", snmp_community);
} }
Short('i') | Long("filter-in") => {
filter_in = Some(parser.value()?.into_string()?);
if let Some(ref filter) = filter_in {
trace!("filter_in: {}", filter);
}
}
Short('o') | Long("filter-out") => {
filter_out = Some(parser.value()?.into_string()?);
if let Some(ref filter) = filter_out {
trace!("filter_out: {}", filter);
}
}
t => { t => {
if let Arg::Long(name) = t { if let Arg::Long(name) = t {
if name.starts_with("warning-") { if name.starts_with("warning-") {
let wmetric = name[8..].to_string(); let wmetric = name[8..].to_string();
let value = parser.value().unwrap().into_string().unwrap(); let value = parser.value()?.into_string()?;
match cmd.as_mut() { match cmd.as_mut() {
Some(ref mut cmd) => { Some(ref mut cmd) => {
if !value.is_empty() { if !value.is_empty() {
@ -105,7 +110,7 @@ fn main() -> Result<(), Error> {
} }
} else if name.starts_with("critical-") { } else if name.starts_with("critical-") {
let cmetric = name[9..].to_string(); let cmetric = name[9..].to_string();
let value = parser.value().unwrap().into_string().unwrap(); let value = parser.value()?.into_string()?;
match cmd.as_mut() { match cmd.as_mut() {
Some(ref mut cmd) => { Some(ref mut cmd) => {
if !value.is_empty() { if !value.is_empty() {
@ -136,7 +141,13 @@ fn main() -> Result<(), Error> {
let url = format!("{}:{}", hostname, port); let url = format!("{}:{}", hostname, port);
let result = match cmd { let result = match cmd {
Some(ref cmd) => cmd.execute(&url, &snmp_version, &snmp_community)?, Some(ref cmd) => cmd.execute(
&url,
&snmp_version,
&snmp_community,
&filter_in,
&filter_out,
)?,
None => { None => {
println!("json is empty"); println!("json is empty");
std::process::exit(3); std::process::exit(3);
@ -144,5 +155,5 @@ fn main() -> Result<(), Error> {
}; };
println!("{}", result.output); println!("{}", result.output);
std::process::exit(result.status as i32); Ok(())
} }