Skip to main content

lightshuttle_runtime/
runtime.rs

1//! Container runtime abstraction plus its supporting domain types.
2
3use std::pin::Pin;
4use std::time::{Duration, SystemTime};
5
6use futures::stream::Stream;
7
8use crate::error::Result;
9use lightshuttle_spec::ContainerSpec;
10
11/// Opaque identifier for a container managed by the runtime.
12///
13/// The internal representation is whatever string the underlying daemon
14/// uses (Docker returns 64-character hexadecimal hashes); callers must
15/// not depend on the format.
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub struct ContainerId(String);
18
19impl ContainerId {
20    /// Build a [`ContainerId`] from a daemon-supplied string.
21    #[must_use]
22    pub fn new(id: impl Into<String>) -> Self {
23        Self(id.into())
24    }
25
26    /// Borrow the raw identifier string.
27    #[must_use]
28    pub fn as_str(&self) -> &str {
29        &self.0
30    }
31}
32
33impl std::fmt::Display for ContainerId {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        f.write_str(&self.0)
36    }
37}
38
39/// Lifecycle status reported by the runtime when inspecting a container.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum ContainerStatus {
42    /// The runtime has accepted the start request but the container is
43    /// not yet running.
44    Starting,
45
46    /// The container is running and either has no healthcheck or has
47    /// not produced a healthcheck result yet.
48    Running,
49
50    /// The container is running and reports a healthy healthcheck.
51    Healthy,
52
53    /// The container is running and reports an unhealthy healthcheck.
54    Unhealthy,
55
56    /// The container has exited.
57    Stopped {
58        /// Exit code reported by the container, when known.
59        exit_code: Option<i32>,
60    },
61}
62
63/// Which stream a log chunk came from.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum LogStream {
66    /// Standard output.
67    Stdout,
68    /// Standard error.
69    Stderr,
70}
71
72/// One chunk of streamed log output.
73#[derive(Debug, Clone)]
74pub struct LogChunk {
75    /// Source stream of the chunk.
76    pub stream: LogStream,
77    /// Wall-clock timestamp reported by the runtime.
78    pub timestamp: SystemTime,
79    /// Raw bytes of the chunk; may or may not end with a newline.
80    pub bytes: Vec<u8>,
81}
82
83/// Boxed stream of log chunks for a single container.
84pub type LogChunkStream = Pin<Box<dyn Stream<Item = Result<LogChunk>> + Send>>;
85
86/// Container runtime abstraction.
87///
88/// The trait is intentionally narrow: it exposes only the operations
89/// that the lifecycle manager needs. Daemon-specific capabilities
90/// (network inspection, image management) stay private to each
91/// implementation.
92///
93/// Implementations live in submodules such as [`crate::DockerRuntime`].
94pub trait ContainerRuntime: Send + Sync {
95    /// Start a container according to `spec`. Pulls the image if not
96    /// already present locally.
97    fn start(
98        &self,
99        spec: &ContainerSpec,
100    ) -> impl std::future::Future<Output = Result<ContainerId>> + Send;
101
102    /// Stop a container, sending `SIGTERM` and then `SIGKILL` after
103    /// `grace`. Idempotent: stopping an already stopped container is a
104    /// no-op.
105    fn stop(
106        &self,
107        id: &ContainerId,
108        grace: Duration,
109    ) -> impl std::future::Future<Output = Result<()>> + Send;
110
111    /// Remove a container by name, forcing removal even if it is still
112    /// running. Idempotent: removing a container that does not exist is a
113    /// no-op. Named volumes are preserved.
114    ///
115    /// The lifecycle manager calls this before every `start` so that a
116    /// re-up or restart replaces the previous container instead of
117    /// colliding with its name.
118    fn remove(&self, name: &str) -> impl std::future::Future<Output = Result<()>> + Send;
119
120    /// Report the current status of a container.
121    fn inspect(
122        &self,
123        id: &ContainerId,
124    ) -> impl std::future::Future<Output = Result<ContainerStatus>> + Send;
125
126    /// Block until the container reports a healthy status or `timeout`
127    /// elapses. Returns [`crate::RuntimeError::Timeout`] in the latter
128    /// case.
129    fn wait_healthy(
130        &self,
131        id: &ContainerId,
132        timeout: Duration,
133    ) -> impl std::future::Future<Output = Result<()>> + Send;
134
135    /// Stream logs from a container. When `follow` is true the stream
136    /// stays open and emits new chunks as they arrive; when false the
137    /// stream completes after the existing logs are drained.
138    fn logs(
139        &self,
140        id: &ContainerId,
141        follow: bool,
142    ) -> impl std::future::Future<Output = Result<LogChunkStream>> + Send;
143}