kcode-k1-web-podman 0.1.0

Fresh rootless Podman execution leaf for one K1 Web check.
Documentation
use super::*;
use std::os::unix::fs::symlink;
use tempfile::{TempDir, tempdir};

struct Fixture {
    root: TempDir,
    podman: PathBuf,
    checker: PathBuf,
    candidate: PathBuf,
    projection: PathBuf,
    log: PathBuf,
    request: PathBuf,
}

impl Fixture {
    fn new(report: &[u8], status: i32, delay: bool) -> Self {
        let root = tempdir().unwrap();
        let path = |name| root.path().join(name);
        let podman = path("podman");
        let checker = path("checker");
        let candidate = path("candidate");
        let projection = path("projection");
        let log = path("log");
        let request = path("request");
        let output = path("output");
        fs::create_dir(&candidate).unwrap();
        fs::create_dir(&projection).unwrap();
        fs::write(&output, report).unwrap();
        executable(&checker, "#!/bin/sh\nexit 2\n");
        let script = format!(
            "#!/bin/sh\nprintf 'CALL\\n' >> {0}\nprintf '%s\\n' \"$@\" >> {0}\ncat > {1}\n{2}cat {3}\nprintf diagnostic >&2\nexit {4}\n",
            quote(&log),
            quote(&request),
            if delay { "sleep 1\n" } else { "" },
            quote(&output),
            status,
        );
        executable(&podman, &script);
        Self {
            root,
            podman,
            checker,
            candidate,
            projection,
            log,
            request,
        }
    }

    fn config(&self) -> WebPodmanConfig {
        WebPodmanConfig {
            podman: self.podman.clone(),
            image: format!("registry.invalid/checker@sha256:{}", "a".repeat(64)),
            checker: self.checker.clone(),
            chromium: PathBuf::from("/usr/bin/chromium"),
            chromium_version: "123.4".to_owned(),
            cpu_millis: 1000,
            memory_bytes: 268_435_456,
            pids_limit: 64,
            tmpfs_bytes: 67_108_864,
            shm_bytes: 67_108_864,
            checker_timeout: Duration::from_millis(100),
            wall_timeout: Duration::from_secs(2),
        }
    }

    fn input(&self) -> CheckInput {
        CheckInput {
            candidate: WebIdInput {
                authority: "0".repeat(24),
                name: "demo".to_owned(),
                version: "1.0.0".to_owned(),
            },
            candidate_root: self.candidate.clone(),
            projection_root: self.projection.clone(),
            entry: "entry.js".to_owned(),
            tests: "tests.js".to_owned(),
            selections: Vec::new(),
        }
    }
}

fn report(outcome: &str, schema: u32) -> Vec<u8> {
    format!(concat!(
        "{{\"schema\":{schema},\"outcome\":{outcome},\"diagnostics\":",
        "{{\"browser_stdout\":[],\"browser_stderr\":[],\"browser_exit\":null,\"page_error\":null,\"server_errors\":[],\"cleanup_errors\":[]}},",
        "\"timings\":{{\"validation_ms\":0,\"server_ms\":0,\"browser_ms\":0,\"cleanup_ms\":0,\"total_ms\":0}}}}\n"
    ), schema = schema, outcome = outcome).into_bytes()
}

fn executable(path: &Path, contents: &str) {
    fs::write(path, contents).unwrap();
    fs::set_permissions(path, fs::Permissions::from_mode(0o700)).unwrap();
}

fn quote(path: &Path) -> String {
    format!("'{}'", path.display())
}

