youki 0.0.5

A container runtime written in Rust
use anyhow::{anyhow, bail, Context, Result};
use oci_spec::runtime::Spec;
use wasmtime::*;
use wasmtime_wasi::WasiCtxBuilder;

use libcontainer::workload::{Executor, EMPTY};

const EXECUTOR_NAME: &str = "wasmtime";

#[derive(Default)]
pub struct WasmtimeExecutor {}

impl Executor for WasmtimeExecutor {
    fn exec(&self, spec: &Spec) -> Result<()> {
        log::info!("Executing workload with wasmtime handler");
        let process = spec.process().as_ref();

        let args = spec
            .process()
            .as_ref()
            .and_then(|p| p.args().as_ref())
            .unwrap_or(&EMPTY);
        if args.is_empty() {
            bail!("at least one process arg must be specified")
        }

        if !args[0].ends_with(".wasm") && !args[0].ends_with(".wat") {
            bail!(
                "first argument must be a wasm or wat module, but was {}",
                args[0]
            )
        }

        let mut cmd = args[0].clone();
        let stripped = args[0].strip_prefix(std::path::MAIN_SEPARATOR);
        if let Some(cmd_stripped) = stripped {
            cmd = cmd_stripped.to_string();
        }

        let envs: Vec<(String, String)> = process
            .and_then(|p| p.env().as_ref())
            .unwrap_or(&EMPTY)
            .iter()
            .filter_map(|e| {
                e.split_once('=')
                    .map(|kv| (kv.0.trim().to_string(), kv.1.trim().to_string()))
            })
            .collect();

        let engine = Engine::default();
        let module = Module::from_file(&engine, &cmd)
            .with_context(|| format!("could not load wasm module from {}", &cmd))?;

        let mut linker = Linker::new(&engine);
        wasmtime_wasi::add_to_linker(&mut linker, |s| s)
            .context("cannot add wasi context to linker")?;

        let wasi = WasiCtxBuilder::new()
            .inherit_stdio()
            .args(args)
            .context("cannot add args to wasi context")?
            .envs(&envs)
            .context("cannot add environment variables to wasi context")?
            .build();

        let mut store = Store::new(&engine, wasi);

        let instance = linker
            .instantiate(&mut store, &module)
            .context("wasm module could not be instantiated")?;
        let start = instance
            .get_func(&mut store, "_start")
            .ok_or_else(|| anyhow!("could not retrieve wasm module main function"))?;

        start
            .call(&mut store, &[], &mut [])
            .context("wasm module was not executed successfully")
    }

    fn can_handle(&self, spec: &Spec) -> Result<bool> {
        if let Some(annotations) = spec.annotations() {
            if let Some(handler) = annotations.get("run.oci.handler") {
                return Ok(handler == "wasm");
            }

            if let Some(variant) = annotations.get("module.wasm.image/variant") {
                return Ok(variant == "compat");
            }
        }

        Ok(false)
    }

    fn name(&self) -> &'static str {
        EXECUTOR_NAME
    }
}