syncable_cli/analyzer/hadolint/rules/
dl3057.rs

1//! DL3057: HEALTHCHECK instruction missing
2//!
3//! Images should have a HEALTHCHECK instruction to allow the container orchestrator
4//! to monitor the health of the container.
5
6use crate::analyzer::hadolint::parser::instruction::Instruction;
7use crate::analyzer::hadolint::rules::{CheckFailure, RuleState, VeryCustomRule, very_custom_rule};
8use crate::analyzer::hadolint::shell::ParsedShell;
9use crate::analyzer::hadolint::types::Severity;
10
11pub fn rule() -> VeryCustomRule<
12    impl Fn(&mut RuleState, u32, &Instruction, Option<&ParsedShell>) + Send + Sync,
13    impl Fn(RuleState) -> Vec<CheckFailure> + Send + Sync,
14> {
15    very_custom_rule(
16        "DL3057",
17        Severity::Info,
18        "HEALTHCHECK instruction missing.",
19        // Step function
20        |state, _line, instr, _shell| {
21            if matches!(instr, Instruction::Healthcheck(_)) {
22                state.data.set_bool("has_healthcheck", true);
23            }
24            // Track if we have any real instructions (not just FROM)
25            if !matches!(instr, Instruction::From(_) | Instruction::Comment(_)) {
26                state.data.set_bool("has_instructions", true);
27            }
28        },
29        // Finalize function - add failure if no healthcheck found
30        |state| {
31            // Only report if there are actual instructions beyond FROM
32            if !state.data.get_bool("has_healthcheck") && state.data.get_bool("has_instructions") {
33                let mut failures = state.failures;
34                failures.push(CheckFailure::new(
35                    "DL3057",
36                    Severity::Info,
37                    "HEALTHCHECK instruction missing.",
38                    1,
39                ));
40                failures
41            } else {
42                state.failures
43            }
44        },
45    )
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::analyzer::hadolint::config::HadolintConfig;
52    use crate::analyzer::hadolint::lint::{LintResult, lint};
53
54    fn lint_dockerfile(content: &str) -> LintResult {
55        lint(content, &HadolintConfig::default())
56    }
57
58    #[test]
59    fn test_missing_healthcheck() {
60        let result = lint_dockerfile("FROM ubuntu:20.04\nRUN echo hello");
61        assert!(result.failures.iter().any(|f| f.code.as_str() == "DL3057"));
62    }
63
64    #[test]
65    fn test_has_healthcheck() {
66        let result = lint_dockerfile(
67            "FROM ubuntu:20.04\nHEALTHCHECK CMD curl -f http://localhost/ || exit 1",
68        );
69        assert!(!result.failures.iter().any(|f| f.code.as_str() == "DL3057"));
70    }
71
72    #[test]
73    fn test_healthcheck_none() {
74        let result = lint_dockerfile("FROM ubuntu:20.04\nHEALTHCHECK NONE");
75        assert!(!result.failures.iter().any(|f| f.code.as_str() == "DL3057"));
76    }
77}