diff --git a/Cargo.lock b/Cargo.lock index 1ea2079..1c69b39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 2b7ad81..f2b5785 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/src/api/folders.rs b/src/api/folders.rs index 4064963..8497c3d 100644 --- a/src/api/folders.rs +++ b/src/api/folders.rs @@ -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 { + 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, + #[serde(skip_serializing_if = "Option::is_none")] pub update_attributes: Option, - pub remove_attributes: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub remove_attributes: Vec, } 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) -> 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, } diff --git a/src/api/mod.rs b/src/api/mod.rs index 89c7411..a453953 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -55,7 +55,7 @@ pub enum HttpMethod { #[serde(rename_all = "camelCase")] pub(crate) struct DomainObject { pub domain_type: DomainType, - pub instance_id: String, + pub id: String, #[serde(default)] pub title: Option, pub extensions: E, @@ -70,7 +70,7 @@ pub(crate) struct DomainObject { #[serde(rename_all = "camelCase")] pub(crate) struct DomainCollection { pub domain_type: DomainType, - pub instance_id: String, + pub id: String, #[serde(default)] pub title: Option, pub value: Vec>, @@ -80,7 +80,7 @@ pub(crate) struct DomainCollection { // pub extensions: Option, } -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 {} diff --git a/src/client.rs b/src/client.rs index 5a905cc..32bdee5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -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); struct InnerClient { http: reqwest::Client, @@ -52,7 +53,7 @@ pub struct ClientBuilder { } impl ClientBuilder { - fn new() -> ClientBuilder { + pub fn new() -> ClientBuilder { Default::default() } } @@ -132,6 +133,15 @@ impl ClientBuilder { "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 { "{}://{}:{}/{}/{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 { - 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 { _marker: PhantomData, } +impl From<&Client> for ApiClient { + fn from(value: &Client) -> Self { + Self { + inner: value.0.clone(), + _marker: Default::default() + } + } +} + impl ApiClient { pub async fn create( &self, diff --git a/src/error.rs b/src/error.rs index d07ab56..648d693 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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}")] diff --git a/src/lib.rs b/src/lib.rs index 5f16b1e..c52dcd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,3 +3,4 @@ mod client; mod error; pub use error::{Error, Result}; +pub use client::{Client, ClientBuilder};