Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,97 +11,97 @@ Docker Volume Analyzer is a tool designed to simplify the management of Docker v

This project aims to make Docker volume management more intuitive and user-friendly.

[![Build Status](https://github.com/glefer/docker-volumes-analyzer/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/glefer/docker-volumes-analyzer/actions)
[![Python Poetry Application](https://github.com/glefer/docker-volumes-analyzer/actions/workflows/main.yml/badge.svg)](https://github.com/glefer/docker-volumes-analyzer/actions/workflows/main.yml)
[![codecov](https://codecov.io/gh/glefer/docker-volumes-analyzer/branch/main/graph/badge.svg?token=JRjmc0emjT)](https://codecov.io/gh/glefer/docker-volumes-analyzer)
![Python](https://img.shields.io/badge/python-3.13-blue)

[![Docker](https://img.shields.io/docker/pulls/glefer/docker-volumes-analyzer)](https://hub.docker.com/r/glefer/docker-volumes-analyzer)

## Installation

### Prérequis
### Prerequisites

- **Python** `>=3.13,<4.0.0`
- **Poetry** `>=2.1.2` installé globalement ([lien d’installation](https://python-poetry.org/docs/#installation))
- Docker en local (si tu veux analyser des volumes)
- **Poetry** `>=2.1.2` installed globally ([installation link](https://python-poetry.org/docs/#installation))
- Docker installed locally (if you want to analyze volumes)

---

### 1. Cloner le projet
### 1. Clone the project

```bash
git clone https://github.com/glefer/docker-volumes-analyzer.git
cd docker-volumes-analyzer
```

### 2. Installer les dépendances
### 2. Install dependencies

```bash
poetry install
```

### 3. Lancer l'application
### 3. Run the application

```bash
poetry run start
```

> ⚠️ L'application utilise le socket Docker à l'emplacement standard : `/var/run/docker.sock`
> ⚠️ The application uses the Docker socket at the standard location: `/var/run/docker.sock`

---

## 🐳 Utilisation via Docker
## 🐳 Usage via Docker

Pas envie d’installer Python ? Utilise simplement l’image Docker :
Don't want to install Python? Simply use the Docker image:

```bash
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -ti glefer/docker-volumes-analyzer:latest
```

### Utiliser une version spécifique
### Use a specific version

```bash
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -ti glefer/docker-volumes-analyzer:0.1.0
```

---

## Lancer les tests
## Run tests

```bash
poetry run pytest
```

Avec couverture :
With coverage:

```bash
poetry run pytest --cov=docker_volume_analyzer
```

---

## 🛠 Développement
## 🛠 Development

Lance un shell virtuel :
Start a virtual shell:

```bash
poetry shell
```

Formatage et vérifications :
Formatting and checks:

```bash
poetry run pre-commit run --all-files
```

---

## 🔧 Structure du projet
## 🔧 Project structure

```
.
├── src/
│ └── docker_volume_analyzer/
│ └── main.py # Point d’entrée
│ └── main.py # Entry point
├── tests/
├── README.md
├── pyproject.toml
Expand All @@ -122,11 +122,11 @@ For major changes, please open an issue first to discuss what you would like to

---

## 📝 Licence
## 📝 License

Ce projet est sous licence **MIT** — voir le fichier [LICENSE](./LICENSE).
This project is licensed under the **MIT** license — see the [LICENSE](./LICENSE) file.

---

## 👨‍💻 Auteur
## 👨‍💻 Author
[github.com/glefer](https://github.com/glefer)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "docker-volume-analyzer"
version = "0.1.0"
description = ""
description = "Docker volume analyzer"
authors = [
{name = "Grégory LEFER",email = "contact@glefer.fr"}
]
Expand Down
22 changes: 22 additions & 0 deletions src/docker_volume_analyzer/docker_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,25 @@ def get_volume_size(self, volume_name: str) -> str:
["sh", "-c", "du -sh /mnt/docker_volume"], volume_name
)
return output.split()[0] if output else "0"

def remove_volume(self, volume_name: str) -> None:
"""
Removes a Docker volume by name.

Args:
volume_name (str): Name of the Docker volume to remove.

Raises:
docker.errors.APIError: If the volume cannot be removed.
"""
try:
volume = self.client.volumes.get(volume_name)
volume.remove(force=True)
except docker.errors.NotFound as e:
raise docker.errors.APIError(
f"Volume '{volume_name}' not found."
) from e
except docker.errors.APIError as e:
raise docker.errors.APIError(
f"Failed to remove volume '{volume_name}': {e}"
) from e
101 changes: 100 additions & 1 deletion src/docker_volume_analyzer/tui.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from textual.app import App, ComposeResult
from textual.containers import Container, Horizontal, Vertical
from textual.screen import ModalScreen
from textual.widgets import DataTable, Footer, Header, Static
from textual.widgets import Button, DataTable, Footer, Header, Static

from docker_volume_analyzer.volume_manager import VolumeManager

Expand All @@ -18,6 +18,7 @@ class DockerTUI(App):
("t", "toggle_dark", "Toggle dark mode"),
("ctrl+q", "quit", "Quit"),
("i", "information", "Show information"),
("d", "delete_volume", "Delete volume"),
]
CSS_PATH = "tui.tcss"

Expand Down Expand Up @@ -90,6 +91,54 @@ def action_information(self):
volume_information = self.volumes.get(volume_name[0])
self.push_screen(VolumeDetailScreen(volume_information))

def action_delete_volume(self):
"""
An action to delete the selected volume.
"""
table = self.query_one(DataTable)
selected_row = table.cursor_row

if selected_row is None:
return
volume_row = table.get_row_at(selected_row)
if volume_row[2] != 0:
self.push_screen(
ErrorScreen("Cannot delete volume with attached containers.")
)
return

def delete_callback(confirmed: bool) -> None:
"""
Callback function to handle the confirmation of volume deletion.

Args:
confirmed (bool): True if the user confirmed deletion
False otherwise.
"""
if confirmed:
try:
self.manager.delete_volume(volume_row[0])
row_key, _ = table.coordinate_to_cell_key(
table.cursor_coordinate
)
table.remove_row(row_key)
self.pop_screen()
self.refresh()
except Exception as e:
self.pop_screen()
self.push_screen(
ErrorScreen(f"Error deleting volume: {e}")
)
else:
self.pop_screen()

self.push_screen(
ConfirmationScreen(
f"Are you sure you want to delete volume '{volume_row[0]}'?",
delete_callback,
)
)


class VolumeDetailScreen(ModalScreen):
"""
Expand Down Expand Up @@ -158,5 +207,55 @@ def action_back(self) -> None:
self.app.refresh()


class ErrorScreen(ModalScreen):
"""
A simple modal screen to display a message.
"""

def __init__(self, message: str):
super().__init__()
self.message = message

def compose(self) -> ComposeResult:
yield Header()
with Container(id="dialog"):
yield Static(self.message, classes="message")
yield Footer()

BINDINGS = [
("b", "back", "Back"),
]

def action_back(self) -> None:
"""Go back to the previous screen."""
self.app.pop_screen()


class ConfirmationScreen(ModalScreen):
"""
A simple modal screen to confirm an action.
"""

def __init__(self, message: str, callback):
super().__init__()
self.message = message
self.callback = callback

def compose(self) -> ComposeResult:
yield Header()
with Container(id="dialog"):
yield Static(self.message, classes="question")
with Horizontal(classes="buttons"):
yield Button("No", variant="error", id="no_button")
yield Button("Yes", variant="success", id="yes_button")
yield Footer()

def on_button_pressed(self, event):
if event.button.id == "yes_button":
self.callback(True)
else:
self.callback(False)


if __name__ == "__main__": # pragma: no cover
DockerTUI().run()
34 changes: 34 additions & 0 deletions src/docker_volume_analyzer/tui.tcss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,40 @@ Screen {
width: 100%;
}

#dialog {
height: 70%;
background: $panel;
color: $text;
border: tall $background;
padding: 1 2;
}

/* The button class */
Button {
width: 1fr;
}

/* Matches the question text */
.question {
text-style: bold;
height: 100%;
content-align: center middle;
}

/* Matches the button container */
.buttons {
width: 100%;
height: auto;
dock: bottom;
padding: 0 20;
}

.message {
text-style: bold;
height: 100%;
content-align: center middle;
}

.info-panel Static {
padding: 0 1;
height: auto;
Expand Down
16 changes: 16 additions & 0 deletions src/docker_volume_analyzer/volume_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,19 @@ def get_volume_size(self, volume_name: str) -> int:
int: Size of the volume in bytes.
"""
return self.client.get_volume_size(volume_name)

def delete_volume(self, volume_name: str) -> bool:
Comment thread
glefer marked this conversation as resolved.
"""
Delete a Docker volume by its name.

Args:
volume_name (str): Name of the Docker volume to delete.

Returns:
bool: True if the volume was deleted successfully, False otherwise.
"""
try:
self.client.remove_volume(volume_name)
return True
except Exception:
return False
Loading