kcode-k1-daemon-code-services 0.1.10

Open the daemon's process-owned RustCode and WebCode services
Documentation
#![doc = include_str!("../Documentation.md")]

use kcode_k1_code_tool_image::CodeToolImage;
use kcode_k1_groups::K1Groups;
use kcode_k1_objects::K1Objects;
use kcode_k1_peering::K1Peering;
use kcode_k1_rust_code_ktool_service::RustCodeKtoolService;
use kcode_k1_rust_coding::{RustCodingConfig, RustCodingConfigValues};
use kcode_k1_rust_projection::K1RustProjection;
use kcode_k1_txn_ordering::K1TxnOrdering;
use kcode_k1_web_bootstrap_import::{ImporterRevisions, WebBootstrapImporter};
use kcode_k1_web_code_ktool_service::{K1WebCodeKtoolService, ServiceConfig, ServiceRevisions};
use kcode_k1_web_code_workspace::K1WebCodeWorkspace;
use kcode_k1_web_podman::{WebPodman, WebPodmanConfig};
use kcode_k1_web_projection::K1WebProjection;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::Arc;
use std::time::Duration;

const RUST_SCHEMA: &str = "k1-rust-code-cache-v1";
const RUST_TOOLCHAIN_POLICY: &str = "rust-1.97-k1-code-tools-v1";
const RUST_CHECK_POLICY: &str = "k1-rust-code-check-v2";
const RUST_COMMAND_POLICY: &str = "k1-rust-code-command-v2";
const WEB_BOOT: &str = "k1-web-code-boot-v1";
const WEB_SCHEMA: &str = "k1-web-code-cache-v1";
const WEB_ROUTE: &str = "k1-web-code-routes-v1";
const WEB_HARNESS: &str = "k1-web-code-harness-v1";
const WEB_CHECK_POLICY: &str = "k1-web-code-check-v1";
const CHECK_DEADLINE: Duration = Duration::from_secs(225);

pub struct CodeServices {
    rust_projection: Arc<K1RustProjection>,
    rust: Arc<RustCodeKtoolService>,
    web: K1WebCodeKtoolService,
    web_bootstrap: WebBootstrapImporter,
}

impl CodeServices {
    #[allow(clippy::too_many_arguments)]
    pub fn open(
        state_root: &Path,
        ordering: Arc<K1TxnOrdering>,
        peering: Arc<K1Peering>,
        groups: Arc<K1Groups>,
        objects: Arc<K1Objects>,
        web_projection: Arc<K1WebProjection>,
    ) -> Result<Self, String> {
        let paths = CodePaths::open(state_root)?;
        let tools = CodeToolImage::open(&paths.tool_build)?;
        let checker = materialize_checker(tools.podman(), tools.image(), &paths.checker)?;
        let (chromium, chromium_version) = discover_chromium(tools.podman(), tools.image())?;
        let rust_config = rust_config(
            tools.podman().as_os_str().to_owned(),
            tools.image().to_owned(),
        )?;
        let web_config = WebPodmanConfig {
            podman: tools.podman().to_path_buf(),
            image: tools.image().to_owned(),
            checker,
            chromium,
            chromium_version,
            cpu_millis: 0,
            memory_bytes: 0,
            pids_limit: 0,
            tmpfs_bytes: 0,
            shm_bytes: 0,
            checker_timeout: CHECK_DEADLINE,
            wall_timeout: CHECK_DEADLINE,
        };
        WebPodman::new(web_config.clone())
            .map_err(|error| format!("Web Podman configuration: {error}"))?;

        let workspaces = Arc::new(
            K1WebCodeWorkspace::open(Arc::clone(&ordering), Arc::clone(&peering))
                .map_err(|error| format!("open Web code workspaces: {error}"))?,
        );
        let rust_projection =
            K1RustProjection::open(paths.rust_projection, paths.rust_control, ordering, peering)
                .map(Arc::new)
                .map_err(|error| format!("open Rust projection: {error}"))?;
        let rust = Arc::new(RustCodeKtoolService::new(
            paths.rust_cache,
            rust_config,
            Arc::clone(&rust_projection),
            Arc::clone(&objects),
            Arc::clone(&groups),
        ));
        let web_bootstrap = WebBootstrapImporter::new(
            paths.web_bootstrap_cache,
            paths.web_projection.clone(),
            importer_revisions(),
            web_config.clone(),
            Arc::clone(&web_projection),
        )
        .map_err(|error| format!("open Web bootstrap importer: {error}"))?;
        let web = K1WebCodeKtoolService::new(
            ServiceConfig::new(
                paths.web_cache,
                paths.web_projection,
                web_revisions(),
                web_config,
            ),
            groups,
            objects,
            web_projection,
            workspaces,
        );
        Ok(Self {
            rust_projection,
            rust,
            web,
            web_bootstrap,
        })
    }

