test and troubleshoot bulk requests

This commit is contained in:
Vincent Stuyck 2025-12-20 04:42:10 +01:00
parent fddc74ef27
commit 0b0fa24078
4 changed files with 160 additions and 18 deletions

View File

@ -5,7 +5,7 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::Client; use crate::Client;
use crate::api::DomainExtention; use crate::api::{BulkReadDomainExtention, BulkUpdateDomainExtention, DomainExtention};
use crate::api::rules::contactgroups::HostContactGroups; use crate::api::rules::contactgroups::HostContactGroups;
use crate::api::rules::snmp::SnmpCommunity; use crate::api::rules::snmp::SnmpCommunity;
use crate::client::ApiClient; use crate::client::ApiClient;
@ -87,8 +87,8 @@ impl FolderAttributes {
pub fn add_label(&mut self, labelkey: String, labelvalue: String) { pub fn add_label(&mut self, labelkey: String, labelvalue: String) {
self.labels.insert(labelkey, labelvalue); self.labels.insert(labelkey, labelvalue);
} }
pub fn with_label(mut self, taggroup: String, tagvalue: String) -> Self { pub fn with_label(mut self, labelkey: String, labelvalue: String) -> Self {
self.add_label(taggroup, tagvalue); self.add_label(labelkey, labelvalue);
self self
} }
@ -114,8 +114,8 @@ impl DomainExtention for FolderConfig {
const DOMAIN_TYPE: super::DomainType = super::DomainType::FolderConfig; const DOMAIN_TYPE: super::DomainType = super::DomainType::FolderConfig;
type CreationRequest = FolderCreationRequest; type CreationRequest = FolderCreationRequest;
type CreationQuery = FolderCreationQuery; type CreationQuery = ();
type ReadQuery = FolderReadQuery; type ReadQuery = ();
type UpdateRequest = FolderUpdateRequest; type UpdateRequest = FolderUpdateRequest;
type DeleteQuery = FolderDeleteQuery; type DeleteQuery = FolderDeleteQuery;
} }
@ -146,6 +146,7 @@ pub struct FolderReadQuery;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct FolderUpdateRequest { pub struct FolderUpdateRequest {
#[serde(skip_serializing_if = "String::is_empty")]
pub title: String, pub title: String,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub attributes: Option<FolderAttributes>, pub attributes: Option<FolderAttributes>,
@ -156,30 +157,46 @@ pub struct FolderUpdateRequest {
} }
impl FolderUpdateRequest { impl FolderUpdateRequest {
pub fn replace(title: String, attributes: FolderAttributes) -> Self { pub fn replace(attributes: FolderAttributes) -> Self {
Self { Self {
title, title: String::new(),
attributes: Some(attributes), attributes: Some(attributes),
update_attributes: None, update_attributes: None,
remove_attributes: Vec::new(), remove_attributes: Vec::new(),
} }
} }
pub fn update(title: String, attributes: FolderAttributes) -> Self { pub fn update(attributes: FolderAttributes) -> Self {
Self { Self {
title, title: String::new(),
attributes: None, attributes: None,
update_attributes: Some(attributes), update_attributes: Some(attributes),
remove_attributes: Vec::new(), remove_attributes: Vec::new(),
} }
} }
pub fn remove(title: String, attributes: Vec<String>) -> Self { pub fn remove(attributes: Vec<String>) -> Self {
Self { Self {
title, title: String::new(),
attributes: None, attributes: None,
update_attributes: None, update_attributes: None,
remove_attributes: attributes, remove_attributes: attributes,
} }
} }
pub fn title(title: String) -> Self {
Self {
title,
attributes: None,
update_attributes: None,
remove_attributes: Vec::new()
}
}
pub fn set_title(&mut self, title: String) {
self.title = title;
}
pub fn with_title(mut self, title: String) -> Self {
self.set_title(title);
self
}
} }
#[derive(Debug, Default, Serialize)] #[derive(Debug, Default, Serialize)]
@ -200,3 +217,36 @@ pub enum FolderDeleteMode {
Recursive, Recursive,
AbotyOnNonEmpty, AbotyOnNonEmpty,
} }
impl BulkReadDomainExtention for FolderConfig {
type BulkReadQuery = FolderBulkReadQuery;
}
#[derive(Serialize)]
pub struct FolderBulkReadQuery {
pub parent: String,
pub recursive: bool,
}
impl FolderBulkReadQuery {
pub fn new(parent: String, recursive: bool) -> Self {
Self { parent, recursive }
}
}
impl BulkUpdateDomainExtention for FolderConfig {
type BulkUpdateRequest = FolderBulkUpdateRequest;
}
#[derive(Serialize)]
pub struct FolderBulkUpdateRequest {
folder: String,
#[serde(flatten)]
update_request: FolderUpdateRequest
}
impl FolderBulkUpdateRequest {
pub fn new(folder: String, update_request: FolderUpdateRequest) -> Self {
Self { folder, update_request }
}
}

