Skip to content

Commit

Permalink
feat: add support for listing volumes (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
robertpsoane authored Jul 13, 2024
1 parent aef1ac7 commit 6acddc5
Show file tree
Hide file tree
Showing 13 changed files with 505 additions and 6 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ tui-big-text = "0.4.5"
strip = true
lto = true
codegen-units = 1
panic = "abort"
opt-level = "z"

[badges]
maintenance = { status = "actively-developed" }
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ The following commands are supported:
| ------------ | ----------- | ------------------------------------ |
| `images` | `image` | Open the `Images` top level page |
| `containers` | `container` | Open the `Containers` top level page |
| `volumes` | `volume` | Open the `Volumes` top level page |
| `quit` | `q` | Close the application |


Expand Down Expand Up @@ -113,8 +114,17 @@ The following actions are available on the Images page:
| Hotkey | Action |
| -------- | -------------------------------------------------------------- |
| `Ctrl+d` | Delete the currently selected image |
| `d` | Toggle whether or not to show dangling images (off by default) |
| `d` | Describe the currently selected image |
| `D` | Toggle whether or not to show dangling images (off by default) |

#### Volumes

The following actions are available on the Volumes page:

| Hotkey | Action |
| -------- | -------------------------------------- |
| `Ctrl+d` | Delete the currently selected volume |
| `d` | Describe the currently selected volume |

#### Logs

Expand Down
28 changes: 28 additions & 0 deletions src/callbacks/delete_volume.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use crate::{docker::volume::DockerVolume, traits::Callback};
use async_trait::async_trait;
use color_eyre::eyre::Result;

#[derive(Debug)]
pub struct DeleteVolume {
docker: bollard::Docker,
volume: DockerVolume,
force: bool,
}

impl DeleteVolume {
pub fn new(docker: bollard::Docker, volume: DockerVolume, force: bool) -> Self {
Self {
docker,
volume,
force,
}
}
}

#[async_trait]
impl Callback for DeleteVolume {
async fn call(&self) -> Result<()> {
let _ = self.volume.delete(&self.docker, self.force).await?;
Ok(())
}
}
2 changes: 1 addition & 1 deletion src/callbacks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub mod delete_container;
pub mod delete_image;

pub mod delete_volume;
pub use delete_container::DeleteContainer;
8 changes: 6 additions & 2 deletions src/components/command_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const IMAGE: &str = "image";
const IMAGES: &str = "images";
const CONTAINER: &str = "container";
const CONTAINERS: &str = "containers";
const VOLUME: &str = "volume";
const VOLUMES: &str = "volumes";

#[derive(Debug)]
pub struct CommandInput {
Expand All @@ -30,8 +32,9 @@ pub struct CommandInput {

impl CommandInput {
pub fn new(tx: Sender<Message<Key, Transition>>, prompt: String) -> Self {
let ac: Autocomplete =
Autocomplete::from(vec![QUIT, Q, IMAGE, IMAGES, CONTAINER, CONTAINERS]);
let ac: Autocomplete = Autocomplete::from(vec![
QUIT, Q, IMAGE, IMAGES, CONTAINER, CONTAINERS, VOLUME, VOLUMES,
]);
Self {
tx,
history: History::new(),
Expand Down Expand Up @@ -89,6 +92,7 @@ impl CommandInput {
Q | QUIT => Some(Transition::Quit),
IMAGE | IMAGES => Some(Transition::ToImagePage(AppContext::default())),
CONTAINER | CONTAINERS => Some(Transition::ToContainerPage(AppContext::default())),
VOLUME | VOLUMES => Some(Transition::ToVolumePage(AppContext::default())),
_ => None,
};

Expand Down
5 changes: 4 additions & 1 deletion src/context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::{
docker::{container::DockerContainer, image::DockerImage, traits::Describe},
docker::{
container::DockerContainer, image::DockerImage, traits::Describe, volume::DockerVolume,
},
events::Transition,
};

Expand All @@ -14,6 +16,7 @@ pub struct AppContext {
pub list_idx: Option<usize>,
pub docker_container: Option<DockerContainer>,
pub docker_image: Option<DockerImage>,
pub docker_volume: Option<DockerVolume>,
pub describable: Option<Box<dyn Describe>>,
}

Expand Down
1 change: 1 addition & 0 deletions src/docker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pub mod image;
pub mod logs;
pub mod traits;
pub mod util;
pub mod volume;
102 changes: 102 additions & 0 deletions src/docker/volume.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use bollard::{
secret::{Volume, VolumeScopeEnum},
volume::RemoveVolumeOptions,
};
use byte_unit::{Byte, UnitType};
use color_eyre::eyre::{bail, Result};
use serde::Serialize;
use std::collections::HashMap;

use super::traits::Describe;

#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct DockerVolume {
pub name: String,
pub driver: String,
pub mountpoint: String,
pub created_at: Option<String>,
pub labels: HashMap<String, String>,
pub scope: Option<VolumeScopeEnum>,
pub options: HashMap<String, String>,
pub ref_count: Option<u64>,
pub size: Option<String>,
}

impl DockerVolume {
pub fn from(v: Volume) -> Self {
let ref_count: Option<u64>;
let size: Option<String>;

if let Some(usage_data) = v.usage_data {
if usage_data.ref_count < 0 {
ref_count = None
} else {
ref_count = Some(usage_data.ref_count as u64)
}

if usage_data.size < 0 {
size = None
} else {
let byte =
Byte::from_u64(usage_data.size as u64).get_appropriate_unit(UnitType::Binary);
size = Some(format!("{byte:.2}"));
}
} else {
ref_count = None;
size = None;
}

Self {
name: v.name,
driver: v.driver,
mountpoint: v.mountpoint,
created_at: v.created_at,
labels: v.labels,
scope: v.scope,
options: v.options,
ref_count,
size,
}
}

pub async fn list(docker: &bollard::Docker) -> Result<Vec<Self>> {
let bollard_volumes = docker.list_volumes::<String>(None).await?;
let mut docker_volumes: Vec<Self> = match bollard_volumes.volumes {
Some(v) => v,
None => bail!(""),
}
.into_iter()
.map(Self::from)
.collect();

docker_volumes.sort_by_key(|v| v.name.clone());

Ok(docker_volumes)
}

pub async fn delete(&self, docker: &bollard::Docker, force: bool) -> Result<()> {
docker
.remove_volume(&self.get_name(), Some(RemoveVolumeOptions { force }))
.await?;
Ok(())
}
}

impl Describe for DockerVolume {
fn get_id(&self) -> String {
self.get_name()
}
fn get_name(&self) -> String {
self.name.clone()
}

fn describe(&self) -> Result<Vec<String>> {
let summary = match serde_yml::to_string(&self) {
Ok(s) => s,
Err(_) => {
bail!("failed to parse container summary")
}
};
Ok(summary.lines().map(String::from).collect())
}
}
1 change: 1 addition & 0 deletions src/events/transition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum Transition {
ToLogPage(AppContext),
ToDescribeContainerPage(AppContext),
ToAttach(AppContext),
ToVolumePage(AppContext),
}

pub async fn send_transition(
Expand Down
1 change: 1 addition & 0 deletions src/pages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pub mod containers;
pub mod describe;
pub mod images;
pub mod logs;
pub mod volume;
Loading

0 comments on commit 6acddc5

Please sign in to comment.