    pub fn rust_projection(&self) -> Arc<K1RustProjection> {
        Arc::clone(&self.rust_projection)
    }

    pub fn web_bootstrap_importer(&self) -> WebBootstrapImporter {
        self.web_bootstrap.clone()
    }

    pub fn into_parts(self) -> (Arc<RustCodeKtoolService>, K1WebCodeKtoolService) {
        (self.rust, self.web)
    }
}

struct CodePaths {
    rust_projection: PathBuf,
    rust_control: PathBuf,
    rust_cache: PathBuf,
    web_projection: PathBuf,
    web_cache: PathBuf,
    web_bootstrap_cache: PathBuf,
    tool_build: PathBuf,
    checker: PathBuf,
}

impl CodePaths {
    fn open(state_root: &Path) -> Result<Self, String> {
        let rust = state_root.join("rust");
        let web = state_root.join("web");
        let tools = state_root.join("code-tools");
        for path in [&rust, &web, &tools] {
            ensure_directory(path)?;
        }
        let value = Self {
            rust_projection: rust.join("projection"),
            rust_control: rust.join("control"),
            rust_cache: rust.join("code-cache"),
            web_projection: web.join("projection"),
            web_cache: web.join("code-cache"),
            web_bootstrap_cache: web.join("bootstrap-cache"),
            tool_build: tools.join("image-build"),
            checker: tools.join("kcode-k1-web-checker"),
        };
        for path in [
            &value.rust_projection,
            &value.rust_control,
            &value.rust_cache,
            &value.web_projection,
            &value.web_cache,
            &value.web_bootstrap_cache,
        ] {
            ensure_directory(path)?;
        }
        Ok(value)
    }
}

fn materialize_checker(podman: &Path, image: &str, destination: &Path) -> Result<PathBuf, String> {
    let mut command = Command::new(podman);
    command
        .arg("run")
        .arg("--rm")
        .arg("--network=none")
        .arg("--pull=never")
        .arg("--entrypoint=/bin/cat")
        .arg(image)
        .arg("/usr/local/cargo/bin/kcode-k1-web-checker");
    let output = run(command, "extract K1 Web checker")?;
    if !output.status.success() {
        return Err(command_failure("extract K1 Web checker", output));
    }
    if output.stdout.is_empty() {
        return Err("extract K1 Web checker: image returned an empty executable".to_owned());
    }
    let staging = destination.with_extension("new");
    fs::write(&staging, output.stdout)
        .map_err(|error| format!("write Web checker {}: {error}", staging.display()))?;
    set_executable(&staging)?;
    fs::rename(&staging, destination)
        .map_err(|error| format!("install Web checker {}: {error}", destination.display()))?;
    fs::canonicalize(destination).map_err(|error| {
        format!(
            "canonicalize Web checker {}: {error}",
            destination.display()
        )
    })
}

#[cfg(unix)]
fn set_executable(path: &Path) -> Result<(), String> {
    use std::os::unix::fs::PermissionsExt as _;
    fs::set_permissions(path, fs::Permissions::from_mode(0o755))
        .map_err(|error| format!("make Web checker executable {}: {error}", path.display()))
}

#[cfg(not(unix))]
fn set_executable(_path: &Path) -> Result<(), String> {
    Ok(())
}

