From 6abfd0d3c712355edfce22c4d931f4bce04c9409 Mon Sep 17 00:00:00 2001 From: "auriane.pusel" Date: Mon, 14 Apr 2025 21:35:29 +0200 Subject: [PATCH 1/5] add documentation --- README.md | 19 ++++++ docs/GUIDE_INSTALLATION.md | 29 +++++++++ docs/GUIDE_UTILISATEUR.md | 119 +++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 README.md create mode 100644 docs/GUIDE_INSTALLATION.md create mode 100644 docs/GUIDE_UTILISATEUR.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..5121e8b --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# JaFeur + +Ce projet a été développé dans le cadre d’un projet de cours à Polytech Montpellier (filière DO). + +## Documentation + +Vous trouverez toute la documentation dans le dossier [`/docs`](./docs) : + +- `GUIDE_INSTALLATION.md` : guide d’installation du projet (via Docker) +- `GUIDE_UTILISATEUR.md` : guide utilisateur pour comprendre et exploiter toutes les fonctionnalités exposées par l’API + +--- + +## Auteurs + +Projet réalisé par : +- Auriane PUSEL +- Noa DESPAUX +- Nathan DILHAN \ No newline at end of file diff --git a/docs/GUIDE_INSTALLATION.md b/docs/GUIDE_INSTALLATION.md new file mode 100644 index 0000000..727dbe3 --- /dev/null +++ b/docs/GUIDE_INSTALLATION.md @@ -0,0 +1,29 @@ +# JaFeur + +JaFeur est une application backend en Java (Spring Boot) permettant de gérer dynamiquement des conteneurs Docker via une API REST. + +## Installation via Docker + +### Lancer l'application + +Mettez vous à la racine du projet JaFeur et lancer cette commande : +```bash +docker-compose up --build +``` + +Cela démarre automatiquement le backend JaFeur et expose l'API sur le port **8080**. + +### Accès API + +Une fois le conteneur lancé, vous pouvez accéder à l’interface Swagger pour tester l’API : + +[http://localhost:8080/swagger-ui/index.html](http://localhost:8080/swagger-ui/index.html) + +--- + +## Groupe JaFeur + +Ce projet a été réalisé par trois élèves de la filière DO de l'école Polytech Montpellier : +- Auriane PUSEL +- Noa DESPAUX +- Nathan DILHAN diff --git a/docs/GUIDE_UTILISATEUR.md b/docs/GUIDE_UTILISATEUR.md new file mode 100644 index 0000000..5aecc6d --- /dev/null +++ b/docs/GUIDE_UTILISATEUR.md @@ -0,0 +1,119 @@ +# Guide Utilisateur – JaFeur + +Bienvenue dans JaFeur, une application backend en Java Spring Boot permettant de gérer dynamiquement des conteneurs Docker via une API REST. + +--- + +## Objectif de l'application + +JaFeur est une interface de contrôle pour lancer, configurer, arrêter et surveiller des conteneurs Docker depuis une API REST. + +--- + +## Prise en main + +## Structure du projet + +- `controllers/` : endpoints REST pour démarrer, arrêter et relancer les conteneurs Docker. +- `services/` : logique métier pour interagir avec Docker en local. +- `model/` : objets de transfert de données. +- `config/` : configuration Spring, notamment l’accès à Docker. +- `src/test/` : tests unitaires des fonctions principales. + +--- +### Accès à l’API + +Une fois l’application lancée (voir documentation d'installation), l’interface Swagger est accessible ici : + +👉 [http://localhost:8080/swagger-ui/index.html](http://localhost:8080/swagger-ui/index.html) + +Swagger permet de visualiser et tester tous les endpoints de l’application de manière interactive. + +--- + + +### Lancer et gérer des applications +- Démarrer une application (conteneur déjà existant) +- Redémarrer ou arrêter une application à la demande +- Supprimer un conteneur devenu inutile + +--- + +### Configurer dynamiquement une application + +Le point d’entrée `/Config/{id}` permet d’ajouter, de modifier ou de supprimer des variables d’environnement sur un conteneur existant. + +Le corps de la requête doit être au format suivant : + +```json +{ + "add": { + "NEW_VAR": "new" + }, + "update": { + "EXISTING_VAR": "updated" + }, + "delete": { + "OLD_VAR": "" + } +} +``` + +- `add` ajoute une ou plusieurs nouvelles variables. +- `update` modifie la valeur de variables existantes. +- `delete` supprime les variables spécifiées (en mettant une valeur vide). +--- + +### Surveiller les erreurs et crashs +- Détection automatique si un conteneur est dans un état anormal (crash) +- Lister tous les conteneurs qui ont planté +- Permet de bâtir des outils de monitoring simples ou de diagnostic + +--- + +### Gérer les images Docker +- **Pull** d’une image publique depuis Docker Hub (ex: `nginx:latest`) +- **Build** d’une image à partir d’un Dockerfile local (ex: projet en développement) +- **Start** d’une image en lui passant directement les paramètres (pas besoin de docker run) +- **Suppression** d’images obsolètes + +--- + +### Accéder à l’état actuel du système Docker +- Voir tous les conteneurs lancés ou arrêtés +- Voir toutes les images présentes localement + +--- + +### Mettre à jour une application +- Rebuild depuis un Dockerfile donné +- Redéploiement automatique d’un conteneur avec la nouvelle version +- Très utile dans un workflow CI/CD manuel ou en déploiement progressif + +--- + +## Astuces + +- Si une image n’existe pas en local, Docker la téléchargera automatiquement (si disponible sur Docker Hub). +- Vérifier que les noms de conteneurs sont uniques pour éviter les conflits. + +--- + +## Test et validation + +L'application intègre des tests unitaires exécutables avec : + +```bash +mvn test +``` + +Ces tests couvrent les principales fonctionnalités, notamment la gestion des conteneurs et les erreurs courantes (image non trouvée, redémarrage invalide, etc). + +--- + +## Groupe JaFeur + +Ce projet a été réalisé par trois élèves de la filière DO de l'école Polytech Montpellier : +- Auriane PUSEL +- Noa DESPAUX +- Nathan DILHAN \ No newline at end of file From 3e86b7a17501d6d1dabdf18ee4a86e382bb982cb Mon Sep 17 00:00:00 2001 From: "noa.despaux" Date: Mon, 14 Apr 2025 23:08:49 +0200 Subject: [PATCH 2/5] removed version from docker-compose --- docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index eaac9f5..f529da4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.8" - networks: traefik-network: name: traefik-network From fb5615adbbed4c0ef3efe4baeffadc674483310b Mon Sep 17 00:00:00 2001 From: "noa.despaux" Date: Mon, 14 Apr 2025 23:09:32 +0200 Subject: [PATCH 3/5] changed tag update and config to application --- src/main/java/controllers/DockerController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/controllers/DockerController.java b/src/main/java/controllers/DockerController.java index 0932be0..84536cf 100644 --- a/src/main/java/controllers/DockerController.java +++ b/src/main/java/controllers/DockerController.java @@ -57,7 +57,7 @@ public void restartApp(@PathVariable("name") String name) { // **Configure application settings** @PostMapping("/Config/{id}") @Operation(summary = "Configure an application", description = "Configures a specific application with the provided parameters.") - @Tag(name = "Configuration") + @Tag(name = "Application") public ResponseEntity configApp(@PathVariable("id") String id, @RequestBody Map config) { return dockerService.configApp(id, config); } @@ -169,7 +169,7 @@ public ResponseEntity removeImage(@RequestParam String imageId) { @PostMapping("/update") @Operation(summary = "Update application", description = "Update a specific application by its name.") - @Tag(name = "Update") + @Tag(name = "Application") public ResponseEntity updateApp(@RequestParam String name, @RequestParam String dockerfilePath) { try { dockerService.updateApp(name, dockerfilePath); From e7459b529f231027d21fff85a2edb73b261d9e22 Mon Sep 17 00:00:00 2001 From: "noa.despaux" Date: Mon, 14 Apr 2025 23:09:55 +0200 Subject: [PATCH 4/5] updated docs --- docs/GUIDE_INSTALLATION.md | 19 +++++++++++++++++-- docs/GUIDE_UTILISATEUR.md | 29 ++++++++++++++++------------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/docs/GUIDE_INSTALLATION.md b/docs/GUIDE_INSTALLATION.md index 727dbe3..8c74143 100644 --- a/docs/GUIDE_INSTALLATION.md +++ b/docs/GUIDE_INSTALLATION.md @@ -4,14 +4,23 @@ JaFeur est une application backend en Java (Spring Boot) permettant de gérer dy ## Installation via Docker +### Clonez le dépôt JaFeur +```bash +git clone https://github.com/aurianecodebien/JaFeur.git +``` +ou +```bash +git clone git@github.com:aurianecodebien/JaFeur.git +``` + ### Lancer l'application Mettez vous à la racine du projet JaFeur et lancer cette commande : ```bash -docker-compose up --build +docker-compose up -d --build ``` -Cela démarre automatiquement le backend JaFeur et expose l'API sur le port **8080**. +Cela démarre automatiquement le backend JaFeur ainsi qu'un conteneur traefik pour gérer les applications. ### Accès API @@ -19,6 +28,12 @@ Une fois le conteneur lancé, vous pouvez accéder à l’interface Swagger pour [http://localhost:8080/swagger-ui/index.html](http://localhost:8080/swagger-ui/index.html) +Le port peut être amené à changer selon les applications existantes dans la machine, le vérifier grâce à la commande suivante : +```bash +docker ps +``` +Regardez ensuite la colonne PORTS pour le conteneur `jafeur`. + --- ## Groupe JaFeur diff --git a/docs/GUIDE_UTILISATEUR.md b/docs/GUIDE_UTILISATEUR.md index 5aecc6d..0542f2d 100644 --- a/docs/GUIDE_UTILISATEUR.md +++ b/docs/GUIDE_UTILISATEUR.md @@ -25,7 +25,7 @@ JaFeur est une interface de contrôle pour lancer, configurer, arrêter et surve Une fois l’application lancée (voir documentation d'installation), l’interface Swagger est accessible ici : -👉 [http://localhost:8080/swagger-ui/index.html](http://localhost:8080/swagger-ui/index.html) +👉 [http://localhost:8080/swagger](http://localhost:8080/swagger) *(changez le port si nécessaire)* Swagger permet de visualiser et tester tous les endpoints de l’application de manière interactive. @@ -33,15 +33,15 @@ Swagger permet de visualiser et tester tous les endpoints de l’application de ### Lancer et gérer des applications -- Démarrer une application (conteneur déjà existant) -- Redémarrer ou arrêter une application à la demande -- Supprimer un conteneur devenu inutile +- Démarrer une application (conteneur déjà existant) `PUT /Start/{name}` +- Redémarrer ou arrêter une application à la demande `PUT /Stop/{name}` +- Supprimer un conteneur devenu inutile `PUT /Remove/{name}` --- ### Configurer dynamiquement une application -Le point d’entrée `/Config/{id}` permet d’ajouter, de modifier ou de supprimer des variables d’environnement sur un conteneur existant. +Le point d’entrée `POST /Config/{id}` permet d’ajouter, de modifier ou de supprimer des variables d’environnement sur un conteneur existant. Le corps de la requête doit être au format suivant : @@ -65,27 +65,30 @@ Le corps de la requête doit être au format suivant : --- ### Surveiller les erreurs et crashs -- Détection automatique si un conteneur est dans un état anormal (crash) -- Lister tous les conteneurs qui ont planté +- Détection automatique si un conteneur est dans un état anormal (crash) +- Lister tous les conteneurs qui ont planté `PUT List/IsCrash` - Permet de bâtir des outils de monitoring simples ou de diagnostic --- ### Gérer les images Docker -- **Pull** d’une image publique depuis Docker Hub (ex: `nginx:latest`) -- **Build** d’une image à partir d’un Dockerfile local (ex: projet en développement) -- **Start** d’une image en lui passant directement les paramètres (pas besoin de docker run) -- **Suppression** d’images obsolètes +- **Pull** d’une image publique depuis Docker Hub (ex: `nginx:latest`) `POST /image/run/{applicationName}` +- **Build** d’une image à partir d’un Dockerfile local (ex: projet en développement) `POST /image/buildDockerfile` +- **Start** d’une image en lui passant directement les paramètres (pas besoin de docker run) `POST /image/start/` +- **Suppression** d’images obsolètes `DELETE /image` --- ### Accéder à l’état actuel du système Docker -- Voir tous les conteneurs lancés ou arrêtés -- Voir toutes les images présentes localement +- Voir tous les conteneurs lancés ou arrêtés `GET /containers{showAll}` + +Si le paramètre `showAll` est à `true`, il permet de voir tous les conteneurs, même ceux qui ne sont pas lancés. +- Voir toutes les images présentes localement `GET /images` --- ### Mettre à jour une application +`POST /update` - Rebuild depuis un Dockerfile donné - Redéploiement automatique d’un conteneur avec la nouvelle version - Très utile dans un workflow CI/CD manuel ou en déploiement progressif From ec19766d6c4e9f3230420719b95e89d1d9903c6f Mon Sep 17 00:00:00 2001 From: "auriane.pusel" Date: Tue, 15 Apr 2025 20:03:04 +0200 Subject: [PATCH 5/5] resolve conflicts --- .../java/controllers/DockerController.java | 14 ++- src/main/java/services/DockerService.java | 59 ++++++---- .../java/org/example/jafeur/ConfigTests.java | 104 ++++++++++++++++++ .../org/example/jafeur/ContainerTests.java | 17 --- 4 files changed, 152 insertions(+), 42 deletions(-) create mode 100644 src/test/java/org/example/jafeur/ConfigTests.java diff --git a/src/main/java/controllers/DockerController.java b/src/main/java/controllers/DockerController.java index 84536cf..c4ab112 100644 --- a/src/main/java/controllers/DockerController.java +++ b/src/main/java/controllers/DockerController.java @@ -54,12 +54,14 @@ public void restartApp(@PathVariable("name") String name) { dockerService.restartContainer(name); } - // **Configure application settings** - @PostMapping("/Config/{id}") - @Operation(summary = "Configure an application", description = "Configures a specific application with the provided parameters.") + @PostMapping("/Config/byName/{name}") + @Operation( + summary = "Configure an application", + description = "Allows adding, updating, or deleting environment variables of a container. The JSON body must contain `add`, `update`, and/or `delete` keys." + ) @Tag(name = "Application") - public ResponseEntity configApp(@PathVariable("id") String id, @RequestBody Map config) { - return dockerService.configApp(id, config); + public ResponseEntity configAppByName(@PathVariable("name") String name, @RequestBody Map config) { + return dockerService.configApp(name, config); } @PutMapping("IsCrash/{name}") @@ -169,7 +171,7 @@ public ResponseEntity removeImage(@RequestParam String imageId) { @PostMapping("/update") @Operation(summary = "Update application", description = "Update a specific application by its name.") - @Tag(name = "Application") + @Tag(name = "Update") public ResponseEntity updateApp(@RequestParam String name, @RequestParam String dockerfilePath) { try { dockerService.updateApp(name, dockerfilePath); diff --git a/src/main/java/services/DockerService.java b/src/main/java/services/DockerService.java index ca146c3..899b168 100644 --- a/src/main/java/services/DockerService.java +++ b/src/main/java/services/DockerService.java @@ -4,7 +4,6 @@ import com.github.dockerjava.api.command.*; import com.github.dockerjava.api.model.*; import model.ContainerRunParam; -import org.apache.catalina.Host; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -16,6 +15,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.*; @Service public class DockerService { @@ -150,34 +150,55 @@ public List listCrashedContainers() { .toList(); } - // Applique une nouvelle configuration d'environnement (redéploiement) - public ResponseEntity configApp(String id, Map conf) { + public ResponseEntity configApp(String name, Map conf) { + try { + Map envMap = new HashMap<>(); - var inspect = dockerClient.inspectContainerCmd(id).exec(); - ContainerRunParam params = new ContainerRunParam( - inspect.getName(), - inspect.getNetworkSettings().getPorts().toString(), - conf, - inspect.getImageId(), - null, - null - ); + InspectContainerResponse container = dockerClient.inspectContainerCmd(name).exec(); - if ("running".equals(dockerClient.inspectContainerCmd(id).exec().getState().getStatus())) { - dockerClient.stopContainerCmd(id).exec(); - } + String[] oldEnvList = container.getConfig().getEnv(); + if (oldEnvList != null) { + for (String env : oldEnvList) { + String[] parts = env.split("=", 2); + if (parts.length == 2) { + envMap.put(parts[0], parts[1]); + } + } + } + + Map toAdd = (Map) conf.getOrDefault("add", Map.of()); + Map toUpdate = (Map) conf.getOrDefault("update", Map.of()); + Map toDelete = (Map) conf.getOrDefault("delete", Map.of()); + + toAdd.forEach(envMap::put); + toUpdate.forEach(envMap::put); + toDelete.keySet().forEach(envMap::remove); + + ContainerRunParam params = new ContainerRunParam( + container.getName().replace("/", ""), // le nom avec '/' à retirer + container.getNetworkSettings().getPorts().toString(), + envMap, + container.getImageId(), + null, + null + ); + + if ("running".equals(container.getState().getStatus())) { + dockerClient.stopContainerCmd(name).exec(); + } + dockerClient.removeContainerCmd(name).exec(); + + System.out.println("Variables finales envoyées à Docker : " + params.getEnv()); - dockerClient.removeContainerCmd(id).exec(); - try { startImage(params); - return ResponseEntity.ok(id); + return ResponseEntity.ok(name); + } catch (Exception e) { e.printStackTrace(); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Error: " + e.getMessage()); } } - /// Image management public String pullImage(String imageName) throws InterruptedException { diff --git a/src/test/java/org/example/jafeur/ConfigTests.java b/src/test/java/org/example/jafeur/ConfigTests.java new file mode 100644 index 0000000..a72225d --- /dev/null +++ b/src/test/java/org/example/jafeur/ConfigTests.java @@ -0,0 +1,104 @@ +package org.example.jafeur; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.CreateContainerResponse; +import com.github.dockerjava.api.command.InspectContainerResponse; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import services.DockerService; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class ConfigTests { + + @Autowired + private DockerService dockerService; + + private final DockerClient dockerClient; + + @Autowired + public ConfigTests(DockerClient dockerClient) { + this.dockerClient = dockerClient; + } + + @BeforeAll + static void setUpBeforeAll() { + System.out.println("Starting ConfigTests..."); + } + + @AfterAll + static void tearDownAfterAll() { + System.out.println("Finished ConfigTests."); + } + + @Test + @Order(1) + void testConfigApp_AddUpdateDeleteEnvVars() throws InterruptedException { + String containerName = "config-test-container"; + + // 🔁 Supprimer s'il existe déjà + dockerService.getAllContainers().stream() + .filter(container -> Arrays.asList(container.getNames()).contains("/" + containerName)) + .findFirst() + .ifPresent(existing -> { + try { + dockerClient.removeContainerCmd(existing.getId()).withForce(true).exec(); + System.out.println("Conteneur existant supprimé"); + } catch (Exception e) { + fail("Could not remove existing container: " + e.getMessage()); + } + }); + + // 🚀 1. Créer le conteneur initial + CreateContainerResponse created = dockerClient.createContainerCmd("alpine") + .withName(containerName) + .withEnv("INITIAL_VAR=initial") + .withCmd("sh", "-c", "env && sleep 9999") + .exec(); + dockerClient.startContainerCmd(created.getId()).exec(); + System.out.println("Conteneur initial lancé"); + + Thread.sleep(500); + + // ➕ 2. Ajout des variables + dockerService.configApp(containerName, Map.of( + "add", Map.of("VAR1", "value1", "VAR2", "toremove") + )); + + Thread.sleep(1000); + + InspectContainerResponse afterAdd = dockerClient.inspectContainerCmd(containerName).exec(); + String[] rawEnvsAfterAdd = afterAdd.getConfig().getEnv(); + assertNotNull(rawEnvsAfterAdd, "Env list is null after add"); + + List envsAfterAdd = Arrays.asList(rawEnvsAfterAdd); + System.out.println("Env après ajout : " + envsAfterAdd); + + assertTrue(envsAfterAdd.contains("VAR1=value1"), "VAR1=value1 n'est pas présent"); + assertTrue(envsAfterAdd.contains("VAR2=toremove"), "VAR2=toremove n'est pas présent"); + + // 🔁 3. Update + Delete + dockerService.configApp(containerName, Map.of( + "update", Map.of("VAR1", "updated-value"), + "delete", Map.of("VAR2", "") + )); + + Thread.sleep(1000); + + InspectContainerResponse afterUpdate = dockerClient.inspectContainerCmd(containerName).exec(); + String[] rawEnvsAfterUpdate = afterUpdate.getConfig().getEnv(); + assertNotNull(rawEnvsAfterUpdate, "Env list is null after update"); + + List envsAfterUpdate = Arrays.asList(rawEnvsAfterUpdate); + System.out.println("Env après update : " + envsAfterUpdate); + + assertTrue(envsAfterUpdate.contains("VAR1=updated-value"), "VAR1 n'est pas mis à jour"); + assertFalse(envsAfterUpdate.stream().anyMatch(e -> e.startsWith("VAR2=")), "VAR2 aurait dû être supprimé"); + } + +} \ No newline at end of file diff --git a/src/test/java/org/example/jafeur/ContainerTests.java b/src/test/java/org/example/jafeur/ContainerTests.java index ef47e7b..82fbc4a 100644 --- a/src/test/java/org/example/jafeur/ContainerTests.java +++ b/src/test/java/org/example/jafeur/ContainerTests.java @@ -80,23 +80,6 @@ void testGetAllContainers() { assertNotNull(dockerService.getAllContainers()); } - @Test - @Order(5) - void testConfigApp() { - String containerName = "test-container"; - dockerService.configApp(containerName, Map.of("VAR_TEST", "new_value", "VAR_TEST2", "new_value2")); - - InspectContainerResponse containerInfo = dockerClient.inspectContainerCmd(containerName).exec(); - List envVariables = Arrays.asList(Objects.requireNonNull(containerInfo.getConfig().getEnv())); - - assertTrue(envVariables.contains("VAR_TEST=new_value")); - assertTrue(envVariables.contains("VAR_TEST2=new_value2")); - - // assert that the container is still running - assertTrue(dockerService.getRunningContainers().stream() - .anyMatch(container -> container.getNames()[0].equals("/" + containerName))); - } - @Test @Order(6) void testStopContainer() {