use core::time::Duration;
use std::time::SystemTime;
use crate::model::{Confidence, MetricState};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum EnvironmentKind {
#[default]
NoEvidenceFound,
Container,
VirtualMachine,
}
impl EnvironmentKind {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::NoEvidenceFound => "no container/VM evidence",
Self::Container => "container",
Self::VirtualMachine => "virtual machine",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum ContainerRuntime {
Docker,
Containerd,
CriO,
Podman,
Lxc,
SystemdMachine,
Unknown,
}
impl ContainerRuntime {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Docker => "docker",
Self::Containerd => "containerd",
Self::CriO => "cri-o",
Self::Podman => "podman",
Self::Lxc => "lxc",
Self::SystemdMachine => "systemd-machine",
Self::Unknown => "container",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ContainerIdentity {
pub runtime: ContainerRuntime,
pub id: Box<str>,
pub kubernetes: bool,
}
impl ContainerIdentity {
#[must_use]
pub fn short_id(&self) -> &str {
let cut = self
.id
.char_indices()
.nth(12)
.map_or(self.id.len(), |(index, _)| index);
self.id.get(..cut).unwrap_or(&self.id)
}
#[must_use]
pub fn label(&self) -> String {
if self.kubernetes {
format!("kubernetes/{} {}", self.runtime.label(), self.short_id())
} else {
format!("{} {}", self.runtime.label(), self.short_id())
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct HostEnvironment {
pub kind: EnvironmentKind,
pub evidence: Box<str>,
pub confidence: Confidence,
pub container: Option<ContainerIdentity>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct HostSnapshot {
pub hostname: MetricState<Box<str>>,
pub os_name: MetricState<Box<str>>,
pub os_version: MetricState<Box<str>>,
pub kernel_version: MetricState<Box<str>>,
pub arch: &'static str,
pub cpu_brand: MetricState<Box<str>>,
pub uptime: MetricState<Duration>,
pub boot_time: MetricState<SystemTime>,
pub environment: MetricState<HostEnvironment>,
}
impl HostSnapshot {
#[must_use]
pub const fn warming_up() -> Self {
Self {
hostname: MetricState::WarmingUp,
os_name: MetricState::WarmingUp,
os_version: MetricState::WarmingUp,
kernel_version: MetricState::WarmingUp,
arch: std::env::consts::ARCH,
cpu_brand: MetricState::WarmingUp,
uptime: MetricState::WarmingUp,
boot_time: MetricState::WarmingUp,
environment: MetricState::WarmingUp,
}
}
#[must_use]
pub fn display_hostname(&self) -> &str {
self.hostname
.displayable()
.map_or("unknown", |(name, _)| name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arch_is_known_at_compile_time_and_never_unavailable() {
let host = HostSnapshot::warming_up();
assert!(!host.arch.is_empty());
assert!(
[
"aarch64",
"x86_64",
"arm",
"x86",
"powerpc64",
"riscv64",
"s390x",
"loongarch64"
]
.contains(&host.arch),
"unexpected arch {}",
host.arch
);
}
#[test]
fn an_unresolved_hostname_renders_as_unknown_not_as_an_empty_title() {
let host = HostSnapshot::warming_up();
assert_eq!(host.display_hostname(), "unknown");
}
#[test]
fn a_stale_hostname_is_still_displayable() {
let mut host = HostSnapshot::warming_up();
host.hostname = MetricState::Available("dev-mbp".into()).into_stale(Duration::from_secs(5));
assert_eq!(host.display_hostname(), "dev-mbp");
}
#[test]
fn absence_of_evidence_is_not_reported_as_bare_metal() {
assert_eq!(EnvironmentKind::default(), EnvironmentKind::NoEvidenceFound);
assert!(
EnvironmentKind::NoEvidenceFound
.label()
.contains("evidence")
);
}
#[test]
fn an_environment_classification_carries_its_evidence_and_confidence() {
let env = HostEnvironment {
kind: EnvironmentKind::Container,
evidence: "/proc/1/cgroup names docker".into(),
confidence: Confidence::High,
container: None,
};
assert!(!env.evidence.is_empty());
assert_eq!(env.confidence, Confidence::High);
}
}