Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eventtemplates): custom event templates in S3 #290

Merged
merged 44 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7853c28
feat(eventtemplates): custom event templates in S3
mwangggg Sep 1, 2023
2e5608b
tmp
mwangggg Sep 6, 2023
cb76383
tmp
mwangggg Oct 25, 2023
3e8d367
rename constant
andrewazores Feb 15, 2024
c84608e
use correct storage bucket
andrewazores Feb 15, 2024
4541222
update import, apply spotless
andrewazores Feb 15, 2024
eeba005
add license header
andrewazores Feb 15, 2024
e7d6e1c
temporary hacks to make compilable
andrewazores Feb 15, 2024
c1243c0
pre-create event templates bucket
andrewazores Feb 15, 2024
15c4a17
injected field should not be static
andrewazores Feb 15, 2024
e67fc6c
add note
andrewazores Feb 15, 2024
192e1e9
refactor, split out custom event templates service
andrewazores Feb 15, 2024
315ace3
extract target templates service to separate class
andrewazores Feb 15, 2024
fd9463c
make constructor private
andrewazores Feb 15, 2024
29728e3
cleanup
andrewazores Feb 15, 2024
6c3d605
add note, reorder parameters
andrewazores Feb 15, 2024
a21a91f
add endpoint for listing only custom templates
andrewazores Feb 15, 2024
0d10a98
update type signature
andrewazores Feb 15, 2024
7b0cf11
tmp
andrewazores Feb 15, 2024
0456058
store attrs as metadata tags
andrewazores Feb 15, 2024
5e8f24c
refactor cleanup
andrewazores Feb 15, 2024
707e101
publish notification on template upload
andrewazores Feb 15, 2024
8a08f50
implement DELETE custom template
andrewazores Feb 15, 2024
e0ee5cb
v1 endpoints redirect to v3
andrewazores Feb 15, 2024
cc62a17
implement more suitable interface
andrewazores Feb 15, 2024
bbdd943
enable retrieval of custom event templates for creating recordings
andrewazores Feb 15, 2024
bbd2df0
update @Blocking methods
andrewazores Feb 15, 2024
890f46c
add API v2.1 redirecting handler
andrewazores Feb 15, 2024
01cc8ff
disable/remove tests
andrewazores Feb 15, 2024
7ed2248
fixup! disable/remove tests
andrewazores Feb 15, 2024
774095d
Revert "enable retrieval of custom event templates for creating recor…
andrewazores Feb 15, 2024
6accdfd
Reapply "enable retrieval of custom event templates for creating reco…
andrewazores Feb 15, 2024
f6cc721
update blocking annotations, make some methods private
andrewazores Feb 15, 2024
bfed0bc
trim strings
andrewazores Feb 15, 2024
6cfa947
remove redundant/bugged template preferred type selection
andrewazores Feb 15, 2024
c839071
refactor, fix bug where templateType in event specifier string became…
andrewazores Feb 16, 2024
e3e9517
fix bug where downloaded templates are Optionals, not direct XML
andrewazores Feb 16, 2024
afcdc93
include content type on template download response
andrewazores Feb 16, 2024
cfa46db
use orElseThrow instead of get
andrewazores Feb 16, 2024
bca3ec9
working on test
andrewazores Feb 16, 2024
74eebe3
better pre-delete checks, emit full template description on notification
andrewazores Feb 16, 2024
dea97bd
handle NoSuchElementException as 404
andrewazores Feb 16, 2024
036b039
more work on test - loading resource not working
andrewazores Feb 16, 2024
a356191
simplified implementation, got tests working
andrewazores Feb 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion smoketest.bash
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ PULL_IMAGES=${PULL_IMAGES:-true}
KEEP_VOLUMES=${KEEP_VOLUMES:-false}
OPEN_TABS=${OPEN_TABS:-false}

PRECREATE_BUCKETS=${PRECREATE_BUCKETS:-archivedrecordings,archivedreports}
PRECREATE_BUCKETS=${PRECREATE_BUCKETS:-archivedrecordings,archivedreports,eventtemplates}

CRYOSTAT_HTTP_PORT=${CRYOSTAT_HTTP_PORT:-8080}
USE_PROXY=${USE_PROXY:-true}
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/io/cryostat/ConfigProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

