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