diff --git a/src/main/java/dev/dinauer/DeploymentResource.java b/src/main/java/dev/dinauer/DeploymentResource.java deleted file mode 100644 index c32d27c..0000000 --- a/src/main/java/dev/dinauer/DeploymentResource.java +++ /dev/null @@ -1,51 +0,0 @@ -package dev.dinauer; - -import dev.dinauer.service.DeploymentService; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.quarkus.security.Authenticated; -import io.smallrye.common.annotation.Blocking; -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 java.util.List; - -@Path("/deployments") -@Blocking -@Authenticated -@ApplicationScoped -public class DeploymentResource -{ - @Inject - DeploymentService deploymentService; - - @GET - @Produces(MediaType.APPLICATION_JSON) - public List getDeployments(@QueryParam("namespace") String namespace) - { - if(namespace != null && !namespace.isBlank()) - { - return deploymentService.findByNamespace(namespace); - } - return deploymentService.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(namespace != null && !namespace.isBlank() && name != null && !name.isBlank() && replicaCount != null && replicaCount > 0) - { - deploymentService.rescale(namespace, name, replicaCount); - } - else - { - throw new RuntimeException("Namespace, 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 dea3ce0..08f8fe3 100644 --- a/src/main/java/dev/dinauer/PodResource.java +++ b/src/main/java/dev/dinauer/PodResource.java @@ -3,13 +3,9 @@ package dev.dinauer; import dev.dinauer.service.PodService; import dev.dinauer.utils.ClientProvider; import io.fabric8.kubernetes.api.model.Pod; -import io.fabric8.kubernetes.client.ConfigBuilder; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.quarkus.runtime.Startup; import io.quarkus.security.Authenticated; import io.smallrye.common.annotation.Blocking; -import jakarta.annotation.PostConstruct; import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -19,7 +15,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; -import java.util.Optional; @Path("/pods") @Startup @@ -28,41 +23,16 @@ import java.util.Optional; @Authenticated public class PodResource { - private static final Logger LOG = LoggerFactory.getLogger(PodResource.class); - - @Inject - ClientProvider clientProvider; - @Inject PodService podService; - @GET - @Produces(MediaType.APPLICATION_JSON) - public List getPods(@QueryParam("namespace") String namespace) - { - if(namespace != null && !namespace.isBlank()) - { - return podService.findByNamespace(namespace); - } - return podService.findAll(); - } - @DELETE @Produces @Consumes - @Path("/{pod-id}") + @Path("/{namespace}/{name}") @RolesAllowed({"admin", "maintainer"}) - public void deletePod(@PathParam("pod-id") String id) + public void getEnv() { - Optional podOptional = podService.findPodById(id); - if(podOptional.isPresent()) - { - Pod pod = podOptional.get(); - clientProvider.getClient() - .pods() - .inNamespace(pod.getMetadata().getNamespace()) - .withName(pod.getMetadata().getName()) - .delete(); - } + } } diff --git a/src/main/java/dev/dinauer/ProcessRunner.java b/src/main/java/dev/dinauer/ProcessRunner.java index 232c2d8..5b2926a 100644 --- a/src/main/java/dev/dinauer/ProcessRunner.java +++ b/src/main/java/dev/dinauer/ProcessRunner.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @ApplicationScoped @@ -34,10 +35,19 @@ public class ProcessRunner text.add(line); } } - int exitCode = p.waitFor(); - if(exitCode == 0) + boolean endedInTime = p.waitFor(10, TimeUnit.SECONDS); + p.destroy(); + if (endedInTime) { - return String.join("\n", text); + int exitCode = p.exitValue(); + if(exitCode == 0) + { + return String.join("\n", text); + } + } + else + { + LOG.error("Process did not end in time."); } throw new RuntimeException("Error executing command: " + command); } diff --git a/src/main/java/dev/dinauer/ResourceResource.java b/src/main/java/dev/dinauer/ResourceResource.java new file mode 100644 index 0000000..1f5626c --- /dev/null +++ b/src/main/java/dev/dinauer/ResourceResource.java @@ -0,0 +1,132 @@ +package dev.dinauer; + +import dev.dinauer.service.*; +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 org.jboss.resteasy.reactive.common.NotImplementedYet; + +import java.util.List; +import java.util.Optional; + +@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; + + @GET + public List get(@PathParam("resource") String resourceType) + { + return getService(resourceType).findAll(); + } + + @GET + @Path("/{namespace}") + public List getByNamespace(@PathParam("resource") String resourceType, @PathParam("namespace") String namespace) + { + return getService(resourceType).findByNamespace(namespace); + } + + @GET + @Path("/{namespace}/{name}") + public Object getByNamespaceAndName(@PathParam("resource") String 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") String 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") String 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(String 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; + } + default -> + { + LOG.errorf("Invalid resource type %s.", resourceType); + throw new BadRequestException(); + } + } + } +} diff --git a/src/main/java/dev/dinauer/ResourceType.java b/src/main/java/dev/dinauer/ResourceType.java new file mode 100644 index 0000000..e77f43f --- /dev/null +++ b/src/main/java/dev/dinauer/ResourceType.java @@ -0,0 +1,12 @@ +package dev.dinauer; + +public class ResourceType +{ + public static final String STATEFUL_SET = "stateful-sets"; + public static final String DEPLOYMENT = "deployments"; + public static final String SERVICE = "services"; + public static final String INGRESS = "ingresses"; + public static final String POD = "pods"; + public static final String CUSTOM_RESOURCE_DEFINITION = "custom-resource-definitions"; + public static final String NODE = "nodes"; +} diff --git a/src/main/java/dev/dinauer/monitoring/nodes/NodeMonitoringService.java b/src/main/java/dev/dinauer/monitoring/nodes/NodeMonitoringService.java index 939eec9..b055ad4 100644 --- a/src/main/java/dev/dinauer/monitoring/nodes/NodeMonitoringService.java +++ b/src/main/java/dev/dinauer/monitoring/nodes/NodeMonitoringService.java @@ -3,6 +3,7 @@ package dev.dinauer.monitoring.nodes; import dev.dinauer.ProcessRunner; import dev.dinauer.monitoring.indexing.IndexingService; import dev.dinauer.utils.ClientProvider; +import io.smallrye.common.annotation.Blocking; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import java.io.IOException; diff --git a/src/main/java/dev/dinauer/service/CustomResourceDefinitionService.java b/src/main/java/dev/dinauer/service/CustomResourceDefinitionService.java new file mode 100644 index 0000000..77620c5 --- /dev/null +++ b/src/main/java/dev/dinauer/service/CustomResourceDefinitionService.java @@ -0,0 +1,51 @@ +package dev.dinauer.service; + +import dev.dinauer.utils.ClientProvider; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.client.dsl.ApiextensionsAPIGroupDSL; +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 CustomResourceDefinitionService implements ResourceService +{ + @Inject + ClientProvider clientProvider; + + @Override + public void delete(String name, String namespace) + { + throw new NotImplementedYet(); + } + + @Override + public CustomResourceDefinition findByNameAndNamespace(String name, String namespace) + { + throw new UnsupportedOperationException(); + } + + @Override + public List findByNamespace(String namespace) + { + throw new UnsupportedOperationException(); + } + + @Override + public List findAll() + { + try (ApiextensionsAPIGroupDSL apiextensions = clientProvider.getClient().apiextensions()) + { + return apiextensions.v1().customResourceDefinitions().list().getItems(); + } + } + + @Override + public Optional findOptionalByNameAndNamespace(String name, String namespace) + { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/dev/dinauer/service/DeploymentService.java b/src/main/java/dev/dinauer/service/DeploymentService.java index 04b6080..75abadb 100644 --- a/src/main/java/dev/dinauer/service/DeploymentService.java +++ b/src/main/java/dev/dinauer/service/DeploymentService.java @@ -1,21 +1,34 @@ package dev.dinauer.service; import dev.dinauer.utils.ClientProvider; -import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.dsl.AppsAPIGroupDSL; 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 DeploymentService +public class DeploymentService implements ResourceService { @Inject ClientProvider clientProvider; + @Override + public void delete(String name, String namespace) + { + throw new NotImplementedYet(); + } + + @Override + public Deployment findByNameAndNamespace(String name, String namespace) + { + return null; + } + + @Override public List findByNamespace(String namespace) { try(AppsAPIGroupDSL dsl = clientProvider.getClient().apps()) @@ -32,6 +45,12 @@ public class DeploymentService } } + @Override + public Optional findOptionalByNameAndNamespace(String name, String namespace) + { + return Optional.empty(); + } + public void rescale(String namespace, String name, int replicaCount) { try(AppsAPIGroupDSL dsl = clientProvider.getClient().apps()) diff --git a/src/main/java/dev/dinauer/service/IngressService.java b/src/main/java/dev/dinauer/service/IngressService.java index 1397905..76c710a 100644 --- a/src/main/java/dev/dinauer/service/IngressService.java +++ b/src/main/java/dev/dinauer/service/IngressService.java @@ -1,21 +1,48 @@ package dev.dinauer.service; +import com.fasterxml.jackson.databind.ObjectMapper; import dev.dinauer.utils.ClientProvider; -import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.client.dsl.NetworkAPIGroupDSL; -import io.fabric8.kubernetes.client.impl.NetworkAPIGroupClient; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.ws.rs.NotFoundException; +import org.jboss.resteasy.reactive.common.NotImplementedYet; import java.util.List; +import java.util.Optional; @ApplicationScoped -public class IngressService +public class IngressService implements ResourceService { @Inject ClientProvider clientProvider; + @Override + public void delete(String name, String namespace) + { + throw new NotImplementedYet(); + } + + @Override + public Ingress findByNameAndNamespace(String name, String namespace) + { + try(NetworkAPIGroupDSL dsl = clientProvider.getClient().network()) + { + List ingresses = dsl.v1().ingresses().list().getItems(); + for (Ingress ingress : ingresses) + { + ObjectMeta meta = ingress.getMetadata(); + if (name.equals(meta.getName()) && namespace.equals(meta.getNamespace())) + { + return ingress; + } + } + return null; + } + } + public List findByNamespace(String namespace) { try(NetworkAPIGroupDSL dsl = clientProvider.getClient().network()) @@ -31,4 +58,10 @@ public class IngressService return dsl.v1().ingresses().inAnyNamespace().list().getItems(); } } + + @Override + public Optional findOptionalByNameAndNamespace(String name, String namespace) + { + return Optional.ofNullable(findByNameAndNamespace(name, namespace)); + } } diff --git a/src/main/java/dev/dinauer/NodeResource.java b/src/main/java/dev/dinauer/service/NodeService.java similarity index 74% rename from src/main/java/dev/dinauer/NodeResource.java rename to src/main/java/dev/dinauer/service/NodeService.java index 2fb0175..4a2876e 100644 --- a/src/main/java/dev/dinauer/NodeResource.java +++ b/src/main/java/dev/dinauer/service/NodeService.java @@ -1,19 +1,15 @@ -package dev.dinauer; +package dev.dinauer.service; import dev.dinauer.monitoring.nodes.NodeStats; import dev.dinauer.utils.ClientProvider; import io.fabric8.kubernetes.api.model.Node; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClientBuilder; -import io.quarkus.security.Authenticated; import io.smallrye.common.annotation.Blocking; -import jakarta.annotation.PostConstruct; 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.core.MediaType; +import org.jboss.resteasy.reactive.common.NotImplementedYet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,25 +18,36 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; +import java.util.Optional; -@Path("/nodes") @ApplicationScoped -@Blocking -@Authenticated -public class NodeResource +public class NodeService implements ResourceService { - private static final Logger LOG = LoggerFactory.getLogger(NodeResource.class); + private static final Logger LOG = LoggerFactory.getLogger(NodeService.class); @Inject ClientProvider clientProvider; @GET + @Blocking @Produces(MediaType.APPLICATION_JSON) - public List getMonitoring() throws IOException, InterruptedException + public List findAll() { List result = new ArrayList<>(); - List stats = getTopNodes(); + List stats = null; + try + { + stats = getTopNodes(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } for(String nodeName : stats) { @@ -105,4 +112,28 @@ public class NodeResource } return Integer.parseInt(input); } + + @Override + public void delete(String name, String namespace) + { + throw new UnsupportedOperationException(); + } + + @Override + public NodeStats findByNameAndNamespace(String name, String namespace) + { + throw new NotImplementedYet(); + } + + @Override + public List findByNamespace(String namespace) + { + throw new NotImplementedYet(); + } + + @Override + public Optional findOptionalByNameAndNamespace(String name, String namespace) + { + throw new NotImplementedYet(); + } } diff --git a/src/main/java/dev/dinauer/service/PodService.java b/src/main/java/dev/dinauer/service/PodService.java index b568c30..447577f 100644 --- a/src/main/java/dev/dinauer/service/PodService.java +++ b/src/main/java/dev/dinauer/service/PodService.java @@ -3,10 +3,7 @@ package dev.dinauer.service; import dev.dinauer.utils.ClientProvider; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.apps.StatefulSet; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.fabric8.kubernetes.client.dsl.AppsAPIGroupDSL; -import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -15,11 +12,32 @@ import java.util.Map; import java.util.Optional; @ApplicationScoped -public class PodService +public class PodService implements ResourceService { @Inject ClientProvider clientProvider; + @Override + public void delete(String name, String namespace) + { + Optional podOptional = findOptionalByNameAndNamespace(name, namespace); + if(podOptional.isPresent()) + { + Pod pod = podOptional.get(); + clientProvider.getClient() + .pods() + .inNamespace(pod.getMetadata().getNamespace()) + .withName(pod.getMetadata().getName()) + .delete(); + } + } + + @Override + public Pod findByNameAndNamespace(String name, String namespace) + { + return clientProvider.getClient().pods().inNamespace(namespace).withName(name).get(); + } + public List findByNamespace(String namespace) { return clientProvider.getClient().pods().inNamespace(namespace).list().getItems(); @@ -48,6 +66,12 @@ public class PodService return clientProvider.getClient().pods().inAnyNamespace().list().getItems(); } + @Override + public Optional findOptionalByNameAndNamespace(String name, String namespace) + { + return Optional.ofNullable(findByNameAndNamespace(name, namespace)); + } + public Optional findPodById(String id) { for(Pod pod : clientProvider.getClient().pods().inAnyNamespace().list().getItems()) diff --git a/src/main/java/dev/dinauer/service/ResourceService.java b/src/main/java/dev/dinauer/service/ResourceService.java new file mode 100644 index 0000000..6a8a9e1 --- /dev/null +++ b/src/main/java/dev/dinauer/service/ResourceService.java @@ -0,0 +1,13 @@ +package dev.dinauer.service; + +import java.util.List; +import java.util.Optional; + +public interface ResourceService +{ + void delete(String name, String namespace); + T findByNameAndNamespace(String name, String namespace); + List findByNamespace(String namespace); + List findAll(); + Optional findOptionalByNameAndNamespace(String name, String namespace); +} diff --git a/src/main/java/dev/dinauer/service/ServiceService.java b/src/main/java/dev/dinauer/service/ServiceService.java index 1917a5d..18bd8c9 100644 --- a/src/main/java/dev/dinauer/service/ServiceService.java +++ b/src/main/java/dev/dinauer/service/ServiceService.java @@ -8,16 +8,29 @@ import io.fabric8.kubernetes.client.KubernetesClientBuilder; import jakarta.annotation.PostConstruct; 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 ServiceService +public class ServiceService implements ResourceService { @Inject ClientProvider clientProvider; + @Override + public void delete(String name, String namespace) + { + throw new NotImplementedYet(); + } + + @Override + public Service findByNameAndNamespace(String name, String namespace) + { + return clientProvider.getClient().services().inNamespace(namespace).withName(name).get(); + } + public List findByNamespace(String namespace) { return clientProvider.getClient().services().inNamespace(namespace).list().getItems(); @@ -28,6 +41,12 @@ public class ServiceService return clientProvider.getClient().services().inAnyNamespace().list().getItems(); } + @Override + public Optional findOptionalByNameAndNamespace(String name, String namespace) + { + return Optional.ofNullable(findByNameAndNamespace(name, namespace)); + } + public Optional findById(String id) { for(Service service : clientProvider.getClient().services().list().getItems()) diff --git a/src/main/java/dev/dinauer/service/StatefulSetService.java b/src/main/java/dev/dinauer/service/StatefulSetService.java new file mode 100644 index 0000000..e56bb4a --- /dev/null +++ b/src/main/java/dev/dinauer/service/StatefulSetService.java @@ -0,0 +1,43 @@ +package dev.dinauer.service; + +import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import jakarta.enterprise.context.ApplicationScoped; +import org.jboss.resteasy.reactive.common.NotImplementedYet; + +import java.util.List; +import java.util.Optional; + +@ApplicationScoped +public class StatefulSetService implements ResourceService +{ + + @Override + public void delete(String name, String namespace) + { + throw new NotImplementedYet(); + } + + @Override + public StatefulSet findByNameAndNamespace(String name, String namespace) + { + return null; + } + + @Override + public List findByNamespace(String namespace) + { + return List.of(); + } + + @Override + public List findAll() + { + return List.of(); + } + + @Override + public Optional findOptionalByNameAndNamespace(String name, String namespace) + { + return Optional.empty(); + } +}