Skip to main content

koi_runtime/
backend.rs

1//! Runtime backend trait.
2//!
3//! Each container/VM/service runtime implements this trait to provide
4//! lifecycle events and instance metadata in a normalized format.
5
6use tokio::sync::mpsc;
7
8use crate::error::RuntimeError;
9use crate::instance::Instance;
10
11/// Lifecycle event emitted by a runtime backend.
12#[derive(Debug, Clone)]
13pub enum RuntimeEvent {
14    /// A new instance was detected or an existing one started.
15    Started(Instance),
16    /// An instance stopped or was destroyed.
17    Stopped {
18        /// Runtime-assigned instance ID.
19        id: String,
20        /// Human-readable name.
21        name: String,
22    },
23    /// An instance's metadata or ports changed (e.g., Docker network reconnect).
24    Updated(Instance),
25    /// The backend lost connection to the runtime API.
26    BackendDisconnected { backend: String, reason: String },
27    /// The backend reconnected and completed reconciliation.
28    BackendReconnected { backend: String },
29}
30
31/// A runtime backend that watches lifecycle events and resolves instance metadata.
32///
33/// Implementations are expected to:
34/// - Connect to the runtime API on `connect()`
35/// - Stream lifecycle events via the `mpsc::Sender` in `watch()`
36/// - Handle reconnection internally (emit `BackendDisconnected`/`BackendReconnected`)
37/// - Provide a point-in-time snapshot via `list_instances()`
38#[async_trait::async_trait]
39pub trait RuntimeBackend: Send + Sync {
40    /// Backend name for logging and status (e.g., "docker", "podman", "systemd").
41    fn name(&self) -> &'static str;
42
43    /// Attempt to connect to the runtime API.
44    ///
45    /// Returns an error if the runtime is not available (socket missing,
46    /// permission denied, API unreachable).
47    async fn connect(&mut self) -> Result<(), RuntimeError>;
48
49    /// List all currently running instances.
50    ///
51    /// Used for reconciliation on startup: the caller diffs this list
52    /// against Koi's current registrations.
53    async fn list_instances(&self) -> Result<Vec<Instance>, RuntimeError>;
54
55    /// Watch for lifecycle events, sending them to the provided channel.
56    ///
57    /// This method should run until the cancellation token is triggered
58    /// or the channel is closed. It should handle transient failures
59    /// (API disconnects) by emitting `BackendDisconnected`, reconnecting,
60    /// and emitting `BackendReconnected` with a fresh reconciliation.
61    async fn watch(
62        &self,
63        tx: mpsc::Sender<RuntimeEvent>,
64        cancel: tokio_util::sync::CancellationToken,
65    ) -> Result<(), RuntimeError>;
66}
67
68/// Selectable runtime backend kinds.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum RuntimeBackendKind {
71    /// Auto-detect available runtime (Docker → Podman → systemd → Incus).
72    Auto,
73    /// Docker Engine API.
74    Docker,
75    /// Podman (Docker-compatible API, different default socket).
76    Podman,
77    /// systemd D-Bus.
78    Systemd,
79    /// Incus/LXD REST API.
80    Incus,
81    /// Kubernetes watch API.
82    Kubernetes,
83}
84
85impl RuntimeBackendKind {
86    /// Parse from a CLI string.
87    pub fn from_str_loose(s: &str) -> Option<Self> {
88        match s.to_lowercase().as_str() {
89            "auto" => Some(Self::Auto),
90            "docker" => Some(Self::Docker),
91            "podman" => Some(Self::Podman),
92            "systemd" => Some(Self::Systemd),
93            "incus" | "lxc" | "lxd" => Some(Self::Incus),
94            "kubernetes" | "k8s" => Some(Self::Kubernetes),
95            _ => None,
96        }
97    }
98}
99
100impl std::fmt::Display for RuntimeBackendKind {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        match self {
103            Self::Auto => write!(f, "auto"),
104            Self::Docker => write!(f, "docker"),
105            Self::Podman => write!(f, "podman"),
106            Self::Systemd => write!(f, "systemd"),
107            Self::Incus => write!(f, "incus"),
108            Self::Kubernetes => write!(f, "kubernetes"),
109        }
110    }
111}