diff --git a/src/main/java/dev/dinauer/EnvVar.java b/src/main/java/dev/dinauer/EnvVar.java new file mode 100644 index 0000000..a069958 --- /dev/null +++ b/src/main/java/dev/dinauer/EnvVar.java @@ -0,0 +1,6 @@ +package dev.dinauer; + +public record EnvVar(String key, String value) +{ + +} diff --git a/src/main/java/dev/dinauer/KubernetesLog.java b/src/main/java/dev/dinauer/KubernetesLog.java new file mode 100644 index 0000000..c503202 --- /dev/null +++ b/src/main/java/dev/dinauer/KubernetesLog.java @@ -0,0 +1,7 @@ +package dev.dinauer; + +import java.time.LocalDateTime; + +public record KubernetesLog(LocalDateTime timestamp, String message) +{ +} diff --git a/src/main/java/dev/dinauer/LogResource.java b/src/main/java/dev/dinauer/LogResource.java index 49267de..380daa9 100644 --- a/src/main/java/dev/dinauer/LogResource.java +++ b/src/main/java/dev/dinauer/LogResource.java @@ -12,7 +12,12 @@ 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 java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -28,16 +33,44 @@ public class LogResource @Inject PodService podService; + @Inject + ProcessRunner processRunner; + @GET @Produces(MediaType.APPLICATION_JSON) - public List getLogs(@PathParam("pod-id") String podId) + public List getLogs(@PathParam("pod-id") String podId) { Optional podOptional = podService.findPodById(podId); if(podOptional.isPresent()) { Pod pod = podOptional.get(); - return List.of(clientProvider.getClient().pods().inNamespace(pod.getMetadata().getNamespace()).withName(pod.getMetadata().getName()).getLog().split("\\r?\\n")); + return getLogs(pod, null); } throw new NotFoundException(); } + + public List getLogs(Pod pod, LocalDateTime from) + { + String command = String.format("kubectl logs %s -n %s --timestamps", pod.getMetadata().getName(), pod.getMetadata().getNamespace()); + List result = new ArrayList<>(); + List logs = processRunner.runToLines(command); + for (String log : logs) + { + int indexFirstSpace = log.indexOf(" "); + if (indexFirstSpace != -1) + { + String timestampRaw = log.substring(0, indexFirstSpace); + String message = log.substring(indexFirstSpace).trim(); + try + { + result.add(new KubernetesLog(LocalDateTime.parse(timestampRaw, DateTimeFormatter.ISO_DATE_TIME), message)); + } + catch (Exception e) + { + + } + } + } + return result; + } } diff --git a/src/main/java/dev/dinauer/PodResource.java b/src/main/java/dev/dinauer/PodResource.java index 08f8fe3..0226eaa 100644 --- a/src/main/java/dev/dinauer/PodResource.java +++ b/src/main/java/dev/dinauer/PodResource.java @@ -14,25 +14,42 @@ import jakarta.ws.rs.core.MediaType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; @Path("/pods") @Startup @ApplicationScoped -@Blocking -@Authenticated public class PodResource { @Inject PodService podService; - @DELETE - @Produces - @Consumes - @Path("/{namespace}/{name}") - @RolesAllowed({"admin", "maintainer"}) - public void getEnv() - { + @Inject + ProcessRunner processRunner; + @GET + @Produces(MediaType.APPLICATION_JSON) + @Consumes + @Blocking + @Path("/{namespace}/{name}/env") + public List getEnv(@PathParam("namespace") String namespace, @PathParam("name") String name) + { + return getVars(podService.findByNameAndNamespace(name, namespace)); + } + + private List getVars(Pod pod) + { + List result = new ArrayList<>(); + List lines = processRunner.runToLines(String.format("kubectl exec -it %s -n %s -- env", pod.getMetadata().getName(), 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/ProcessRunner.java b/src/main/java/dev/dinauer/ProcessRunner.java index 5b2926a..cf05f45 100644 --- a/src/main/java/dev/dinauer/ProcessRunner.java +++ b/src/main/java/dev/dinauer/ProcessRunner.java @@ -10,7 +10,6 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; @ApplicationScoped public class ProcessRunner @@ -18,14 +17,30 @@ public class ProcessRunner @Inject Logger LOG; - public String run(String command) throws IOException, InterruptedException + public String runToText(String command) + { + return String.join("\n", runToLines(command)); + } + + public List runToLines(String command) { LOG.infof("Running command: %s", command); ProcessBuilder pb = new ProcessBuilder(command.split("\\s+")); pb.redirectErrorStream(true); - Process p = pb.start(); + try + { + Process p = pb.start(); + return runAndCollectLogs(p); + } + catch (IOException | InterruptedException e) + { + throw new RuntimeException("Failed to run command " + command); + } + } + private List runAndCollectLogs(Process p) throws InterruptedException + { List text = new ArrayList<>(); try(BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) { @@ -35,20 +50,19 @@ public class ProcessRunner text.add(line); } } + catch (IOException e) + { + throw new RuntimeException(e); + } boolean endedInTime = p.waitFor(10, TimeUnit.SECONDS); - p.destroy(); if (endedInTime) { int exitCode = p.exitValue(); if(exitCode == 0) { - return String.join("\n", text); + return text; } } - else - { - LOG.error("Process did not end in time."); - } - throw new RuntimeException("Error executing command: " + command); + throw new InterruptedException(); } } diff --git a/src/main/java/dev/dinauer/ResourceResource.java b/src/main/java/dev/dinauer/ResourceResource.java index 1f5626c..a79e0a9 100644 --- a/src/main/java/dev/dinauer/ResourceResource.java +++ b/src/main/java/dev/dinauer/ResourceResource.java @@ -6,7 +6,6 @@ import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import org.jboss.logging.Logger; -import org.jboss.resteasy.reactive.common.NotImplementedYet; import java.util.List; import java.util.Optional; @@ -38,6 +37,9 @@ public class ResourceResource @Inject NodeService nodeService; + @Inject + SecretService secretService; + @GET public List get(@PathParam("resource") String resourceType) { @@ -122,6 +124,10 @@ public class ResourceResource { return nodeService; } + case ResourceType.SECRET -> + { + return secretService; + } default -> { LOG.errorf("Invalid resource type %s.", resourceType); diff --git a/src/main/java/dev/dinauer/ResourceType.java b/src/main/java/dev/dinauer/ResourceType.java index e77f43f..a3e3f3d 100644 --- a/src/main/java/dev/dinauer/ResourceType.java +++ b/src/main/java/dev/dinauer/ResourceType.java @@ -9,4 +9,5 @@ public class ResourceType public static final String POD = "pods"; public static final String CUSTOM_RESOURCE_DEFINITION = "custom-resource-definitions"; public static final String NODE = "nodes"; + public static final String SECRET = "secrets"; } diff --git a/src/main/java/dev/dinauer/monitoring/TopNodesService.java b/src/main/java/dev/dinauer/monitoring/TopNodesService.java new file mode 100644 index 0000000..32101fc --- /dev/null +++ b/src/main/java/dev/dinauer/monitoring/TopNodesService.java @@ -0,0 +1,73 @@ +package dev.dinauer.monitoring; + +import dev.dinauer.ProcessRunner; +import dev.dinauer.monitoring.nodes.NodeStats; +import dev.dinauer.utils.ClientProvider; +import io.fabric8.kubernetes.api.model.Node; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import java.util.ArrayList; +import java.util.List; + +@ApplicationScoped +public class TopNodesService +{ + @Inject + ClientProvider clientProvider; + + @Inject + ProcessRunner processRunner; + + public List findAll() + { + List result = new ArrayList<>(); + + List stats = runTopNodesCommand(); + + + for(String nodeName : stats) + { + String[] parts = nodeName.split("\\s+"); + if(parts.length == 5) + { + String name = parts[0]; + Node node = clientProvider.getClient().nodes().withName(name).get(); + Integer absoluteCpu = extractInteger(parts[1]); + Integer relativeCpu = extractInteger(parts[2]); + Integer absoluteMemory = extractMemory(parts[3]); + Integer relativeMemory = extractInteger(parts[4]); + result.add(new NodeStats(node, absoluteCpu, relativeCpu, Integer.parseInt(node.getStatus().getAllocatable().get("cpu").getAmount()) * 1000, absoluteMemory, relativeMemory, extractMemory(node.getStatus().getAllocatable().get("memory").getAmount()))); + } + } + return result; + } + + private List runTopNodesCommand() + { + String command = String.format("kubectl --kubeconfig=%s top nodes --no-headers", clientProvider.pathToKubeconfig()); + return processRunner.runToLines(command); + } + + private Integer extractInteger(String input) + { + return Integer.valueOf(input.replace("m", "").replace("%", "")); + } + + private Integer extractMemory(String input) + { + if(input.contains("Ki")) + { + return Integer.parseInt(input.replace("Ki", "")); + } + if(input.contains("Mi")) + { + return Integer.parseInt(input.replace("Mi", "")) * 1024; + } + if(input.contains("Gi")) + { + return Integer.parseInt(input.replace("Gi", "")) * 1024 * 1024; + } + return Integer.parseInt(input); + } +} diff --git a/src/main/java/dev/dinauer/monitoring/nodes/NodeMonitoringService.java b/src/main/java/dev/dinauer/monitoring/nodes/NodeMonitoringService.java index b055ad4..426c774 100644 --- a/src/main/java/dev/dinauer/monitoring/nodes/NodeMonitoringService.java +++ b/src/main/java/dev/dinauer/monitoring/nodes/NodeMonitoringService.java @@ -1,7 +1,9 @@ package dev.dinauer.monitoring.nodes; import dev.dinauer.ProcessRunner; +import dev.dinauer.monitoring.TopNodesService; import dev.dinauer.monitoring.indexing.IndexingService; +import dev.dinauer.service.NodeService; import dev.dinauer.utils.ClientProvider; import io.smallrye.common.annotation.Blocking; import jakarta.enterprise.context.ApplicationScoped; @@ -15,65 +17,22 @@ import java.util.Map; public class NodeMonitoringService { @Inject - ClientProvider clientProvider; - - @Inject - ProcessRunner processRunner; + TopNodesService topNodesService; @Inject IndexingService indexingService; public void run() throws IOException, InterruptedException { - List result = new ArrayList<>(); - - String[] stats = getTopNodes().split("\n"); - - for(String nodeName : stats) + List nodes = topNodesService.findAll(); + for (NodeStats node : nodes) { - String[] parts = nodeName.split("\\s+"); - if(parts.length == 5) - { - String name = parts[0]; - String node = clientProvider.getClient().nodes().withName(name).get().getMetadata().getUid(); - int absoluteCpu = extractInteger(parts[1]); - int relativeCpu = extractInteger(parts[2]); - int absoluteMemory = extractMemory(parts[3]); - int relativeMemory = extractInteger(parts[4]); - Map metrics = Map.ofEntries( - Map.entry("RELATIVE_CPU", (long) relativeCpu), - Map.entry("RELATIVE_MEMORY", (long) relativeMemory), - Map.entry("ABSOLUTE_MEMORY", (long) absoluteMemory)); - indexingService.index(String.format("NODE-%s", node), "NODE_METRICS", metrics); - } + Map metrics = Map.ofEntries( + Map.entry("RELATIVE_CPU", (long) node.relativeCpuUsage()), + Map.entry("RELATIVE_MEMORY", (long) node.relativeMemory()), + Map.entry("ABSOLUTE_MEMORY", (long) node.absoluteMemory()), + Map.entry("ABSOLUTE_CPU", (long) node.absoluteCpuUsage())); + indexingService.index(String.format("NODE-%s", node), "NODE_METRICS", metrics); } } - - private String getTopNodes() throws IOException, InterruptedException - { - String command = String.format("kubectl --kubeconfig=%s top nodes --no-headers", clientProvider.pathToKubeconfig()); - return processRunner.run(command); - } - - private Integer extractInteger(String input) - { - return Integer.valueOf(input.replace("m", "").replace("%", "")); - } - - private Integer extractMemory(String input) - { - if(input.contains("Ki")) - { - return Integer.parseInt(input.replace("Ki", "")); - } - if(input.contains("Mi")) - { - return Integer.parseInt(input.replace("Mi", "")) * 1024; - } - if(input.contains("Gi")) - { - return Integer.parseInt(input.replace("Gi", "")) * 1024 * 1024; - } - return Integer.parseInt(input); - } } diff --git a/src/main/java/dev/dinauer/service/CustomResourceDefinitionService.java b/src/main/java/dev/dinauer/service/CustomResourceDefinitionService.java index 77620c5..4f7b019 100644 --- a/src/main/java/dev/dinauer/service/CustomResourceDefinitionService.java +++ b/src/main/java/dev/dinauer/service/CustomResourceDefinitionService.java @@ -31,7 +31,7 @@ public class CustomResourceDefinitionService implements ResourceService findByNamespace(String namespace) { - throw new UnsupportedOperationException(); + return findAll(); } @Override diff --git a/src/main/java/dev/dinauer/service/NodeService.java b/src/main/java/dev/dinauer/service/NodeService.java index 6c99f88..f5c6b7d 100644 --- a/src/main/java/dev/dinauer/service/NodeService.java +++ b/src/main/java/dev/dinauer/service/NodeService.java @@ -1,122 +1,22 @@ package dev.dinauer.service; +import dev.dinauer.monitoring.TopNodesService; import dev.dinauer.monitoring.nodes.NodeStats; -import dev.dinauer.utils.ClientProvider; -import io.fabric8.kubernetes.api.model.Node; -import io.smallrye.common.annotation.Blocking; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; import org.jboss.resteasy.reactive.common.NotImplementedYet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.concurrent.TimeUnit; @ApplicationScoped public class NodeService implements ResourceService { - private static final Logger LOG = LoggerFactory.getLogger(NodeService.class); - @Inject - ClientProvider clientProvider; + TopNodesService topNodesService; - @GET - @Blocking - @Produces(MediaType.APPLICATION_JSON) public List findAll() { - List result = new ArrayList<>(); - - List stats = null; - try - { - stats = getTopNodes(); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - catch (InterruptedException e) - { - throw new RuntimeException(e); - } - - for(String nodeName : stats) - { - String[] parts = nodeName.split("\\s+"); - if(parts.length == 5) - { - String name = parts[0]; - Node node = clientProvider.getClient().nodes().withName(name).get(); - Integer absoluteCpu = extractInteger(parts[1]); - Integer relativeCpu = extractInteger(parts[2]); - Integer absoluteMemory = extractMemory(parts[3]); - Integer relativeMemory = extractInteger(parts[4]); - result.add(new NodeStats(node, absoluteCpu, relativeCpu, Integer.parseInt(node.getStatus().getAllocatable().get("cpu").getAmount()) * 1000, absoluteMemory, relativeMemory, extractMemory(node.getStatus().getAllocatable().get("memory").getAmount()))); - } - } - return result; - } - - private List getTopNodes() throws IOException, InterruptedException - { - List commands = List.of("kubectl", String.format("--kubeconfig=%s", clientProvider.pathToKubeconfig()), "top", "nodes", "--no-headers"); - LOG.info("Executing command: {}", String.join(" ", commands)); - ProcessBuilder pb = new ProcessBuilder(commands); - Process p = pb.start(); - - List text = new ArrayList<>(); - try(BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) - { - String line; - while((line = br.readLine()) != null) - { - text.add(line); - } - } - boolean hasEndedInTime = p.waitFor(10, TimeUnit.SECONDS); - p.destroy(); - if (hasEndedInTime) - { - int exitCode = p.waitFor(); - if(exitCode == 0) - { - LOG.info("Found {} nodes", text.size()); - return text; - } - } - throw new RuntimeException("Failed to retrieve top nodes."); - } - - private Integer extractInteger(String input) - { - return Integer.valueOf(input.replace("m", "").replace("%", "")); - } - - private Integer extractMemory(String input) - { - if(input.contains("Ki")) - { - return Integer.parseInt(input.replace("Ki", "")); - } - if(input.contains("Mi")) - { - return Integer.parseInt(input.replace("Mi", "")) * 1024; - } - if(input.contains("Gi")) - { - return Integer.parseInt(input.replace("Gi", "")) * 1024 * 1024; - } - return Integer.parseInt(input); + return topNodesService.findAll(); } @Override @@ -134,7 +34,7 @@ public class NodeService implements ResourceService @Override public List findByNamespace(String namespace) { - throw new NotImplementedYet(); + return findAll(); } @Override diff --git a/src/main/java/dev/dinauer/service/SecretService.java b/src/main/java/dev/dinauer/service/SecretService.java new file mode 100644 index 0000000..b40e1ee --- /dev/null +++ b/src/main/java/dev/dinauer/service/SecretService.java @@ -0,0 +1,47 @@ +package dev.dinauer.service; + +import dev.dinauer.utils.ClientProvider; +import io.fabric8.kubernetes.api.model.Secret; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.jboss.resteasy.reactive.common.NotImplementedYet; + +import java.util.List; +import java.util.Optional; + +@ApplicationScoped +public class SecretService implements ResourceService +{ + @Inject + ClientProvider clientProvider; + + @Override + public void delete(String name, String namespace) + { + throw new NotImplementedYet(); + } + + @Override + public Secret findByNameAndNamespace(String name, String namespace) + { + throw new NotImplementedYet(); + } + + @Override + public List findByNamespace(String namespace) + { + return clientProvider.getClient().secrets().inNamespace(namespace).list().getItems(); + } + + @Override + public List findAll() + { + return clientProvider.getClient().secrets().inAnyNamespace().list().getItems(); + } + + @Override + public Optional findOptionalByNameAndNamespace(String name, String namespace) + { + throw new NotImplementedYet(); + } +}