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