🚸 Improve Log Inspection
This commit is contained in:
parent
e165df6076
commit
a6b31b8ca5
16
pom.xml
16
pom.xml
@ -172,14 +172,6 @@
|
||||
<lineEnding>LF</lineEnding>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>validate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>net.revelc.code</groupId>
|
||||
@ -194,14 +186,6 @@
|
||||
<include>**/*.java</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
48
src/main/java/dev/dinauer/DeploymentResource.java
Normal file
48
src/main/java/dev/dinauer/DeploymentResource.java
Normal file
@ -0,0 +1,48 @@
|
||||
package dev.dinauer;
|
||||
|
||||
import dev.dinauer.service.DeploymentService;
|
||||
import dev.dinauer.service.NodeService;
|
||||
import dev.dinauer.service.PodService;
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import io.fabric8.kubernetes.api.model.apps.Deployment;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Path("/resources/deployments")
|
||||
@Authenticated
|
||||
public class DeploymentResource
|
||||
{
|
||||
@Inject
|
||||
DeploymentService deploymentService;
|
||||
|
||||
@Inject
|
||||
PodService podService;
|
||||
|
||||
@PATCH
|
||||
@Path("/{namespace}/{name}")
|
||||
@Consumes(MediaType.TEXT_PLAIN)
|
||||
@Produces
|
||||
public void rescaleDeployment(@PathParam("namespace") String namespace, @PathParam("name") String name, Integer replicaCount)
|
||||
{
|
||||
if (namespace != null && !namespace.isBlank() && name != null && !name.isBlank() && replicaCount != null && replicaCount > 0)
|
||||
{
|
||||
deploymentService.rescale(namespace, name, replicaCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Namespace or name cannot be null or empty. Replica count cannot be null and must be greater than 0.");
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{namespace}/{name}/pods")
|
||||
public List<Pod> getPodsByDeployment(String namespace, String name)
|
||||
{
|
||||
return podService.findByDeployment(namespace, name);
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
package dev.dinauer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import io.smallrye.common.annotation.Blocking;
|
||||
|
||||
import dev.dinauer.service.IngressService;
|
||||
|
||||
@Path("/ingresses")
|
||||
@Blocking
|
||||
@Authenticated
|
||||
public class IngressResource
|
||||
{
|
||||
@Inject
|
||||
IngressService ingressService;
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<Ingress> getIngresses(@QueryParam("namespace") String namespace)
|
||||
{
|
||||
if (namespace != null)
|
||||
{
|
||||
ingressService.findByNamespace(namespace);
|
||||
}
|
||||
return ingressService.findAll();
|
||||
}
|
||||
}
|
||||
54
src/main/java/dev/dinauer/NodeResource.java
Normal file
54
src/main/java/dev/dinauer/NodeResource.java
Normal file
@ -0,0 +1,54 @@
|
||||
package dev.dinauer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import dev.dinauer.inspect.websocket.ResourceType;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import dev.dinauer.service.*;
|
||||
|
||||
@Path("/resources/nodes")
|
||||
public class NodeResource
|
||||
{
|
||||
@Inject
|
||||
DeploymentService deploymentService;
|
||||
|
||||
@Inject
|
||||
NodeService nodeService;
|
||||
|
||||
@GET
|
||||
public List<?> get()
|
||||
{
|
||||
return nodeService.findAll();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{namespace}")
|
||||
public Object getAllNodes()
|
||||
{
|
||||
return nodeService.findAll();
|
||||
}
|
||||
|
||||
@PATCH
|
||||
@Path("/{namespace}/{name}")
|
||||
@Consumes(MediaType.TEXT_PLAIN)
|
||||
@Produces
|
||||
@RolesAllowed("admin")
|
||||
public void rescaleDeployment(@PathParam("namespace") String namespace, @PathParam("name") String name, Integer replicaCount)
|
||||
{
|
||||
if (ResourceType.DEPLOYMENT.equals("XY"))
|
||||
{
|
||||
if (namespace != null && !namespace.isBlank() && name != null && !name.isBlank() && replicaCount != null && replicaCount > 0)
|
||||
{
|
||||
deploymentService.rescale(namespace, name, replicaCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Must be a deployment or namespace or name cannot be null or empty. Replica count cannot be null and must be greater than 0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,33 +1,33 @@
|
||||
package dev.dinauer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import dev.dinauer.inspect.env.EnvVar;
|
||||
import dev.dinauer.inspect.env.EnvironmentVariableService;
|
||||
import dev.dinauer.utils.ProcessRunner;
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import io.quarkus.runtime.Startup;
|
||||
import io.smallrye.common.annotation.Blocking;
|
||||
|
||||
import dev.dinauer.service.PodService;
|
||||
import dev.dinauer.utils.ClientProvider;
|
||||
|
||||
@Path("/pods")
|
||||
@Startup
|
||||
@ApplicationScoped
|
||||
@Authenticated
|
||||
public class PodResource
|
||||
{
|
||||
@Inject
|
||||
PodService podService;
|
||||
|
||||
@Inject
|
||||
ProcessRunner processRunner;
|
||||
|
||||
@Inject
|
||||
ClientProvider clientProvider;
|
||||
EnvironmentVariableService environmentVariableService;
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@ -36,22 +36,7 @@ public class PodResource
|
||||
@Path("/{namespace}/{name}/{containerName}/env")
|
||||
public List<EnvVar> getEnv(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("containerName") String containerName)
|
||||
{
|
||||
return getVars(podService.findByNameAndNamespace(name, namespace), containerName);
|
||||
}
|
||||
|
||||
private List<EnvVar> getVars(Pod pod, String containerName)
|
||||
{
|
||||
List<EnvVar> result = new ArrayList<>();
|
||||
List<String> lines = processRunner.runToLines(String.format("kubectl exec -it %s -c %s -n %s -- env", pod.getMetadata().getName(), containerName, pod.getMetadata().getNamespace()));
|
||||
for (String line : lines)
|
||||
{
|
||||
int indexOfFirstEquals = line.indexOf("=");
|
||||
if (indexOfFirstEquals != -1)
|
||||
{
|
||||
result.add(new EnvVar(line.substring(0, indexOfFirstEquals), line.substring(indexOfFirstEquals + 1)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return environmentVariableService.getVars(podService.findByNameAndNamespace(name, namespace), containerName);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
|
||||
@ -1,137 +0,0 @@
|
||||
package dev.dinauer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import dev.dinauer.service.*;
|
||||
|
||||
@Path("/resources/{resource}")
|
||||
public class ResourceResource
|
||||
{
|
||||
@Inject
|
||||
Logger LOG;
|
||||
|
||||
@Inject
|
||||
StatefulSetService statefulSetService;
|
||||
|
||||
@Inject
|
||||
DeploymentService deploymentService;
|
||||
|
||||
@Inject
|
||||
PodService podService;
|
||||
|
||||
@Inject
|
||||
CustomResourceDefinitionService customResourceDefinitionService;
|
||||
|
||||
@Inject
|
||||
IngressService ingressService;
|
||||
|
||||
@Inject
|
||||
ServiceService serviceService;
|
||||
|
||||
@Inject
|
||||
NodeService nodeService;
|
||||
|
||||
@Inject
|
||||
SecretService secretService;
|
||||
|
||||
@Inject
|
||||
ConfigMapService configMapService;
|
||||
|
||||
@GET
|
||||
public List<?> get(@PathParam("resource") ResourceType resourceType)
|
||||
{
|
||||
return getService(resourceType).findAll();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{namespace}")
|
||||
public List<?> getByNamespace(@PathParam("resource") ResourceType resourceType, @PathParam("namespace") String namespace)
|
||||
{
|
||||
return getService(resourceType).findByNamespace(namespace);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{namespace}/{name}")
|
||||
public Object getByNamespaceAndName(@PathParam("resource") ResourceType resourceType, @PathParam("namespace") String namespace, @PathParam("name") String name)
|
||||
{
|
||||
Optional<?> resourceOptional = getService(resourceType).findOptionalByNameAndNamespace(name, namespace);
|
||||
if (resourceOptional.isPresent())
|
||||
{
|
||||
return resourceOptional.get();
|
||||
}
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{namespace}/{name}")
|
||||
public void deleteByNamespaceAndName(@PathParam("resource") ResourceType resourceType, @PathParam("namespace") String namespace, @PathParam("name") String name)
|
||||
{
|
||||
getService(resourceType).delete(name, namespace);
|
||||
}
|
||||
|
||||
@PATCH
|
||||
@Path("/{namespace}/{name}")
|
||||
@Consumes(MediaType.TEXT_PLAIN)
|
||||
@Produces
|
||||
@RolesAllowed("admin")
|
||||
public void rescaleDeployment(@PathParam("resource") ResourceType resourceType, @PathParam("namespace") String namespace, @PathParam("name") String name, Integer replicaCount)
|
||||
{
|
||||
if (ResourceType.DEPLOYMENT.equals(resourceType))
|
||||
{
|
||||
if (namespace != null && !namespace.isBlank() && name != null && !name.isBlank() && replicaCount != null && replicaCount > 0)
|
||||
{
|
||||
deploymentService.rescale(namespace, name, replicaCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Must be a deployment or namespace or name cannot be null or empty. Replica count cannot be null and must be greater than 0.");
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceService<?> getService(ResourceType resourceType)
|
||||
{
|
||||
switch (resourceType)
|
||||
{
|
||||
case ResourceType.STATEFUL_SET -> {
|
||||
return statefulSetService;
|
||||
}
|
||||
case ResourceType.DEPLOYMENT -> {
|
||||
return deploymentService;
|
||||
}
|
||||
case ResourceType.SERVICE -> {
|
||||
return serviceService;
|
||||
}
|
||||
case ResourceType.INGRESS -> {
|
||||
return ingressService;
|
||||
}
|
||||
case ResourceType.POD -> {
|
||||
return podService;
|
||||
}
|
||||
case ResourceType.CUSTOM_RESOURCE_DEFINITION -> {
|
||||
return customResourceDefinitionService;
|
||||
}
|
||||
case ResourceType.NODE -> {
|
||||
return nodeService;
|
||||
}
|
||||
case ResourceType.SECRET -> {
|
||||
return secretService;
|
||||
}
|
||||
case ResourceType.CONFIG_MAP -> {
|
||||
return configMapService;
|
||||
}
|
||||
default -> {
|
||||
LOG.errorf("Invalid resource type %s.", resourceType);
|
||||
throw new BadRequestException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
package dev.dinauer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.quarkus.runtime.Startup;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import io.smallrye.common.annotation.Blocking;
|
||||
|
||||
import dev.dinauer.service.ServiceService;
|
||||
|
||||
@Path("/services")
|
||||
@Startup
|
||||
@ApplicationScoped
|
||||
@Blocking
|
||||
@Authenticated
|
||||
public class ServiceResource
|
||||
{
|
||||
@Inject
|
||||
ServiceService serviceService;
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<Service> getServices(@QueryParam("namespace") String namespace)
|
||||
{
|
||||
if (namespace != null && !namespace.isBlank())
|
||||
{
|
||||
return serviceService.findByNamespace(namespace);
|
||||
}
|
||||
return serviceService.findAll();
|
||||
}
|
||||
}
|
||||
@ -21,11 +21,6 @@ public class WorkdirProvider
|
||||
this.workdir = path;
|
||||
}
|
||||
|
||||
public String getWorkdir(Path subpath)
|
||||
{
|
||||
return workdir.resolve(subpath).toString();
|
||||
}
|
||||
|
||||
public Path getWorkdirPath(Path subpath)
|
||||
{
|
||||
return workdir.resolve(subpath);
|
||||
|
||||
@ -20,11 +20,7 @@ public class TokenService
|
||||
{
|
||||
JsonWebToken token = getToken(queryString);
|
||||
Optional<String> purpose = token.claim("purpose");
|
||||
if (purpose.isPresent())
|
||||
{
|
||||
return purpose.get().equals("ws:connect");
|
||||
}
|
||||
return false;
|
||||
return purpose.map(s -> s.equals("ws:connect")).orElse(false);
|
||||
}
|
||||
|
||||
private JsonWebToken getToken(String query)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package dev.dinauer;
|
||||
package dev.dinauer.inspect.env;
|
||||
|
||||
public record EnvVar(String key, String value)
|
||||
{
|
||||
31
src/main/java/dev/dinauer/inspect/env/EnvironmentVariableService.java
vendored
Normal file
31
src/main/java/dev/dinauer/inspect/env/EnvironmentVariableService.java
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package dev.dinauer.inspect.env;
|
||||
|
||||
import dev.dinauer.utils.ProcessRunner;
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ApplicationScoped
|
||||
public class EnvironmentVariableService
|
||||
{
|
||||
@Inject
|
||||
ProcessRunner processRunner;
|
||||
|
||||
public List<EnvVar> getVars(Pod pod, String containerName)
|
||||
{
|
||||
List<EnvVar> result = new ArrayList<>();
|
||||
List<String> lines = processRunner.runToLines(String.format("kubectl exec -it %s -c %s -n %s -- env", pod.getMetadata().getName(), containerName, pod.getMetadata().getNamespace()));
|
||||
for (String line : lines)
|
||||
{
|
||||
int indexOfFirstEquals = line.indexOf("=");
|
||||
if (indexOfFirstEquals != -1)
|
||||
{
|
||||
result.add(new EnvVar(line.substring(0, indexOfFirstEquals), line.substring(indexOfFirstEquals + 1)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
package dev.dinauer.inspect.log;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import dev.dinauer.service.PodService;
|
||||
import dev.dinauer.utils.ClientProvider;
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import io.fabric8.kubernetes.client.dsl.LogWatch;
|
||||
import io.fabric8.kubernetes.client.dsl.PodResource;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.websocket.OnClose;
|
||||
import jakarta.websocket.OnOpen;
|
||||
import jakarta.websocket.Session;
|
||||
import jakarta.websocket.server.PathParam;
|
||||
import jakarta.websocket.server.ServerEndpoint;
|
||||
import org.eclipse.microprofile.context.ManagedExecutor;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@ServerEndpoint("/logs/deployments/{namespace}/{name}")
|
||||
@ApplicationScoped
|
||||
public class DeploymentLogWebsocket
|
||||
{
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new JavaTimeModule()).disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
private final Map<Session, LogWatch> sessions = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
Logger LOG;
|
||||
|
||||
@Inject
|
||||
ClientProvider clientProvider;
|
||||
|
||||
@Inject
|
||||
ManagedExecutor executor;
|
||||
|
||||
@Inject
|
||||
PodService podService;
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session, @PathParam("namespace") String namespace, @PathParam("name") String name)
|
||||
{
|
||||
executor.submit(() -> {
|
||||
List<Pod> pods = podService.findByDeployment(namespace, name);
|
||||
List<KubernetesLog> existingLogs = new ArrayList<>();
|
||||
for (Pod pod : pods)
|
||||
{
|
||||
PodResource resource = clientProvider.getClient().pods().inNamespace(pod.getMetadata().getNamespace()).withName(pod.getMetadata().getName());
|
||||
existingLogs.addAll(podService.getLogs(resource));
|
||||
}
|
||||
send(session, existingLogs.stream().sorted(KubernetesLog::orderByTimestamp).toList());
|
||||
|
||||
for (Pod pod : pods)
|
||||
{
|
||||
CompletableFuture.runAsync(() -> {
|
||||
LogWatch watch = clientProvider.getClient().pods().inNamespace(pod.getMetadata().getNamespace()).withName(pod.getMetadata().getName()).usingTimestamps().tailingLines(0).watchLog();
|
||||
sessions.put(session, watch);
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(watch.getOutput())))
|
||||
{
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null && !Thread.currentThread().isInterrupted())
|
||||
{
|
||||
send(session, toLog(List.of(line), pod.getMetadata().getName()));
|
||||
}
|
||||
LOG.info("Ended");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.errorf("Error reading output of log watch: %s", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session) throws IOException
|
||||
{
|
||||
LogWatch watch = sessions.remove(session);
|
||||
if (watch != null)
|
||||
{
|
||||
watch.close();
|
||||
}
|
||||
session.close();
|
||||
}
|
||||
|
||||
private void send(Session session, List<KubernetesLog> logs)
|
||||
{
|
||||
try
|
||||
{
|
||||
session.getAsyncRemote().sendText(OBJECT_MAPPER.writeValueAsString(logs));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.errorf("Error sending logs to frontend via websocket: %s", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private List<KubernetesLog> toLog(List<String> logs, String podName)
|
||||
{
|
||||
List<KubernetesLog> result = new ArrayList<>();
|
||||
for (String log : logs)
|
||||
{
|
||||
int indexFirstSpace = log.indexOf(" ");
|
||||
if (indexFirstSpace != -1)
|
||||
{
|
||||
String timestampRaw = log.substring(0, indexFirstSpace);
|
||||
try
|
||||
{
|
||||
String message = log.substring(indexFirstSpace + 1);
|
||||
result.add(new KubernetesLog(LocalDateTime.parse(timestampRaw, DateTimeFormatter.ISO_DATE_TIME), message, podName));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.errorf("Error parsing log: %s", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,11 @@
|
||||
package dev.dinauer.inspect.log;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
public record KubernetesLog(LocalDateTime timestamp, String message)
|
||||
public record KubernetesLog(LocalDateTime timestamp, String message, String podName)
|
||||
{
|
||||
public static int orderByTimestamp(KubernetesLog o1, KubernetesLog o2) {
|
||||
return (int) (o1.timestamp.toEpochSecond(ZoneOffset.UTC) - o2.timestamp.toEpochSecond(ZoneOffset.UTC));
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,11 @@ import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
import dev.dinauer.inspect.utils.InputStreamWatcher;
|
||||
import dev.dinauer.service.PodService;
|
||||
import io.fabric8.kubernetes.client.dsl.PodResource;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import io.smallrye.mutiny.infrastructure.Infrastructure;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.websocket.OnClose;
|
||||
@ -15,7 +20,6 @@ import jakarta.websocket.Session;
|
||||
import jakarta.websocket.server.PathParam;
|
||||
import jakarta.websocket.server.ServerEndpoint;
|
||||
|
||||
import org.eclipse.microprofile.context.ManagedExecutor;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@ -26,9 +30,9 @@ import io.fabric8.kubernetes.client.dsl.LogWatch;
|
||||
|
||||
import dev.dinauer.utils.ClientProvider;
|
||||
|
||||
@ServerEndpoint("/logs/{namespace}/{name}")
|
||||
@ServerEndpoint("/logs/pods/{namespace}/{name}")
|
||||
@ApplicationScoped
|
||||
public class LogWebsocket
|
||||
public class PodLogWebsocket
|
||||
{
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new JavaTimeModule()).disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
private final Map<Session, LogWatch> sessions = new HashMap<>();
|
||||
@ -40,32 +44,18 @@ public class LogWebsocket
|
||||
ClientProvider clientProvider;
|
||||
|
||||
@Inject
|
||||
ManagedExecutor executor;
|
||||
PodService podService;
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session, @PathParam("namespace") String namespace, @PathParam("name") String name)
|
||||
{
|
||||
executor.submit(() -> {
|
||||
List<String> existingLogs = Arrays.stream(clientProvider.getClient().pods().inNamespace(namespace).withName(name).usingTimestamps().tailingLines(200).getLog().split("\n")).toList();
|
||||
send(session, toLog(existingLogs));
|
||||
|
||||
LogWatch watch = clientProvider.getClient().pods().inNamespace(namespace).withName(name).usingTimestamps().tailingLines(0).watchLog();
|
||||
sessions.put(session, watch);
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(watch.getOutput())))
|
||||
{
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null && !Thread.currentThread().isInterrupted())
|
||||
{
|
||||
send(session, toLog(List.of(line)));
|
||||
}
|
||||
LOG.info("Ended");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.errorf("Error reading output of log watch: %s", e.getMessage());
|
||||
}
|
||||
});
|
||||
Uni.createFrom().voidItem().invoke(() -> {
|
||||
PodResource resource = clientProvider.getClient().pods().inNamespace(namespace).withName(name);
|
||||
send(session, podService.getLogs(resource));
|
||||
sessions.put(session, podService.watchLogs(resource, (logs) -> {
|
||||
send(session, logs);
|
||||
}));
|
||||
}).runSubscriptionOn(Infrastructure.getDefaultWorkerPool()).subscribe().with(result -> {}, error -> {});
|
||||
}
|
||||
|
||||
@OnClose
|
||||
@ -91,7 +81,12 @@ public class LogWebsocket
|
||||
}
|
||||
}
|
||||
|
||||
private List<KubernetesLog> toLog(List<String> logs)
|
||||
private List<KubernetesLog> toLog(String log, String podName)
|
||||
{
|
||||
return toLog(List.of(log), podName);
|
||||
}
|
||||
|
||||
private List<KubernetesLog> toLog(List<String> logs, String podName)
|
||||
{
|
||||
List<KubernetesLog> result = new ArrayList<>();
|
||||
for (String log : logs)
|
||||
@ -103,7 +98,7 @@ public class LogWebsocket
|
||||
try
|
||||
{
|
||||
String message = log.substring(indexFirstSpace + 1);
|
||||
result.add(new KubernetesLog(LocalDateTime.parse(timestampRaw, DateTimeFormatter.ISO_DATE_TIME), message));
|
||||
result.add(new KubernetesLog(LocalDateTime.parse(timestampRaw, DateTimeFormatter.ISO_DATE_TIME), message, podName));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -0,0 +1,29 @@
|
||||
package dev.dinauer.inspect.utils;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class InputStreamWatcher
|
||||
{
|
||||
private static final Logger LOG = Logger.getLogger(InputStreamWatcher.class);
|
||||
|
||||
public static void watch(InputStream stream, Consumer<String> consume)
|
||||
{
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream)))
|
||||
{
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null)
|
||||
{
|
||||
consume.accept(line);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.errorf("Error reading output of log watch: %s", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package dev.dinauer;
|
||||
package dev.dinauer.inspect.websocket;
|
||||
|
||||
public enum ResourceType
|
||||
{
|
||||
@ -22,7 +22,6 @@ import io.fabric8.kubernetes.client.Watch;
|
||||
import io.fabric8.kubernetes.client.Watcher;
|
||||
import io.fabric8.kubernetes.client.WatcherException;
|
||||
|
||||
import dev.dinauer.ResourceType;
|
||||
import dev.dinauer.inspect.TokenService;
|
||||
import dev.dinauer.service.ResourceService;
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ import jakarta.ws.rs.BadRequestException;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import dev.dinauer.ResourceType;
|
||||
import dev.dinauer.service.*;
|
||||
|
||||
@ApplicationScoped
|
||||
@ -103,7 +102,7 @@ public class ServiceFactory
|
||||
{
|
||||
return namespaceService;
|
||||
}
|
||||
default :
|
||||
default:
|
||||
{
|
||||
LOG.errorf("Invalid resource type %s.", resourceType);
|
||||
throw new BadRequestException();
|
||||
|
||||
@ -8,7 +8,7 @@ import jakarta.inject.Inject;
|
||||
import io.fabric8.kubernetes.api.model.Node;
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
|
||||
import dev.dinauer.ProcessRunner;
|
||||
import dev.dinauer.utils.ProcessRunner;
|
||||
import dev.dinauer.monitoring.nodes.MonitoredNode;
|
||||
import dev.dinauer.monitoring.nodes.NodeMetrics;
|
||||
import dev.dinauer.monitoring.nodes.client.NodeDiskMetrics;
|
||||
|
||||
@ -9,7 +9,7 @@ import jakarta.inject.Inject;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
|
||||
import dev.dinauer.ProcessRunner;
|
||||
import dev.dinauer.utils.ProcessRunner;
|
||||
import dev.dinauer.monitoring.MonitoringService;
|
||||
import dev.dinauer.monitoring.entity.MonitoringConfig;
|
||||
import dev.dinauer.monitoring.entity.MonitoringType;
|
||||
|
||||
@ -10,7 +10,7 @@ import jakarta.inject.Inject;
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import io.quarkus.scheduler.Scheduled;
|
||||
|
||||
import dev.dinauer.ProcessRunner;
|
||||
import dev.dinauer.utils.ProcessRunner;
|
||||
|
||||
@ApplicationScoped
|
||||
public class PodMetricsService
|
||||
|
||||
@ -15,7 +15,7 @@ import org.jboss.logging.Logger;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
|
||||
import dev.dinauer.ProcessRunner;
|
||||
import dev.dinauer.utils.ProcessRunner;
|
||||
import dev.dinauer.monitoring.MonitoringService;
|
||||
import dev.dinauer.monitoring.entity.MonitoringConfig;
|
||||
import dev.dinauer.monitoring.entity.MonitoringType;
|
||||
|
||||
@ -42,21 +42,6 @@ public class VolumeUsageRepo
|
||||
Files.write(file, (OBJECT_MAPPER.writeValueAsString(usage) + "\n").getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||
}
|
||||
|
||||
public List<VolumeUsage> findByMonitoringIdAndPodId(String monitoringId, String podId) throws IOException
|
||||
{
|
||||
Path file = getMonitoringPath(monitoringId).resolve(podId);
|
||||
if (Files.exists(file))
|
||||
{
|
||||
List<VolumeUsage> result = new ArrayList<>();
|
||||
for (String line : Files.readAllLines(file, StandardCharsets.UTF_8))
|
||||
{
|
||||
result.add(OBJECT_MAPPER.readValue(line, VolumeUsage.class));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<VolumeUsage> findAll(String monitoringId) throws IOException
|
||||
{
|
||||
List<VolumeUsage> result = new ArrayList<>();
|
||||
|
||||
47
src/main/java/dev/dinauer/service/LogParser.java
Normal file
47
src/main/java/dev/dinauer/service/LogParser.java
Normal file
@ -0,0 +1,47 @@
|
||||
package dev.dinauer.service;
|
||||
|
||||
import dev.dinauer.inspect.log.KubernetesLog;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ApplicationScoped
|
||||
public class LogParser
|
||||
{
|
||||
@Inject
|
||||
Logger LOG;
|
||||
|
||||
public List<KubernetesLog> toLog(String log, String podName)
|
||||
{
|
||||
return toLog(List.of(log), podName);
|
||||
}
|
||||
|
||||
public List<KubernetesLog> toLog(List<String> logs, String podName)
|
||||
{
|
||||
List<KubernetesLog> result = new ArrayList<>();
|
||||
for (String log : logs)
|
||||
{
|
||||
int indexFirstSpace = log.indexOf(" ");
|
||||
if (indexFirstSpace != -1)
|
||||
{
|
||||
String timestampRaw = log.substring(0, indexFirstSpace);
|
||||
try
|
||||
{
|
||||
String message = log.substring(indexFirstSpace + 1);
|
||||
result.add(new KubernetesLog(LocalDateTime.parse(timestampRaw, DateTimeFormatter.ISO_DATE_TIME), message, podName));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.errorf("Error parsing log: %s", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,16 @@
|
||||
package dev.dinauer.service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import dev.dinauer.inspect.log.KubernetesLog;
|
||||
import dev.dinauer.inspect.utils.InputStreamWatcher;
|
||||
import io.fabric8.kubernetes.api.model.apps.Deployment;
|
||||
import io.fabric8.kubernetes.client.dsl.LogWatch;
|
||||
import io.fabric8.kubernetes.client.dsl.PodResource;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@ -21,6 +28,12 @@ public class PodService implements ResourceService<Pod>
|
||||
@Inject
|
||||
ClientProvider clientProvider;
|
||||
|
||||
@Inject
|
||||
LogParser logParser;
|
||||
|
||||
@Inject
|
||||
dev.dinauer.PodResource podResource;
|
||||
|
||||
@Override
|
||||
public void delete(String name, String namespace)
|
||||
{
|
||||
@ -60,6 +73,19 @@ public class PodService implements ResourceService<Pod>
|
||||
}
|
||||
}
|
||||
|
||||
public List<Pod> findByDeployment(String namespace, String name)
|
||||
{
|
||||
try (AppsAPIGroupDSL apps = clientProvider.getClient().apps())
|
||||
{
|
||||
Deployment set = apps.deployments().inNamespace(namespace).withName(name).get();
|
||||
if (set != null)
|
||||
{
|
||||
return findByNamespaceAndLabels(namespace, set.getSpec().getSelector().getMatchLabels());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Pod> findByNamespaceAndLabels(String namespace, Map<String, String> labels)
|
||||
{
|
||||
return clientProvider.getClient().pods().inNamespace(namespace).withLabels(labels).list().getItems();
|
||||
@ -103,4 +129,17 @@ public class PodService implements ResourceService<Pod>
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public List<KubernetesLog> getLogs(PodResource resource)
|
||||
{
|
||||
List<String> existingLogs = Arrays.stream(resource.usingTimestamps().tailingLines(200).getLog().split("\n")).toList();
|
||||
return logParser.toLog(existingLogs, resource.get().getMetadata().getName());
|
||||
}
|
||||
|
||||
public LogWatch watchLogs(PodResource resource, Consumer<List<KubernetesLog>> consume)
|
||||
{
|
||||
LogWatch watch = resource.usingTimestamps().tailingLines(0).watchLog();
|
||||
InputStreamWatcher.watch(watch.getOutput(), line -> consume.accept(logParser.toLog(line, resource.get().getMetadata().getName())));
|
||||
return watch;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
package dev.dinauer.settings;
|
||||
|
||||
public record Settings(Boolean kubeconfigDefaultPath, String kubeconfigPath, Integer refreshInterval)
|
||||
{
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
package dev.dinauer.settings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import dev.dinauer.WorkdirProvider;
|
||||
|
||||
@ApplicationScoped
|
||||
public class SettingsRepo
|
||||
{
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
@Inject
|
||||
WorkdirProvider workdirProvider;
|
||||
|
||||
public Settings find() throws IOException
|
||||
{
|
||||
return OBJECT_MAPPER.readValue(new File(workdirProvider.getWorkdir(Path.of("settings.json"))), Settings.class);
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
package dev.dinauer.settings;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import org.jboss.resteasy.reactive.common.NotImplementedYet;
|
||||
|
||||
import io.quarkus.security.Authenticated;
|
||||
|
||||
@Path("/settings")
|
||||
@ApplicationScoped
|
||||
@Authenticated
|
||||
public class SettingsResource
|
||||
{
|
||||
@Inject
|
||||
SettingsRepo settingsRepo;
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Settings getSettings() throws IOException
|
||||
{
|
||||
return settingsRepo.find();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@RolesAllowed("admin")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void updateSettings()
|
||||
{
|
||||
throw new NotImplementedYet();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package dev.dinauer;
|
||||
package dev.dinauer.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
Loading…
x
Reference in New Issue
Block a user