harmont_cli/runner/
mod.rs1use std::collections::HashMap;
9use std::fmt;
10use std::future::Future;
11use std::pin::Pin;
12use std::sync::Arc;
13
14use anyhow::Result;
15use hm_plugin_protocol::{BuildEvent, ExecutorInput, StepResult};
16use tokio_util::sync::CancellationToken;
17
18use crate::orchestrator::archive::ArchiveStore;
19use crate::orchestrator::docker_client::DockerClient;
20use crate::orchestrator::events::EventBus;
21
22pub mod docker;
23
24#[derive(Clone, Debug)]
30pub struct RunContext {
31 pub docker: DockerClient,
32 pub event_bus: Arc<EventBus>,
33 pub archives: Arc<ArchiveStore>,
34 pub cancel: CancellationToken,
35}
36
37pub trait StepRunner: Send + Sync + fmt::Debug {
45 fn name(&self) -> &str;
47
48 fn execute(
57 &self,
58 ctx: &RunContext,
59 input: ExecutorInput,
60 ) -> Pin<Box<dyn Future<Output = Result<StepResult>> + Send + '_>>;
61}
62
63pub trait OutputRenderer: Send + fmt::Debug {
68 fn on_event(&mut self, event: &BuildEvent);
70}
71
72#[derive(Default)]
77pub struct RunnerRegistry {
78 runners: HashMap<String, Arc<dyn StepRunner>>,
79 default: Option<String>,
80}
81
82impl RunnerRegistry {
83 #[must_use]
85 pub fn new() -> Self {
86 Self {
87 runners: HashMap::new(),
88 default: None,
89 }
90 }
91
92 pub fn register(&mut self, runner: Arc<dyn StepRunner>, is_default: bool) {
96 let name = runner.name().to_owned();
97 if is_default {
98 self.default = Some(name.clone());
99 }
100 self.runners.insert(name, runner);
101 }
102
103 #[must_use]
106 pub fn resolve(&self, name: Option<&str>) -> Option<Arc<dyn StepRunner>> {
107 let key = name.or(self.default.as_deref())?;
108 self.runners.get(key).cloned()
109 }
110
111 #[must_use]
113 pub fn default_runner_name(&self) -> Option<&str> {
114 self.default.as_deref()
115 }
116
117 #[must_use]
119 pub fn runner_names(&self) -> Vec<&str> {
120 let mut names: Vec<&str> = self.runners.keys().map(String::as_str).collect();
121 names.sort_unstable();
122 names
123 }
124}
125
126impl fmt::Debug for RunnerRegistry {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 f.debug_struct("RunnerRegistry")
129 .field("runners", &self.runners.keys().collect::<Vec<_>>())
130 .field("default", &self.default)
131 .finish()
132 }
133}
134
135#[cfg(test)]
136#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
137mod tests {
138 use super::*;
139
140 #[derive(Debug)]
142 struct StubRunner {
143 runner_name: String,
144 }
145
146 impl StubRunner {
147 fn new(name: &str) -> Self {
148 Self {
149 runner_name: name.to_owned(),
150 }
151 }
152 }
153
154 impl StepRunner for StubRunner {
155 fn name(&self) -> &str {
156 &self.runner_name
157 }
158
159 fn execute(
160 &self,
161 _ctx: &RunContext,
162 _input: ExecutorInput,
163 ) -> Pin<Box<dyn Future<Output = Result<StepResult>> + Send + '_>> {
164 Box::pin(async {
165 Ok(StepResult {
166 exit_code: 0,
167 committed_snapshot: None,
168 artifacts: vec![],
169 })
170 })
171 }
172 }
173
174 #[test]
175 fn resolve_by_name() {
176 let mut reg = RunnerRegistry::new();
177 reg.register(Arc::new(StubRunner::new("docker")), false);
178 reg.register(Arc::new(StubRunner::new("local")), false);
179
180 let runner = reg.resolve(Some("docker")).unwrap();
181 assert_eq!(runner.name(), "docker");
182
183 let runner = reg.resolve(Some("local")).unwrap();
184 assert_eq!(runner.name(), "local");
185
186 assert!(reg.resolve(Some("nope")).is_none());
187 }
188
189 #[test]
190 fn resolve_default() {
191 let mut reg = RunnerRegistry::new();
192 reg.register(Arc::new(StubRunner::new("docker")), true);
193 reg.register(Arc::new(StubRunner::new("local")), false);
194
195 let runner = reg.resolve(None).unwrap();
197 assert_eq!(runner.name(), "docker");
198 assert_eq!(reg.default_runner_name(), Some("docker"));
199 }
200
201 #[test]
202 fn no_default_returns_none() {
203 let mut reg = RunnerRegistry::new();
204 reg.register(Arc::new(StubRunner::new("docker")), false);
205
206 assert!(reg.resolve(None).is_none());
207 assert!(reg.default_runner_name().is_none());
208 }
209
210 #[test]
211 fn runner_names_sorted() {
212 let mut reg = RunnerRegistry::new();
213 reg.register(Arc::new(StubRunner::new("zeta")), false);
214 reg.register(Arc::new(StubRunner::new("alpha")), false);
215 reg.register(Arc::new(StubRunner::new("mid")), false);
216
217 assert_eq!(reg.runner_names(), vec!["alpha", "mid", "zeta"]);
218 }
219
220 #[test]
221 fn debug_impl() {
222 let reg = RunnerRegistry::new();
223 let _ = format!("{reg:?}");
225 }
226}