diff --git a/pom.xml b/pom.xml
index 2405efb..62734b6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -172,14 +172,6 @@
LF
UTF-8
-
-
- validate
-
- validate
-
-
-
net.revelc.code
@@ -194,14 +186,6 @@
**/*.java
-
-
- validate
-
- check
-
-
-
diff --git a/src/main/java/dev/dinauer/DeploymentResource.java b/src/main/java/dev/dinauer/DeploymentResource.java
new file mode 100644
index 0000000..c42dbbc
--- /dev/null
+++ b/src/main/java/dev/dinauer/DeploymentResource.java
@@ -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 getPodsByDeployment(String namespace, String name)
+ {
+ return podService.findByDeployment(namespace, name);
+ }
+}
diff --git a/src/main/java/dev/dinauer/IngressResource.java b/src/main/java/dev/dinauer/IngressResource.java
deleted file mode 100644
index ae054c9..0000000
--- a/src/main/java/dev/dinauer/IngressResource.java
+++ /dev/null
@@ -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 getIngresses(@QueryParam("namespace") String namespace)
- {
- if (namespace != null)
- {
- ingressService.findByNamespace(namespace);
- }
- return ingressService.findAll();
- }
-}
diff --git a/src/main/java/dev/dinauer/NodeResource.java b/src/main/java/dev/dinauer/NodeResource.java
new file mode 100644
index 0000000..44f4a11
--- /dev/null
+++ b/src/main/java/dev/dinauer/NodeResource.java
@@ -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.");
+ }
+ }
+}
diff --git a/src/main/java/dev/dinauer/PodResource.java b/src/main/java/dev/dinauer/PodResource.java
index 0872f96..e741eb5 100644
--- a/src/main/java/dev/dinauer/PodResource.java
+++ b/src/main/java/dev/dinauer/PodResource.java
@@ -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 getEnv(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("containerName") String containerName)
{
- return getVars(podService.findByNameAndNamespace(name, namespace), containerName);
- }
-
- private List getVars(Pod pod, String containerName)
- {
- List result = new ArrayList<>();
- List 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
diff --git a/src/main/java/dev/dinauer/ResourceResource.java b/src/main/java/dev/dinauer/ResourceResource.java
deleted file mode 100644
index 5236182..0000000
--- a/src/main/java/dev/dinauer/ResourceResource.java
+++ /dev/null
@@ -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();
- }
- }
- }
-}
diff --git a/src/main/java/dev/dinauer/ServiceResource.java b/src/main/java/dev/dinauer/ServiceResource.java
deleted file mode 100644
index 3e7dc83..0000000
--- a/src/main/java/dev/dinauer/ServiceResource.java
+++ /dev/null
@@ -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 getServices(@QueryParam("namespace") String namespace)
- {
- if (namespace != null && !namespace.isBlank())
- {
- return serviceService.findByNamespace(namespace);
- }
- return serviceService.findAll();
- }
-}
diff --git a/src/main/java/dev/dinauer/WorkdirProvider.java b/src/main/java/dev/dinauer/WorkdirProvider.java
index 9e6a19f..8713236 100644
--- a/src/main/java/dev/dinauer/WorkdirProvider.java
+++ b/src/main/java/dev/dinauer/WorkdirProvider.java
@@ -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);
diff --git a/src/main/java/dev/dinauer/inspect/TokenService.java b/src/main/java/dev/dinauer/inspect/TokenService.java
index 1127cbb..a9ebb73 100644
--- a/src/main/java/dev/dinauer/inspect/TokenService.java
+++ b/src/main/java/dev/dinauer/inspect/TokenService.java
@@ -20,11 +20,7 @@ public class TokenService
{
JsonWebToken token = getToken(queryString);
Optional 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)
diff --git a/src/main/java/dev/dinauer/EnvVar.java b/src/main/java/dev/dinauer/inspect/env/EnvVar.java
similarity index 61%
rename from src/main/java/dev/dinauer/EnvVar.java
rename to src/main/java/dev/dinauer/inspect/env/EnvVar.java
index a069958..c5de35a 100644
--- a/src/main/java/dev/dinauer/EnvVar.java
+++ b/src/main/java/dev/dinauer/inspect/env/EnvVar.java
@@ -1,4 +1,4 @@
-package dev.dinauer;
+package dev.dinauer.inspect.env;
public record EnvVar(String key, String value)
{
diff --git a/src/main/java/dev/dinauer/inspect/env/EnvironmentVariableService.java b/src/main/java/dev/dinauer/inspect/env/EnvironmentVariableService.java
new file mode 100644
index 0000000..e5980bf
--- /dev/null
+++ b/src/main/java/dev/dinauer/inspect/env/EnvironmentVariableService.java
@@ -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 getVars(Pod pod, String containerName)
+ {
+ List result = new ArrayList<>();
+ List 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;
+ }
+}
diff --git a/src/main/java/dev/dinauer/inspect/log/DeploymentLogWebsocket.java b/src/main/java/dev/dinauer/inspect/log/DeploymentLogWebsocket.java
new file mode 100644
index 0000000..d171480
--- /dev/null
+++ b/src/main/java/dev/dinauer/inspect/log/DeploymentLogWebsocket.java
@@ -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 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 pods = podService.findByDeployment(namespace, name);
+ List 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 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 toLog(List logs, String podName)
+ {
+ List 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;
+ }
+}
diff --git a/src/main/java/dev/dinauer/inspect/log/KubernetesLog.java b/src/main/java/dev/dinauer/inspect/log/KubernetesLog.java
index d29c3c6..d2f0f93 100644
--- a/src/main/java/dev/dinauer/inspect/log/KubernetesLog.java
+++ b/src/main/java/dev/dinauer/inspect/log/KubernetesLog.java
@@ -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));
+ }
}
diff --git a/src/main/java/dev/dinauer/inspect/log/LogWebsocket.java b/src/main/java/dev/dinauer/inspect/log/PodLogWebsocket.java
similarity index 66%
rename from src/main/java/dev/dinauer/inspect/log/LogWebsocket.java
rename to src/main/java/dev/dinauer/inspect/log/PodLogWebsocket.java
index 65760ee..2097747 100644
--- a/src/main/java/dev/dinauer/inspect/log/LogWebsocket.java
+++ b/src/main/java/dev/dinauer/inspect/log/PodLogWebsocket.java
@@ -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 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 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 toLog(List logs)
+ private List toLog(String log, String podName)
+ {
+ return toLog(List.of(log), podName);
+ }
+
+ private List toLog(List logs, String podName)
{
List 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)
{
diff --git a/src/main/java/dev/dinauer/inspect/utils/InputStreamWatcher.java b/src/main/java/dev/dinauer/inspect/utils/InputStreamWatcher.java
new file mode 100644
index 0000000..4fdbc5b
--- /dev/null
+++ b/src/main/java/dev/dinauer/inspect/utils/InputStreamWatcher.java
@@ -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 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());
+ }
+ }
+}
diff --git a/src/main/java/dev/dinauer/ResourceType.java b/src/main/java/dev/dinauer/inspect/websocket/ResourceType.java
similarity index 96%
rename from src/main/java/dev/dinauer/ResourceType.java
rename to src/main/java/dev/dinauer/inspect/websocket/ResourceType.java
index d33850d..6eedb74 100644
--- a/src/main/java/dev/dinauer/ResourceType.java
+++ b/src/main/java/dev/dinauer/inspect/websocket/ResourceType.java
@@ -1,4 +1,4 @@
-package dev.dinauer;
+package dev.dinauer.inspect.websocket;
public enum ResourceType
{
diff --git a/src/main/java/dev/dinauer/inspect/websocket/ResourceWebsocket.java b/src/main/java/dev/dinauer/inspect/websocket/ResourceWebsocket.java
index f6faee8..04c4e78 100644
--- a/src/main/java/dev/dinauer/inspect/websocket/ResourceWebsocket.java
+++ b/src/main/java/dev/dinauer/inspect/websocket/ResourceWebsocket.java
@@ -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;
diff --git a/src/main/java/dev/dinauer/inspect/websocket/ServiceFactory.java b/src/main/java/dev/dinauer/inspect/websocket/ServiceFactory.java
index fc651a6..23add60 100644
--- a/src/main/java/dev/dinauer/inspect/websocket/ServiceFactory.java
+++ b/src/main/java/dev/dinauer/inspect/websocket/ServiceFactory.java
@@ -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();
diff --git a/src/main/java/dev/dinauer/monitoring/TopNodesService.java b/src/main/java/dev/dinauer/monitoring/TopNodesService.java
index 041a6e8..c978335 100644
--- a/src/main/java/dev/dinauer/monitoring/TopNodesService.java
+++ b/src/main/java/dev/dinauer/monitoring/TopNodesService.java
@@ -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;
diff --git a/src/main/java/dev/dinauer/monitoring/memory/MemoryMonitoringJobRunner.java b/src/main/java/dev/dinauer/monitoring/memory/MemoryMonitoringJobRunner.java
index 5a2172a..60b2639 100644
--- a/src/main/java/dev/dinauer/monitoring/memory/MemoryMonitoringJobRunner.java
+++ b/src/main/java/dev/dinauer/monitoring/memory/MemoryMonitoringJobRunner.java
@@ -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;
diff --git a/src/main/java/dev/dinauer/monitoring/pods/PodMetricsService.java b/src/main/java/dev/dinauer/monitoring/pods/PodMetricsService.java
index 0c6ab22..52fcddf 100644
--- a/src/main/java/dev/dinauer/monitoring/pods/PodMetricsService.java
+++ b/src/main/java/dev/dinauer/monitoring/pods/PodMetricsService.java
@@ -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
diff --git a/src/main/java/dev/dinauer/monitoring/volume/VolumeMonitoringJobRunner.java b/src/main/java/dev/dinauer/monitoring/volume/VolumeMonitoringJobRunner.java
index 7302a7c..549d839 100644
--- a/src/main/java/dev/dinauer/monitoring/volume/VolumeMonitoringJobRunner.java
+++ b/src/main/java/dev/dinauer/monitoring/volume/VolumeMonitoringJobRunner.java
@@ -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;
diff --git a/src/main/java/dev/dinauer/monitoring/volume/VolumeUsageRepo.java b/src/main/java/dev/dinauer/monitoring/volume/VolumeUsageRepo.java
index 1aeb92b..9b4c50d 100644
--- a/src/main/java/dev/dinauer/monitoring/volume/VolumeUsageRepo.java
+++ b/src/main/java/dev/dinauer/monitoring/volume/VolumeUsageRepo.java
@@ -42,21 +42,6 @@ public class VolumeUsageRepo
Files.write(file, (OBJECT_MAPPER.writeValueAsString(usage) + "\n").getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
}
- public List findByMonitoringIdAndPodId(String monitoringId, String podId) throws IOException
- {
- Path file = getMonitoringPath(monitoringId).resolve(podId);
- if (Files.exists(file))
- {
- List 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 findAll(String monitoringId) throws IOException
{
List result = new ArrayList<>();
diff --git a/src/main/java/dev/dinauer/service/LogParser.java b/src/main/java/dev/dinauer/service/LogParser.java
new file mode 100644
index 0000000..4130145
--- /dev/null
+++ b/src/main/java/dev/dinauer/service/LogParser.java
@@ -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 toLog(String log, String podName)
+ {
+ return toLog(List.of(log), podName);
+ }
+
+ public List toLog(List logs, String podName)
+ {
+ List 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;
+ }
+}
diff --git a/src/main/java/dev/dinauer/service/PodService.java b/src/main/java/dev/dinauer/service/PodService.java
index ff14d71..6651ba9 100644
--- a/src/main/java/dev/dinauer/service/PodService.java
+++ b/src/main/java/dev/dinauer/service/PodService.java
@@ -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
@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
}
}
+ public List 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 findByNamespaceAndLabels(String namespace, Map labels)
{
return clientProvider.getClient().pods().inNamespace(namespace).withLabels(labels).list().getItems();
@@ -103,4 +129,17 @@ public class PodService implements ResourceService
}
return Optional.empty();
}
+
+ public List getLogs(PodResource resource)
+ {
+ List 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> consume)
+ {
+ LogWatch watch = resource.usingTimestamps().tailingLines(0).watchLog();
+ InputStreamWatcher.watch(watch.getOutput(), line -> consume.accept(logParser.toLog(line, resource.get().getMetadata().getName())));
+ return watch;
+ }
}
diff --git a/src/main/java/dev/dinauer/settings/Settings.java b/src/main/java/dev/dinauer/settings/Settings.java
deleted file mode 100644
index 968ff13..0000000
--- a/src/main/java/dev/dinauer/settings/Settings.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package dev.dinauer.settings;
-
-public record Settings(Boolean kubeconfigDefaultPath, String kubeconfigPath, Integer refreshInterval)
-{
-}
diff --git a/src/main/java/dev/dinauer/settings/SettingsRepo.java b/src/main/java/dev/dinauer/settings/SettingsRepo.java
deleted file mode 100644
index c970d8a..0000000
--- a/src/main/java/dev/dinauer/settings/SettingsRepo.java
+++ /dev/null
@@ -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);
- }
-}
diff --git a/src/main/java/dev/dinauer/settings/SettingsResource.java b/src/main/java/dev/dinauer/settings/SettingsResource.java
deleted file mode 100644
index 86b4d88..0000000
--- a/src/main/java/dev/dinauer/settings/SettingsResource.java
+++ /dev/null
@@ -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();
- }
-}
diff --git a/src/main/java/dev/dinauer/ProcessRunner.java b/src/main/java/dev/dinauer/utils/ProcessRunner.java
similarity index 98%
rename from src/main/java/dev/dinauer/ProcessRunner.java
rename to src/main/java/dev/dinauer/utils/ProcessRunner.java
index a8f3c4d..e0a449d 100644
--- a/src/main/java/dev/dinauer/ProcessRunner.java
+++ b/src/main/java/dev/dinauer/utils/ProcessRunner.java
@@ -1,4 +1,4 @@
-package dev.dinauer;
+package dev.dinauer.utils;
import java.io.BufferedReader;
import java.io.IOException;