public class ConfigProperties {
public static final String AWS_BUCKET_NAME_ARCHIVES = "storage.buckets.archives.name";
public static final String AWS_BUCKET_NAME_EVENT_TEMPLATES =
"storage.buckets.event-templates.name";
public static final String AWS_OBJECT_EXPIRATION_LABELS =
"storage.buckets.archives.expiration-label";

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/io/cryostat/ExceptionMappers.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.cryostat;

import java.util.NoSuchElementException;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;

Expand Down Expand Up @@ -45,6 +46,11 @@ public RestResponse<Void> mapNoResultException(NoResultException ex) {
return RestResponse.notFound();
}

@ServerExceptionMapper
public RestResponse<Void> mapNoSuchElementException(NoSuchElementException ex) {
return RestResponse.notFound();
}

@ServerExceptionMapper
public RestResponse<Void> mapConstraintViolationException(ConstraintViolationException ex) {
logger.warn(ex);
Expand Down
141 changes: 117 additions & 24 deletions src/main/java/io/cryostat/events/EventTemplates.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,35 @@
*/
package io.cryostat.events;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import io.cryostat.core.sys.FileSystem;
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.HttpMimeType;

import io.smallrye.common.annotation.Blocking;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.DELETE;
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.HttpHeaders;
import jakarta.ws.rs.core.Response;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.RestPath;
import org.jboss.resteasy.reactive.RestResponse;
import org.jboss.resteasy.reactive.multipart.FileUpload;
import org.jsoup.nodes.Document;

@Path("")
public class EventTemplates {
Expand All @@ -45,7 +57,10 @@ public class EventTemplates {
"Cryostat",
TemplateType.TARGET);

@Inject TargetConnectionManager connectionManager;
@Inject FileSystem fs;
@Inject TargetTemplateService.Factory targetTemplateServiceFactory;
@Inject S3TemplateService customTemplateService;
@Inject Logger logger;

@GET
@Path("/api/v1/targets/{connectUrl}/templates")
Expand All @@ -58,52 +73,130 @@ public Response listTemplatesV1(@RestPath URI connectUrl) throws Exception {
.build();
}

@POST
@Path("/api/v1/templates")
@RolesAllowed("write")
public Response postTemplatesV1(@RestForm("template") FileUpload body) {
return Response.status(RestResponse.Status.PERMANENT_REDIRECT)
.location(URI.create("/api/v3/event_templates"))
.build();
}

@POST
@Path("/api/v3/event_templates")
@RolesAllowed("write")
public void postTemplates(@RestForm("template") FileUpload body) throws IOException {
if (body == null || body.filePath() == null || !"template".equals(body.name())) {
throw new BadRequestException();
}
try (var stream = fs.newInputStream(body.filePath())) {
customTemplateService.addTemplate(stream);
} catch (InvalidEventTemplateException | InvalidXmlException e) {
throw new BadRequestException(e);
}
}

@DELETE
@Path("/api/v1/templates/{templateName}")
@RolesAllowed("write")
public Response deleteTemplatesV1(@RestPath String templateName) {
return Response.status(RestResponse.Status.PERMANENT_REDIRECT)
.location(URI.create(String.format("/api/v3/event_templates/%s", templateName)))
.build();
}

@DELETE
@Blocking
@Path("/api/v3/event_templates/{templateName}")
@RolesAllowed("write")
public void deleteTemplates(@RestPath String templateName) {
customTemplateService.deleteTemplate(templateName);
}

@GET
@Path("/api/v1/targets/{connectUrl}/templates/{templateName}/type/{templateType}")
@RolesAllowed("read")
public Response getTargetTemplateV1(
@RestPath URI connectUrl,
@RestPath String templateName,
@RestPath TemplateType templateType)
throws Exception {
@RestPath TemplateType templateType) {
Target target = Target.getTargetByConnectUrl(connectUrl);
return Response.status(RestResponse.Status.PERMANENT_REDIRECT)
.location(
URI.create(
String.format(
"/api/v3/targets/%d/event_templates/%s/%s",
target.id, templateName, templateType)))
target.id, templateType, templateName)))
.build();
}

@GET
@Path("/api/v2.1/targets/{connectUrl}/templates/{templateName}/type/{templateType}")
@RolesAllowed("read")
public Response getTargetTemplateV2_1(
@RestPath URI connectUrl,
@RestPath String templateName,
@RestPath TemplateType templateType) {
Target target = Target.getTargetByConnectUrl(connectUrl);
return Response.status(RestResponse.Status.PERMANENT_REDIRECT)
.location(
URI.create(
String.format(
"/api/v3/targets/%d/event_templates/%s/%s",
target.id, templateType, templateName)))
.build();
}

@GET
@Blocking
@Path("/api/v3/event_templates")
@RolesAllowed("read")
public List<Template> listTemplates() throws Exception {
var list = new ArrayList<Template>();
list.add(ALL_EVENTS_TEMPLATE);
list.addAll(customTemplateService.getTemplates());
return list;
}

@GET
@Blocking
@Path("/api/v3/targets/{id}/event_templates")
@RolesAllowed("read")
public List<Template> listTemplates(@RestPath long id) throws Exception {
public List<Template> listTargetTemplates(@RestPath long id) throws Exception {
Target target = Target.find("id", id).singleResult();
return connectionManager.executeConnectedTask(
target,
connection -> {
List<Template> list =
new ArrayList<>(connection.getTemplateService().getTemplates());
list.add(ALL_EVENTS_TEMPLATE);
return list;
});
var list = new ArrayList<Template>();
list.add(ALL_EVENTS_TEMPLATE);
list.addAll(targetTemplateServiceFactory.create(target).getTemplates());
list.addAll(customTemplateService.getTemplates());
return list;
}

@GET
@Path("/api/v3/targets/{id}/event_templates/{templateName}/{templateType}")
@Blocking
@Path("/api/v3/targets/{id}/event_templates/{templateType}/{templateName}")
@RolesAllowed("read")
public String getTargetTemplate(
@RestPath long id, @RestPath String templateName, @RestPath TemplateType templateType)
public Response getTargetTemplate(
@RestPath long id, @RestPath TemplateType templateType, @RestPath String templateName)
throws Exception {
Target target = Target.find("id", id).singleResult();
return connectionManager.executeConnectedTask(
target,
conn ->
conn.getTemplateService()
Document doc;
switch (templateType) {
case TARGET:
doc =
targetTemplateServiceFactory
.create(target)
.getXml(templateName, templateType)
.orElseThrow(NotFoundException::new)
.toString());
.orElseThrow();
break;
case CUSTOM:
doc = customTemplateService.getXml(templateName, templateType).orElseThrow();
break;
default:
throw new BadRequestException();
}
return Response.status(RestResponse.Status.OK)
.header(HttpHeaders.CONTENT_TYPE, HttpMimeType.JFC.mime())
.entity(doc.toString())
.build();
}
}
Loading
Loading