Skip to main content

omnyssh_core/ssh/services/
mod.rs

1//! Service provider registry and trait definitions.
2//!
3//! Each service (Docker, PostgreSQL, Nginx, etc.) implements the
4//! [`ServiceProvider`] trait. The registry discovers which providers
5//! apply to a given server based on the probe output.
6
7use async_trait::async_trait;
8
9use crate::event::{ServiceKind, ServiceMetric};
10use crate::ssh::probe::ProbeOutput;
11
12// Service provider modules
13pub mod docker;
14pub mod nginx;
15pub mod nodejs;
16pub mod postgresql;
17pub mod redis;
18
19/// Trait for service-specific detection from probe output.
20///
21/// Each provider implements:
22/// 1. Quick detection from probe output
23/// 2. Basic metric extraction from Quick Scan output
24#[async_trait]
25pub trait ServiceProvider: Send + Sync {
26    /// Returns the service type this provider handles.
27    fn kind(&self) -> ServiceKind;
28
29    /// Quick check: is this service present on the server?
30    ///
31    /// Called during Quick Scan with the parsed probe output.
32    /// Should be fast — only check for presence, not detailed metrics.
33    fn detect(&self, probe_output: &ProbeOutput) -> bool;
34
35    /// Extract basic metrics from Quick Scan probe output.
36    ///
37    /// This is called immediately during Quick Scan to provide basic
38    /// service information. Default implementation returns empty metrics.
39    fn quick_metrics(&self, _probe_output: &ProbeOutput) -> Vec<ServiceMetric> {
40        Vec::new()
41    }
42}
43
44/// Service registry that manages all available providers.
45pub struct ServiceRegistry {
46    providers: Vec<Box<dyn ServiceProvider>>,
47}
48
49impl ServiceRegistry {
50    /// Create a new registry with all built-in providers.
51    /// Only 5 core services are supported: Docker, Nginx, PostgreSQL, Redis, Node.js.
52    pub fn new() -> Self {
53        let providers: Vec<Box<dyn ServiceProvider>> = vec![
54            Box::new(docker::DockerProvider),
55            Box::new(nginx::NginxProvider),
56            Box::new(postgresql::PostgreSQLProvider),
57            Box::new(redis::RedisProvider),
58            Box::new(nodejs::NodeJSProvider),
59        ];
60
61        Self { providers }
62    }
63
64    /// Detect which services are present based on probe output.
65    ///
66    /// Returns a list of service kinds that were detected.
67    pub fn detect_services(&self, probe_output: &ProbeOutput) -> Vec<ServiceKind> {
68        self.providers
69            .iter()
70            .filter(|p| p.detect(probe_output))
71            .map(|p| p.kind())
72            .collect()
73    }
74
75    /// Get a provider by service kind.
76    pub fn get_provider(&self, kind: &ServiceKind) -> Option<&dyn ServiceProvider> {
77        self.providers
78            .iter()
79            .find(|p| &p.kind() == kind)
80            .map(|boxed| &**boxed)
81    }
82}
83
84impl Default for ServiceRegistry {
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90/// Helper to create a simple service metric.
91pub fn metric_int(name: impl Into<String>, value: i64) -> ServiceMetric {
92    ServiceMetric {
93        name: name.into(),
94        value: crate::event::MetricValue::Integer(value),
95    }
96}
97
98// ---------------------------------------------------------------------------
99// Tests
100// ---------------------------------------------------------------------------
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_registry_creation() {
108        let registry = ServiceRegistry::new();
109        assert_eq!(registry.providers.len(), 5); // Docker, Nginx, PostgreSQL, Redis, Node.js
110    }
111
112    #[test]
113    fn test_registry_get_provider() {
114        let registry = ServiceRegistry::new();
115        assert!(registry.get_provider(&ServiceKind::Docker).is_some());
116        assert!(registry.get_provider(&ServiceKind::Nginx).is_some());
117        assert!(registry.get_provider(&ServiceKind::PostgreSQL).is_some());
118        assert!(registry.get_provider(&ServiceKind::Redis).is_some());
119        assert!(registry.get_provider(&ServiceKind::NodeJS).is_some());
120    }
121}