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