diff --git a/src/api/rules.rs b/src/api/rules.rs index 3948dc9..860987d 100644 --- a/src/api/rules.rs +++ b/src/api/rules.rs @@ -136,3 +136,237 @@ impl ApiClient { } domain_bulk_read!(Ruleset, RulesetQuery); + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +pub struct Rule { + pub ruleset: String, + pub folder: String, + pub folder_index: usize, + pub properties: RuleProperties, + pub value_raw: String, + pub conditions: RuleCondition +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +pub struct RuleProperties { + pub description: String, + pub comment: String, + pub documentation_url: String, + pub disabled: bool +} + +// TODO: implement +pub trait RuleMatch { + fn matches(&self, other: Self) -> bool; +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +pub struct RuleCondition { + pub host_name: MatchCondition, + pub host_tags: Vec, + pub host_label_groups: Vec, + pub service_label_groups: Vec, + pub service_description: MatchCondition +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +pub struct MatchCondition { + match_on: Vec, + operator: MatchOperator +} + +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +#[serde(rename_all = "snake_case")] +pub enum MatchOperator { + #[default] + OneOf, + NoneOf +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +pub struct HostTagCondition { + key: String, + match_on: TagMatchOperator +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +pub enum TagMatchOperator { + Is(String), + IsNot(String), + OneOff(Vec), + NoneOff(Vec), +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +pub struct LabelGroupCondition { + operator: LabelOperator, + label_group: Vec +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +pub struct LabelCondition { + operator: LabelOperator, + label: String +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +pub enum LabelOperator { + #[default] And, Or, Not +} + +mod serde_tags { + use serde::{Deserialize, Serialize, de::{self, Visitor, Unexpected, Error}, ser::SerializeStruct}; + + use crate::api::rules::{HostTagCondition, TagMatchOperator}; + + impl Serialize for HostTagCondition { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + let mut map = serializer.serialize_struct("HostTagCondition", 3)?; + + map.serialize_field("key", &self.key)?; + + match &self.match_on { + TagMatchOperator::Is(value) => { + map.serialize_field("operator", "is")?; + map.serialize_field("value", &value)?; + }, + TagMatchOperator::IsNot(value) => { + map.serialize_field("operator", "is_not")?; + map.serialize_field("value", &value)?; + }, + TagMatchOperator::OneOff(items) => { + map.serialize_field("operator", "one_of")?; + map.serialize_field("value", items)?; + }, + TagMatchOperator::NoneOff(items) => { + map.serialize_field("operator", "none_of")?; + map.serialize_field("value", items)?; + }, + }; + + map.end() + } + } + + struct HostTagConditionVisitor; + + impl <'de> Visitor<'de> for HostTagConditionVisitor { + type Value = HostTagCondition; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("expected a struct with fields key, operator and value") + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, { + + #[derive(Debug, Deserialize)] + #[serde(rename_all = "lowercase")] + enum Field { + Key, + Operator, + Value + } + + + #[derive(Debug, Deserialize)] + #[serde(rename_all = "snake_case")] + enum Operator { + Is, IsNot, OneOff, NoneOff + } + + #[derive(Debug, Deserialize)] + #[serde(untagged)] + enum ValueKind { + Single(String), + Multiple(Vec) + } + + impl ValueKind { + fn into_single(self) -> Option { + if let Self::Single(s) = self { + Some(s) + } else { + None + } + } + fn into_multiple(self) -> Result, String> { + match self { + Self::Multiple(ss) => Ok(ss), + Self::Single(s) => Err(s) + } + } + } + + let mut tkey: Option = None; + let mut toperator: Option = None; + let mut tvalue: Option = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Key => {tkey.insert(map.next_value()?);}, + Field::Operator => {toperator.insert(map.next_value()?);}, + Field::Value => {tvalue.insert(map.next_value()?);} + }; + }; + + let key = tkey.ok_or(A::Error::missing_field("key"))?; + let match_on = match toperator.ok_or(de::Error::missing_field("operator"))? { + Operator::Is => TagMatchOperator::Is( + tvalue.and_then(|v| v.into_single()) + .ok_or(A::Error::invalid_value( + Unexpected::Seq, + &"single string" + ))? + ), + Operator::IsNot => TagMatchOperator::IsNot( + tvalue.and_then(|v| v.into_single()) + .ok_or(A::Error::invalid_value( + Unexpected::Seq, + &"single string" + ))? + ), + Operator::OneOff => TagMatchOperator::OneOff( + tvalue.ok_or(A::Error::missing_field("value"))? + .into_multiple() + .map_err(|s| A::Error::invalid_value( + Unexpected::Str(s.as_str()), + &"sequence of stirngs" + ))? + ), + Operator::NoneOff => TagMatchOperator::NoneOff( + tvalue.ok_or(A::Error::missing_field("value"))? + .into_multiple() + .map_err(|s| A::Error::invalid_value( + Unexpected::Str(s.as_str()), + &"sequence of stirngs" + ))? + ), + }; + + Ok(HostTagCondition { key, match_on }) + } + } + + impl <'de> Deserialize<'de> for HostTagCondition { + + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> { + deserializer.deserialize_map(HostTagConditionVisitor) + } + } +} diff --git a/src/main.rs b/src/main.rs index f1d1a01..03077d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,13 +8,11 @@ const PASSWORD: &str = "C5t71DUPAu2D"; #[tokio::main] async fn main() -> Result<()> { - let log_config = simplelog::ConfigBuilder::new() - .set_location_level(simplelog::LevelFilter::Error) - .build(); - simplelog::TermLogger::init( simplelog::LevelFilter::Trace, - log_config, + simplelog::ConfigBuilder::new() + .set_location_level(simplelog::LevelFilter::Error) + .build(), simplelog::TerminalMode::Stderr, simplelog::ColorChoice::Auto, )