Skip to main content

hm_vm/
types.rs

1use std::path::PathBuf;
2use std::time::Duration;
3
4/// Where to boot the VM from.
5#[derive(Clone, Debug)]
6pub enum ImageSource {
7    /// OCI image reference (e.g., "alpine:latest").
8    Image(String),
9    /// Fork from a previous snapshot.
10    Snapshot(SnapshotId),
11}
12
13/// What to execute inside a VM.
14#[derive(Clone, Debug)]
15pub struct Action {
16    pub source: ImageSource,
17    pub cmd: String,
18    pub env: Vec<(String, String)>,
19    pub working_dir: String,
20    pub timeout: Option<Duration>,
21    /// Host directory to copy into `working_dir` before execution.
22    /// Skipped on cache hits (snapshot already contains prior state).
23    pub inject: Option<PathBuf>,
24}
25
26/// How to cache the result.
27#[derive(Clone, Debug)]
28pub enum CachingPolicy {
29    /// Do not cache.
30    None,
31    /// Cache the resulting snapshot under this key.
32    Cache { key: String },
33}
34
35/// Typed instruction for `Vm::snapshot`, describing how the committed
36/// snapshot should be tagged.
37///
38/// This replaces a previously stringly-encoded convention where a bare label
39/// meant "ephemeral" and a `repo:tag` label meant "cached", a contract that
40/// the producer (`vm.rs`) and consumer (the backend) had to agree on
41/// out-of-band. Encoding the distinction as an enum makes it compiler-checked.
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub enum SnapshotLabel {
44    /// Uncached snapshot. The backend chooses a unique tag (e.g. the
45    /// container id) so concurrent sibling steps do not race to write the
46    /// same image reference.
47    Ephemeral,
48    /// Cached snapshot tagged from this cache key (parsed as `repo:tag`).
49    Cached(String),
50}
51
52/// Opaque snapshot handle. Backend-specific contents.
53///
54/// The inner representation is private so a snapshot id can only be minted
55/// through [`SnapshotId::new`]; read access goes through the `AsRef<str>` /
56/// `Deref<Target = str>` impls or the `Display` impl. This keeps the handle a
57/// distinct domain newtype rather than an interchangeable `String`.
58#[derive(Clone, Debug, Hash, PartialEq, Eq, derive_more::Display)]
59#[display("{_0}")]
60pub struct SnapshotId(String);
61
62impl SnapshotId {
63    /// Construct a snapshot handle from a backend-specific id.
64    pub fn new(id: impl Into<String>) -> Self {
65        Self(id.into())
66    }
67}
68
69impl AsRef<str> for SnapshotId {
70    fn as_ref(&self) -> &str {
71        &self.0
72    }
73}
74
75impl std::ops::Deref for SnapshotId {
76    type Target = str;
77
78    fn deref(&self) -> &str {
79        &self.0
80    }
81}
82
83/// Result of executing an action.
84#[derive(Clone, Debug)]
85pub struct ExecutionResult {
86    pub exit_code: i32,
87    pub snapshot: Option<SnapshotId>,
88    pub cached: bool,
89}
90
91/// VM resource configuration.
92#[derive(Clone, Debug, Default)]
93pub struct VmConfig {
94    pub cpus: Option<u32>,
95    pub memory_mib: Option<u64>,
96    pub disk_size_gb: Option<u64>,
97}
98
99/// Receives stdout/stderr lines during execution.
100pub trait OutputSink: Send + Sync {
101    fn on_stdout(&self, line: &str);
102    fn on_stderr(&self, line: &str);
103}
104
105/// No-op sink for when output is not needed.
106#[derive(Debug)]
107pub struct NullSink;
108
109impl OutputSink for NullSink {
110    fn on_stdout(&self, _line: &str) {}
111    fn on_stderr(&self, _line: &str) {}
112}