towerops-agent/src/api_client.rs
2026-01-09 13:57:27 -06:00

119 lines
3.4 KiB
Rust

use crate::config::{AgentConfig, HeartbeatMetadata};
use crate::metrics::Metric;
use serde_json::json;
use std::time::Duration;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ApiError {
#[error("HTTP request failed: {0}")]
RequestFailed(String),
#[error("HTTP status error: {0}")]
StatusError(u16),
#[error("JSON parsing error: {0}")]
JsonError(#[from] std::io::Error),
#[error("Task join error: {0}")]
JoinError(String),
}
pub type Result<T> = std::result::Result<T, ApiError>;
/// API client for communicating with the Towerops server
#[derive(Clone)]
pub struct ApiClient {
base_url: String,
token: String,
}
impl ApiClient {
/// Create a new API client
pub fn new(base_url: String, token: String) -> Result<Self> {
Ok(Self { base_url, token })
}
/// Fetch configuration from the API
pub async fn fetch_config(&self) -> Result<AgentConfig> {
let url = format!("{}/api/v1/agent/config", self.base_url);
let token = self.token.clone();
let config = tokio::task::spawn_blocking(move || {
let response = ureq::get(&url)
.set("Authorization", &format!("Bearer {}", token))
.timeout(Duration::from_secs(30))
.call()
.map_err(|e| ApiError::RequestFailed(e.to_string()))?;
let status = response.status();
if status != 200 {
return Err(ApiError::StatusError(status));
}
let config: AgentConfig = response
.into_json()
.map_err(|e| ApiError::RequestFailed(e.to_string()))?;
Ok(config)
})
.await
.map_err(|e| ApiError::JoinError(e.to_string()))??;
Ok(config)
}
/// Submit metrics to the API
pub async fn submit_metrics(&self, metrics: Vec<Metric>) -> Result<()> {
if metrics.is_empty() {
return Ok(());
}
let url = format!("{}/api/v1/agent/metrics", self.base_url);
let token = self.token.clone();
tokio::task::spawn_blocking(move || {
let response = ureq::post(&url)
.set("Authorization", &format!("Bearer {}", token))
.timeout(Duration::from_secs(30))
.send_json(json!({ "metrics": metrics }))
.map_err(|e| ApiError::RequestFailed(e.to_string()))?;
let status = response.status();
if status != 200 {
return Err(ApiError::StatusError(status));
}
Ok(())
})
.await
.map_err(|e| ApiError::JoinError(e.to_string()))??;
Ok(())
}
/// Send heartbeat to the API
pub async fn heartbeat(&self, metadata: HeartbeatMetadata) -> Result<()> {
let url = format!("{}/api/v1/agent/heartbeat", self.base_url);
let token = self.token.clone();
tokio::task::spawn_blocking(move || {
let response = ureq::post(&url)
.set("Authorization", &format!("Bearer {}", token))
.timeout(Duration::from_secs(30))
.send_json(&metadata)
.map_err(|e| ApiError::RequestFailed(e.to_string()))?;
let status = response.status();
if status != 200 {
return Err(ApiError::StatusError(status));
}
Ok(())
})
.await
.map_err(|e| ApiError::JoinError(e.to_string()))??;
Ok(())
}
}