syspulse_core/health/
command.rs1use 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}