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;