View File

@ -93,7 +93,9 @@ pub trait DomainExtention: DeserializeOwned + Serialize {
// these are essentials tags for domain extentions to signal that bulk operations are possible // 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 // not every extention supports bulk operations. and not all extentions that do support all of them
pub trait BulkCreateDomainExtention: DomainExtention {} pub trait BulkCreateDomainExtention: DomainExtention {}
pub trait BulkReadDomainExtention: DomainExtention {} pub trait BulkReadDomainExtention: DomainExtention {
type BulkReadQuery: Serialize;
}
pub trait BulkUpdateDomainExtention: DomainExtention { pub trait BulkUpdateDomainExtention: DomainExtention {
type BulkUpdateRequest: Serialize; type BulkUpdateRequest: Serialize;
} }

View File

@ -6,6 +6,7 @@ use std::sync::Arc;
use log::{debug, trace}; use log::{debug, trace};
use reqwest::header::{HeaderName, HeaderValue}; use reqwest::header::{HeaderName, HeaderValue};
use reqwest::{Certificate, Request}; use reqwest::{Certificate, Request};
use serde::Serialize;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
@ -221,6 +222,14 @@ impl InnerClient {
pub(crate) async fn handle_request(&self, request: Request) -> Result<String> { pub(crate) async fn handle_request(&self, request: Request) -> Result<String> {
let permit = self.semaphore.acquire().await.unwrap(); let permit = self.semaphore.acquire().await.unwrap();
debug!("sending {}-request to {}", request.method(), request.url()); debug!("sending {}-request to {}", request.method(), request.url());
trace!(
"with body: {}",
request.body()
.as_ref()
.and_then(|b| b.as_bytes())
.map(String::from_utf8_lossy)
.unwrap_or_default()
);
let response = self let response = self
.http .http
@ -351,7 +360,7 @@ impl InnerClient {
} }
pub(crate) async fn bulk_read_domain_objects<E: BulkReadDomainExtention>( pub(crate) async fn bulk_read_domain_objects<E: BulkReadDomainExtention>(
&self, &self,
query: &E::ReadQuery, query: &E::BulkReadQuery,
) -> Result<Vec<DomainObject<E>>> { ) -> Result<Vec<DomainObject<E>>> {
let request = self let request = self
.http .http
@ -365,12 +374,17 @@ impl InnerClient {
} }
pub(crate) async fn bulk_update_domain_objects<E: BulkUpdateDomainExtention>( pub(crate) async fn bulk_update_domain_objects<E: BulkUpdateDomainExtention>(
&self, &self,
body: &[E::BulkUpdateRequest], entries: &[E::BulkUpdateRequest],
) -> Result<()> { ) -> Result<()> {
#[derive(Serialize)]
struct Request<'a, D: BulkUpdateDomainExtention> {
entries: &'a [D::BulkUpdateRequest]
}
let request = self let request = self
.http .http
.put(self.bulk_action_url(E::DOMAIN_TYPE, BulkAction::Update)) .put(self.bulk_action_url(E::DOMAIN_TYPE, BulkAction::Update))
.json(body) .json(&Request::<E>{ entries })
.build() .build()
.unwrap(); .unwrap();
@ -458,7 +472,7 @@ impl<D: BulkCreateDomainExtention> ApiClient<D> {
} }
impl<D: BulkReadDomainExtention> ApiClient<D> { impl<D: BulkReadDomainExtention> ApiClient<D> {
pub async fn bulk_read(&self, query: &D::ReadQuery) -> Result<Vec<D>> { pub async fn bulk_read(&self, query: &D::BulkReadQuery) -> Result<Vec<D>> {
let objs = self.inner.bulk_read_domain_objects::<D>(query).await?; let objs = self.inner.bulk_read_domain_objects::<D>(query).await?;
let exts = objs.into_iter().map(|obj| obj.extensions).collect(); let exts = objs.into_iter().map(|obj| obj.extensions).collect();
Ok(exts) Ok(exts)
@ -466,8 +480,8 @@ impl<D: BulkReadDomainExtention> ApiClient<D> {
} }
impl<D: BulkUpdateDomainExtention> ApiClient<D> { impl<D: BulkUpdateDomainExtention> ApiClient<D> {
pub async fn bulk_update(&self, request: &[D::BulkUpdateRequest]) -> Result<()> { pub async fn bulk_update(&self, entries: &[D::BulkUpdateRequest]) -> Result<()> {
self.inner.bulk_update_domain_objects::<D>(request).await self.inner.bulk_update_domain_objects::<D>(entries).await
} }
} }

76
src/main.rs Normal file
View File

@ -0,0 +1,76 @@
use checkmk_api::{Client, Result};
use log::{error, info};
const HOSTNAME: &str = "shion.stuyckv.local";
const SITENAME: &str = "dev";
const USERNAME: &str = "cmkadmin";
const PASSWORD: &str = "C5t71DUPAu2D";
#[tokio::main]
async fn main() -> Result<()> {
simplelog::TermLogger::init(
simplelog::LevelFilter::Trace,
simplelog::Config::default(),
simplelog::TerminalMode::Stderr,
simplelog::ColorChoice::Auto
)
.unwrap();
let client = Client::builder()
.on_host_and_site(HOSTNAME.to_string(), SITENAME.to_string())
.with_credentials(USERNAME.to_string(), PASSWORD.to_string())
.without_ssl()
.build()
.unwrap();
test_folders(client.clone()).await?;
Ok(())
}
async fn test_folders(client: Client) -> Result<()> {
use checkmk_api::api::folders::*;
const TESTDIR: &str = "test";
const TESTDIRQRY: &str = "~test";
info!("testing folders");
let folderapi = client.folder_api();
let creq = FolderCreationRequest::new(
TESTDIR.to_string(),
"Testing".to_string(),
ROOT_FOLDER.to_string(),
FolderAttributes::default()
);
let ureq1 = FolderUpdateRequest::replace(
FolderAttributes::default()
.with_site("dev".to_string())
);
let ureq2 = FolderUpdateRequest::update(
FolderAttributes::default()
.with_label("test".to_string(), "testing".to_string())
);
info!("creating testfolder");
folderapi.create(&creq, &()).await
.inspect_err(|e| error!("failed to create folder: {e}"))?;
let folder = folderapi.read(TESTDIRQRY, &()).await
.inspect_err(|e| error!("failed to read folder: {e}"))?;
info!("folder config on site: {folder:?}");
info!("updating folder");
folderapi.update(TESTDIRQRY, &ureq1).await
.inspect_err(|e| error!("failed to update folder: {e}"))?;
let folders = folderapi.bulk_read(&FolderBulkReadQuery::new("~".to_string(), false)).await
.inspect_err(|e| error!("failed to read all folders: {e}"));
info!("folder config on site: {folders:?}");
folderapi.bulk_update(&[FolderBulkUpdateRequest::new(TESTDIRQRY.to_string(), ureq2)]).await
.inspect_err(|e| error!("failed to do a bulk update of folders: {e}"))?;
info!("deleting folder");
folderapi.delete(TESTDIRQRY, &FolderDeleteQuery::default()).await
.inspect_err(|e| error!("failed to delete folder: {e}"))?;
Ok(())
}