use bincode::{Decode, Encode};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
pub enum ContainerState {
Created,
Running,
Paused,
Restarting,
Exited,
Dead,
Removing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
pub enum EngineKind {
Docker,
Podman,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)]
pub struct ContainerSnapshot {
pub id: String,
pub id_full: String,
pub name: String,
pub image: String,
pub state: ContainerState,
pub status_text: String,
pub cpu_pct: f32,
pub mem_used_bytes: u64,
pub mem_limit_bytes: u64,
pub net_rx_bytes: u64,
pub net_tx_bytes: u64,
pub block_read_bytes: u64,
pub block_write_bytes: u64,
pub started_at_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)]
pub struct ContainersSnapshot {
pub engine: EngineKind,
pub daemon_up: bool,
pub containers: Vec<ContainerSnapshot>,
}
impl ContainersSnapshot {
pub fn unavailable() -> Self {
Self {
engine: EngineKind::Unknown,
daemon_up: false,
containers: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use bincode::config;
fn sample_snapshot() -> ContainerSnapshot {
ContainerSnapshot {
id: "abc123def456".into(),
id_full: "abc123def4567890000000000000000000000000000000000000000000000000".into(),
name: "nginx".into(),
image: "nginx:1.27".into(),
state: ContainerState::Running,
status_text: "Up 2 hours".into(),
cpu_pct: 3.5,
mem_used_bytes: 128 * 1024 * 1024,
mem_limit_bytes: 512 * 1024 * 1024,
net_rx_bytes: 1_000_000,
net_tx_bytes: 500_000,
block_read_bytes: 4 * 1024 * 1024,
block_write_bytes: 2 * 1024 * 1024,
started_at_ms: 1_700_000_000_000,
}
}
#[test]
fn container_snapshot_derive_round_trip() {
let original = sample_snapshot();
let cfg = config::standard();
let bytes = bincode::encode_to_vec(&original, cfg).expect("encode");
let (decoded, _len): (ContainerSnapshot, usize) =
bincode::decode_from_slice(&bytes, cfg).expect("decode");
assert_eq!(original, decoded);
}
#[test]
fn engine_kind_derive_round_trip() {
let cfg = config::standard();
for kind in [EngineKind::Docker, EngineKind::Podman, EngineKind::Unknown] {
let bytes = bincode::encode_to_vec(kind, cfg).expect("encode");
let (decoded, _): (EngineKind, usize) =
bincode::decode_from_slice(&bytes, cfg).expect("decode");
assert_eq!(kind, decoded);
}
}
#[test]
fn container_state_is_exhaustive() {
for state in [
ContainerState::Created,
ContainerState::Running,
ContainerState::Paused,
ContainerState::Restarting,
ContainerState::Exited,
ContainerState::Dead,
ContainerState::Removing,
] {
let _label: &'static str = match state {
ContainerState::Created => "created",
ContainerState::Running => "running",
ContainerState::Paused => "paused",
ContainerState::Restarting => "restarting",
ContainerState::Exited => "exited",
ContainerState::Dead => "dead",
ContainerState::Removing => "removing",
};
}
}
#[test]
fn containers_snapshot_unavailable_is_empty() {
let s = ContainersSnapshot::unavailable();
assert!(!s.daemon_up);
assert!(s.containers.is_empty());
assert_eq!(s.engine, EngineKind::Unknown);
}
}