syncable_cli/analyzer/hadolint/rules/
dl3047.rs

1//! DL3047: wget vs curl consistency
2//!
3//! Avoid using both wget and curl in the same Dockerfile.
4//! Pick one to reduce image size.
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        "DL3047",
15        Severity::Info,
16        "Avoid using both `wget` and `curl` since they serve the same purpose.",
17        |state, line, instr, shell| {
18            match instr {
19                Instruction::From(_) => {
20                    // Reset tracking for new stage
21                    state.data.set_bool("seen_wget", false);
22                    state.data.set_bool("seen_curl", false);
23                    state.data.set_bool("reported_dl3047", false);
24                }
25                Instruction::Run(_) => {
26                    if let Some(shell) = shell {
27                        let uses_wget = shell.using_program("wget");
28                        let uses_curl = shell.using_program("curl");
29
30                        if uses_wget {
31                            state.data.set_bool("seen_wget", true);
32                        }
33                        if uses_curl {
34                            state.data.set_bool("seen_curl", true);
35                        }
36
37                        // Report if both are now seen and not already reported
38                        let seen_both =
39                            state.data.get_bool("seen_wget") && state.data.get_bool("seen_curl");
40                        let already_reported = state.data.get_bool("reported_dl3047");
41
42                        if seen_both && !already_reported {
43                            state.add_failure(
44                                "DL3047",
45                                Severity::Info,
46                                "Avoid using both `wget` and `curl` since they serve the same purpose.",
47                                line,
48                            );
49                            state.data.set_bool("reported_dl3047", true);
50                        }
51                    }
52                }
53                _ => {}
54            }
55        },
56    )
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use crate::analyzer::hadolint::config::HadolintConfig;
63    use crate::analyzer::hadolint::lint::{LintResult, lint};
64
65    fn lint_dockerfile(content: &str) -> LintResult {
66        lint(content, &HadolintConfig::default())
67    }
68
69    #[test]
70    fn test_wget_only() {
71        let result = lint_dockerfile("FROM ubuntu:20.04\nRUN wget https://example.com/file");
72        assert!(!result.failures.iter().any(|f| f.code.as_str() == "DL3047"));
73    }
74
75    #[test]
76    fn test_curl_only() {
77        let result = lint_dockerfile("FROM ubuntu:20.04\nRUN curl -O https://example.com/file");
78        assert!(!result.failures.iter().any(|f| f.code.as_str() == "DL3047"));
79    }
80
81    #[test]
82    fn test_both_wget_and_curl() {
83        let result = lint_dockerfile(
84            "FROM ubuntu:20.04\nRUN wget https://example.com/file1\nRUN curl -O https://example.com/file2",
85        );
86        assert!(result.failures.iter().any(|f| f.code.as_str() == "DL3047"));
87    }
88
89    #[test]
90    fn test_both_in_same_run() {
91        let result = lint_dockerfile(
92            "FROM ubuntu:20.04\nRUN wget https://a.com/f && curl -O https://b.com/g",
93        );
94        assert!(result.failures.iter().any(|f| f.code.as_str() == "DL3047"));
95    }
96
97    #[test]
98    fn test_different_stages() {
99        // Different stages should track separately
100        let result = lint_dockerfile(
101            "FROM ubuntu:20.04 AS stage1\nRUN wget https://a.com/f\nFROM ubuntu:20.04 AS stage2\nRUN curl https://b.com/g",
102        );
103        assert!(!result.failures.iter().any(|f| f.code.as_str() == "DL3047"));
104    }
105}