preparation for docker containers handling

This commit is contained in:
Arthur Wambst 2025-08-31 14:30:49 +02:00
parent e290b9314c
commit 27332ff809
No known key found for this signature in database
15 changed files with 223 additions and 80 deletions

View File

@ -1,6 +1,9 @@
package fr.la_banquise.backend.data.model;
import com.github.dockerjava.api.model.Container;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
@ -9,6 +12,7 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
/**
@ -16,6 +20,7 @@ import lombok.NoArgsConstructor;
*/
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "instance")
public class Instance {
@Id
@ -23,23 +28,26 @@ public class Instance {
@SequenceGenerator(name = "InstanceSeq", sequenceName = "instance_id_seq",
allocationSize = 1, initialValue = 1)
public Long id;
@Column(unique = true, nullable = false)
public String name;
public Long port;
public Container container;
// So people cant have more than one
// container per JI because name will be login-jiId
public int port;
//@JsonBackReference
@JsonIgnore
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
public User owner;
@ManyToOne
@JoinColumn(name = "practical_id", nullable = false)
public Sujet sujet;
//@ManyToOne @JoinColumn(name = "ji_id", nullable = false) public Ji ji;
public Instance(String name, Long port, User user, Sujet sujet) {
public Instance(String name, int port, User user ) {//, Ji ji) {
this.name = name;
this.port = port;
this.owner = user;
this.sujet = sujet;
//this.ji = ji;
}
}

View File

@ -9,6 +9,7 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import java.util.List;
@ -28,16 +29,19 @@ public class Ji {
public String name;
public String description;
@JsonIgnore
@ManyToMany
@JoinTable(
name = "ji_user", // Table de liaison
@JoinTable(name = "ji_respos", // Table de liaison
joinColumns = @JoinColumn(name = "ji_id"),
inverseJoinColumns = @JoinColumn(name = "user_id")
)
inverseJoinColumns = @JoinColumn(name = "user_id"))
public List<User> respos;
public String date;
@ManyToOne @JoinColumn(name = "site_id") @JsonIgnore public Site site;
@ManyToOne
@JoinColumn(name = "site_id") //@JsonIgnore
public Site site;
@OneToMany @JoinColumn(name = "instance_id") public List<Instance> instances;
public Ji(String name, String description, List<User> respos, String date,
Site site) {

View File

@ -1,6 +1,7 @@
package fr.la_banquise.backend.data.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
@ -31,6 +32,9 @@ public class Site {
public String description;
public String address;
/*@OneToMany(mappedBy = "ji", cascade = CascadeType.ALL)
public List<Instance> instances;*/
@OneToMany(mappedBy = "site") @JsonIgnore public List<Ji> jiInSite;
public Site(String name, String description, String address) {

View File

@ -19,6 +19,7 @@ import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@ -73,14 +74,13 @@ public class User {
.collect(Collectors.toSet());
}
public User(String name, String password, RolesAsso role,
List<Instance> instances) {
public User(String name, String password, RolesAsso role) {
this.name = name;
this.password = password;
if (role == RolesAsso.NONE)
this.role = new HashSet<>();
else
this.role = new HashSet<>(Arrays.asList(role));
this.instances = instances;
this.instances = new ArrayList<>();
}
}

View File

@ -28,7 +28,7 @@ public class Endpoints {
DashboardResponse dashboard = new DashboardResponse();
dashboard.tps =
sujetService.getAllSujetsRespo(identity.getPrincipal().getName());
dashboard.instances = instanceService.getAllInstances(username);
//dashboard.instances = instanceService.getAllInstances(username);
return Response.ok(dashboard).build();
}
}

View File

@ -1,38 +1,57 @@
package fr.la_banquise.backend.rest;
import fr.la_banquise.backend.rest.request.InstanceRequest;
import fr.la_banquise.backend.services.InstanceService;
import fr.la_banquise.backend.services.JiService;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
/**
* InstanceEndpoints
*/
@Path("/api/instances")
@Path("/api/ji/")
@Produces(MediaType.APPLICATION_JSON)
public class InstanceEndpoints {
@Inject
SecurityIdentity identity;
@Inject SecurityIdentity identity;
@Inject
InstanceService instanceService;
@Inject JiService jiService;
@Inject InstanceService instanceService;
@GET
@RolesAllowed("ROOT")
@Path("/instances")
public Response getAllInstances() {
String username = identity.getPrincipal().getName();
return Response.ok(instanceService.getAllInstances(username)).build();
return Response.ok(instanceService.getAllInstances()).build();
}
@POST
public Response createInstance(InstanceRequest request) {
instanceService.createInstance(request.name, request.ssh, request.pwd, request.port, request.username, request.tpId);
@Path("/{id}/instance")
public Response createInstance(@PathParam("id") Long jiId,
@QueryParam("username") String username) {
jiService.createInstance(jiId, username);
return Response.ok().build();
}
@POST
@Path("/{id}/instance/container")
public Response createContainer(@PathParam("id") Long jiId,
@QueryParam("username") String username) {
return Response.ok(jiService.createContainer(jiId, username)).build();
}
@GET
@Path("/{id}/instance/container")
public Response getStatusContainer(@PathParam("id") Long jiId,
@QueryParam("username") String username) {
return Response.ok(jiService.getStatusContainer(jiId, username)).build();
}
}

View File

@ -13,7 +13,7 @@ import java.util.Map;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
@Path("/ji")
@Path("/api/ji")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class JiResource {
@ -25,7 +25,7 @@ public class JiResource {
@GET
@Path("/listall")
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("root")
@RolesAllowed("ROOT")
public Response listall() {
try {
List<Ji> ji = jiService.getAllJiAdmin();
@ -39,9 +39,10 @@ public class JiResource {
@POST
@Path("/create")
@RolesAllowed("root")
@RolesAllowed("ROOT")
public Response createJi(@QueryParam("name") String name,
@QueryParam("desc") String desc,
@QueryParam("desc") String desc, // TODO : change
// desc to date
@QueryParam("address") String address,
@QueryParam("respo") String respo,
@QueryParam("site") String name_site) {

View File

@ -26,7 +26,7 @@ public class SiteEndpoints {
@GET
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("root")
@RolesAllowed("ROOT")
@Operation(summary = "Lists all existing sites",
description = "Lists all sites. Root role required.")
@APIResponses({
@ -47,7 +47,7 @@ public class SiteEndpoints {
}
@POST
@RolesAllowed("root")
@RolesAllowed("ROOT")
@Operation(summary = "Creates a site",
description = "Creates a site if no name is not a duplicate.")
@APIResponses({

View File

@ -43,7 +43,7 @@ public class SujetEndpoints {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("root")
@RolesAllowed("ROOT")
public Response createSujet(SujetRequest sujet) {
return Response.ok(sujetService.createSujet(sujet)).build();
}
@ -51,7 +51,7 @@ public class SujetEndpoints {
@POST
@Path("/respo")
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("root")
@RolesAllowed("ROOT")
public Response addRespoSujet(@QueryParam("name_sujet") String name_sujet,
@QueryParam("name_user") String name_user) {
try {

View File

@ -1,16 +1,9 @@
package fr.la_banquise.backend.rest.request;
import io.smallrye.common.constraint.Nullable;
/**
* InstanceRequest
*/
public class InstanceRequest {
public String name;
public String ssh;
public String pwd;
public String username;
public Long port;
@Nullable
public Long tpId;
public Long jiId;
}

View File

@ -1,15 +1,27 @@
package fr.la_banquise.backend.services;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.Ports;
import fr.la_banquise.backend.data.model.Instance;
import fr.la_banquise.backend.data.model.Sujet;
import fr.la_banquise.backend.data.model.Ji;
import fr.la_banquise.backend.data.model.User;
import fr.la_banquise.backend.data.repository.InstanceRepository;
import fr.la_banquise.backend.data.repository.SujetRepository;
import fr.la_banquise.backend.data.repository.JiRepository;
import fr.la_banquise.backend.data.repository.UserRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* InstanceService
@ -21,7 +33,9 @@ public class InstanceService {
@Inject UserRepository userRepository;
@Inject SujetRepository sujetRepository;
@Inject JiRepository jiRepository;
@Inject DockerClient dockerClient;
public List<Instance> getAllInstances() {
return instanceRepository.findAll().list();
@ -36,44 +50,100 @@ public class InstanceService {
return instanceRepository.findById(id);
}
public InspectContainerResponse.ContainerState getStatusContainer(Long id) {
Instance instance = instanceRepository.findById(id);
InspectContainerResponse container =
dockerClient.inspectContainerCmd(instance.name).exec();
return container.getState();
}
@Transactional
public Instance createInstance(String name, Long port, String username,
Long sujetId) {
public Instance createInstance(String username, Ji ji) {
User user = userRepository.findByName(username);
Sujet sujet = sujetRepository.findById(sujetId);
Instance instance = new Instance(name, port, user, sujet);
String name = username + "-" + ji.id;
int port = getFreePort(1).iterator().next();
Instance instance = new Instance(name, port, user);
instanceRepository.persist(instance);
return instance;
}
@Transactional
public Instance createInstance(String name, String ssh, String pwd,
Long port, User user, Long sujetId) {
Sujet sujet = sujetRepository.findById(sujetId);
Instance instance = new Instance(name, port, user, sujet);
instanceRepository.persist(instance);
return instance;
public String createContainer(Long instanceId) {
Instance instance = instanceRepository.findById(instanceId);
ExposedPort tcpSsh = ExposedPort.tcp(22);
Ports portBindings = new Ports();
portBindings.bind(tcpSsh, Ports.Binding.bindPort(instance.port));
HostConfig hostConfig =
HostConfig.newHostConfig().withPortBindings(portBindings);
Map<String, String> labels = new HashMap<>();
labels.put("test", "banquise");
labels.put("environment", "development");
labels.put("version", "1.0");
labels.put("created-by", "java-docker-api");
CreateContainerResponse container =
dockerClient.createContainerCmd("nginx:latest")
.withName(instance.name)
.withLabels(labels)
.withExposedPorts(tcpSsh)
.withHostConfig(hostConfig)
.exec();
return container.getId();
}
public boolean deleteContainer(String containerName) {
try {
dockerClient.removeContainerCmd(containerName).exec();
return true;
} catch (Exception e) {
return false;
}
}
@Transactional
public boolean deleteInstance(Long id) {
return instanceRepository.deleteById(id);
}
@Transactional
public boolean deleteAllInstances() {
instanceRepository.deleteAll();
return true;
}
@Transactional
public void deleteJDMIInstances() {
instanceRepository.deleteAll();
}
@Transactional
public Instance updateInstance(Long id) {
Instance instance = instanceRepository.findById(id);
return instance;
if (!containerExists(instance.name))
return instanceRepository.deleteById(id);
return false;
}
public boolean containerExists(String containerName) {
try {
dockerClient.inspectContainerCmd(containerName).exec();
return true;
} catch (Exception e) {
return false;
}
}
public Set<Integer> getUsedPorts() {
Set<Integer> retour = new HashSet<>();
List<Instance> allInstances = getAllInstances();
for (Instance instance : allInstances) {
retour.add(instance.port);
}
return retour;
}
public Set<Integer> getFreePort(int nbPortsNeeded) {
Set<Integer> used = getUsedPorts();
Set<Integer> retour = new HashSet<>();
int port = 40000;
// max 1000 ports used at a same time
while (retour.size() < nbPortsNeeded && port < 41000) {
if (!used.contains(port)) {
retour.add(port);
}
port++;
}
return retour;
}
}

View File

@ -1,5 +1,6 @@
package fr.la_banquise.backend.services;
import fr.la_banquise.backend.data.model.Instance;
import fr.la_banquise.backend.data.model.Ji;
import fr.la_banquise.backend.data.model.Site;
import fr.la_banquise.backend.data.model.User;
@ -9,7 +10,6 @@ import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.core.SecurityContext;
import java.util.List;
@ApplicationScoped
@ -18,6 +18,7 @@ public class JiService {
@Inject JiRepository jiRepository;
@Inject UserRepository userRepository;
@Inject SiteService siteService;
@Inject InstanceService instanceService;
@Inject SecurityContext security;
@Transactional
@ -58,4 +59,44 @@ public class JiService {
siteService.removeJi(ji.site, ji);
jiRepository.delete(ji);
}
@Transactional
public Instance createInstance(Long id, String username) {
Ji ji = jiRepository.findById(id);
Instance instance = instanceService.createInstance(username, ji);
ji.instances.add(instance);
return instance;
}
@Transactional
public String createContainer(Long id, String username) {
Ji ji = jiRepository.findById(id);
String retour = "";
for (Instance instance : ji.instances) {
if (instance.name.equals(username + "-" + id)) {
retour = instanceService.createContainer(instance.id);
break;
}
}
if (retour == "")
throw new Error("instance or container not found");
return retour;
}
public String getStatusContainer(Long id, String username) {
Ji ji = jiRepository.findById(id);
String retour = "";
for (Instance instance : ji.instances) {
if (instance.name.equals(username + "-" + id)) {
retour =
instanceService.getStatusContainer(instance.id).toString();
break;
}
}
if (retour == "")
throw new Error("instance or container not found");
return retour;
}
}

View File

@ -35,7 +35,7 @@ public class UserService {
public User createUser(UserRequest request) {
User user =
new User(request.name, BcryptUtil.bcryptHash(request.password),
RolesAsso.NONE, new ArrayList<>());
RolesAsso.NONE);
userRepository.persist(user);
return user;
}

View File

@ -37,7 +37,7 @@ quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.quinoa.dev-server.port=5173
quarkus.quinoa.enable-spa-routing=true
quarkus.docker.docker-host=unix:///run/user/1000/docker.sock
quarkus.docker.docker-host=unix:///run/user/1001/docker.sock
#quarkus.security.auth.enabled-in-dev-mode=false
quarkus.hibernate-orm.sql-load-script=import-dev.sql

View File

@ -1,3 +1,6 @@
-- Ce fichier est exécuté automatiquement en mode dev
INSERT INTO penguin (name, password) VALUES ('root', '$2a$10$lzKAv4aj6s0jtneg0Ikx/eEBb6p.6N6yo7ZF.myqYxEA9MWbMwvNu');
INSERT INTO user_roles (User_id, role) VALUES (1, 'ROOT');
INSERT INTO site (name, description, address) VALUES ('test', 'test', 'test');
INSERT INTO ji (name, description, date, site_id) VALUES ('ji', 'test', 'date', 1);
INSERT INTO ji_respos (ji_id, User_id) VALUES (1, 1);