syncable_cli/analyzer/hadolint/rules/
dl3000.rs

1//! DL3000: Use absolute WORKDIR
2//!
3//! WORKDIR should use an absolute path to avoid confusion about the
4//! starting directory.
5
6use crate::analyzer::hadolint::parser::instruction::Instruction;
7use crate::analyzer::hadolint::rules::{SimpleRule, simple_rule};
8use crate::analyzer::hadolint::shell::ParsedShell;
9use crate::analyzer::hadolint::types::Severity;
10
11pub fn rule() -> SimpleRule<impl Fn(&Instruction, Option<&ParsedShell>) -> bool + Send + Sync> {
12    simple_rule(
13        "DL3000",
14        Severity::Error,
15        "Use absolute WORKDIR",
16        |instr, _shell| {
17            match instr {
18                Instruction::Workdir(path) => {
19                    // Allow absolute paths and variables
20                    path.starts_with('/') || path.starts_with('$') || is_windows_absolute(path)
21                }
22                _ => true,
23            }
24        },
25    )
26}
27
28/// Check if path is a Windows absolute path.
29fn is_windows_absolute(path: &str) -> bool {
30    let chars: Vec<char> = path.chars().collect();
31    chars.len() >= 2 && chars[0].is_ascii_alphabetic() && chars[1] == ':'
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use crate::analyzer::hadolint::rules::{Rule, RuleState};
38
39    #[test]
40    fn test_absolute_path() {
41        let rule = rule();
42        let mut state = RuleState::new();
43
44        // Good: absolute path
45        let instr = Instruction::Workdir("/app".to_string());
46        rule.check(&mut state, 1, &instr, None);
47        assert!(state.failures.is_empty());
48    }
49
50    #[test]
51    fn test_relative_path() {
52        let rule = rule();
53        let mut state = RuleState::new();
54
55        // Bad: relative path
56        let instr = Instruction::Workdir("app".to_string());
57        rule.check(&mut state, 1, &instr, None);
58        assert_eq!(state.failures.len(), 1);
59        assert_eq!(state.failures[0].code.as_str(), "DL3000");
60    }
61
62    #[test]
63    fn test_variable_path() {
64        let rule = rule();
65        let mut state = RuleState::new();
66
67        // Good: variable
68        let instr = Instruction::Workdir("$APP_DIR".to_string());
69        rule.check(&mut state, 1, &instr, None);
70        assert!(state.failures.is_empty());
71    }
72}