darpan 0.2.5

Linux developer service monitoring utility with auto-detection, real-time health checks, and interactive TUI for databases, APIs, Docker containers, and more
Documentation
use crate::core::detector_registry::DetectorRegistry;
use crate::health::{HealthCheckerRegistry, ScriptHealthChecker};
use crate::logs::{log_registry::LogStreamerRegistry, script::ScriptLogStreamer};
use crate::models::HealthCheckType;
use crate::models::Service;
use crate::packs::loader::ServicePackLoader;
use crate::packs::schema::ServicePack;
use anyhow::Result;
use std::path::Path;
use tracing::{debug, info};

/// Integrates service packs into registries and applies pack data to services
pub struct PackIntegration;

impl PackIntegration {
    /// Load packs and integrate them into registries
    pub fn integrate_packs(
        project_path: &Path,
        _detector_registry: &mut DetectorRegistry,
        health_registry: &mut HealthCheckerRegistry,
        log_registry: &mut LogStreamerRegistry,
    ) -> Result<Vec<ServicePack>> {
        let packs = ServicePackLoader::load_all(project_path)?;

        if packs.is_empty() {
            return Ok(packs);
        }

        info!("Integrating {} service pack(s)", packs.len());

        // Register script-based health checkers from packs
        for pack in &packs {
            for signature in &pack.signatures {
                if let Some(ref health_check) = signature.health_check {
                    if let HealthCheckType::Custom { ref script } = health_check.check_type {
                        let checker_id = format!("pack:{}:{}", pack.name, signature.name);
                        let checker_id_clone = checker_id.clone();
                        let checker = ScriptHealthChecker::new(
                            script.clone(),
                            health_check.timeout,
                        );
                        health_registry.register(checker_id, Box::new(checker));
                        debug!("Registered script health checker: {}", checker_id_clone);
                    }
                }
            }

            // Register script-based log streamers from packs
            for signature in &pack.signatures {
                for log_path in &signature.log_paths {
                    // Check if it's a command (starts with a command, not a file path)
                    if log_path.contains(' ') && !log_path.starts_with('/') && !log_path.starts_with('.') {
                        let streamer_id = format!("pack:{}:{}:logs", pack.name, signature.name);
                        let streamer_id_clone = streamer_id.clone();
                        let streamer = ScriptLogStreamer::new(
                            log_path.clone(),
                            Some(format!("{}:{}", pack.name, signature.name)),
                        );
                        log_registry.register(streamer_id, Box::new(streamer));
                        debug!("Registered script log streamer: {}", streamer_id_clone);
                    }
                }
            }
        }

        Ok(packs)
    }

    /// Apply pack defaults to a service
    pub fn apply_pack_defaults(service: &mut Service, packs: &[ServicePack]) {
        for pack in packs {
            // Apply default health checks
            for (pattern, health_check) in &pack.default_health_checks {
                if Self::matches_pattern(&service.name, pattern) {
                    if service.health_check.check_type == HealthCheckType::Port {
                        // Only apply if no explicit health check is set
                        service.health_check = health_check.clone();
                        debug!("Applied health check from pack '{}' to service '{}'", pack.name, service.name);
                    }
                }
            }

            // Apply default log paths
            for (pattern, log_paths) in &pack.default_log_paths {
                if Self::matches_pattern(&service.name, pattern) {
                    if service.log_file_path.is_none() && !log_paths.is_empty() {
                        service.log_file_path = Some(log_paths[0].clone());
                        debug!("Applied log path from pack '{}' to service '{}'", pack.name, service.name);
                    }
                }
            }

            // Apply default systemd units
            for (pattern, unit) in &pack.default_systemd_units {
                if Self::matches_pattern(&service.name, pattern) {
                    if service.systemd_unit.is_none() {
                        service.systemd_unit = Some(unit.clone());
                        debug!("Applied systemd unit from pack '{}' to service '{}'", pack.name, service.name);
                    }
                }
            }

            // Check signatures for exact matches
            for signature in &pack.signatures {
                if Self::signature_matches(service, signature) {
                    // Apply signature defaults
                    if service.role == crate::models::ServiceRole::Unknown {
                        service.role = signature.role;
                    }

                    if let Some(ref health_check) = signature.health_check {
                        if service.health_check.check_type == HealthCheckType::Port {
                            service.health_check = health_check.clone();
                        }
                    }

                    if service.log_file_path.is_none() && !signature.log_paths.is_empty() {
                        service.log_file_path = Some(signature.log_paths[0].clone());
                    }

                    if service.systemd_unit.is_none() {
                        if let Some(ref unit) = signature.systemd_unit {
                            service.systemd_unit = Some(unit.clone());
                        }
                    }

                    debug!("Applied signature from pack '{}' to service '{}'", pack.name, service.name);
                    break;
                }
            }
        }
    }

    /// Check if a service name matches a pattern (simple substring matching)
    fn matches_pattern(name: &str, pattern: &str) -> bool {
        // Simple case-insensitive substring matching
        // For regex support, we'd need to add a regex dependency
        name.to_lowercase().contains(&pattern.to_lowercase())
    }

    /// Check if a service matches a pack signature
    fn signature_matches(service: &Service, signature: &crate::packs::schema::ServicePackSignature) -> bool {
        // Check port match
        if let Some(port) = signature.port {
            if service.port != port {
                return false;
            }
        }

        // Check process pattern match
        if let Some(ref pattern) = signature.process_pattern {
            if let Some(ref process_name) = service.process_name {
                if !process_name.to_lowercase().contains(&pattern.to_lowercase()) {
                    return false;
                }
            } else {
                return false;
            }
        }

        // Check systemd unit match
        if let Some(ref unit_pattern) = signature.systemd_unit {
            if let Some(ref unit) = service.systemd_unit {
                if !unit.contains(unit_pattern) {
                    return false;
                }
            } else {
                return false;
            }
        }

        true
    }
}