syncable_cli/analyzer/hadolint/rules/
dl3061.rs

1//! DL3061: Invalid image name in FROM
2//!
3//! The image name in FROM should be valid.
4
5use crate::analyzer::hadolint::parser::instruction::Instruction;
6use crate::analyzer::hadolint::rules::{SimpleRule, simple_rule};
7use crate::analyzer::hadolint::shell::ParsedShell;
8use crate::analyzer::hadolint::types::Severity;
9
10pub fn rule() -> SimpleRule<impl Fn(&Instruction, Option<&ParsedShell>) -> bool + Send + Sync> {
11    simple_rule(
12        "DL3061",
13        Severity::Error,
14        "Invalid image name in `FROM`.",
15        |instr, _shell| match instr {
16            Instruction::From(base_image) => is_valid_image_name(&base_image.image.name),
17            _ => true,
18        },
19    )
20}
21
22fn is_valid_image_name(name: &str) -> bool {
23    if name.is_empty() {
24        return false;
25    }
26
27    // Allow scratch as a special case
28    if name == "scratch" {
29        return true;
30    }
31
32    // Allow variable expansion
33    if name.starts_with('$') {
34        return true;
35    }
36
37    // Image name can have:
38    // - Registry prefix: registry.example.com/
39    // - Namespace: namespace/
40    // - Name: imagename
41
42    // Basic validation: should contain only valid chars
43    let valid_chars = |c: char| {
44        c.is_ascii_lowercase()
45            || c.is_ascii_digit()
46            || c == '-'
47            || c == '_'
48            || c == '.'
49            || c == '/'
50            || c == ':'
51    };
52
53    name.chars().all(valid_chars)
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use crate::analyzer::hadolint::config::HadolintConfig;
60    use crate::analyzer::hadolint::lint::{LintResult, lint};
61
62    fn lint_dockerfile(content: &str) -> LintResult {
63        lint(content, &HadolintConfig::default())
64    }
65
66    #[test]
67    fn test_valid_image() {
68        let result = lint_dockerfile("FROM ubuntu:20.04");
69        assert!(!result.failures.iter().any(|f| f.code.as_str() == "DL3061"));
70    }
71
72    #[test]
73    fn test_valid_registry_image() {
74        let result = lint_dockerfile("FROM registry.example.com/myimage:latest");
75        assert!(!result.failures.iter().any(|f| f.code.as_str() == "DL3061"));
76    }
77
78    #[test]
79    fn test_scratch() {
80        let result = lint_dockerfile("FROM scratch");
81        assert!(!result.failures.iter().any(|f| f.code.as_str() == "DL3061"));
82    }
83
84    #[test]
85    fn test_variable_image() {
86        let result = lint_dockerfile("ARG BASE=ubuntu\nFROM $BASE");
87        assert!(!result.failures.iter().any(|f| f.code.as_str() == "DL3061"));
88    }
89}