syncable_cli/analyzer/hadolint/rules/
dl4003.rs

1//! DL4003: Multiple CMD instructions
2//!
3//! Only one CMD instruction should be present. If multiple are present,
4//! only the last one takes effect.
5
6use crate::analyzer::hadolint::parser::instruction::Instruction;
7use crate::analyzer::hadolint::rules::{custom_rule, CustomRule, RuleState};
8use crate::analyzer::hadolint::shell::ParsedShell;
9use crate::analyzer::hadolint::types::Severity;
10
11pub fn rule() -> CustomRule<impl Fn(&mut RuleState, u32, &Instruction, Option<&ParsedShell>) + Send + Sync> {
12    custom_rule(
13        "DL4003",
14        Severity::Warning,
15        "Multiple `CMD` instructions found. If you list more than one `CMD` then only the last `CMD` will take effect",
16        |state, line, instr, _shell| {
17            match instr {
18                Instruction::From(_) => {
19                    // Reset count for each stage
20                    state.data.set_int("cmd_count", 0);
21                }
22                Instruction::Cmd(_) => {
23                    let count = state.data.get_int("cmd_count") + 1;
24                    state.data.set_int("cmd_count", count);
25
26                    if count > 1 {
27                        state.add_failure(
28                            "DL4003",
29                            Severity::Warning,
30                            "Multiple `CMD` instructions found. If you list more than one `CMD` then only the last `CMD` will take effect",
31                            line,
32                        );
33                    }
34                }
35                _ => {}
36            }
37        },
38    )
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44    use crate::analyzer::hadolint::parser::instruction::{Arguments, BaseImage};
45    use crate::analyzer::hadolint::rules::Rule;
46
47    #[test]
48    fn test_single_cmd() {
49        let rule = rule();
50        let mut state = RuleState::new();
51
52        let from = Instruction::From(BaseImage::new("ubuntu"));
53        let cmd = Instruction::Cmd(Arguments::List(vec!["node".to_string()]));
54
55        rule.check(&mut state, 1, &from, None);
56        rule.check(&mut state, 2, &cmd, None);
57        assert!(state.failures.is_empty());
58    }
59
60    #[test]
61    fn test_multiple_cmds() {
62        let rule = rule();
63        let mut state = RuleState::new();
64
65        let from = Instruction::From(BaseImage::new("ubuntu"));
66        let cmd1 = Instruction::Cmd(Arguments::List(vec!["node".to_string()]));
67        let cmd2 = Instruction::Cmd(Arguments::List(vec!["npm".to_string()]));
68
69        rule.check(&mut state, 1, &from, None);
70        rule.check(&mut state, 2, &cmd1, None);
71        rule.check(&mut state, 3, &cmd2, None);
72        assert_eq!(state.failures.len(), 1);
73        assert_eq!(state.failures[0].code.as_str(), "DL4003");
74    }
75
76    #[test]
77    fn test_multiple_stages_ok() {
78        let rule = rule();
79        let mut state = RuleState::new();
80
81        let from1 = Instruction::From(BaseImage::new("node"));
82        let cmd1 = Instruction::Cmd(Arguments::List(vec!["npm".to_string()]));
83        let from2 = Instruction::From(BaseImage::new("alpine"));
84        let cmd2 = Instruction::Cmd(Arguments::List(vec!["node".to_string()]));
85
86        rule.check(&mut state, 1, &from1, None);
87        rule.check(&mut state, 2, &cmd1, None);
88        rule.check(&mut state, 3, &from2, None);
89        rule.check(&mut state, 4, &cmd2, None);
90        assert!(state.failures.is_empty());
91    }
92}