karapace/crates/karapace-runtime/src/backend.rs
Marco Allegretti 8493831222 feat: karapace-runtime — namespace/OCI/mock backends, sandbox, host integration
- RuntimeBackend trait: resolve, build, enter, exec, destroy, status
- Namespace backend: unshare + fuse-overlayfs + chroot (unprivileged)
- OCI backend: crun/runc/youki support
- Mock backend: deterministic test backend with configurable resolution
- Image downloading from images.linuxcontainers.org with blake3 verification
- Sandbox script generation with POSIX shell-quote injection prevention
- Host integration: Wayland, X11, PipeWire, PulseAudio, D-Bus, GPU, audio, SSH agent
- Desktop app export as .desktop files on the host
- SecurityPolicy: mount whitelist, device policy, env var allow/deny, resource limits
- Prerequisite detection with distro-specific install instructions
- OSC 777 terminal markers for container-aware terminals
2026-02-22 18:36:46 +01:00

82 lines
2.4 KiB
Rust

use crate::RuntimeError;
use karapace_schema::{NormalizedManifest, ResolutionResult};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeSpec {
pub env_id: String,
pub root_path: String,
pub overlay_path: String,
pub store_root: String,
pub manifest: NormalizedManifest,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeStatus {
pub env_id: String,
pub running: bool,
pub pid: Option<u32>,
}
pub trait RuntimeBackend: Send + Sync {
fn name(&self) -> &str;
fn available(&self) -> bool;
/// Resolve dependencies: download/identify the base image and query the
/// package manager for exact versions of each requested package.
/// Returns a ResolutionResult with content digest and pinned versions.
fn resolve(&self, spec: &RuntimeSpec) -> Result<ResolutionResult, RuntimeError>;
fn build(&self, spec: &RuntimeSpec) -> Result<(), RuntimeError>;
fn enter(&self, spec: &RuntimeSpec) -> Result<(), RuntimeError>;
fn exec(
&self,
_spec: &RuntimeSpec,
_command: &[String],
) -> Result<std::process::Output, RuntimeError> {
Err(RuntimeError::ExecFailed(format!(
"exec not supported by {} backend",
self.name()
)))
}
fn destroy(&self, spec: &RuntimeSpec) -> Result<(), RuntimeError>;
fn status(&self, env_id: &str) -> Result<RuntimeStatus, RuntimeError>;
}
pub fn select_backend(
name: &str,
store_root: &str,
) -> Result<Box<dyn RuntimeBackend>, RuntimeError> {
match name {
"namespace" => Ok(Box::new(
crate::namespace::NamespaceBackend::with_store_root(store_root),
)),
"oci" => Ok(Box::new(crate::oci::OciBackend::with_store_root(
store_root,
))),
"mock" => Ok(Box::new(crate::mock::MockBackend::new())),
other => Err(RuntimeError::BackendUnavailable(other.to_owned())),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn select_valid_backends() {
assert!(select_backend("namespace", "/tmp/test-store").is_ok());
assert!(select_backend("oci", "/tmp/test-store").is_ok());
assert!(select_backend("mock", "/tmp/test-store").is_ok());
}
#[test]
fn select_invalid_backend_fails() {
assert!(select_backend("nonexistent", "/tmp/test-store").is_err());
}
}