test and bugfix basic folder crud

This commit is contained in:
Vincent Stuyck 2025-12-20 04:07:52 +01:00
parent ebc349de64
commit fddc74ef27
7 changed files with 159 additions and 20 deletions

93
Cargo.lock generated
View File

@ -91,6 +91,7 @@ dependencies = [
"secrecy",
"serde",
"serde_json",
"simplelog",
"thiserror",
"tokio",
]
@ -125,6 +126,15 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "deranged"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [
"powerfmt",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
@ -661,6 +671,12 @@ dependencies = [
"tempfile",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-traits"
version = "0.2.19"
@ -670,6 +686,15 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.21.3"
@ -776,6 +801,12 @@ dependencies = [
"zerovec",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@ -1144,6 +1175,17 @@ dependencies = [
"libc",
]
[[package]]
name = "simplelog"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0"
dependencies = [
"log",
"termcolor",
"time",
]
[[package]]
name = "slab"
version = "0.4.11"
@ -1243,6 +1285,15 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "2.0.17"
@ -1263,6 +1314,39 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
[[package]]
name = "time-macros"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinystr"
version = "0.8.2"
@ -1566,6 +1650,15 @@ dependencies = [
"rustls-pki-types",
]
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "windows-core"
version = "0.62.2"

View File

@ -14,5 +14,6 @@ reqwest = { version = "0.12.26", features = ["json", "rustls-tls"] }
secrecy = { version = "0.10.3", features = ["serde"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
simplelog = "0.12.2"
thiserror = "2.0.17"
tokio = { version = "1.48.0", features = ["full"] }

View File

@ -4,9 +4,19 @@ use std::fmt::Display;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::Client;
use crate::api::DomainExtention;
use crate::api::rules::contactgroups::HostContactGroups;
use crate::api::rules::snmp::SnmpCommunity;
use crate::client::ApiClient;
pub const ROOT_FOLDER: &str = "/";
impl Client {
pub fn folder_api(&self) -> ApiClient<FolderConfig> {
self.into()
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct FolderConfig {
@ -137,9 +147,12 @@ pub struct FolderReadQuery;
#[derive(Debug, Serialize)]
pub struct FolderUpdateRequest {
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub attributes: Option<FolderAttributes>,
#[serde(skip_serializing_if = "Option::is_none")]
pub update_attributes: Option<FolderAttributes>,
pub remove_attributes: Option<FolderAttributes>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub remove_attributes: Vec<String>,
}
impl FolderUpdateRequest {
@ -148,7 +161,7 @@ impl FolderUpdateRequest {
title,
attributes: Some(attributes),
update_attributes: None,
remove_attributes: None,
remove_attributes: Vec::new(),
}
}
pub fn update(title: String, attributes: FolderAttributes) -> Self {
@ -156,20 +169,20 @@ impl FolderUpdateRequest {
title,
attributes: None,
update_attributes: Some(attributes),
remove_attributes: None,
remove_attributes: Vec::new(),
}
}
pub fn remove(title: String, attributes: FolderAttributes) -> Self {
pub fn remove(title: String, attributes: Vec<String>) -> Self {
Self {
title,
attributes: None,
update_attributes: None,
remove_attributes: Some(attributes),
remove_attributes: attributes,
}
}
}
#[derive(Debug, Serialize)]
#[derive(Debug, Default, Serialize)]
pub struct FolderDeleteQuery {
pub delete_mode: FolderDeleteMode,
}
@ -180,9 +193,10 @@ impl FolderDeleteQuery {
}
}
#[derive(Debug, Serialize)]
#[derive(Debug, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum FolderDeleteMode {
#[default]
Recursive,
AbotyOnNonEmpty,
}

View File

@ -55,7 +55,7 @@ pub enum HttpMethod {
#[serde(rename_all = "camelCase")]
pub(crate) struct DomainObject<E> {
pub domain_type: DomainType,
pub instance_id: String,
pub id: String,
#[serde(default)]
pub title: Option<String>,
pub extensions: E,
@ -70,7 +70,7 @@ pub(crate) struct DomainObject<E> {
#[serde(rename_all = "camelCase")]
pub(crate) struct DomainCollection<E> {
pub domain_type: DomainType,
pub instance_id: String,
pub id: String,
#[serde(default)]
pub title: Option<String>,
pub value: Vec<DomainObject<E>>,
@ -80,7 +80,7 @@ pub(crate) struct DomainCollection<E> {
// pub extensions: Option<E>,
}
pub(crate) trait DomainExtention: DeserializeOwned + Serialize {
pub trait DomainExtention: DeserializeOwned + Serialize {
const DOMAIN_TYPE: DomainType;
type CreationRequest: Serialize;
@ -92,9 +92,9 @@ pub(crate) 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(crate) trait BulkCreateDomainExtention: DomainExtention {}
pub(crate) trait BulkReadDomainExtention: DomainExtention {}
pub(crate) trait BulkUpdateDomainExtention: DomainExtention {
pub trait BulkCreateDomainExtention: DomainExtention {}
pub trait BulkReadDomainExtention: DomainExtention {}
pub trait BulkUpdateDomainExtention: DomainExtention {
type BulkUpdateRequest: Serialize;
}
pub(crate) trait BulkDeleteDomainExtention: DomainExtention {}
pub trait BulkDeleteDomainExtention: DomainExtention {}

View File

@ -3,7 +3,7 @@ use std::marker::PhantomData;
use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use log::trace;
use log::{debug, trace};
use reqwest::header::{HeaderName, HeaderValue};
use reqwest::{Certificate, Request};
use serde::de::DeserializeOwned;
@ -15,6 +15,7 @@ use crate::api::{
};
use crate::{Error, Result};
#[derive(Clone)]
pub struct Client(Arc<InnerClient>);
struct InnerClient {
http: reqwest::Client,
@ -52,7 +53,7 @@ pub struct ClientBuilder<T> {
}
impl<T> ClientBuilder<T> {
fn new() -> ClientBuilder<RequiresLocation> {
pub fn new() -> ClientBuilder<RequiresLocation> {
Default::default()
}
}
@ -132,6 +133,15 @@ impl ClientBuilder<RequiresNothing> {
"https"
}
}
fn port(&self) -> u16 {
if self.port != 0 {
self.port
} else if self.ssl == SslStrategy::NoSll {
80
} else {
443
}
}
fn basic_auth_header(&self) -> ClientBuildResult<(HeaderName, HeaderValue)> {
let header = format!("Bearer {} {}", self.username, self.password);
let mut header = HeaderValue::from_str(&header)?;
@ -144,7 +154,7 @@ impl ClientBuilder<RequiresNothing> {
"{}://{}:{}/{}/{API_BASE}",
self.protocol(),
self.hostname,
self.port,
self.port(),
self.sitename,
);
trace!("checkmk url: {url}");
@ -209,20 +219,28 @@ impl InnerClient {
}
pub(crate) async fn handle_request(&self, request: Request) -> Result<String> {
trace!("sending {}-request to {}", request.method(), request.url());
let permit = self.semaphore.acquire().await.unwrap();
debug!("sending {}-request to {}", request.method(), request.url());
let response = self
.http
.execute(request)
.await
.map_err(Error::SendRequest)?;
let status = response.status();
let body = response.text().await.map_err(Error::ReceiveBody)?;
drop(permit);
trace!("response from checkmk ({status}): {body}");
if body.contains("Checkmk: Site Not Started") {
return Err(Error::CheckmkNotStarted);
}
if status.is_success() {
Ok(body)
} else {
let cmkerror = serde_json::from_str(&body).map_err(Error::DeserializeResponse)?;
let cmkerror = serde_json::from_str(&body)
.map_err(Error::DeserializeResponse)?;
Err(Error::CheckmkError(cmkerror))
}
}
@ -396,6 +414,15 @@ pub struct ApiClient<D: DomainExtention> {
_marker: PhantomData<D>,
}
impl <D: DomainExtention> From<&Client> for ApiClient<D> {
fn from(value: &Client) -> Self {
Self {
inner: value.0.clone(),
_marker: Default::default()
}
}
}
impl<D: DomainExtention> ApiClient<D> {
pub async fn create(
&self,

View File

@ -25,6 +25,9 @@ pub enum Error {
#[error("Recieved an error from checkmk ({}): {}", .0.status, .0.detail)]
CheckmkError(#[source] CheckmkError),
#[error("Checkmk site has not yet started. log in as site user and execute 'omd start'")]
CheckmkNotStarted,
#[error("Missing header: {0}")]
MissingHeader(&'static str),
#[error("Failed to parse ETag header: {0}")]

View File

@ -3,3 +3,4 @@ mod client;
mod error;
pub use error::{Error, Result};
pub use client::{Client, ClientBuilder};