From 174b27f31a3c819609da26c8f97aea7f054b0f61 Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Thu, 23 Oct 2025 12:37:16 +0200 Subject: [PATCH] Phase 3: Add wildcard support for service pattern matching Implement glob pattern matching for service filters: - nginx* matches nginx, nginx-config-reload, etc. - *backup matches any service ending with 'backup' - docker*prune matches docker-weekly-prune, etc. - Exact matches still work as before (backward compatible) Addresses TODO.md requirement for '*' filtering support. --- agent/src/collectors/systemd.rs | 60 +++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/agent/src/collectors/systemd.rs b/agent/src/collectors/systemd.rs index 6b75391..bb86a31 100644 --- a/agent/src/collectors/systemd.rs +++ b/agent/src/collectors/systemd.rs @@ -169,9 +169,9 @@ impl SystemdCollector { continue; } - // Check if this service matches our filter patterns + // Check if this service matches our filter patterns (supports wildcards) for pattern in service_name_filters { - if service_name == pattern { + if self.matches_pattern(service_name, pattern) { debug!( "INCLUDING service '{}' because it matches pattern '{}'", service_name, pattern @@ -186,6 +186,62 @@ impl SystemdCollector { Ok(services) } + /// Check if service name matches pattern (supports wildcards like nginx*) + fn matches_pattern(&self, service_name: &str, pattern: &str) -> bool { + if pattern.contains('*') { + // Wildcard pattern matching + if pattern.ends_with('*') { + // Pattern like "nginx*" - match if service starts with "nginx" + let prefix = &pattern[..pattern.len() - 1]; + service_name.starts_with(prefix) + } else if pattern.starts_with('*') { + // Pattern like "*backup" - match if service ends with "backup" + let suffix = &pattern[1..]; + service_name.ends_with(suffix) + } else { + // Pattern like "nginx*backup" - simple glob matching + self.simple_glob_match(service_name, pattern) + } + } else { + // Exact match (existing behavior) + service_name == pattern + } + } + + /// Simple glob pattern matching for patterns with * in middle + fn simple_glob_match(&self, text: &str, pattern: &str) -> bool { + let parts: Vec<&str> = pattern.split('*').collect(); + if parts.is_empty() { + return false; + } + + let mut pos = 0; + for (i, part) in parts.iter().enumerate() { + if part.is_empty() { + continue; + } + + if i == 0 { + // First part must match at start + if !text[pos..].starts_with(part) { + return false; + } + pos += part.len(); + } else if i == parts.len() - 1 { + // Last part must match at end + return text[pos..].ends_with(part); + } else { + // Middle part must be found somewhere + if let Some(found_pos) = text[pos..].find(part) { + pos += found_pos + part.len(); + } else { + return false; + } + } + } + true + } + /// Get service status using systemctl fn get_service_status(&self, service: &str) -> Result<(String, String)> { let output = Command::new("systemctl")