modules, filters, actions, config

rust-rewrite
Y 2019-12-22 13:27:31 +01:00
parent c99c3e111c
commit d441ed3b14
7 changed files with 358 additions and 57 deletions

View File

@ -8,6 +8,8 @@ edition = "2018"
[dependencies]
chrono = "0.4"
inventory = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.8"
systemd = "0.4.0"
systemd = "0.4"

View File

@ -1,18 +1,22 @@
use crate::modules::{Module,ModuleArgs};
use crate::modules::{Action,AvailableAction,ModuleArgs};
use crate::common::Record;
#[derive(Debug)]
pub struct Noop {}
inventory::submit! {
AvailableAction::new("action_noop", move |a| Box::new(Noop::from_args(a)))
}
impl Noop {
pub fn from_args(mut _args: ModuleArgs) -> Noop {
Noop {}
}
}
impl Module for Noop {
fn run(&self, _record: &mut Record) -> Result<bool, ()> {
Ok(true)
impl Action for Noop {
fn act(&self, _record: &mut Record) -> Result<(), ()> {
Ok(())
}
}
@ -21,9 +25,9 @@ mod tests {
use std::collections::HashMap;
use crate::common::Record;
use crate::actions::Noop;
use crate::modules::{Module,ModuleArgs};
use crate::modules::{Action,ModuleArgs};
fn generate_empty_args_record() -> (ModuleArgs<'static>, Record<'static>) {
fn generate_empty_args_record() -> (ModuleArgs, Record<'static>) {
let args = HashMap::with_capacity(0);
let record = HashMap::with_capacity(0);
(args, record)
@ -33,6 +37,6 @@ mod tests {
fn noop_does_nothing() {
let (args, mut record) = generate_empty_args_record();
let action = Noop::from_args(args);
assert!(action.run(&mut record).unwrap());
assert_eq!((), action.act(&mut record).unwrap());
}
}

View File

@ -6,7 +6,9 @@ pub enum Value {
Bool(bool),
Str(String),
Int(isize),
Date(DateTime<chrono::Utc>)
Date(DateTime<chrono::Utc>),
Map(HashMap<String, Value>),
List(Vec<Value>)
}
pub type Record<'a> = HashMap<&'a str, Value>;

56
src/config/file.rs Normal file
View File

@ -0,0 +1,56 @@
use std::env;
use std::ffi::{OsString};
use std::io::{BufReader,Error,Result};
use std::path::Path;
use std::fs::File;
const ENV_VARIABLE: &'static str = "PYRUSE_CONF";
enum ConfFile {
Json(OsString),
Yaml(OsString)
}
pub fn from_file() {
match find_file(find_candidates()) {
ConfFile::Json(path) => super::parse_json(BufReader::new(File::open(path).expect("Read error"))),
ConfFile::Yaml(path) => super::parse_yaml(BufReader::new(File::open(path).expect("Read error")))
};
}
fn find_candidates() -> Vec<ConfFile> {
match env::var_os(ENV_VARIABLE) {
Some(path) => {
let s = Path::new(&path)
.extension()
.and_then(|e| Some(e.to_string_lossy()))
.and_then(|s| Some(s.to_ascii_lowercase()))
.unwrap_or_default();
match s.as_ref() {
"json" => vec![ConfFile::Json(path)],
"yaml" | "yml" => vec![ConfFile::Yaml(path)],
_ => panic!("Cannot determine file format from file name: {}", path.to_string_lossy())
}
},
None => {
vec![
ConfFile::Json(OsString::from("pyruse.json")),
ConfFile::Yaml(OsString::from("pyruse.yaml")),
ConfFile::Yaml(OsString::from("pyruse.yml"))
]
}
}
}
fn find_file(conf_candidates: Vec<ConfFile>) -> ConfFile {
for name in conf_candidates {
match name {
ConfFile::Json(ref path) | ConfFile::Yaml(ref path) => {
if Path::new(&path).exists() {
return name;
}
}
}
}
panic!("No configuration found. Consider setting ${}, or creating one of these in $PWD: pyruse.json, pyruse.yaml or pyruse.yml", ENV_VARIABLE)
}

View File

@ -1,18 +1,205 @@
use crate::common::Record;
use crate::modules::{ModuleArgs,ModuleType};
use serde::de::{self,Deserializer,MapAccess,SeqAccess,Visitor};
use serde::Deserialize;
use serde_json;
use serde_yaml;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::io::Read;
use crate::common::Value;
use crate::modules::ModuleArgs;
pub struct Config<'a> {
actions: Vec<Chain<'a>>,
options: Record<'a>
mod file;
thread_local!(static CONFIG: RefCell<Option<Config>> = RefCell::new(None));
#[derive(Debug,Deserialize)]
pub struct Config {
actions: HashMap<String, Chain>,
#[serde(flatten)]
options: HashMap<String, Value>
}
pub struct Chain<'a> {
name: String,
steps: Vec<Step<'a>>
type Chain = Vec<Step>;
#[derive(Debug,Deserialize)]
pub enum StepType {
#[serde(rename(deserialize = "action"))]
Action(String),
#[serde(rename(deserialize = "filter"))]
Filter(String)
}
pub struct Step<'a> {
module_name: String,
module_type: ModuleType,
args: ModuleArgs<'a>
#[derive(Debug,Deserialize)]
pub struct Step {
#[serde(flatten)]
module: StepType,
args: ModuleArgs,
#[serde(rename(deserialize = "then"))]
then_dest: Option<String>,
#[serde(rename(deserialize = "else"))]
else_dest: Option<String>
}
/* *** serde for Value *** */
struct ValueVisitor;
impl<'de> Visitor<'de> for ValueVisitor {
type Value = Value;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a boolean, string, or integer")
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Bool(v))
}
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Int(v as isize))
}
fn visit_char<E>(self, v: char) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Str(v.to_string()))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Str(String::from(v)))
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Str(String::from(v)))
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Str(v))
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Str(std::str::from_utf8(v).expect("Strings in the configuration must be UTF-8").to_string()))
}
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Str(std::str::from_utf8(v).expect("Strings in the configuration must be UTF-8").to_string()))
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E> where E: de::Error {
Ok(Value::Str(String::from_utf8(v).expect("Strings in the configuration must be UTF-8")))
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> where A: SeqAccess<'de> {
let mut result = Vec::with_capacity(seq.size_hint().unwrap_or(0));
while let Some(v) = seq.next_element()? {
result.push(v);
}
Ok(Value::List(result))
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where A: MapAccess<'de> {
let mut result = HashMap::with_capacity(map.size_hint().unwrap_or(0));
while let Some((k, v)) = map.next_entry()? {
result.insert(k, v);
}
Ok(Value::Map(result))
}
}
impl<'de> Deserialize<'de> for Value {
fn deserialize<D>(deserializer: D) -> Result<Value, D::Error> where D: Deserializer<'de> {
deserializer.deserialize_any(ValueVisitor)
}
}
fn parse_json(data: impl Read) {
CONFIG.with(|config| {
config.replace(Some(serde_json::from_reader(data).expect("Failed to parse configuration")));
});
}
fn parse_yaml(data: impl Read) {
CONFIG.with(|config| {
config.replace(Some(serde_yaml::from_reader(data).expect("Failed to parse configuration")));
});
}
//fn handle_serde(data: se)
#[cfg(test)]
mod tests {
use super::parse_json;
#[test]
fn parse_json_works() {
let json = r#"
{
"actions": {
"Detect request errors with Nextcloud": [
{
"filter": "filter_equals",
"args": { "field": "SYSLOG_IDENTIFIER", "value": "uwsgi" }
},
{
"filter": "filter_pcre",
"args": { "field": "MESSAGE", "re": "^\\[[^]]+\\] ([^ ]+) .*\\] ([A-Z]+ /[^?]*)(?:\\?.*)? => .*\\(HTTP/1.1 5..\\)", "save": [ "thatIP", "HTTPrequest" ] },
"else": "… Report insufficient buffer-size for Nextcloud QUERY_STRING"
},
{
"action": "action_dailyReport",
"args": { "level": "INFO", "message": "IP {thatIP} failed to {HTTPrequest} on Nextcloud", "details": "FIRSTLAST" }
}
]
},
"debug": false
}
"#.as_bytes();
parse_json(json);
super::CONFIG.with(|config| {
println!("{:#?}", config.borrow());
});
}
}

