From cabb054dc50e3416e60585c6d0617bb2191c29d4 Mon Sep 17 00:00:00 2001 From: Vincent Stuyck Date: Sat, 20 Dec 2025 13:05:50 +0100 Subject: [PATCH] add hosts and implement (bulk)crud operations --- src/api/folders.rs | 55 ++++++++------ src/api/hosts.rs | 177 +++++++++++++++++++++++++++++++++++++++++++++ src/api/mod.rs | 2 + src/client.rs | 18 ++++- src/lib.rs | 1 + src/main.rs | 5 +- 6 files changed, 229 insertions(+), 29 deletions(-) create mode 100644 src/api/hosts.rs diff --git a/src/api/folders.rs b/src/api/folders.rs index b3aec9a..4362d13 100644 --- a/src/api/folders.rs +++ b/src/api/folders.rs @@ -4,11 +4,10 @@ use std::fmt::Display; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::{Client, Result}; +use crate::{ApiClient, Client, Result}; use crate::api::{BulkReadDomainExtention, BulkUpdateDomainExtention, DomainExtention}; use crate::api::rules::contactgroups::HostContactGroups; use crate::api::rules::snmp::SnmpCommunity; -use crate::client::ApiClient; pub const ROOT_FOLDER: &str = "/"; @@ -120,34 +119,46 @@ impl DomainExtention for FolderConfig { type DeleteQuery = FolderDeleteQuery; } -#[derive(Debug, Serialize)] +#[derive(Debug, Default, Clone, Serialize)] pub struct FolderCreationRequest { - pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, pub title: String, pub parent: String, - pub attributes: FolderAttributes, + #[serde(skip_serializing_if = "Option::is_none")] + pub attributes: Option, } impl FolderCreationRequest { - pub fn new(name: String, title: String, parent: String, attributes: FolderAttributes) -> Self { + pub fn new(title: String, parent: String) -> Self { Self { - name, - title, - parent, - attributes, + title, parent, + name: None, + attributes: None } } + + pub fn set_name(&mut self, name: String) { + self.name = Some(name); + } + pub fn with_name(mut self, name: String) -> Self { + self.set_name(name); + self + } + + pub fn set_attributes(&mut self, attributes: FolderAttributes) { + self.attributes = Some(attributes); + } + pub fn with_attributes(mut self, attributes: FolderAttributes) -> Self { + self.set_attributes(attributes); + self + } } -#[derive(Debug, Serialize)] -pub struct FolderCreationQuery; -#[derive(Debug, Serialize)] -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 title: Option, #[serde(skip_serializing_if = "Option::is_none")] pub attributes: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -159,7 +170,7 @@ pub struct FolderUpdateRequest { impl FolderUpdateRequest { pub fn replace(attributes: FolderAttributes) -> Self { Self { - title: String::new(), + title: None, attributes: Some(attributes), update_attributes: None, remove_attributes: Vec::new(), @@ -167,7 +178,7 @@ impl FolderUpdateRequest { } pub fn update(attributes: FolderAttributes) -> Self { Self { - title: String::new(), + title: None, attributes: None, update_attributes: Some(attributes), remove_attributes: Vec::new(), @@ -175,7 +186,7 @@ impl FolderUpdateRequest { } pub fn remove(attributes: Vec) -> Self { Self { - title: String::new(), + title: None, attributes: None, update_attributes: None, remove_attributes: attributes, @@ -184,14 +195,14 @@ impl FolderUpdateRequest { pub fn title(title: String) -> Self { Self { - title, + title: Some(title), attributes: None, update_attributes: None, remove_attributes: Vec::new() } } pub fn set_title(&mut self, title: String) { - self.title = title; + self.title = Some(title); } pub fn with_title(mut self, title: String) -> Self { self.set_title(title); diff --git a/src/api/hosts.rs b/src/api/hosts.rs new file mode 100644 index 0000000..7d510b9 --- /dev/null +++ b/src/api/hosts.rs @@ -0,0 +1,177 @@ +use serde::{Deserialize, Serialize}; + +use crate::{Client, ApiClient}; +use crate::api::{BulkCreateDomainExtention, BulkDeleteDomainExtention, BulkReadDomainExtention, BulkUpdateDomainExtention, DomainExtention}; +use crate::api::folders::FolderAttributes; + + +impl Client { + pub fn host_api(&self) -> ApiClient { + self.into() + } +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct HostConfig { + pub folder: String, + pub attributes: FolderAttributes, + pub effective_attributes: FolderAttributes, + pub is_cluster: bool, + pub is_offline: bool, + pub cluster_nodes: Option> +} + +impl DomainExtention for HostConfig { + const DOMAIN_TYPE: super::DomainType = super::DomainType::HostConfig; + + type CreationRequest = HostCreationRequest; + type CreationQuery = HostCreationQuery; + type ReadQuery = HostReadQuery; + type UpdateRequest = HostUpdateRequest; + type DeleteQuery = (); +} + +#[derive(Serialize)] +pub struct HostCreationRequest { + pub hostname: String, + pub folder: String, + pub attributes: FolderAttributes +} +#[derive(Serialize)] +pub struct HostCreationQuery { + pub bake_agent: bool +} +#[derive(Serialize)] +pub struct HostReadQuery { + pub effective_attributes: bool +} +#[derive(Serialize)] +pub struct HostUpdateRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub attributes: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub update_attributes: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub remove_attributes: Vec, +} + +impl HostCreationRequest { + pub fn new(hostname: String, folder: String, attributes: FolderAttributes) -> Self { + HostCreationRequest { hostname, folder, attributes } + } +} + +impl HostCreationQuery { + pub fn new(bake_agent: bool) -> Self { + Self { bake_agent } + } +} + +impl HostReadQuery { + pub fn new(effective_attributes: bool) -> Self { + Self { effective_attributes } + } +} + +impl HostUpdateRequest { + pub fn replace(attributes: FolderAttributes) -> Self { + Self { + attributes: Some(attributes), + update_attributes: None, + remove_attributes: Vec::new(), + } + } + pub fn update(attributes: FolderAttributes) -> Self { + Self { + attributes: None, + update_attributes: Some(attributes), + remove_attributes: Vec::new(), + } + } + pub fn remove(attributes: Vec) -> Self { + Self { + attributes: None, + update_attributes: None, + remove_attributes: attributes, + } + } +} + +impl BulkCreateDomainExtention for HostConfig {} +impl BulkReadDomainExtention for HostConfig { + type BulkReadQuery = HostBulkReadQuery; +} +impl BulkUpdateDomainExtention for HostConfig { + type BulkUpdateRequest = HostBulkUpdateRequest; +} +impl BulkDeleteDomainExtention for HostConfig {} + +#[derive(Debug, Default, Clone, Serialize)] +pub struct HostBulkReadQuery { + effective_attributes: bool, + #[serde(skip_serializing_if = "Option::is_none")] + fields: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + hostnames: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + site: Option +} + +impl HostBulkReadQuery { + pub fn new() -> Self { + Self::default() + } + + pub fn set_effective_attributes(&mut self, effective_attributes: bool) { + self.effective_attributes = effective_attributes; + } + pub fn with_effective_attributes(mut self, effective_attributes: bool) -> Self { + self.set_effective_attributes(effective_attributes); + self + } + + pub fn set_fields(&mut self, fields: String) { + self.fields = Some(fields); + } + pub fn with_fields(mut self, fields: String) -> Self { + self.set_fields(fields); + self + } + + pub fn add_hostname(&mut self, hostname: String) { + self.hostnames.push(hostname); + } + pub fn with_hostname(mut self, hostname: String) -> Self { + self.add_hostname(hostname); + self + } + + pub fn set_hostnames(&mut self, hostnames: Vec) { + self.hostnames = hostnames; + } + pub fn with_hostnames(mut self, hostnames: Vec) -> Self { + self.set_hostnames(hostnames); + self + } + + pub fn set_site(&mut self, site: String) { + self.site = Some(site); + } + pub fn with_site(mut self, site: String) -> Self { + self.set_site(site); + self + } +} + +#[derive(Serialize)] +pub struct HostBulkUpdateRequest { + host_name: String, + #[serde(flatten)] + update_request: HostUpdateRequest +} + +impl HostBulkUpdateRequest { + pub fn new(host_name: String, update_request: HostUpdateRequest) -> Self { + Self { host_name, update_request } + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 7cf07f2..0285f9c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,4 +1,5 @@ pub mod folders; +pub mod hosts; pub(crate) mod rules; use std::fmt; @@ -92,6 +93,7 @@ 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 +// and those that do tend to have a bit different types pub trait BulkCreateDomainExtention: DomainExtention {} pub trait BulkReadDomainExtention: DomainExtention { type BulkReadQuery: Serialize; diff --git a/src/client.rs b/src/client.rs index 85ed53e..d45fdd3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -345,13 +345,18 @@ impl InnerClient { pub(crate) async fn bulk_create_domain_objects( &self, - request: &[E::CreationRequest], + entries: &[E::CreationRequest], query: &E::CreationQuery, ) -> Result<()> { + #[derive(Serialize)] + struct Request<'a, D: BulkCreateDomainExtention> { + entries: &'a [D::CreationRequest] + } + let request = self .http .post(self.bulk_action_url(E::DOMAIN_TYPE, BulkAction::Create)) - .json(request) + .json(&Request::{ entries }) .query(query) .build() .unwrap(); @@ -392,12 +397,17 @@ impl InnerClient { } pub(crate) async fn bulk_delete_domain_objects( &self, - ids: &[String], + entries: &[String], ) -> Result<()> { + #[derive(Serialize)] + struct Request<'a> { + entries: &'a [String] + } + let request = self .http .post(self.bulk_action_url(E::DOMAIN_TYPE, BulkAction::Delete)) - .json(&ids) + .json(&Request { entries }) .build() .unwrap(); diff --git a/src/lib.rs b/src/lib.rs index c52dcd6..05086d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,4 @@ mod error; pub use error::{Error, Result}; pub use client::{Client, ClientBuilder}; +pub(crate) use client::ApiClient; diff --git a/src/main.rs b/src/main.rs index 602ee0f..8ab7d1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,11 +37,10 @@ async fn test_folders(client: Client) -> Result<()> { let folderapi = client.folder_api(); let creq = FolderCreationRequest::new( - TESTDIR.to_string(), "Testing".to_string(), ROOT_FOLDER.to_string(), - FolderAttributes::default() - ); + ) + .with_name(TESTDIR.to_string()); let ureq1 = FolderUpdateRequest::replace( FolderAttributes::default() .with_site("dev".to_string())