~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless-runner-k8s

0
0
105 lines · 3.1 KB Raw History
//! Runs the executor end to end against a fake `kubectl`, so the command
//! sequence is checked without a cluster.

use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};

use knotless_runner_k8s::cli::{self, Command};
use knotless_runner_k8s::runner;

#[test]
fn runs_steps_through_kubectl() {
    let root = temp_dir();
    let work = root.join("work");
    fs::create_dir_all(&work).unwrap();
    fs::write(work.join("hello.txt"), "hello\n").unwrap();
    let steps = root.join("steps.tsv");
    fs::write(&steps, "build\tcat hello.txt\ntest\ttest -f hello.txt\n").unwrap();
    let fake = root.join("kubectl");
    fs::write(
        &fake,
        r#"#!/bin/sh
printf '%s\n' "$*" >> "$FAKE_KUBECTL_LOG"
case "$*" in
  *" apply "*) cat >/dev/null ;;
  *" get pods "*) printf 'pod-1' ;;
  *" exec "*)
    case "$*" in
      *"tar -xf"*) cat >/dev/null ;;
    esac
    ;;
esac
exit 0
"#,
    )
    .unwrap();
    let mut mode = fs::metadata(&fake).unwrap().permissions();
    mode.set_mode(0o755);
    fs::set_permissions(&fake, mode).unwrap();
    let log = root.join("kubectl.log");
    let result = root.join("result.json");
    let run_log = root.join("run.log");

    let args: Vec<String> = vec![
        "run".into(),
        "--name".into(),
        "fake run".into(),
        "--image".into(),
        "busybox:1.36".into(),
        "--kubectl".into(),
        fake.display().to_string(),
        "--workdir".into(),
        work.display().to_string(),
        "--steps-file".into(),
        steps.display().to_string(),
        "--runner".into(),
        "did:plc:runner".into(),
        "--log".into(),
        run_log.display().to_string(),
        "--result".into(),
        result.display().to_string(),
    ];
    let Command::Run(options) = cli::parse(&args).unwrap() else {
        panic!("not a run");
    };
    // The fake client writes here, through the environment it inherits.
    unsafe {
        std::env::set_var("FAKE_KUBECTL_LOG", &log);
    }
    let outcome = runner::run(&options).unwrap();
    assert_eq!(outcome.status, "success", "{:?}", outcome.error);
    assert_eq!(outcome.steps.len(), 2);
    assert!(outcome.steps.iter().all(|s| s.status == "success"));
    assert!(outcome.job_deleted);
    assert!(result.exists());
    let calls = fs::read_to_string(&log).unwrap();
    for wanted in [
        "apply -f -",
        "wait --for=condition=Ready",
        "get pods",
        "exec",
        "delete job",
    ] {
        assert!(calls.contains(wanted), "missing {wanted:?} in:\n{calls}");
    }
    let logged = fs::read_to_string(&run_log).unwrap();
    assert!(logged.contains("==> step build"));
    assert!(logged.contains("==> step test"));
    let _ = fs::remove_dir_all(&root);
}

fn temp_dir() -> PathBuf {
    let stamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    let dir = std::env::current_dir()
        .unwrap()
        .join("target")
        .join("test-tmp")
        .join(format!("{}-{stamp}", std::process::id()));
    fs::create_dir_all(&dir).unwrap();
    dir
}