#![doc = include_str!("../Documentation.md")]
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_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 CONTAINERFILE: &str = include_str!("assets/Containerfile");
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-v1";
const RUST_COMMAND_POLICY: &str = "k1-rust-code-command-v1";
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: Arc<RustCodeKtoolService>,
web: K1WebCodeKtoolService,
}
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 podman = resolve_podman()?;
let image = tool_image();
ensure_tool_image(&podman, &image, &paths.tool_build)?;
let checker = materialize_checker(&podman, &image, &paths.checker)?;
let (chromium, chromium_version) = discover_chromium(&podman, &image)?;
let rust_config = rust_config(podman.clone().into_os_string(), image.clone())?;
let web_config = WebPodmanConfig {
podman,
image,
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,
rust_projection,
Arc::clone(&objects),
Arc::clone(&groups),
));
let web = K1WebCodeKtoolService::new(
ServiceConfig::new(
paths.web_cache,
paths.web_projection,
web_revisions(),
web_config,
),
groups,
objects,
web_projection,
workspaces,
);
Ok(Self { rust, web })
}
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,
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"),
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,
] {
ensure_directory(path)?;
}
Ok(value)
}
}
fn resolve_podman() -> Result<PathBuf, String> {
let path =
std::env::var_os("PATH").ok_or_else(|| "find podman: PATH is unavailable".to_owned())?;
resolve_executable("podman", std::env::split_paths(&path))
.ok_or_else(|| "find executable podman on PATH".to_owned())
}
fn resolve_executable(name: &str, paths: impl IntoIterator<Item = PathBuf>) -> Option<PathBuf> {
paths.into_iter().find_map(|directory| {
let candidate = directory.join(name);
executable(&candidate)
.then(|| fs::canonicalize(candidate).ok())
.flatten()
.filter(|path| path.is_absolute())
})
}
#[cfg(unix)]
fn executable(path: &Path) -> bool {
use std::os::unix::fs::PermissionsExt as _;
fs::metadata(path)
.is_ok_and(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
}
#[cfg(not(unix))]
fn executable(path: &Path) -> bool {
fs::metadata(path).is_ok_and(|metadata| metadata.is_file())
}
fn tool_image() -> String {
let mut hash = 0xcbf29ce484222325_u64;
for byte in CONTAINERFILE.bytes() {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x100000001b3);
}
format!(
"localhost/kcode-k1-code-tools:{}-{hash:016x}",
env!("CARGO_PKG_VERSION")
)
}
fn ensure_tool_image(podman: &Path, image: &str, build_root: &Path) -> Result<(), String> {
let mut inspect = Command::new(podman);
inspect.arg("image").arg("exists").arg(image);
let inspected = run(inspect, "inspect K1 code tool image")?;
if inspected.status.success() {
return Ok(());
}
if inspected.status.code() != Some(1) {
return Err(command_failure("inspect K1 code tool image", inspected));
}
if build_root.exists() {
fs::remove_dir_all(build_root).map_err(|error| {
format!(
"replace code-tool image build directory {}: {error}",
build_root.display()
)
})?;
}
fs::create_dir(build_root).map_err(|error| {
format!(
"create code-tool image build directory {}: {error}",
build_root.display()
)
})?;
let containerfile = build_root.join("Containerfile");
fs::write(&containerfile, CONTAINERFILE).map_err(|error| {
format!(
"write embedded Containerfile {}: {error}",
containerfile.display()
)
})?;
let mut build = Command::new(podman);
build
.arg("build")
.arg("--tag")
.arg(image)
.arg("--file")
.arg(&containerfile)
.arg(build_root);
let built = run(build, "build K1 code tool image")?;
let _ = fs::remove_dir_all(build_root);
if built.status.success() {
Ok(())
} else {
Err(command_failure("build K1 code tool image", built))
}
}
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 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.checker,
root.path().join("code-tools/kcode-k1-web-checker")
);
}
#[cfg(unix)]
#[test]
fn podman_resolver_accepts_only_an_executable_file() {
use std::os::unix::fs::PermissionsExt as _;
let root = tempfile::tempdir().unwrap();
let podman = root.path().join("podman");
fs::write(&podman, b"#!/bin/sh\nexit 0\n").unwrap();
fs::set_permissions(&podman, fs::Permissions::from_mode(0o700)).unwrap();
assert_eq!(
resolve_executable("podman", [root.path().to_path_buf()]),
Some(fs::canonicalize(&podman).unwrap())
);
fs::set_permissions(&podman, fs::Permissions::from_mode(0o600)).unwrap();
assert!(resolve_executable("podman", [root.path().to_path_buf()]).is_none());
}
#[test]
fn tool_image_owns_the_full_runtime_without_environment_configuration() {
assert!(CONTAINERFILE.contains("rust:1.97-bookworm"));
assert!(CONTAINERFILE.contains("chromium"));
assert!(CONTAINERFILE.contains("kcode-k1-web-checker --version 0.1.1"));
assert!(tool_image().starts_with("localhost/kcode-k1-code-tools:0.1.4-"));
assert_eq!(CHECK_DEADLINE, Duration::from_secs(225));
}
}