add default implementations for domain extention queries

This commit is contained in:
Vincent Stuyck 2025-12-20 01:42:01 +01:00
parent 8e35b8466b
commit 52cff0c9fa
8 changed files with 275 additions and 23 deletions

14
Cargo.lock generated
View File

@ -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]]

View File

@ -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"] }

54
src/api/folders.rs Normal file
View File

@ -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<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contactgroups: Option<HostContactGroups>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub parents: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub snmp_community: Option<SnmpCommunity>,
#[serde(default)]
pub labels: HashMap<String, String>,
#[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<MetaData>,
#[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<String, String>,
// 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<Utc>,
pub updated_at: DateTime<Utc>,
// can be null when builtin by checkmk, like the root diretory
pub created_by: Option<String>,
}
// impl DomainExtention for FolderConfig {
// }

View File

@ -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<String>,
// body_params: HashMap<String, String> // 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<String>,
// // body_params: HashMap<String, String> // 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 {}

View File

@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HostContactGroups {
groups: Vec<String>,
r#use: bool,
use_for_services: bool,
recuse_use: bool,
recuse_perms: bool
}

2
src/api/rules/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod contactgroups;
pub mod snmp;

69
src/api/rules/snmp.rs Normal file
View File

@ -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,
},
}

View File

@ -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<E: DomainExtention>(
&self,
id: impl Display,
params: &E::DeleteQuery,
query: &E::DeleteQuery,
) -> Result<()> {
let request = self.http
.delete(self.object_url(E::DOMAIN_TYPE, id))
.query(&params)
.query(&query)
.build()
.unwrap();
self.invoke_api(request).await
}
pub(crate) async fn bulk_create_domain_objects<E: DomainExtention>(
pub(crate) async fn bulk_create_domain_objects<E: BulkCreateDomainExtention>(
&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<E: DomainExtention>(
pub(crate) async fn bulk_read_domain_objects<E: BulkReadDomainExtention>(
&self,
query: &E::ReadQuery,
) -> Result<Vec<DomainObject<E>>> {
@ -319,9 +319,9 @@ impl InnerClient {
let response: DomainCollection<E> = self.query_api(request).await?;
Ok(response.value)
}
pub(crate) async fn bulk_update_domain_objects<E: DomainExtention>(
pub(crate) async fn bulk_update_domain_objects<E: BulkUpdateDomainExtention>(
&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<E: DomainExtention>(
pub(crate) async fn bulk_delete_domain_objects<E: BulkDeleteDomainExtention>(
&self,
ids: &[String],
) -> Result<()> {
@ -362,3 +362,95 @@ impl Display for BulkAction {
}
}
}
pub struct ApiClient<D: DomainExtention> {
inner: Arc<InnerClient>,
_marker: PhantomData<D>
}
impl <D: DomainExtention> ApiClient<D> {
pub async fn create(
&self,
request: &D::CreationRequest,
query: &D::CreationQuery
) -> Result<()> {
self.inner.create_domain_object::<D>(request, query).await
}
pub async fn read(
&self,
id: impl Display,
query: &D::ReadQuery
) -> Result<D> {
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::<D>(id, request)
.await
}
pub async fn delete(
&self,
id: impl Display,
query: &D::DeleteQuery
) -> Result<()> {
self.inner
.delete_domain_object::<D>(id, query)
.await
}
}
impl <D: BulkCreateDomainExtention> ApiClient<D> {
pub async fn bulk_create(
&self,
request: &[D::CreationRequest],
query: &D::CreationQuery,
) -> Result<()> {
self.inner
.bulk_create_domain_objects::<D>(request, query)
.await
}
}
impl <D: BulkReadDomainExtention> ApiClient<D> {
pub async fn bulk_read(
&self,
query: &D::ReadQuery,
) -> Result<Vec<D>> {
let objs = self.inner
.bulk_read_domain_objects::<D>(query)
.await?;
let exts = objs.into_iter()
.map(|obj| obj.extensions)
.collect();
Ok(exts)
}
}
impl <D: BulkUpdateDomainExtention> ApiClient<D> {
pub async fn bulk_update(
&self,
request: &[D::BulkUpdateRequest],
) -> Result<()> {
self.inner
.bulk_update_domain_objects::<D>(request)
.await
}
}
impl <D: BulkDeleteDomainExtention> ApiClient<D> {
pub async fn bulk_delete(
&self,
ids: &[String],
) -> Result<()> {
self.inner
.bulk_delete_domain_objects::<D>(ids)
.await
}
}