From 7853c28136e9403396278d1082b55ac6b0fe62fd Mon Sep 17 00:00:00 2001 From: Ming Wang Date: Fri, 1 Sep 2023 15:18:46 -0400 Subject: [PATCH 01/44] feat(eventtemplates): custom event templates in S3 --- .../io/cryostat/events/EventTemplates.java | 177 +++++++++++++++++- src/main/resources/application.properties | 1 + src/test/java/itest/TemplatePostDeleteIT.java | 8 +- 3 files changed, 183 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/cryostat/events/EventTemplates.java b/src/main/java/io/cryostat/events/EventTemplates.java index 61d80cee0..ab3280c2d 100644 --- a/src/main/java/io/cryostat/events/EventTemplates.java +++ b/src/main/java/io/cryostat/events/EventTemplates.java @@ -15,23 +15,53 @@ */ package io.cryostat.events; +import java.io.IOException; import java.net.URI; +import java.text.ParseException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import org.openjdk.jmc.flightrecorder.controlpanel.ui.configuration.model.xml.JFCGrammar; +import org.openjdk.jmc.flightrecorder.controlpanel.ui.configuration.model.xml.XMLAttributeInstance; +import org.openjdk.jmc.flightrecorder.controlpanel.ui.configuration.model.xml.XMLModel; +import org.openjdk.jmc.flightrecorder.controlpanel.ui.configuration.model.xml.XMLTagInstance; +import org.openjdk.jmc.flightrecorder.controlpanel.ui.configuration.model.xml.XMLValidationResult; +import org.openjdk.jmc.flightrecorder.controlpanel.ui.model.EventConfiguration; + +import io.cryostat.core.templates.MutableTemplateService.InvalidEventTemplateException; +import io.cryostat.core.templates.MutableTemplateService.InvalidXmlException; import io.cryostat.core.templates.Template; import io.cryostat.core.templates.TemplateType; import io.cryostat.targets.Target; import io.cryostat.targets.TargetConnectionManager; - +import io.cryostat.util.HttpStatusCodeIdentifier; +import io.quarkus.runtime.StartupEvent; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.mutiny.Uni; +import io.vertx.core.Vertx; import jakarta.annotation.security.RolesAllowed; +import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.core.Response; +import org.apache.http.entity.ContentType; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.RestForm; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.HeadBucketRequest; + import org.jboss.resteasy.reactive.RestPath; import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.multipart.FileUpload; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; @Path("") public class EventTemplates { @@ -45,7 +75,37 @@ public class EventTemplates { "Cryostat", TemplateType.TARGET); + @Inject Vertx vertx; @Inject TargetConnectionManager connectionManager; + @Inject S3Client storage; + @Inject Logger logger; + + @ConfigProperty(name = "storage.buckets.event-templates.name") + String eventTemplatesBucket; + + void onStart(@Observes StartupEvent evt) { + boolean exists = false; + try { + exists = + HttpStatusCodeIdentifier.isSuccessCode( + storage.headBucket( + HeadBucketRequest.builder() + .bucket(eventTemplatesBucket) + .build()) + .sdkHttpResponse() + .statusCode()); + } catch (Exception e) { + logger.info(e); + } + if (!exists) { + try { + storage.createBucket( + CreateBucketRequest.builder().bucket(eventTemplatesBucket).build()); + } catch (Exception e) { + logger.error(e); + } + } + } @GET @Path("/api/v1/targets/{connectUrl}/templates") @@ -58,6 +118,32 @@ public Response listTemplatesV1(@RestPath URI connectUrl) throws Exception { .build(); } + @POST + @Path("/api/v1/templates") + @RolesAllowed("write") + public Uni postTemplatesV1(@RestForm("template") FileUpload body) throws Exception { + CompletableFuture cf = new CompletableFuture<>(); + var path = body.filePath(); + vertx.fileSystem() + .readFile(path.toString()) + .onComplete( + ar -> { + try { + addTemplate(ar.result().toString()); + cf.complete(null); + } catch (Exception e) { + logger.error(e); + cf.completeExceptionally(e); + } + }) + .onFailure( + ar -> { + logger.error(ar.getCause()); + cf.completeExceptionally(ar.getCause()); + }); + return Uni.createFrom().future(cf); + } + @GET @Path("/api/v1/targets/{connectUrl}/templates/{templateName}/type/{templateType}") @RolesAllowed("read") @@ -106,4 +192,93 @@ public String getTargetTemplate( .orElseThrow(NotFoundException::new) .toString()); } + + // static class S3TemplateService implements TemplateService { + // S3Client s3; + + // @Override + // public Optional> getEvents(String arg0, TemplateType arg1) + // throws FlightRecorderException { + // return Optional.empty(); + // } + + // @Override + // public List