View File

@ -1,4 +1,4 @@
use crate::modules::{Module,ModuleArgs};
use crate::modules::{AvailableFilter,Filter,ModuleArgs};
use crate::common::Record;
use crate::common::Value;
@ -8,6 +8,10 @@ pub struct Equals {
value: Value
}
inventory::submit! {
AvailableFilter::new("filter_equals", move |a| Box::new(Equals::from_args(a)))
}
impl Equals {
pub fn from_args(mut args: ModuleArgs) -> Equals {
Equals {
@ -20,11 +24,11 @@ impl Equals {
}
}
impl Module for Equals {
fn run(&self, record: &mut Record) -> Result<bool, ()> {
impl Filter for Equals {
fn filter(&self, record: &mut Record) -> bool {
match (record.get(&self.field.as_ref()), &self.value) {
(Some(ref v1), ref v2) => Ok(v1 == v2),
(None, _) => Ok(false)
(Some(ref v1), ref v2) => v1 == v2,
(None, _) => false
}
}
}
@ -35,21 +39,21 @@ mod tests {
use std::collections::HashMap;
use crate::common::{Record,Value};
use crate::filters::Equals;
use crate::modules::{Module,ModuleArgs};
use crate::modules::{Filter,ModuleArgs};
fn generate_args_record_equal<'a>(name: &'a str, value: Value) -> (ModuleArgs<'static>, Record<'a>) {
fn generate_args_record_equal<'a>(name: &'a str, value: Value) -> (ModuleArgs, Record<'a>) {
let mut args = HashMap::with_capacity(2);
args.insert("field", Value::Str(String::from(name)));
args.insert("value", value.clone());
args.insert(String::from("field"), Value::Str(String::from(name)));
args.insert(String::from("value"), value.clone());
let mut record = HashMap::with_capacity(1);
record.insert(name, value);
(args, record)
}
fn generate_args_record_custom<'a>(ref_name: &str, ref_value: Value, test_name: &'a str, test_value: Value) -> (ModuleArgs<'static>, Record<'a>) {
fn generate_args_record_custom<'a>(ref_name: &str, ref_value: Value, test_name: &'a str, test_value: Value) -> (ModuleArgs, Record<'a>) {
let mut args = HashMap::with_capacity(2);
args.insert("field", Value::Str(String::from(ref_name)));
args.insert("value", ref_value);
args.insert(String::from("field"), Value::Str(String::from(ref_name)));
args.insert(String::from("value"), ref_value);
let mut record = HashMap::with_capacity(1);
record.insert(test_name, test_value);
(args, record)
@ -59,41 +63,41 @@ mod tests {
fn filter_equals_should_return_true() {
let (args, mut record) = generate_args_record_equal("a_boolean", Value::Bool(false));
let filter = Equals::from_args(args);
assert!(filter.run(&mut record).unwrap());
assert!(filter.filter(&mut record));
let (args, mut record) = generate_args_record_equal("a_string", Value::Str(String::from("Hello!")));
let filter = Equals::from_args(args);
assert!(filter.run(&mut record).unwrap());
assert!(filter.filter(&mut record));
let (args, mut record) = generate_args_record_equal("an_integer", Value::Int(2));
let filter = Equals::from_args(args);
assert!(filter.run(&mut record).unwrap());
assert!(filter.filter(&mut record));
let (args, mut record) = generate_args_record_equal("a_date", Value::Date(Utc::now()));
let filter = Equals::from_args(args);
assert!(filter.run(&mut record).unwrap());
assert!(filter.filter(&mut record));
}
#[test]
fn filter_equals_should_return_false() {
let (args, mut record) = generate_args_record_custom("a_boolean", Value::Bool(true), "a_boolean", Value::Bool(false));
let filter = Equals::from_args(args);
assert!(! filter.run(&mut record).unwrap());
assert!(! filter.filter(&mut record));
let (args, mut record) = generate_args_record_custom("a_string", Value::Str(String::from("Hello!")), "a_string", Value::Str(String::from("World!")));
let filter = Equals::from_args(args);
assert!(! filter.run(&mut record).unwrap());
assert!(! filter.filter(&mut record));
let (args, mut record) = generate_args_record_custom("an_integer", Value::Int(2), "an_integer", Value::Int(3));
let filter = Equals::from_args(args);
assert!(! filter.run(&mut record).unwrap());
assert!(! filter.filter(&mut record));
let (args, mut record) = generate_args_record_custom("a_date", Value::Date(Utc::now()), "a_date", Value::Date(Utc::now()));
let filter = Equals::from_args(args);
assert!(! filter.run(&mut record).unwrap());
assert!(! filter.filter(&mut record));
let (args, mut record) = generate_args_record_custom("first_one", Value::Int(1), "second_one", Value::Int(1));
let filter = Equals::from_args(args);
assert!(! filter.run(&mut record).unwrap());
assert!(! filter.filter(&mut record));
}
}

View File

@ -1,23 +1,69 @@
use crate::common::Record;
use crate::{actions,filters};
use std::collections::HashMap;
use crate::common::{Record,Value};
struct Available {
pub struct AvailableAction {
name: &'static str,
cons: fn(ModuleArgs) -> Box<dyn Module>
cons: fn(ModuleArgs) -> Box<dyn Action>
}
const AVAILABLE: &[Available] = &[
Available { name: "action_noop", cons: move |a| Box::new(actions::Noop::from_args(a)) },
Available { name: "filter_equals", cons: move |a| Box::new(filters::Equals::from_args(a)) }
];
pub trait Module {
fn run(&self, record: &mut Record) -> Result<bool, ()>;
impl AvailableAction {
pub fn new(name: &'static str, cons: fn(ModuleArgs) -> Box<dyn Action>) -> Self {
AvailableAction { name, cons }
}
}
pub type ModuleArgs<'a> = Record<'a>;
inventory::collect!(AvailableAction);
pub enum ModuleType {
Filter,
Action
pub struct AvailableFilter {
name: &'static str,
cons: fn(ModuleArgs) -> Box<dyn Filter>
}
impl AvailableFilter {
pub fn new(name: &'static str, cons: fn(ModuleArgs) -> Box<dyn Filter>) -> Self {
AvailableFilter { name, cons }
}
}
inventory::collect!(AvailableFilter);
pub enum Module {
Action(Box<dyn Action>),
Filter(Box<dyn Filter>)
}
impl Module {
pub fn get_module(name: &str, args: ModuleArgs) -> Result<Module, ()> {
for action in inventory::iter::<AvailableAction> {
if action.name == name {
return Ok(Module::Action((action.cons)(args)))
}
}
for filter in inventory::iter::<AvailableFilter> {
if filter.name == name {
return Ok(Module::Filter((filter.cons)(args)))
}
}
Err(())
}
pub fn run(&self, record: &mut Record) -> Result<bool, ()> {
match self {
Module::Action(a) => match a.act(record) {
Ok(()) => Ok(true),
Err(()) => Err(())
},
Module::Filter(f) => Ok(f.filter(record))
}
}
}
pub trait Action {
fn act(&self, record: &mut Record) -> Result<(), ()>;
}
pub trait Filter {
fn filter(&self, record: &mut Record) -> bool;
}
pub type ModuleArgs = HashMap<String, Value>;