diff --git a/Cargo.lock b/Cargo.lock index 5de6786..1ea2079 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,17 @@ dependencies = [ "libc", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -73,6 +84,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" name = "checkmk-api" version = "0.1.0" dependencies = [ + "async-trait", "chrono", "log", "reqwest", @@ -834,7 +846,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 04d4b58..2b7ad81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ authors = [ ] [dependencies] +async-trait = "0.1.89" chrono = { version = "0.4.42", features = ["serde"] } log = "0.4.29" reqwest = { version = "0.12.26", features = ["json", "rustls-tls"] } diff --git a/src/api/folders.rs b/src/api/folders.rs new file mode 100644 index 0000000..8176560 --- /dev/null +++ b/src/api/folders.rs @@ -0,0 +1,54 @@ +use std::collections::HashMap; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::api::DomainExtention; +use crate::api::rules::contactgroups::HostContactGroups; +use crate::api::rules::snmp::SnmpCommunity; + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct FolderConfig { + pub path: String, + pub attributes: FolderAttributes, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct FolderAttributes { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub site: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub contactgroups: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub parents: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub snmp_community: Option, + #[serde(default)] + pub labels: HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + /// this attribute is not optional when queried from the api. + /// It cannot be filled in when creating a new folder. + pub meta_data: Option, + #[serde(flatten, skip_serializing_if = "HashMap::is_empty")] + /// Additional attributes that can be used to categorize hosts. every tag is part of a tag_group + /// NOTE that checkmk always returns taggroups prefixed with "tag_" + pub tags: HashMap, + + // TODO: to be implemented + // #[serde(flatten, default)] + // pub networkscan: NetworkScan, + // #[serde(flatten, default)] + // pub management: ManagementProtocol, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MetaData { + pub created_at: DateTime, + pub updated_at: DateTime, + // can be null when builtin by checkmk, like the root diretory + pub created_by: Option, +} + +// impl DomainExtention for FolderConfig { + +// } diff --git a/src/api/mod.rs b/src/api/mod.rs index 4742522..bbf09ba 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,3 +1,6 @@ +pub mod folders; +pub(crate) mod rules; + use std::fmt; use serde::{Deserialize, Serialize, de::DeserializeOwned}; @@ -36,18 +39,18 @@ pub enum HttpMethod { Delete, } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub(crate) struct RelationLink { - #[serde(rename = "domainType")] - pub domain_type: DomainType, - pub rel: String, - pub href: String, - pub method: HttpMethod, - pub r#type: String, - #[serde(default)] - pub title: Option, - // body_params: HashMap // i will deal with this later -} +// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +// pub(crate) struct RelationLink { +// #[serde(rename = "domainType")] +// pub domain_type: DomainType, +// pub rel: String, +// pub href: String, +// pub method: HttpMethod, +// pub r#type: String, +// #[serde(default)] +// pub title: Option, +// // body_params: HashMap // i will deal with this later +// } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -87,3 +90,12 @@ pub(crate) trait DomainExtention: DeserializeOwned + Serialize { type UpdateRequest: Serialize; type DeleteQuery: Serialize; } + +// these are essentials tags for domain extentions to signal that bulk operations are possible +// not every extention supports bulk operations. and not all extentions that do support all of them +pub(crate) trait BulkCreateDomainExtention: DomainExtention {} +pub(crate) trait BulkReadDomainExtention: DomainExtention {} +pub(crate) trait BulkUpdateDomainExtention: DomainExtention { + type BulkUpdateRequest: Serialize; +} +pub(crate) trait BulkDeleteDomainExtention: DomainExtention {} diff --git a/src/api/rules/contactgroups.rs b/src/api/rules/contactgroups.rs new file mode 100644 index 0000000..d8bb91a --- /dev/null +++ b/src/api/rules/contactgroups.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct HostContactGroups { + groups: Vec, + r#use: bool, + use_for_services: bool, + recuse_use: bool, + recuse_perms: bool +} diff --git a/src/api/rules/mod.rs b/src/api/rules/mod.rs new file mode 100644 index 0000000..58797ea --- /dev/null +++ b/src/api/rules/mod.rs @@ -0,0 +1,2 @@ +pub mod contactgroups; +pub mod snmp; diff --git a/src/api/rules/snmp.rs b/src/api/rules/snmp.rs new file mode 100644 index 0000000..a58a6ff --- /dev/null +++ b/src/api/rules/snmp.rs @@ -0,0 +1,69 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum SnmpV3AuthProtocol { + #[serde(rename = "MD5-96")] + Md5, + #[serde(rename = "SHA-1-96")] + Sha96, + #[serde(rename = "SHA-2-224")] + Sha224, + #[serde(rename = "SHA-2-256")] + Sha256, + #[serde(rename = "SHA-2-384")] + Sha384, + #[serde(rename = "SHA-2-512")] + Sha512, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SnmpV3Auth { + pub auth_protocol: SnmpV3AuthProtocol, + pub security_name: String, + pub auth_password: String, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum SnmpV3PrivProtocol { + #[serde(rename = "CBC-DES")] + Des, + #[serde(rename = "3DES-EDE")] + Des3, + #[serde(rename = "AES-128")] + Aes128, + #[serde(rename = "AES-192")] + Aes192, + #[serde(rename = "AES-256")] + Aes256, + #[serde(rename = "AES-192-Blumenthal")] + Aes192Blumenthal, + #[serde(rename = "AES-256-Blumenthal")] + Aes256Blumenthal, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SnmpV3Priv { + pub privacy_protocol: SnmpV3PrivProtocol, + pub privacy_password: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum SnmpCommunity { + V1V2Community { + community: String, + }, + NoAuthNoPriv { + security_name: String, + }, + AuthNoPriv { + #[serde(flatten)] + auth: SnmpV3Auth, + }, + AuthPriv { + #[serde(flatten)] + authentication: SnmpV3Auth, + #[serde(flatten)] + privacy: SnmpV3Priv, + }, +} diff --git a/src/client.rs b/src/client.rs index 97a5754..b2039a8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,7 +9,7 @@ use reqwest::header::{HeaderName, HeaderValue}; use serde::de::DeserializeOwned; use tokio::sync::Semaphore; -use crate::api::{DomainCollection, DomainExtention, DomainObject, DomainType}; +use crate::api::{BulkCreateDomainExtention, BulkDeleteDomainExtention, BulkReadDomainExtention, BulkUpdateDomainExtention, DomainCollection, DomainExtention, DomainObject, DomainType}; use crate::{Error, Result}; @@ -281,32 +281,32 @@ impl InnerClient { pub(crate) async fn delete_domain_object( &self, id: impl Display, - params: &E::DeleteQuery, + query: &E::DeleteQuery, ) -> Result<()> { let request = self.http .delete(self.object_url(E::DOMAIN_TYPE, id)) - .query(¶ms) + .query(&query) .build() .unwrap(); self.invoke_api(request).await } - pub(crate) async fn bulk_create_domain_objects( + pub(crate) async fn bulk_create_domain_objects( &self, - body: &[E::CreationRequest], + request: &[E::CreationRequest], query: &E::CreationQuery ) -> Result<()> { let request = self.http .post(self.bulk_action_url(E::DOMAIN_TYPE, BulkAction::Create)) - .json(body) + .json(request) .query(query) .build() .unwrap(); self.invoke_api(request).await } - pub(crate) async fn bulk_read_domain_objects( + pub(crate) async fn bulk_read_domain_objects( &self, query: &E::ReadQuery, ) -> Result>> { @@ -319,9 +319,9 @@ impl InnerClient { let response: DomainCollection = self.query_api(request).await?; Ok(response.value) } - pub(crate) async fn bulk_update_domain_objects( + pub(crate) async fn bulk_update_domain_objects( &self, - body: &[E::UpdateRequest], + body: &[E::BulkUpdateRequest], ) -> Result<()> { let request = self.http .put(self.bulk_action_url(E::DOMAIN_TYPE, BulkAction::Update)) @@ -331,7 +331,7 @@ impl InnerClient { self.invoke_api(request).await } - pub(crate) async fn bulk_delete_domain_objects( + pub(crate) async fn bulk_delete_domain_objects( &self, ids: &[String], ) -> Result<()> { @@ -362,3 +362,95 @@ impl Display for BulkAction { } } } + +pub struct ApiClient { + inner: Arc, + _marker: PhantomData +} + +impl ApiClient { + pub async fn create( + &self, + request: &D::CreationRequest, + query: &D::CreationQuery + ) -> Result<()> { + self.inner.create_domain_object::(request, query).await + } + pub async fn read( + &self, + id: impl Display, + query: &D::ReadQuery + ) -> Result { + self.inner + .read_domain_object(id, query) + .await + .map(|obj| obj.extensions) + } + pub async fn update( + &self, + id: impl Display, + request: &D::UpdateRequest + ) -> Result<()> { + self.inner + .update_domain_object::(id, request) + .await + } + pub async fn delete( + &self, + id: impl Display, + query: &D::DeleteQuery + ) -> Result<()> { + self.inner + .delete_domain_object::(id, query) + .await + } +} + +impl ApiClient { + pub async fn bulk_create( + &self, + request: &[D::CreationRequest], + query: &D::CreationQuery, + ) -> Result<()> { + self.inner + .bulk_create_domain_objects::(request, query) + .await + } +} + +impl ApiClient { + pub async fn bulk_read( + &self, + query: &D::ReadQuery, + ) -> Result> { + let objs = self.inner + .bulk_read_domain_objects::(query) + .await?; + let exts = objs.into_iter() + .map(|obj| obj.extensions) + .collect(); + Ok(exts) + } +} + +impl ApiClient { + pub async fn bulk_update( + &self, + request: &[D::BulkUpdateRequest], + ) -> Result<()> { + self.inner + .bulk_update_domain_objects::(request) + .await + } +} + +impl ApiClient { + pub async fn bulk_delete( + &self, + ids: &[String], + ) -> Result<()> { + self.inner + .bulk_delete_domain_objects::(ids) + .await + } +}