Skip to content

Commit

Permalink
fix: stop and delete containers in tokio task
Browse files Browse the repository at this point in the history
  • Loading branch information
robertpsoane committed Jun 29, 2024
1 parent 7c7ebeb commit 9a8738e
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 25 deletions.
30 changes: 27 additions & 3 deletions src/callbacks/delete_container.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
use crate::{docker::container::DockerContainer, traits::Callback};
use crate::{
docker::container::DockerContainer,
events::{Key, Message, Transition},
traits::Callback,
};
use async_trait::async_trait;
use color_eyre::eyre::Result;
use tokio::sync::mpsc::Sender;

#[derive(Debug)]
pub struct DeleteContainer {
docker: bollard::Docker,
container: DockerContainer,
force: bool,
tx: Sender<Message<Key, Transition>>,
}

impl DeleteContainer {
pub fn new(docker: bollard::Docker, container: DockerContainer, force: bool) -> Self {
pub fn new(
docker: bollard::Docker,
container: DockerContainer,
force: bool,
tx: Sender<Message<Key, Transition>>,
) -> Self {
Self {
docker,
container,
force,
tx,
}
}
}
#[async_trait]
impl Callback for DeleteContainer {
async fn call(&self) -> Result<()> {
let _ = self.container.delete(&self.docker, self.force).await?;
let container = self.container.clone();
let docker = self.docker.clone();
let force = self.force;
let tx = self.tx.clone();
tokio::spawn(async move {
let message = if container.delete(&docker, force).await.is_ok() {
Message::Tick
} else {
let msg = format!("Failed to delete container {}", container.id);
Message::Error(msg)
};
let _ = tx.send(message).await;
});
Ok(())
}
}
17 changes: 13 additions & 4 deletions src/docker/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,29 @@ impl DockerContainer {

let running = matches!(c.state.unwrap_or_default().as_str(), "running");

let names = c
.names
.clone()
.unwrap_or_default()
.into_iter()
.map(|n| n.strip_prefix('/').unwrap_or_default().into())
.collect::<Vec<String>>()
.join(", ");

Self {
id: c.id.clone().unwrap_or_default(),
image: c.image.clone().unwrap_or_default(),
command: c.command.clone().unwrap_or_default(),
created: datetime,
status: c.status.clone().unwrap_or_default(),
ports,
names: c.names.clone().unwrap_or_default().join(", "),
names,
running,
}
}

pub async fn list(docker: &bollard::Docker) -> Result<Vec<Self>> {
let containrs = docker
let containers = docker
.list_containers(Some(ListContainersOptions::<String> {
all: true,
..Default::default()
Expand All @@ -75,7 +84,7 @@ impl DockerContainer {
.map(Self::from)
.collect();

Ok(containrs)
Ok(containers)
}

pub async fn delete(&self, docker: &bollard::Docker, force: bool) -> Result<()> {
Expand All @@ -100,7 +109,7 @@ impl DockerContainer {
docker
.stop_container(&self.id, None)
.await
.context("failed to start container")?;
.context("failed to stop container")?;
Ok(())
}

Expand Down
1 change: 1 addition & 0 deletions src/events/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub enum Message<I, T> {
Tick,
Input(I),
Transition(T),
Error(String),

Check warning on line 6 in src/events/message.rs

View workflow job for this annotation

GitHub Actions / check / stable / clippy

field `0` is never read

warning: field `0` is never read --> src/events/message.rs:6:11 | 6 | Error(String), | ----- ^^^^^^ | | | field in this variant | = note: `#[warn(dead_code)]` on by default help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field | 6 | Error(()), | ~~

Check warning on line 6 in src/events/message.rs

View workflow job for this annotation

GitHub Actions / check / beta / clippy

field `0` is never read

warning: field `0` is never read --> src/events/message.rs:6:11 | 6 | Error(String), | ----- ^^^^^^ | | | field in this variant | = note: `#[warn(dead_code)]` on by default help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field | 6 | Error(()), | ~~
}

#[derive(PartialEq, Debug, Clone)]
Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ async fn main() -> color_eyre::Result<()> {
Message::Tick => {
app.update(Key::Null).await;
}

Message::Error(_) => {
// This needs implementing
}
}
}

Expand Down
50 changes: 38 additions & 12 deletions src/pages/containers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use ratatui::{
widgets::{Row, Table, TableState},
Frame,
};
use std::sync::{Arc, Mutex};
use std::{
collections::HashSet,
sync::{Arc, Mutex},
};
use tokio::sync::mpsc::Sender;

use crate::{
Expand Down Expand Up @@ -55,6 +58,7 @@ pub struct Containers {
containers: Vec<DockerContainer>,
list_state: TableState,
modal: Option<BooleanModal<ModalTypes>>,
stopping_containers: Arc<Mutex<HashSet<String>>>,
}

#[async_trait::async_trait]
Expand Down Expand Up @@ -197,6 +201,7 @@ impl Containers {
containers: vec![],
list_state: TableState::default(),
modal: None,
stopping_containers: Arc::new(Mutex::new(HashSet::new())),
}
}

Expand Down Expand Up @@ -249,7 +254,27 @@ impl Containers {

async fn stop_container(&mut self) -> Result<Option<()>> {
if let Ok(container) = self.get_container() {
container.stop(&self.docker).await?;
self.stopping_containers
.lock()
.unwrap()
.insert(container.id.clone());

let c = container.clone();
let docker = self.docker.clone();
let tx = self.tx.clone();
let stopping_containers = self.stopping_containers.clone();
tokio::spawn(async move {
let message = if c.stop(&docker).await.is_ok() {
Message::Tick
} else {
let msg = format!("Failed to delete container {}", c.id);
Message::Error(msg)
};
stopping_containers.lock().unwrap().remove(&c.id);
let _ = tx.send(message).await;
});

// Second spawned taskt is used to update the state

self.refresh().await?;
return Ok(Some(()));
Expand All @@ -262,19 +287,17 @@ impl Containers {
let name = container.names.clone();
let image = container.image.clone();

let message = match container.running {
true => {
format!("Are you sure you wish to delete container {name} (image = {image})? This container is currently running; this will result in a force deletion.")
}
false => {
format!("Are you sure you wish to delete container {name} (image = {image})?")
}
let message = if container.running {
format!("Are you sure you wish to delete container {name} (image = {image})? This container is currently running; this will result in a force deletion.")
} else {
format!("Are you sure you wish to delete container {name} (image = {image})?")
};

let cb = Arc::new(FutureMutex::new(DeleteContainer::new(
self.docker.clone(),
container.clone(),
container.running,
self.tx.clone(),
)));

let mut modal =
Expand All @@ -291,9 +314,12 @@ impl Containers {
impl Component for Containers {
fn draw(&mut self, f: &mut Frame<'_>, area: Rect) {
let rows = self.containers.clone().into_iter().map(|c| {
let style = match c.running {
true => Style::default().fg(self.config.theme.positive_highlight()),
false => Style::default(),
let style = if self.stopping_containers.lock().unwrap().contains(&c.id) {
Style::default().fg(self.config.theme.negative_highlight())
} else if c.running {
Style::default().fg(self.config.theme.positive_highlight())
} else {
Style::default()
};

Row::new(vec![
Expand Down
7 changes: 1 addition & 6 deletions src/ui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,7 @@ impl App {
}

async fn update_view_mode(&mut self, message: Key) -> Result<MessageResponse> {
if let MessageResponse::Consumed = self
.page_manager
.update(message)
.await
.context("unable to update body")?
{
if let MessageResponse::Consumed = self.page_manager.update(message).await? {
return Ok(MessageResponse::Consumed);
}

Expand Down

0 comments on commit 9a8738e

Please sign in to comment.