use std::path::{Path, PathBuf};
use workload_spec::WorkloadSpec;
pub const SERVICE_IDENT_ENV: &str = "YAH_SERVICE_IDENT";
pub const SCRYER_SOCKET_ENV: &str = "YAH_SCRYER_SOCKET";
pub const GUEST_SOCKET_PATH: &str = "/run/yah/scryer.sock";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MountNs {
Host,
Own,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Collector {
host_socket: Option<PathBuf>,
}
impl Collector {
pub fn disabled() -> Self {
Self { host_socket: None }
}
pub fn at(host_socket: impl Into<PathBuf>) -> Self {
Self {
host_socket: Some(host_socket.into()),
}
}
pub fn from_option(host_socket: Option<impl Into<PathBuf>>) -> Self {
match host_socket {
Some(p) => Self::at(p),
None => Self::disabled(),
}
}
pub fn host_socket(&self) -> Option<&Path> {
self.host_socket.as_deref()
}
pub fn guest_bind(&self) -> Option<(String, String)> {
self.host_socket
.as_ref()
.map(|p| (p.to_string_lossy().into_owned(), GUEST_SOCKET_PATH.to_string()))
}
pub fn env_for(&self, spec: &WorkloadSpec, ns: MountNs) -> Vec<(String, String)> {
let Some(host) = &self.host_socket else {
return Vec::new();
};
let socket = match ns {
MountNs::Host => host.to_string_lossy().into_owned(),
MountNs::Own => GUEST_SOCKET_PATH.to_string(),
};
[
(SERVICE_IDENT_ENV, spec.expose.mesh.identity.0.clone()),
(SCRYER_SOCKET_ENV, socket),
]
.into_iter()
.filter(|(k, _)| !spec.env.iter().any(|e| e.name == *k))
.map(|(k, v)| (k.to_string(), v))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use workload_spec::{
EnvValue, EnvVar, ExposeSpec, ImageRef, MeshExpose, MeshIdent, Millis, NamespaceId,
ResourceLimits, RestartPolicy, StopPolicy, TenantId, TierTag,
};
fn spec(ident: &str) -> WorkloadSpec {
WorkloadSpec {
name: ident.to_string(),
image: ImageRef {
registry: "docker.io".to_string(),
repository: "library/alpine".to_string(),
tag: "latest".to_string(),
digest: workload_spec::testing::test_digest(),
},
tier: TierTag("infra".to_string()),
tenant: TenantId::singleton(),
namespace: NamespaceId::singleton(),
replicas: 1,
command: Some(vec!["sleep".to_string(), "30".to_string()]),
entrypoint: None,
workdir: None,
user: None,
env: vec![],
secrets: vec![],
volumes: vec![],
resources: ResourceLimits {
memory_mb: 64,
cpu_millis: 128,
memory_request_mb: None,
cpu_limit_millis: None,
pids_max: None,
scratch_floor_mb: None,
},
depends_on: vec![],
requires: vec![],
healthcheck: None,
restart_policy: RestartPolicy::Never,
archetype: None,
stop_policy: StopPolicy {
signal: 15,
grace_period: Millis::from_secs(5),
},
expose: ExposeSpec {
mesh: MeshExpose {
identity: MeshIdent(ident.to_string()),
ports: MeshExpose::anonymous_ports([]),
allow_from: vec![],
},
public: None,
operator: None,
},
labels: Default::default(),
durability: None,
annotations: Default::default(),
files: Vec::new(),
}
}
#[test]
fn a_disabled_collector_injects_nothing_in_either_namespace() {
let c = Collector::disabled();
assert!(c.env_for(&spec("door"), MountNs::Host).is_empty());
assert!(c.env_for(&spec("door"), MountNs::Own).is_empty());
assert_eq!(c.guest_bind(), None);
}
#[test]
fn a_host_namespace_workload_gets_the_host_path_verbatim() {
let c = Collector::at("/run/yah/scryer.sock");
assert_eq!(
c.env_for(&spec("door"), MountNs::Host),
vec![
("YAH_SERVICE_IDENT".to_string(), "door".to_string()),
(
"YAH_SCRYER_SOCKET".to_string(),
"/run/yah/scryer.sock".to_string()
),
]
);
}
#[test]
fn an_own_namespace_workload_gets_the_guest_path_and_a_matching_bind() {
let c = Collector::at("/var/run/yah/scryer-1.sock");
let env = c.env_for(&spec("door"), MountNs::Own);
assert_eq!(env[1].1, GUEST_SOCKET_PATH);
assert_eq!(
c.guest_bind(),
Some((
"/var/run/yah/scryer-1.sock".to_string(),
GUEST_SOCKET_PATH.to_string()
))
);
assert_eq!(env[1].1, c.guest_bind().unwrap().1);
}
#[test]
fn a_spec_that_names_either_variable_keeps_its_own_value() {
let c = Collector::at("/run/yah/scryer.sock");
let mut s = spec("door");
s.env.push(EnvVar {
name: "YAH_SCRYER_SOCKET".to_string(),
value: EnvValue::Literal {
value: "/tmp/mine.sock".to_string(),
},
});
let env = c.env_for(&s, MountNs::Host);
assert_eq!(env.len(), 1);
assert_eq!(env[0].0, "YAH_SERVICE_IDENT");
}
#[test]
fn the_service_ident_is_the_mesh_identity_not_the_workload_name() {
let mut s = spec("door");
s.name = "some-deployment-name".to_string();
s.expose.mesh.identity = MeshIdent("inner.door".to_string());
let env = Collector::at("/s.sock").env_for(&s, MountNs::Host);
assert_eq!(env[0].1, "inner.door");
}
}