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::{very_custom_rule, VeryCustomRule, RuleState, CheckFailure};
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("DL3057", Severity::Info, "HEALTHCHECK instruction missing.", 1));
35                failures
36            } else {
37                state.failures
38            }
39        },
40    )
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::analyzer::hadolint::lint::{lint, LintResult};
47    use crate::analyzer::hadolint::config::HadolintConfig;
48
49    fn lint_dockerfile(content: &str) -> LintResult {
50        lint(content, &HadolintConfig::default())
51    }
52
53    #[test]
54    fn test_missing_healthcheck() {
55        let result = lint_dockerfile("FROM ubuntu:20.04\nRUN echo hello");
56        assert!(result.failures.iter().any(|f| f.code.as_str() == "DL3057"));
57    }
58
59    #[test]
60    fn test_has_healthcheck() {
61        let result = lint_dockerfile("FROM ubuntu:20.04\nHEALTHCHECK CMD curl -f http://localhost/ || exit 1");
62        assert!(!result.failures.iter().any(|f| f.code.as_str() == "DL3057"));
63    }
64
65    #[test]
66    fn test_healthcheck_none() {
67        let result = lint_dockerfile("FROM ubuntu:20.04\nHEALTHCHECK NONE");
68        assert!(!result.failures.iter().any(|f| f.code.as_str() == "DL3057"));
69    }
70}