#[test]
fn success_uses_exact_fresh_command_and_rewrites_request() {
    let bytes = report("{\"kind\":\"success\"}", SCHEMA_VERSION);
    let fixture = Fixture::new(&bytes, 0, false);
    let podman = WebPodman::new(fixture.config()).unwrap();
    for _ in 0..2 {
        let output = podman.check(fixture.input()).unwrap();
        assert_eq!(output.diagnostics.status.code(), Some(0));
        assert_eq!(output.diagnostics.stdout, bytes);
        assert_eq!(output.diagnostics.stderr, b"diagnostic");
    }
    let fixed = [
        "--remote=false",
        "run",
        "--rm",
        "--pull=never",
        "--network=none",
        "--read-only",
        "--userns=keep-id",
        "--cap-drop=ALL",
        "--security-opt=no-new-privileges",
        "--http-proxy=false",
        "--ipc=private",
        "--workdir=/tmp",
        "--env=HOME=/tmp/home",
        "--env=TMPDIR=/tmp",
        "--cpus=1.000",
        "--memory=268435456",
        "--memory-swap=268435456",
        "--pids-limit=64",
        "--tmpfs=/tmp:rw,nosuid,nodev,noexec,size=67108864",
        "--shm-size=67108864",
    ];
    assert_eq!(
        podman
            .args
            .iter()
            .map(|value| value.to_str().unwrap())
            .collect::<Vec<_>>(),
        fixed
    );
    let expected = format!(
        "CALL\n{}\n--volume\n{}:{}:{}\n--volume\n{}:{}:{}\n--volume\n{}:{}:{}\n--\n{}\n{}\n",
        fixed.join("\n"),
        fixture.candidate.display(),
        CANDIDATE,
        DATA_OPTIONS,
        fixture.projection.display(),
        PROJECTION,
        DATA_OPTIONS,
        fixture.checker.display(),
        CHECKER,
        CHECKER_OPTIONS,
        fixture.config().image,
        CHECKER,
    );
    assert_eq!(
        fs::read_to_string(&fixture.log).unwrap(),
        expected.repeat(2)
    );
    let request =
        kcode_k1_web_checker_protocol::decode_request(&fs::read(&fixture.request).unwrap())
            .unwrap();
    assert_eq!(request.candidate_root, Path::new(CANDIDATE));
    assert_eq!(request.projection_root, Path::new(PROJECTION));
    assert_eq!(request.timeout_ms, 100);
    assert!(
        podman.checker_digest().starts_with("sha256:")
            && podman.command_policy_identity().starts_with("sha256:")
    );
}

#[test]
fn failures_are_separate_and_paths_are_validated() {
    let failure = report(
        "{\"kind\":\"failure\",\"stage\":\"test\",\"message\":\"failed\"}",
        SCHEMA_VERSION,
    );
    let completed = Fixture::new(&failure, 1, false);
    assert!(matches!(
        WebPodman::new(completed.config())
            .unwrap()
            .check(completed.input())
            .unwrap()
            .report
            .outcome,
        Outcome::Failure { .. }
    ));
    let schema = Fixture::new(&report("{\"kind\":\"success\"}", 2), 0, false);
    assert!(matches!(
        WebPodman::new(schema.config())
            .unwrap()
            .check(schema.input()),
        Err(WebPodmanError::Infrastructure {
            report: Some(_),
            ..
        })
    ));
    let nonzero = Fixture::new(&report("{\"kind\":\"success\"}", SCHEMA_VERSION), 3, false);
    assert!(matches!(
        WebPodman::new(nonzero.config())
            .unwrap()
            .check(nonzero.input()),
        Err(WebPodmanError::Infrastructure { .. })
    ));
    let spawn = Fixture::new(&report("{\"kind\":\"success\"}", SCHEMA_VERSION), 0, false);
    let runner = WebPodman::new(spawn.config()).unwrap();
    executable(&spawn.podman, "#!/missing/interpreter\n");
    assert!(matches!(
        runner.check(spawn.input()),
        Err(WebPodmanError::Spawn(_))
    ));
    let timeout = Fixture::new(&report("{\"kind\":\"success\"}", SCHEMA_VERSION), 0, true);
    let mut config = timeout.config();
    config.wall_timeout = Duration::from_millis(120);
    config.checker_timeout = Duration::from_millis(20);
    assert!(matches!(
        WebPodman::new(config).unwrap().check(timeout.input()),
        Err(WebPodmanError::Timeout { .. })
    ));
    let link = completed.root.path().join("checker-link");
    symlink(&completed.checker, &link).unwrap();
    let mut config = completed.config();
    config.checker = link;
    assert!(matches!(
        WebPodman::new(config),
        Err(WebPodmanError::InvalidInput { .. })
    ));
    let runner = WebPodman::new(completed.config()).unwrap();
    let mut input = completed.input();
    input.projection_root = input.candidate_root.clone();
    assert!(matches!(
        runner.check(input),
        Err(WebPodmanError::InvalidInput { .. })
    ));
}