Skip to main content

syspulse_core/health/
command.rs

1use async_trait::async_trait;
2use std::time::Duration;
3use tokio::time::timeout;
4
5use super::HealthChecker;
6use crate::daemon::{HealthCheckSpec, HealthStatus};
7use crate::error::{Result, SyspulseError};
8
9pub struct CommandHealthChecker {
10    spec: HealthCheckSpec,
11}
12
13impl CommandHealthChecker {
14    pub fn new(spec: HealthCheckSpec) -> Self {
15        Self { spec }
16    }
17}
18
19#[async_trait]
20impl HealthChecker for CommandHealthChecker {
21    async fn check(&self) -> Result<HealthStatus> {
22        let dur = Duration::from_secs(self.spec.timeout_secs);
23
24        #[cfg(unix)]
25        let mut cmd = {
26            let mut c = tokio::process::Command::new("sh");
27            c.arg("-c").arg(&self.spec.target);
28            c
29        };
30
31        #[cfg(windows)]
32        let mut cmd = {
33            let mut c = tokio::process::Command::new("cmd");
34            c.arg("/C").arg(&self.spec.target);
35            c
36        };
37
38        cmd.stdout(std::process::Stdio::null())
39            .stderr(std::process::Stdio::null());
40
41        match timeout(dur, cmd.status()).await {
42            Ok(Ok(status)) => {
43                if status.success() {
44                    Ok(HealthStatus::Healthy)
45                } else {
46                    tracing::debug!(
47                        command = %self.spec.target,
48                        exit_code = ?status.code(),
49                        "Command health check returned non-zero"
50                    );
51                    Ok(HealthStatus::Unhealthy)
52                }
53            }
54            Ok(Err(e)) => {
55                tracing::debug!(
56                    command = %self.spec.target,
57                    error = %e,
58                    "Command health check failed to execute"
59                );
60                Ok(HealthStatus::Unhealthy)
61            }
62            Err(_) => {
63                tracing::debug!(
64                    command = %self.spec.target,
65                    "Command health check timed out"
66                );
67                Err(SyspulseError::Timeout(dur))
68            }
69        }
70    }
71
72    fn spec(&self) -> &HealthCheckSpec {
73        &self.spec
74    }
75}