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; 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.Entity;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
@ -9,6 +12,7 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator; import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
@ -16,6 +20,7 @@ import lombok.NoArgsConstructor;
*/ */
@Entity @Entity
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor
@Table(name = "instance") @Table(name = "instance")
public class Instance { public class Instance {
@Id @Id
@ -23,23 +28,26 @@ public class Instance {
@SequenceGenerator(name = "InstanceSeq", sequenceName = "instance_id_seq", @SequenceGenerator(name = "InstanceSeq", sequenceName = "instance_id_seq",
allocationSize = 1, initialValue = 1) allocationSize = 1, initialValue = 1)
public Long id; public Long id;
public String name;
public Long port; @Column(unique = true, nullable = false)
public Container container; public String name;
// So people cant have more than one
// container per JI because name will be login-jiId
public int port;
//@JsonBackReference //@JsonBackReference
@JsonIgnore
@ManyToOne @ManyToOne
@JoinColumn(name = "user_id", nullable = false) @JoinColumn(name = "user_id", nullable = false)
public User owner; public User owner;
@ManyToOne //@ManyToOne @JoinColumn(name = "ji_id", nullable = false) public Ji ji;
@JoinColumn(name = "practical_id", nullable = false)
public Sujet sujet;
public Instance(String name, Long port, User user, Sujet sujet) { public Instance(String name, int port, User user ) {//, Ji ji) {
this.name = name; this.name = name;
this.port = port; this.port = port;
this.owner = user; 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.JoinTable;
import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.SequenceGenerator; import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.util.List; import java.util.List;
@ -28,16 +29,19 @@ public class Ji {
public String name; public String name;
public String description; public String description;
@JsonIgnore
@ManyToMany @ManyToMany
@JoinTable( @JoinTable(name = "ji_respos", // Table de liaison
name = "ji_user", // Table de liaison joinColumns = @JoinColumn(name = "ji_id"),
joinColumns = @JoinColumn(name = "ji_id"), inverseJoinColumns = @JoinColumn(name = "user_id"))
inverseJoinColumns = @JoinColumn(name = "user_id")
)
public List<User> respos; public List<User> respos;
public String date; 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, public Ji(String name, String description, List<User> respos, String date,
Site site) { Site site) {

View File

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

View File

@ -19,6 +19,7 @@ import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.persistence.SequenceGenerator; import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -73,14 +74,13 @@ public class User {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
public User(String name, String password, RolesAsso role, public User(String name, String password, RolesAsso role) {
List<Instance> instances) {
this.name = name; this.name = name;
this.password = password; this.password = password;
if (role == RolesAsso.NONE) if (role == RolesAsso.NONE)
this.role = new HashSet<>(); this.role = new HashSet<>();
else else
this.role = new HashSet<>(Arrays.asList(role)); 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(); DashboardResponse dashboard = new DashboardResponse();
dashboard.tps = dashboard.tps =
sujetService.getAllSujetsRespo(identity.getPrincipal().getName()); sujetService.getAllSujetsRespo(identity.getPrincipal().getName());
dashboard.instances = instanceService.getAllInstances(username); //dashboard.instances = instanceService.getAllInstances(username);
return Response.ok(dashboard).build(); return Response.ok(dashboard).build();
} }
} }

View File

@ -1,38 +1,57 @@
package fr.la_banquise.backend.rest; 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.InstanceService;
import fr.la_banquise.backend.services.JiService;
import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.SecurityIdentity;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST; import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces; import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
/** /**
* InstanceEndpoints * InstanceEndpoints
*/ */
@Path("/api/instances") @Path("/api/ji/")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public class InstanceEndpoints { public class InstanceEndpoints {
@Inject @Inject SecurityIdentity identity;
SecurityIdentity identity;
@Inject @Inject JiService jiService;
InstanceService instanceService; @Inject InstanceService instanceService;
@GET @GET
@RolesAllowed("ROOT")
@Path("/instances")
public Response getAllInstances() { public Response getAllInstances() {
String username = identity.getPrincipal().getName(); return Response.ok(instanceService.getAllInstances()).build();
return Response.ok(instanceService.getAllInstances(username)).build();
} }
@POST @POST
public Response createInstance(InstanceRequest request) { @Path("/{id}/instance")
instanceService.createInstance(request.name, request.ssh, request.pwd, request.port, request.username, request.tpId); public Response createInstance(@PathParam("id") Long jiId,
@QueryParam("username") String username) {
jiService.createInstance(jiId, username);
return Response.ok().build(); 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.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
@Path("/ji") @Path("/api/ji")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public class JiResource { public class JiResource {
@ -25,7 +25,7 @@ public class JiResource {
@GET @GET
@Path("/listall") @Path("/listall")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("root") @RolesAllowed("ROOT")
public Response listall() { public Response listall() {
try { try {
List<Ji> ji = jiService.getAllJiAdmin(); List<Ji> ji = jiService.getAllJiAdmin();
@ -39,9 +39,10 @@ public class JiResource {
@POST @POST
@Path("/create") @Path("/create")
@RolesAllowed("root") @RolesAllowed("ROOT")
public Response createJi(@QueryParam("name") String name, 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("address") String address,
@QueryParam("respo") String respo, @QueryParam("respo") String respo,
@QueryParam("site") String name_site) { @QueryParam("site") String name_site) {

View File

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

View File

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

View File

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

View File

@ -1,15 +1,27 @@
package fr.la_banquise.backend.services; 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.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.model.User;
import fr.la_banquise.backend.data.repository.InstanceRepository; 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 fr.la_banquise.backend.data.repository.UserRepository;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
* InstanceService * InstanceService
@ -21,7 +33,9 @@ public class InstanceService {
@Inject UserRepository userRepository; @Inject UserRepository userRepository;
@Inject SujetRepository sujetRepository; @Inject JiRepository jiRepository;
@Inject DockerClient dockerClient;
public List<Instance> getAllInstances() { public List<Instance> getAllInstances() {
return instanceRepository.findAll().list(); return instanceRepository.findAll().list();
@ -36,44 +50,100 @@ public class InstanceService {
return instanceRepository.findById(id); 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 @Transactional
public Instance createInstance(String name, Long port, String username, public Instance createInstance(String username, Ji ji) {
Long sujetId) {
User user = userRepository.findByName(username); User user = userRepository.findByName(username);
Sujet sujet = sujetRepository.findById(sujetId); String name = username + "-" + ji.id;
Instance instance = new Instance(name, port, user, sujet); int port = getFreePort(1).iterator().next();
Instance instance = new Instance(name, port, user);
instanceRepository.persist(instance); instanceRepository.persist(instance);
return instance; return instance;
} }
@Transactional public String createContainer(Long instanceId) {
public Instance createInstance(String name, String ssh, String pwd, Instance instance = instanceRepository.findById(instanceId);
Long port, User user, Long sujetId) {
Sujet sujet = sujetRepository.findById(sujetId); ExposedPort tcpSsh = ExposedPort.tcp(22);
Instance instance = new Instance(name, port, user, sujet);
instanceRepository.persist(instance); Ports portBindings = new Ports();
return instance; 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 @Transactional
public boolean deleteInstance(Long id) { 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); 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; 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.Ji;
import fr.la_banquise.backend.data.model.Site; import fr.la_banquise.backend.data.model.Site;
import fr.la_banquise.backend.data.model.User; import fr.la_banquise.backend.data.model.User;
@ -9,7 +10,6 @@ import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.SecurityContext;
import java.util.List; import java.util.List;
@ApplicationScoped @ApplicationScoped
@ -18,6 +18,7 @@ public class JiService {
@Inject JiRepository jiRepository; @Inject JiRepository jiRepository;
@Inject UserRepository userRepository; @Inject UserRepository userRepository;
@Inject SiteService siteService; @Inject SiteService siteService;
@Inject InstanceService instanceService;
@Inject SecurityContext security; @Inject SecurityContext security;
@Transactional @Transactional
@ -58,4 +59,44 @@ public class JiService {
siteService.removeJi(ji.site, ji); siteService.removeJi(ji.site, ji);
jiRepository.delete(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) { public User createUser(UserRequest request) {
User user = User user =
new User(request.name, BcryptUtil.bcryptHash(request.password), new User(request.name, BcryptUtil.bcryptHash(request.password),
RolesAsso.NONE, new ArrayList<>()); RolesAsso.NONE);
userRepository.persist(user); userRepository.persist(user);
return 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.dev-server.port=5173
quarkus.quinoa.enable-spa-routing=true 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.security.auth.enabled-in-dev-mode=false
quarkus.hibernate-orm.sql-load-script=import-dev.sql quarkus.hibernate-orm.sql-load-script=import-dev.sql

View File

@ -1,3 +1,6 @@
-- Ce fichier est exécuté automatiquement en mode dev -- Ce fichier est exécuté automatiquement en mode dev
INSERT INTO penguin (name, password) VALUES ('root', '$2a$10$lzKAv4aj6s0jtneg0Ikx/eEBb6p.6N6yo7ZF.myqYxEA9MWbMwvNu'); 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 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);