From 55fb6914406f6e251543656704fa1542e93d4665 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Mon, 19 Jan 2026 17:20:49 -0600 Subject: [PATCH] refactor: remove 4 more dependencies (rand, ureq, tiny_http, anyhow) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed dependencies: - rand: Replaced with SystemTime-based random for ICMP ID/sequence - ureq: Removed Docker Hub version checking (Watchtower handles updates) - tiny_http: Replaced with raw TCP listener for /health endpoint - anyhow: Replaced with std::error::Error trait Benefits: - Binary size: 2.3M → 1.5M (800KB reduction, 35% smaller!) - Dependencies: 19 → 11 direct dependencies (-8 total) - Tests: 80 → 67 (removed Docker Hub API tests) - All tests passing Custom implementations: - ICMP random: Use nanosecond timestamp for ID/sequence - Health endpoint: Simple HTTP/1.1 parser on raw TCP - Error handling: type Result = std::result::Result> Total progress from initial state: - Binary: 2.4M → 1.5M (37.5% reduction!) - Dependencies removed: 8 (thiserror, hostname, chrono, base64, rand, ureq, tiny_http, anyhow) --- Cargo.lock | 126 +------------------ Cargo.toml | 4 - src/health.rs | 92 ++++++++------ src/ping.rs | 31 +++-- src/version.rs | 270 +--------------------------------------- src/websocket_client.rs | 11 +- 6 files changed, 86 insertions(+), 448 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0885fe0..adbfa0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aho-corasick" version = "1.1.4" @@ -73,18 +67,6 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bitflags" version = "2.10.0" @@ -128,12 +110,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "chunked_transfer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" - [[package]] name = "clap" version = "4.5.54" @@ -189,15 +165,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - [[package]] name = "crypto-common" version = "0.1.7" @@ -275,16 +242,6 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" -[[package]] -name = "flate2" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -444,12 +401,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "icu_collections" version = "2.1.1" @@ -613,15 +564,6 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - [[package]] name = "mio" version = "1.1.1" @@ -878,22 +820,7 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.8", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls" -version = "0.23.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" -dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki", "subtle", "zeroize", ] @@ -918,17 +845,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustls-webpki" -version = "0.103.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "serde" version = "1.0.228" @@ -1100,18 +1016,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - [[package]] name = "tinystr" version = "0.8.2" @@ -1154,7 +1058,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.22.4", + "rustls", "rustls-pki-types", "tokio", ] @@ -1167,7 +1071,7 @@ checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" dependencies = [ "futures-util", "log", - "rustls 0.22.4", + "rustls", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1179,22 +1083,18 @@ dependencies = [ name = "towerops-agent" version = "0.1.0" dependencies = [ - "anyhow", "clap", "futures", "log", "prost", "prost-build", "prost-types", - "rand", "serde", "serde_json", "snmp", "socket2 0.5.10", - "tiny_http", "tokio", "tokio-tungstenite", - "ureq", ] [[package]] @@ -1210,7 +1110,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls 0.22.4", + "rustls", "rustls-pki-types", "sha1", "thiserror", @@ -1236,24 +1136,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "ureq" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" -dependencies = [ - "base64", - "flate2", - "log", - "once_cell", - "rustls 0.23.36", - "rustls-pki-types", - "serde", - "serde_json", - "url", - "webpki-roots 0.26.11", -] - [[package]] name = "url" version = "2.5.8" diff --git a/Cargo.toml b/Cargo.toml index dbd2f59..947ddf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] snmp = "0.2" -ureq = { version = "2.10", features = ["json"] } tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time", "net"] } tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] } futures = "0.3" @@ -15,10 +14,7 @@ log = { version = "0.4", features = ["std"] } clap = { version = "4.0", features = ["derive", "env"] } prost = "0.13" prost-types = "0.13" -tiny_http = "0.12" -anyhow = "1.0" socket2 = "0.5" -rand = "0.8" [build-dependencies] prost-build = "0.13" diff --git a/src/health.rs b/src/health.rs index 47a93f8..a85858b 100644 --- a/src/health.rs +++ b/src/health.rs @@ -1,9 +1,11 @@ -use anyhow::Result; use log::info; use serde::{Deserialize, Serialize}; use std::sync::Arc; use std::time::Instant; -use tiny_http::{Response, Server}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpListener; + +type Result = std::result::Result>; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HealthStatus { @@ -12,54 +14,64 @@ pub struct HealthStatus { pub uptime_seconds: u64, } -/// Start a simple health check HTTP server +/// Start a simple health check HTTP server using raw TCP pub async fn start_health_server(port: u16) -> Result<()> { let start_time = Arc::new(Instant::now()); + let addr = format!("0.0.0.0:{}", port); - tokio::task::spawn_blocking(move || { - let addr = format!("0.0.0.0:{}", port); - info!("Starting health endpoint on {}", addr); + info!("Starting health endpoint on {}", addr); + let listener = TcpListener::bind(&addr).await?; - let server = Server::http(&addr) - .map_err(|e| anyhow::anyhow!("Failed to start health server: {}", e))?; + loop { + match listener.accept().await { + Ok((mut socket, _)) => { + let start_time = Arc::clone(&start_time); - for request in server.incoming_requests() { - let path = request.url(); + tokio::spawn(async move { + let mut buffer = [0u8; 1024]; - if path == "/health" { - let uptime = start_time.elapsed().as_secs(); - let status = HealthStatus { - status: "healthy".to_string(), - version: env!("CARGO_PKG_VERSION").to_string(), - uptime_seconds: uptime, - }; + // Read the HTTP request + if let Ok(n) = socket.read(&mut buffer).await { + if n > 0 { + let request = String::from_utf8_lossy(&buffer[..n]); - let json = serde_json::to_string(&status).unwrap_or_else(|_| { - r#"{"status":"error","message":"Failed to serialize health status"}"# - .to_string() + // Check if this is a GET request to /health + if request.starts_with("GET /health") { + let uptime = start_time.elapsed().as_secs(); + let status = HealthStatus { + status: "healthy".to_string(), + version: env!("CARGO_PKG_VERSION").to_string(), + uptime_seconds: uptime, + }; + + let json = serde_json::to_string(&status).unwrap_or_else(|_| { + r#"{"status":"error","message":"Failed to serialize health status"}"# + .to_string() + }); + + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", + json.len(), + json + ); + + let _ = socket.write_all(response.as_bytes()).await; + } else { + // 404 for other paths + let response = "HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\n\r\nNot found"; + let _ = socket.write_all(response.as_bytes()).await; + } + } + } + + let _ = socket.shutdown().await; }); - - let response = Response::from_string(json) - .with_header( - tiny_http::Header::from_bytes( - &b"Content-Type"[..], - &b"application/json"[..], - ) - .unwrap(), - ) - .with_status_code(200); - - let _ = request.respond(response); - } else { - let _ = request.respond(Response::from_string("Not found").with_status_code(404)); + } + Err(e) => { + log::warn!("Failed to accept health check connection: {}", e); } } - - Ok::<(), anyhow::Error>(()) - }) - .await??; - - Ok(()) + } } #[cfg(test)] diff --git a/src/ping.rs b/src/ping.rs index c11e448..f6b32aa 100644 --- a/src/ping.rs +++ b/src/ping.rs @@ -1,9 +1,10 @@ -use anyhow::{anyhow, Result}; use std::net::{IpAddr, Ipv4Addr}; use std::time::Duration; use tokio::net::UdpSocket; use tokio::time::timeout; +type Result = std::result::Result>; + /// ICMP message types const ICMP_ECHO: u8 = 8; const ICMP_ECHOREPLY: u8 = 0; @@ -33,7 +34,7 @@ pub async fn ping(ip: IpAddr, timeout_duration: Duration) -> Result { match ip { IpAddr::V4(ipv4) => ping_ipv4(ipv4, timeout_duration).await?, IpAddr::V6(_) => { - return Err(anyhow!("IPv6 ping not yet implemented")); + return Err("IPv6 ping not yet implemented".into()); } } @@ -41,9 +42,13 @@ pub async fn ping(ip: IpAddr, timeout_duration: Duration) -> Result { } async fn ping_ipv4(ip: Ipv4Addr, timeout_duration: Duration) -> Result<()> { - // Generate unique identifier and sequence number - let identifier = rand::random::(); - let sequence = rand::random::(); + // Generate unique identifier and sequence number using timestamp + // This provides sufficient randomness for ICMP ping identification + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap(); + let identifier = (now.as_nanos() & 0xFFFF) as u16; + let sequence = ((now.as_nanos() >> 16) & 0xFFFF) as u16; // Build ICMP echo request packet let packet = build_icmp_echo_request(identifier, sequence); @@ -74,8 +79,10 @@ async fn ping_ipv4(ip: Ipv4Addr, timeout_duration: Duration) -> Result<()> { let mut buf = [0u8; 1024]; let n = timeout(timeout_duration, socket.recv(&mut buf)) .await - .map_err(|_| anyhow!("Ping timeout"))? - .map_err(|e| anyhow!("Failed to receive ping reply: {}", e))?; + .map_err(|_| -> Box { "Ping timeout".into() })? + .map_err(|e| -> Box { + format!("Failed to receive ping reply: {}", e).into() + })?; // Parse and validate the reply parse_icmp_reply(&buf[..n], identifier, sequence)?; @@ -163,16 +170,16 @@ fn parse_icmp_reply(packet: &[u8], expected_identifier: u16, expected_sequence: format!("len={} (too short)", packet.len()) }; - Err(anyhow!( + Err(format!( "Invalid ICMP reply packet (expected seq={}): {}", expected_sequence, packet_preview - )) + ).into()) } fn try_parse_icmp(packet: &[u8], _expected_identifier: u16, expected_sequence: u16) -> Result<()> { if packet.len() < 8 { - return Err(anyhow!("Packet too short")); + return Err("Packet too short".into()); } let icmp_type = packet[0]; @@ -186,13 +193,13 @@ fn try_parse_icmp(packet: &[u8], _expected_identifier: u16, expected_sequence: u if icmp_type == ICMP_ECHOREPLY && icmp_code == 0 && sequence == expected_sequence { Ok(()) } else { - Err(anyhow!( + Err(format!( "ICMP packet mismatch (type={}, code={}, id={}, seq={})", icmp_type, icmp_code, identifier, sequence - )) + ).into()) } } diff --git a/src/version.rs b/src/version.rs index de76336..8db5d18 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,133 +1,15 @@ -use log::{info, warn}; -use serde::Deserialize; -use std::cmp::Ordering; - -const DOCKER_IMAGE: &str = "gmcintire/towerops-agent"; +use log::info; // Get version at runtime - prefers BUILD_VERSION from build.rs, falls back to Cargo.toml fn current_version() -> &'static str { option_env!("BUILD_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")) } -#[derive(Debug, Deserialize)] -struct DockerHubResponse { - results: Vec, -} - -#[derive(Debug, Deserialize)] -struct DockerHubTag { - name: String, -} - -#[derive(Debug, PartialEq, Eq)] -struct Version { - major: u32, - minor: u32, - patch: u32, -} - -impl Version { - fn parse(s: &str) -> Option { - let s = s.strip_prefix('v').unwrap_or(s); - let parts: Vec<&str> = s.split('.').collect(); - - if parts.len() != 3 { - return None; - } - - Some(Version { - major: parts[0].parse().ok()?, - minor: parts[1].parse().ok()?, - patch: parts[2].parse().ok()?, - }) - } -} - -impl PartialOrd for Version { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Version { - fn cmp(&self, other: &Self) -> Ordering { - match self.major.cmp(&other.major) { - Ordering::Equal => match self.minor.cmp(&other.minor) { - Ordering::Equal => self.patch.cmp(&other.patch), - other => other, - }, - other => other, - } - } -} - -/// Startup check - logs current version and checks for updates +/// Startup check - logs current version pub fn check_for_updates() { let current_ver = current_version(); info!("Current version: {}", current_ver); - - match get_latest_version() { - Ok(latest) => { - let current = Version::parse(current_ver); - let latest_version = Version::parse(&latest); - - match (current, latest_version) { - (Some(curr), Some(lat)) => { - if lat > curr { - warn!( - "⚠️ Newer version available: {} (current: {})", - latest, current_ver - ); - warn!(" Automatic updates will pull new version every hour"); - } else { - info!("✓ Running latest version ({})", current_ver); - } - } - _ => { - info!("Could not compare versions, automatic updates enabled"); - } - } - } - Err(e) => { - warn!("Could not check for updates: {}", e); - info!("Automatic updates enabled - will check every hour"); - } - } -} - -/// Get the latest version from Docker Hub -fn get_latest_version() -> Result> { - let url = format!( - "https://hub.docker.com/v2/repositories/{}/tags?page_size=100", - DOCKER_IMAGE - ); - - let response: DockerHubResponse = ureq::get(&url) - .timeout(std::time::Duration::from_secs(10)) - .call()? - .into_json()?; - - extract_latest_version_from_response(response) -} - -/// Extract the latest version from Docker Hub response -fn extract_latest_version_from_response( - response: DockerHubResponse, -) -> Result> { - // Filter for semver tags and find the latest - let mut versions: Vec = response - .results - .iter() - .filter_map(|tag| Version::parse(&tag.name)) - .collect(); - - versions.sort(); - versions.reverse(); // Highest version first - - versions - .first() - .map(|v| format!("{}.{}.{}", v.major, v.minor, v.patch)) - .ok_or_else(|| "No valid semver tags found".into()) + info!("Watchtower will automatically update to new versions"); } #[cfg(test)] @@ -142,150 +24,6 @@ mod tests { // Just verify it's a non-empty string } - #[test] - fn test_version_parse_valid() { - let v = Version::parse("1.2.3").unwrap(); - assert_eq!(v.major, 1); - assert_eq!(v.minor, 2); - assert_eq!(v.patch, 3); - } - #[test] - fn test_version_parse_with_v_prefix() { - let v = Version::parse("v2.5.8").unwrap(); - assert_eq!(v.major, 2); - assert_eq!(v.minor, 5); - assert_eq!(v.patch, 8); - } - - #[test] - fn test_version_parse_invalid() { - assert!(Version::parse("1.2").is_none()); - assert!(Version::parse("1.2.3.4").is_none()); - assert!(Version::parse("a.b.c").is_none()); - assert!(Version::parse("1.2.x").is_none()); - assert!(Version::parse("").is_none()); - } - - #[test] - fn test_version_comparison_major() { - let v1 = Version::parse("2.0.0").unwrap(); - let v2 = Version::parse("1.9.9").unwrap(); - assert!(v1 > v2); - assert!(v2 < v1); - } - - #[test] - fn test_version_comparison_minor() { - let v1 = Version::parse("1.5.0").unwrap(); - let v2 = Version::parse("1.4.9").unwrap(); - assert!(v1 > v2); - assert!(v2 < v1); - } - - #[test] - fn test_version_comparison_patch() { - let v1 = Version::parse("1.2.4").unwrap(); - let v2 = Version::parse("1.2.3").unwrap(); - assert!(v1 > v2); - assert!(v2 < v1); - } - - #[test] - fn test_version_comparison_equal() { - let v1 = Version::parse("1.2.3").unwrap(); - let v2 = Version::parse("1.2.3").unwrap(); - assert_eq!(v1, v2); - assert!(!(v1 > v2)); - assert!(!(v1 < v2)); - } - - #[test] - fn test_version_sorting() { - let mut versions = vec![ - Version::parse("1.2.3").unwrap(), - Version::parse("2.0.0").unwrap(), - Version::parse("1.5.0").unwrap(), - Version::parse("1.2.10").unwrap(), - ]; - versions.sort(); - assert_eq!(versions[0], Version::parse("1.2.3").unwrap()); - assert_eq!(versions[1], Version::parse("1.2.10").unwrap()); - assert_eq!(versions[2], Version::parse("1.5.0").unwrap()); - assert_eq!(versions[3], Version::parse("2.0.0").unwrap()); - } - - #[test] - fn test_dockerhub_response_deserialize() { - let json = r#"{"results":[{"name":"v1.0.0"},{"name":"v1.0.1"}]}"#; - let response: DockerHubResponse = serde_json::from_str(json).unwrap(); - assert_eq!(response.results.len(), 2); - assert_eq!(response.results[0].name, "v1.0.0"); - assert_eq!(response.results[1].name, "v1.0.1"); - } - - #[test] - fn test_extract_latest_version_from_response() { - let response = DockerHubResponse { - results: vec![ - DockerHubTag { - name: "v1.0.0".to_string(), - }, - DockerHubTag { - name: "v1.2.0".to_string(), - }, - DockerHubTag { - name: "v1.1.5".to_string(), - }, - DockerHubTag { - name: "latest".to_string(), - }, // Should be ignored - ], - }; - - let latest = extract_latest_version_from_response(response).unwrap(); - assert_eq!(latest, "1.2.0"); - } - - #[test] - fn test_extract_latest_version_no_valid_tags() { - let response = DockerHubResponse { - results: vec![ - DockerHubTag { - name: "latest".to_string(), - }, - DockerHubTag { - name: "main".to_string(), - }, - ], - }; - - let result = extract_latest_version_from_response(response); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("No valid semver")); - } - - #[test] - fn test_extract_latest_version_empty() { - let response = DockerHubResponse { results: vec![] }; - - let result = extract_latest_version_from_response(response); - assert!(result.is_err()); - } - - #[test] - fn test_version_debug() { - let v = Version { - major: 1, - minor: 2, - patch: 3, - }; - let debug_str = format!("{:?}", v); - assert!(debug_str.contains("1")); - assert!(debug_str.contains("2")); - assert!(debug_str.contains("3")); - } - - // Note: check_for_updates() and get_latest_version() are tested via integration tests - // as they require network access to Docker Hub + // Note: check_for_updates() just logs the version, no testing needed } diff --git a/src/websocket_client.rs b/src/websocket_client.rs index f4ab31f..0ad6257 100644 --- a/src/websocket_client.rs +++ b/src/websocket_client.rs @@ -6,7 +6,6 @@ /// /// Connection URL: {url}/socket/agent/websocket /// Authentication: Token sent in Phoenix channel join payload -use anyhow::{Context, Result}; use futures::{SinkExt, StreamExt}; use prost::Message; use std::collections::HashMap; @@ -17,6 +16,8 @@ use tokio_tungstenite::{ connect_async, tungstenite::protocol::Message as WsMessage, MaybeTlsStream, WebSocketStream, }; +type Result = std::result::Result>; + use crate::ping::ping; use crate::proto::agent::{ AgentHeartbeat, AgentJob, AgentJobList, JobType, MonitoringCheck, QueryType, SnmpResult, @@ -66,7 +67,7 @@ impl AgentClient { log::error!("WebSocket connection failed: {}", e); e }) - .context("Failed to connect to WebSocket")?; + .map_err(|e| format!("Failed to connect to WebSocket: {}", e))?; log::info!("Connected to Towerops server at {}", url); @@ -323,7 +324,9 @@ impl AgentClient { /// Execute an SNMP job and collect results. async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender) -> Result<()> { - let snmp_device = job.snmp_device.context("Job missing SNMP device info")?; + let snmp_device = job + .snmp_device + .ok_or("Job missing SNMP device info")?; let mut oid_values: HashMap = HashMap::new(); let snmp_client = SnmpClient::new(); @@ -468,7 +471,7 @@ fn base64_decode(encoded: &str) -> Result> { } else { let val = decode_map[byte as usize]; if val == 0xFF { - anyhow::bail!("Invalid base64 character"); + return Err("Invalid base64 character".into()); } buf[i] = val; }