diff --git a/src/api/folders.rs b/src/api/folders.rs index 8497c3d..75caaa1 100644 --- a/src/api/folders.rs +++ b/src/api/folders.rs @@ -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, @@ -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) -> Self { + pub fn remove(attributes: Vec) -> 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 } + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index a453953..7cf07f2 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -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; } diff --git a/src/client.rs b/src/client.rs index 32bdee5..ec4411f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -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 { 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( &self, - query: &E::ReadQuery, + query: &E::BulkReadQuery, ) -> Result>> { let request = self .http @@ -365,12 +374,17 @@ impl InnerClient { } pub(crate) async fn bulk_update_domain_objects( &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::{ entries }) .build() .unwrap(); @@ -458,7 +472,7 @@ impl ApiClient { } impl ApiClient { - pub async fn bulk_read(&self, query: &D::ReadQuery) -> Result> { + pub async fn bulk_read(&self, query: &D::BulkReadQuery) -> Result> { let objs = self.inner.bulk_read_domain_objects::(query).await?; let exts = objs.into_iter().map(|obj| obj.extensions).collect(); Ok(exts) @@ -466,8 +480,8 @@ impl ApiClient { } impl ApiClient { - pub async fn bulk_update(&self, request: &[D::BulkUpdateRequest]) -> Result<()> { - self.inner.bulk_update_domain_objects::(request).await + pub async fn bulk_update(&self, entries: &[D::BulkUpdateRequest]) -> Result<()> { + self.inner.bulk_update_domain_objects::(entries).await } } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..602ee0f --- /dev/null +++ b/src/main.rs @@ -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(()) +}