1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//! Executor registry. Holds available executors per host, exposes lookup and
//! aggregation operations (cross-executor list / count) used by the main loop.
use super::{Executor, ExecutorFilter, ExecutorKind, OwnedRunner, ProvisionError};
use std::collections::HashMap;
use std::sync::Arc;
pub struct Registry {
executors: HashMap<ExecutorKind, Arc<dyn Executor>>,
}
impl Registry {
/// Probe what executors can plausibly run on this host, honouring an
/// optional `--executors` allow-list. `None` means "all kinds available
/// for this host" — the historical default. `Some(set)` skips probing
/// any executor not in the set; this is wired from the `--executors`
/// CLI flag (issue #15) so docker-only operators can suppress the
/// meda/lume auto-probe.
///
/// Construction failures (daemon down, binary missing) silently drop
/// that executor — it just won't appear in the registry. Caller
/// resolves missing executors as `BackendUnavailable`.
pub fn probe_filtered(filter: &ExecutorFilter) -> Self {
let mut executors: HashMap<ExecutorKind, Arc<dyn Executor>> = HashMap::new();
if filter.allows(ExecutorKind::Docker) {
// Docker works on any OS (Linux native, macOS via Docker Desktop) —
// register if the daemon answers a ping.
let docker = super::docker::DockerExecutor::new();
match docker.client_ping() {
Ok(v) => {
log::info!("docker daemon reachable: {v}");
executors.insert(ExecutorKind::Docker, Arc::new(docker));
}
Err(e) => log::info!("docker daemon not available (skipping): {e}"),
}
} else {
log::info!("docker executor disabled by --executors filter");
}
#[cfg(target_os = "linux")]
{
if filter.allows(ExecutorKind::Meda) {
if let Ok(m) = super::meda::MedaExecutor::new() {
executors.insert(ExecutorKind::Meda, Arc::new(m));
}
} else {
log::info!("meda executor disabled by --executors filter");
}
}
#[cfg(target_os = "macos")]
{
if filter.allows(ExecutorKind::Lume) {
if let Ok(l) = super::lume::LumeExecutor::new() {
executors.insert(ExecutorKind::Lume, Arc::new(l));
}
} else {
log::info!("lume executor disabled by --executors filter");
}
}
Self { executors }
}
/// Build a Registry from an explicit map. Intended for tests that need to
/// inject a fake executor without probing the host. Not part of the
/// production wiring — `probe()` is the canonical constructor.
#[cfg(test)]
pub fn from_executors(executors: HashMap<ExecutorKind, Arc<dyn Executor>>) -> Self {
Self { executors }
}
pub fn get(&self, kind: ExecutorKind) -> Result<Arc<dyn Executor>, ProvisionError> {
self.executors.get(&kind).cloned().ok_or_else(|| {
ProvisionError::Incompatible(format!("executor {kind:?} not available on this host"))
})
}
/// Names of registered executors as lowercase strings (`"docker"`, `"meda"`,
/// `"lume"`), sorted for deterministic wire output. Sent on the agent's GET
/// /agent body so the cirun api can route by capability instead of host OS.
pub fn kind_names(&self) -> Vec<&'static str> {
let mut out: Vec<&'static str> = self.executors.keys().map(|k| k.name()).collect();
out.sort_unstable();
out
}
/// Aggregate count of running runners across every registered executor.
/// Errors per-executor are logged and that executor counts as 0.
pub async fn total_count_running(&self) -> usize {
let mut total = 0;
for (kind, exec) in &self.executors {
match exec.count_running().await {
Ok(n) => total += n,
Err(e) => log::warn!("count_running({kind:?}) failed: {e}"),
}
}
total
}
/// Cross-executor list. Returns `(kind, runner)` pairs so the caller can
/// preserve the binding for reporting / orphan-cleanup decisions.
pub async fn list_all(&self) -> Vec<(ExecutorKind, OwnedRunner)> {
let mut all = Vec::new();
for (kind, exec) in &self.executors {
match exec.list_owned().await {
Ok(runners) => all.extend(runners.into_iter().map(|r| (*kind, r))),
Err(e) => log::warn!("list_owned({kind:?}) failed: {e}"),
}
}
all
}
}