Skip to content

Commit

Permalink
✨ add container health (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
pyaillet authored Jan 20, 2024
1 parent 00cec5f commit 1c5e2af
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 19 deletions.
44 changes: 35 additions & 9 deletions src/runtime/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -634,11 +634,37 @@ fn parse_ports(exposed_ports: Option<HashMap<String, HashMap<(), ()>>>) -> Vec<(
}

fn parse_state(state: Option<bollard::service::ContainerState>) -> 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,
}
}

Expand Down
59 changes: 49 additions & 10 deletions src/runtime/model.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{collections::HashMap, fmt::Display};

use bollard::service::ContainerStateStatusEnum;
use humansize::{FormatSizeI, BINARY};

use ratatui::{
Expand Down Expand Up @@ -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,
Expand All @@ -200,14 +210,11 @@ pub enum ContainerStatus {
Unknown,
}

impl<T> From<T> for ContainerStatus
where
T: AsRef<str>,
{
fn from(value: T) -> Self {
impl From<String> 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,
Expand All @@ -218,11 +225,31 @@ where
}
}

impl From<ContainerStateStatusEnum> 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<ContainerStatus> 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(),
Expand All @@ -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()),
Expand Down Expand Up @@ -373,11 +409,14 @@ impl<'a> From<&ContainerDetails> for Vec<Line<'a>> {
.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(),
);
Expand Down

0 comments on commit 1c5e2af

Please sign in to comment.