#![allow(clippy::unwrap_used)]
use std::sync::atomic::{AtomicU64, Ordering};
static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
fn get_unique_temp_path() -> String {
let id = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
let tid = std::thread::current().id();
format!("/tmp/dcode_dockerfile_{:?}_{}", tid, id)
}
fn lint_dockerfile(content: &str) -> (bool, String) {
use std::fs;
use std::process::Command;
let tmp_path = get_unique_temp_path();
fs::write(&tmp_path, content).unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_bashrs"))
.args(["lint", &tmp_path])
.output()
.unwrap();
let _ = fs::remove_file(&tmp_path);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let combined = format!("{}\n{}", stdout, stderr);
(output.status.success(), combined)
}
fn has_rule(output: &str, rule: &str) -> bool {
output.contains(rule)
}
#[test]
fn test_d001_latest_tag() {
let dockerfile = r#"FROM ubuntu:latest
RUN echo hello
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning = output.to_lowercase().contains("latest")
|| has_rule(&output, "DOCKER")
|| has_rule(&output, "DF003");
if !has_warning {
println!("D001: WARNING - Should detect :latest tag usage");
}
}
#[test]
fn test_d002_missing_digest() {
let dockerfile = r#"FROM ubuntu:22.04
RUN echo hello
"#;
let (_, output) = lint_dockerfile(dockerfile);
if !output.contains("sha256") && !output.contains("digest") {
println!("D002: INFO - Could suggest pinning digest for reproducibility");
}
}
#[test]
fn test_d003_unversioned_package() {
let dockerfile = r#"FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl
"#;
let (_, output) = lint_dockerfile(dockerfile);
if !output.contains("version") && !output.contains("pin") {
println!("D003: INFO - Could warn about unversioned package install");
}
}
#[test]
fn test_d004_http_url() {
let dockerfile = r#"FROM ubuntu:22.04
RUN curl -O http://example.com/file.tar.gz
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning =
output.to_lowercase().contains("http") || output.to_lowercase().contains("insecure");
if !has_warning {
println!("D004: INFO - Could warn about insecure HTTP downloads");
}
}
#[test]
fn test_d005_secret_in_env() {
let dockerfile = r#"FROM ubuntu:22.04
ENV SECRET_KEY=mysecretkey123
ENV PASSWORD=admin123
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning = output.to_lowercase().contains("secret")
|| output.to_lowercase().contains("password")
|| has_rule(&output, "DOCKER002")
|| has_rule(&output, "DF002");
if !has_warning {
println!("D005: WARNING - Should detect secrets in ENV");
}
}
#[test]
fn test_d006_copy_dot() {
let dockerfile = r#"FROM ubuntu:22.04
COPY . .
"#;
let (_, output) = lint_dockerfile(dockerfile);
if !output.contains("COPY") && !output.contains("explicit") {
println!("D006: INFO - Could warn about 'COPY . .' pattern");
}
}
#[test]
fn test_d007_separate_apt_update() {
let dockerfile = r#"FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y curl
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning = output.to_lowercase().contains("combine")
|| output.to_lowercase().contains("cache")
|| has_rule(&output, "DOCKER003");
if !has_warning {
println!("D007: WARNING - Should warn about separate apt-get update");
}
}
#[test]
fn test_d008_admin_port() {
let dockerfile = r#"FROM ubuntu:22.04
EXPOSE 22
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning = output.to_lowercase().contains("22")
|| output.to_lowercase().contains("ssh")
|| output.to_lowercase().contains("admin");
if !has_warning {
println!("D008: INFO - Could warn about exposing admin ports (22, 23, 3389)");
}
}
#[test]
fn test_d009_user_root() {
let dockerfile = r#"FROM ubuntu:22.04
RUN apt-get update
USER root
CMD ["bash"]
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning = output.to_lowercase().contains("root")
|| output.to_lowercase().contains("user")
|| has_rule(&output, "DOCKER001")
|| has_rule(&output, "DF001");
if !has_warning {
println!("D009: WARNING - Should detect running as root");
}
}
#[test]
fn test_d010_add_http() {
let dockerfile = r#"FROM ubuntu:22.04
ADD http://example.com/file.tar.gz /app/
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning =
output.to_lowercase().contains("add") || output.to_lowercase().contains("copy");
if !has_warning {
println!("D010: INFO - Could suggest COPY + curl instead of ADD http://");
}
}
#[test]
fn test_d011_chmod_777() {
let dockerfile = r#"FROM ubuntu:22.04
RUN chmod 777 /app
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning = output.contains("777")
|| output.to_lowercase().contains("permissive")
|| output.to_lowercase().contains("chmod");
if !has_warning {
println!("D011: INFO - Could warn about chmod 777");
}
}
#[test]
fn test_d012_curl_pipe_sh() {
let dockerfile = r#"FROM ubuntu:22.04
RUN curl -sSL https://example.com/install.sh | sh
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning = output.to_lowercase().contains("pipe")
|| output.to_lowercase().contains("curl")
|| has_rule(&output, "DOCKER004");
if !has_warning {
println!("D012: WARNING - Should detect curl | sh pattern");
}
}
#[test]
fn test_d013_missing_workdir() {
let dockerfile = r#"FROM ubuntu:22.04
COPY app /app
RUN cd /app && ./build.sh
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning = output.to_lowercase().contains("workdir");
if !has_warning {
println!("D013: INFO - Could suggest using WORKDIR instead of cd");
}
}
#[test]
fn test_d014_healthcheck_none() {
let dockerfile = r#"FROM ubuntu:22.04
HEALTHCHECK NONE
CMD ["bash"]
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning = output.to_lowercase().contains("healthcheck") || has_rule(&output, "DF004");
if !has_warning {
println!("D014: INFO - Could warn about HEALTHCHECK NONE");
}
}
#[test]
fn test_d015_missing_label() {
let dockerfile = r#"FROM ubuntu:22.04
RUN echo hello
CMD ["bash"]
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning =
output.to_lowercase().contains("label") || output.to_lowercase().contains("maintainer");
if !has_warning {
println!("D015: INFO - Could suggest adding LABEL for metadata");
}
}
#[test]
fn test_d016_missing_user_simple() {
let dockerfile = r#"FROM debian:12-slim
COPY app /app
CMD ["/app"]
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_warning = has_rule(&output, "DOCKER001")
|| output.to_lowercase().contains("user")
|| output.to_lowercase().contains("root");
if !has_warning {
println!("D016: WARNING - Should detect missing USER directive");
}
}
#[test]
fn test_d017_user_present_good() {
let dockerfile = r#"FROM debian:12-slim
RUN useradd -m appuser
USER appuser
COPY app /app
CMD ["/app"]
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_user_warning = has_rule(&output, "DOCKER001");
if has_user_warning {
println!("D017: BUG - Should not warn when USER is properly set");
}
}
#[test]
fn test_d018_scratch_exempt() {
let dockerfile = r#"FROM scratch
COPY app /app
CMD ["/app"]
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_user_warning = has_rule(&output, "DOCKER001");
if has_user_warning {
println!("D018: INFO - FROM scratch may be exempt from USER requirement");
}
}
#[test]
fn test_d019_multistage_user() {
let dockerfile = r#"FROM golang:1.21 AS builder
RUN go build -o /app
FROM alpine:3.19
RUN adduser -D appuser
USER appuser
COPY --from=builder /app /app
CMD ["/app"]
"#;
let (_, output) = lint_dockerfile(dockerfile);
let has_user_warning = has_rule(&output, "DOCKER001");
if has_user_warning {
println!("D019: BUG - Multi-stage with USER in final stage should pass");
}
}
#[test]
include!("dockerfile_dcode_tests_tests_d020_numeric.rs");