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 crate::Client;
use crate::api::DomainExtention;
use crate::api::{BulkReadDomainExtention, BulkUpdateDomainExtention, DomainExtention};
use crate::api::rules::contactgroups::HostContactGroups;
use crate::api::rules::snmp::SnmpCommunity;
use crate::client::ApiClient;
@ -87,8 +87,8 @@ impl FolderAttributes {
pub fn add_label(&mut self, labelkey: String, labelvalue: String) {
self.labels.insert(labelkey, labelvalue);
}
pub fn with_label(mut self, taggroup: String, tagvalue: String) -> Self {
self.add_label(taggroup, tagvalue);
pub fn with_label(mut self, labelkey: String, labelvalue: String) -> Self {
self.add_label(labelkey, labelvalue);
self
}
@ -114,8 +114,8 @@ impl DomainExtention for FolderConfig {
const DOMAIN_TYPE: super::DomainType = super::DomainType::FolderConfig;
type CreationRequest = FolderCreationRequest;
type CreationQuery = FolderCreationQuery;
type ReadQuery = FolderReadQuery;
type CreationQuery = ();
type ReadQuery = ();
type UpdateRequest = FolderUpdateRequest;
type DeleteQuery = FolderDeleteQuery;
}
@ -146,6 +146,7 @@ pub struct FolderReadQuery;
#[derive(Debug, Serialize)]
pub struct FolderUpdateRequest {
#[serde(skip_serializing_if = "String::is_empty")]
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub attributes: Option<FolderAttributes>,
@ -156,30 +157,46 @@ pub struct FolderUpdateRequest {
}
impl FolderUpdateRequest {
pub fn replace(title: String, attributes: FolderAttributes) -> Self {
pub fn replace(attributes: FolderAttributes) -> Self {
Self {
title,
title: String::new(),
attributes: Some(attributes),
update_attributes: None,
remove_attributes: Vec::new(),
}
}
pub fn update(title: String, attributes: FolderAttributes) -> Self {
pub fn update(attributes: FolderAttributes) -> Self {
Self {
title,
title: String::new(),
attributes: None,
update_attributes: Some(attributes),
remove_attributes: Vec::new(),
}
}
pub fn remove(title: String, attributes: Vec<String>) -> Self {
pub fn remove(attributes: Vec<String>) -> Self {
Self {
title,
title: String::new(),
attributes: None,
update_attributes: None,
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)]
@ -200,3 +217,36 @@ pub enum FolderDeleteMode {
Recursive,
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
// not every extention supports bulk operations. and not all extentions that do support all of them
pub trait BulkCreateDomainExtention: DomainExtention {}
pub trait BulkReadDomainExtention: DomainExtention {}
pub trait BulkReadDomainExtention: DomainExtention {
type BulkReadQuery: Serialize;
}
pub trait BulkUpdateDomainExtention: DomainExtention {
type BulkUpdateRequest: Serialize;
}

View File

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