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.
This commit is contained in:
Christoffer Martinsson 2025-10-23 12:37:16 +02:00
parent dc11538ae9
commit 174b27f31a

View File

@ -169,9 +169,9 @@ impl SystemdCollector {
continue; 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 { for pattern in service_name_filters {
if service_name == pattern { if self.matches_pattern(service_name, pattern) {
debug!( debug!(
"INCLUDING service '{}' because it matches pattern '{}'", "INCLUDING service '{}' because it matches pattern '{}'",
service_name, pattern service_name, pattern
@ -186,6 +186,62 @@ impl SystemdCollector {
Ok(services) 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 /// Get service status using systemctl
fn get_service_status(&self, service: &str) -> Result<(String, String)> { fn get_service_status(&self, service: &str) -> Result<(String, String)> {
let output = Command::new("systemctl") let output = Command::new("systemctl")