From c44dcc08a1582c3d9c31ec32f9b86aa58fdf3295 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Aillet Date: Sat, 20 Jan 2024 01:34:56 +0100 Subject: [PATCH] :sparkles: add container health --- src/runtime/docker.rs | 44 +++++++++++++++++++++++++------- src/runtime/model.rs | 59 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/src/runtime/docker.rs b/src/runtime/docker.rs index d3466e1..42daa9d 100644 --- a/src/runtime/docker.rs +++ b/src/runtime/docker.rs @@ -8,7 +8,7 @@ use bollard::{ exec::{CreateExecOptions, ResizeExecOptions, StartExecResults}, image::{ListImagesOptions, RemoveImageOptions}, network::{InspectNetworkOptions, ListNetworksOptions}, - service::{Network, Volume}, + service::{HealthStatusEnum, Network, Volume}, volume::{ListVolumesOptions, RemoveVolumeOptions}, Docker, }; @@ -30,8 +30,8 @@ use tokio_util::sync::CancellationToken; use crate::utils::get_or_not_found; use super::{ - Compose, ContainerDetails, ContainerSummary, Filter, ImageSummary, NetworkSummary, - VolumeSummary, + Compose, ContainerDetails, ContainerHealth, ContainerStatus, ContainerSummary, Filter, + ImageSummary, NetworkSummary, VolumeSummary, }; const DEFAULT_TIMEOUT: u64 = 120; @@ -338,7 +338,7 @@ impl Client { .ok_or(eyre!("No container configuration"))?; let status = parse_state(container_details.state); let container_top = match status { - super::ContainerStatus::Running => self + super::ContainerStatus::Running(_) => self .client .top_processes( &cid, @@ -634,11 +634,37 @@ fn parse_ports(exposed_ports: Option>>) -> Vec<( } fn parse_state(state: Option) -> super::ContainerStatus { - match state { - Some(state) => state - .status - .map_or(super::ContainerStatus::Unknown, |s| s.into()), - None => super::ContainerStatus::Unknown, + if state.is_none() { + return ContainerStatus::Unknown; + } + let state = state.unwrap(); + let status = if let Some(status) = state.status { + status.into() + } else { + ContainerStatus::Unknown + }; + match status { + ContainerStatus::Running(ContainerHealth::Unknown) => { + if let Some(h) = state.health { + match h.status { + Some(HealthStatusEnum::NONE) | Some(HealthStatusEnum::EMPTY) | None => { + ContainerStatus::Running(ContainerHealth::Unknown) + } + Some(HealthStatusEnum::HEALTHY) => { + ContainerStatus::Running(ContainerHealth::Healthy) + } + Some(HealthStatusEnum::UNHEALTHY) => { + ContainerStatus::Running(ContainerHealth::Unhealthy) + } + Some(HealthStatusEnum::STARTING) => { + ContainerStatus::Running(ContainerHealth::Starting) + } + } + } else { + ContainerStatus::Running(ContainerHealth::Unknown) + } + } + s => s, } } diff --git a/src/runtime/model.rs b/src/runtime/model.rs index 81febed..d0122de 100644 --- a/src/runtime/model.rs +++ b/src/runtime/model.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, fmt::Display}; +use bollard::service::ContainerStateStatusEnum; use humansize::{FormatSizeI, BINARY}; use ratatui::{ @@ -187,11 +188,20 @@ impl<'a> From<&ImageSummary> for Row<'a> { } } +#[allow(dead_code)] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub enum ContainerHealth { + Unknown, + Healthy, + Unhealthy, + Starting, +} + #[allow(dead_code)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum ContainerStatus { Created, - Running, + Running(ContainerHealth), Paused, Restarting, Removing, @@ -200,14 +210,11 @@ pub enum ContainerStatus { Unknown, } -impl From for ContainerStatus -where - T: AsRef, -{ - fn from(value: T) -> Self { +impl From for ContainerStatus { + fn from(value: String) -> Self { match value.as_ref() { "created" => ContainerStatus::Created, - "running" => ContainerStatus::Running, + "running" => ContainerStatus::Running(ContainerHealth::Unknown), "paused" => ContainerStatus::Paused, "restarting" => ContainerStatus::Restarting, "removing" => ContainerStatus::Removing, @@ -218,11 +225,31 @@ where } } +impl From for ContainerStatus { + fn from(value: ContainerStateStatusEnum) -> Self { + match value { + ContainerStateStatusEnum::DEAD => ContainerStatus::Dead, + ContainerStateStatusEnum::EMPTY => ContainerStatus::Unknown, + ContainerStateStatusEnum::EXITED => ContainerStatus::Exited, + ContainerStateStatusEnum::CREATED => ContainerStatus::Created, + ContainerStateStatusEnum::PAUSED => ContainerStatus::Paused, + ContainerStateStatusEnum::RUNNING => ContainerStatus::Running(ContainerHealth::Unknown), + ContainerStateStatusEnum::REMOVING => ContainerStatus::Removing, + ContainerStateStatusEnum::RESTARTING => ContainerStatus::Restarting, + } + } +} + impl From for String { fn from(value: ContainerStatus) -> Self { match value { ContainerStatus::Created => "created".into(), - ContainerStatus::Running => "running".into(), + ContainerStatus::Running(h) => match h { + ContainerHealth::Unknown => "running".into(), + ContainerHealth::Healthy => "running (healthy)".into(), + ContainerHealth::Unhealthy => "running (unhealthy)".into(), + ContainerHealth::Starting => "running (starting)".into(), + }, ContainerStatus::Paused => "paused".into(), ContainerStatus::Restarting => "restarting".into(), ContainerStatus::Removing => "removing".into(), @@ -237,7 +264,16 @@ impl ContainerStatus { fn format(&self) -> Span<'static> { match self { ContainerStatus::Created => Span::styled("created", Style::new().dark_gray()), - ContainerStatus::Running => Span::styled("running", Style::new().green()), + ContainerStatus::Running(h) => match h { + ContainerHealth::Unknown => Span::styled("running", Style::new().green()), + ContainerHealth::Healthy => Span::styled("running (healthy)", Style::new().green()), + ContainerHealth::Unhealthy => { + Span::styled("running (unhealthy)", Style::new().yellow()) + } + ContainerHealth::Starting => { + Span::styled("running (starting)", Style::new().green()) + } + }, ContainerStatus::Paused => Span::styled("paused", Style::new().dark_gray()), ContainerStatus::Restarting => Span::styled("restarting", Style::new().yellow()), ContainerStatus::Removing => Span::styled("removing", Style::new().red()), @@ -373,11 +409,14 @@ impl<'a> From<&ContainerDetails> for Vec> { .network .iter() .flat_map(|(n, ip)| match ip { + None => vec![Line::styled(format!(" - Name: {}", n), style)], + Some(ip) if ip.is_empty() => { + vec![Line::styled(format!(" - Name: {}", n), style)] + } Some(ip) => vec![ Line::styled(format!(" - Name: {}", n), style), Line::styled(format!(" IPAddress: {}", ip), style), ], - None => vec![Line::styled(format!(" - Name: {}", n), style)], }) .collect(), );