fn discover_chromium(podman: &Path, image: &str) -> Result<(PathBuf, String), String> {
    let mut command = Command::new(podman);
    command
        .arg("run")
        .arg("--rm")
        .arg("--network=none")
        .arg("--pull=never")
        .arg("--entrypoint=/bin/sh")
        .arg(image)
        .arg("-c")
        .arg("command -v chromium; chromium --version");
    let output = run(command, "discover Chromium in K1 code tool image")?;
    if !output.status.success() {
        return Err(command_failure(
            "discover Chromium in K1 code tool image",
            output,
        ));
    }
    let text = String::from_utf8(output.stdout)
        .map_err(|_| "discover Chromium: image output was not UTF-8".to_owned())?;
    let mut lines = text.lines().filter(|line| !line.trim().is_empty());
    let path = PathBuf::from(
        lines
            .next()
            .ok_or_else(|| "discover Chromium: executable path was absent".to_owned())?,
    );
    let version = lines
        .next()
        .ok_or_else(|| "discover Chromium: version was absent".to_owned())?
        .to_owned();
    if !path.is_absolute() {
        return Err("discover Chromium: executable path was not absolute".to_owned());
    }
    Ok((path, version))
}

fn rust_config(podman_program: OsString, image: String) -> Result<RustCodingConfig, String> {
    RustCodingConfig::new(RustCodingConfigValues {
        schema_id: RUST_SCHEMA.into(),
        toolchain_policy: RUST_TOOLCHAIN_POLICY.into(),
        image,
        rust_toolchain: "1.97".into(),
        check_policy: RUST_CHECK_POLICY.into(),
        target_triple: rust_target()?.into(),
        command_policy: RUST_COMMAND_POLICY.into(),
        podman_program,
    })
    .map_err(|error| format!("Rust coding configuration: {error}"))
}

fn rust_target() -> Result<&'static str, String> {
    match std::env::consts::ARCH {
        "x86_64" => Ok("x86_64-unknown-linux-gnu"),
        "aarch64" => Ok("aarch64-unknown-linux-gnu"),
        architecture => Err(format!(
            "unsupported Rust code host architecture: {architecture}"
        )),
    }
}

fn web_revisions() -> ServiceRevisions {
    ServiceRevisions {
        boot: WEB_BOOT.into(),
        schema: WEB_SCHEMA.into(),
        route: WEB_ROUTE.into(),
        harness: WEB_HARNESS.into(),
        check_policy: WEB_CHECK_POLICY.into(),
    }
}

fn importer_revisions() -> ImporterRevisions {
    ImporterRevisions {
        boot: WEB_BOOT.into(),
        schema: WEB_SCHEMA.into(),
        route: WEB_ROUTE.into(),
        harness: WEB_HARNESS.into(),
        check_policy: WEB_CHECK_POLICY.into(),
    }
}

fn run(mut command: Command, label: &str) -> Result<Output, String> {
    command
        .output()
        .map_err(|error| format!("{label}: could not start command: {error}"))
}

fn command_failure(label: &str, output: Output) -> String {
    format!(
        "{label} exited with {}\n--- stdout ---\n{}\n--- stderr ---\n{}",
        output
            .status
            .code()
            .map_or_else(|| "signal".to_owned(), |code| code.to_string()),
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    )
}

fn ensure_directory(path: &Path) -> Result<(), String> {
    match fs::symlink_metadata(path) {
        Ok(metadata) if metadata.is_dir() && !metadata.file_type().is_symlink() => Ok(()),
        Ok(_) => Err(format!(
            "code-service path is not an ordinary directory: {}",
            path.display()
        )),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => fs::create_dir(path)
            .map_err(|error| format!("create code-service directory {}: {error}", path.display())),
        Err(error) => Err(format!(
            "inspect code-service directory {}: {error}",
            path.display()
        )),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fixed_paths_are_beneath_state_root() {
        let root = tempfile::tempdir().unwrap();
        let paths = CodePaths::open(root.path()).unwrap();
        assert_eq!(paths.rust_projection, root.path().join("rust/projection"));
        assert_eq!(paths.web_cache, root.path().join("web/code-cache"));
        assert_eq!(
            paths.web_bootstrap_cache,
            root.path().join("web/bootstrap-cache")
        );
        assert_eq!(
            paths.checker,
            root.path().join("code-tools/kcode-k1-web-checker")
        );
        assert_eq!(paths.tool_build, root.path().join("code-tools/image-build"));
    }

    #[test]
    fn package_owns_only_service_policy() {
        assert_eq!(CHECK_DEADLINE, Duration::from_secs(225));
        assert_eq!(RUST_TOOLCHAIN_POLICY, "rust-1.97-k1-code-tools-v1");
        assert_eq!(RUST_CHECK_POLICY, "k1-rust-code-check-v2");
        assert_eq!(RUST_COMMAND_POLICY, "k1-rust-code-command-v2");
    }
}