[template] formalized the template definition
- Introduced `Template` & `Template.Parser` interfaces - There main template implementation is the `ScriptTemplate` and its parser is bound to `Template.Parser` - There are also xContent templates - YAML & JSON that just render the model as xContent. (used as a fallback in webhook action) - updated all actions to use the new template infrastructure Also - introduced mockito for unit testing - removed `WebhookTest` as it was effectively testing the template functionality... we'll add a proper test for teh webhook action in a later commit Original commit: elastic/x-pack-elasticsearch@34a90e8c2f
This commit is contained in:
parent
48bf4e8a8b
commit
2136210711
14
pom.xml
14
pom.xml
|
@ -86,6 +86,20 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>1.9.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency> <!--required for mockito-->
|
||||
<groupId>org.objenesis</groupId>
|
||||
<artifactId>objenesis</artifactId>
|
||||
<version>2.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Remove this when LocalDiscovery gets fixed in core -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.elasticsearch.alerts.rest.AlertsRestModule;
|
|||
import org.elasticsearch.alerts.scheduler.SchedulerModule;
|
||||
import org.elasticsearch.alerts.support.TemplateUtils;
|
||||
import org.elasticsearch.alerts.support.init.InitializingModule;
|
||||
import org.elasticsearch.alerts.support.template.TemplateModule;
|
||||
import org.elasticsearch.alerts.transform.TransformModule;
|
||||
import org.elasticsearch.alerts.transport.AlertsTransportModule;
|
||||
import org.elasticsearch.alerts.condition.ConditionModule;
|
||||
|
@ -28,6 +29,7 @@ public class AlertsModule extends AbstractModule implements SpawnModules {
|
|||
public Iterable<? extends Module> spawnModules() {
|
||||
return ImmutableList.of(
|
||||
new InitializingModule(),
|
||||
new TemplateModule(),
|
||||
new AlertsClientModule(),
|
||||
new TransformModule(),
|
||||
new AlertsRestModule(),
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.alerts.actions;
|
|||
import org.elasticsearch.alerts.ExecutionContext;
|
||||
import org.elasticsearch.alerts.Payload;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
|
@ -19,8 +20,8 @@ import java.io.IOException;
|
|||
*/
|
||||
public abstract class Action<R extends Action.Result> implements ToXContent {
|
||||
|
||||
public static final String ALERT_NAME_VARIABLE_NAME = "alert_name";
|
||||
public static final String RESPONSE_VARIABLE_NAME = "response";
|
||||
public static final String ALERT_NAME_VARIABLE = "alert_name";
|
||||
public static final String PAYLOAD_VARIABLE = "payload";
|
||||
|
||||
protected final ESLogger logger;
|
||||
|
||||
|
@ -38,7 +39,12 @@ public abstract class Action<R extends Action.Result> implements ToXContent {
|
|||
*/
|
||||
public abstract R execute(ExecutionContext context, Payload payload) throws IOException;
|
||||
|
||||
|
||||
protected static ImmutableMap<String, Object> templateModel(ExecutionContext ctx, Payload payload) {
|
||||
return ImmutableMap.<String, Object>builder()
|
||||
.put(ALERT_NAME_VARIABLE, ctx.alert().name())
|
||||
.put(PAYLOAD_VARIABLE, payload.data())
|
||||
.build();
|
||||
}
|
||||
/**
|
||||
* Parses xcontent to a concrete action of the same type.
|
||||
*/
|
||||
|
@ -57,8 +63,6 @@ public abstract class Action<R extends Action.Result> implements ToXContent {
|
|||
R parseResult(XContentParser parser) throws IOException;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static abstract class Result implements ToXContent {
|
||||
|
||||
public static final ParseField SUCCESS_FIELD = new ParseField("success");
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.alerts.actions;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ActionSettingsException extends ActionException {
|
||||
|
||||
public ActionSettingsException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public ActionSettingsException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
|
@ -8,9 +8,11 @@ package org.elasticsearch.alerts.actions.email;
|
|||
import org.elasticsearch.alerts.ExecutionContext;
|
||||
import org.elasticsearch.alerts.Payload;
|
||||
import org.elasticsearch.alerts.actions.Action;
|
||||
import org.elasticsearch.alerts.actions.ActionSettingsException;
|
||||
import org.elasticsearch.alerts.actions.email.service.*;
|
||||
import org.elasticsearch.alerts.support.StringTemplateUtils;
|
||||
import org.elasticsearch.alerts.support.template.Template;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
|
@ -20,8 +22,6 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*/
|
||||
|
@ -33,21 +33,18 @@ public class EmailAction extends Action<EmailAction.Result> {
|
|||
private final Authentication auth;
|
||||
private final Profile profile;
|
||||
private final String account;
|
||||
private final StringTemplateUtils.Template subject;
|
||||
private final StringTemplateUtils.Template textBody;
|
||||
private final StringTemplateUtils.Template htmlBody;
|
||||
private final Template subject;
|
||||
private final Template textBody;
|
||||
private final Template htmlBody;
|
||||
private final boolean attachPayload;
|
||||
|
||||
private final EmailService emailService;
|
||||
private final StringTemplateUtils templateUtils;
|
||||
|
||||
public EmailAction(ESLogger logger, EmailService emailService, StringTemplateUtils templateUtils,
|
||||
Email.Builder email, Authentication auth, Profile profile, String account,
|
||||
StringTemplateUtils.Template subject, StringTemplateUtils.Template textBody,
|
||||
StringTemplateUtils.Template htmlBody, boolean attachPayload) {
|
||||
public EmailAction(ESLogger logger, EmailService emailService, Email.Builder email, Authentication auth, Profile profile,
|
||||
String account, Template subject, Template textBody, Template htmlBody, boolean attachPayload) {
|
||||
|
||||
super(logger);
|
||||
this.emailService = emailService;
|
||||
this.templateUtils = templateUtils;
|
||||
this.email = email;
|
||||
this.auth = auth;
|
||||
this.profile = profile;
|
||||
|
@ -65,21 +62,14 @@ public class EmailAction extends Action<EmailAction.Result> {
|
|||
|
||||
@Override
|
||||
public Result execute(ExecutionContext ctx, Payload payload) throws IOException {
|
||||
ImmutableMap<String, Object> model = templateModel(ctx, payload);
|
||||
|
||||
email.id(ctx.id());
|
||||
|
||||
Map<String, Object> alertParams = new HashMap<>();
|
||||
alertParams.put(Action.ALERT_NAME_VARIABLE_NAME, ctx.alert().name());
|
||||
alertParams.put(RESPONSE_VARIABLE_NAME, payload.data());
|
||||
|
||||
String text = templateUtils.executeTemplate(subject, alertParams);
|
||||
email.subject(text);
|
||||
|
||||
text = templateUtils.executeTemplate(textBody, alertParams);
|
||||
email.textBody(text);
|
||||
email.subject(subject.render(model));
|
||||
email.textBody(textBody.render(model));
|
||||
|
||||
if (htmlBody != null) {
|
||||
text = templateUtils.executeTemplate(htmlBody, alertParams);
|
||||
email.htmlBody(text);
|
||||
email.htmlBody(htmlBody.render(model));
|
||||
}
|
||||
|
||||
if (attachPayload) {
|
||||
|
@ -100,17 +90,17 @@ public class EmailAction extends Action<EmailAction.Result> {
|
|||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
if (account != null) {
|
||||
builder.field(Parser.ACCOUNT_FIELD.getPreferredName(), account);
|
||||
builder.field(EmailAction.Parser.ACCOUNT_FIELD.getPreferredName(), account);
|
||||
}
|
||||
if (profile != null) {
|
||||
builder.field(Parser.PROFILE_FIELD.getPreferredName(), profile);
|
||||
builder.field(EmailAction.Parser.PROFILE_FIELD.getPreferredName(), profile);
|
||||
}
|
||||
builder.field(Email.TO_FIELD.getPreferredName(), (ToXContent) email.to());
|
||||
if (subject != null) {
|
||||
StringTemplateUtils.writeTemplate(Email.SUBJECT_FIELD.getPreferredName(), subject, builder, params);
|
||||
builder.field(Email.SUBJECT_FIELD.getPreferredName(), subject);
|
||||
}
|
||||
if (textBody != null) {
|
||||
StringTemplateUtils.writeTemplate(Email.TEXT_BODY_FIELD.getPreferredName(), textBody, builder, params);
|
||||
builder.field(Email.TEXT_BODY_FIELD.getPreferredName(), textBody);
|
||||
}
|
||||
return builder.endObject();
|
||||
}
|
||||
|
@ -125,14 +115,14 @@ public class EmailAction extends Action<EmailAction.Result> {
|
|||
public static final ParseField EMAIL_FIELD = new ParseField("email");
|
||||
public static final ParseField REASON_FIELD = new ParseField("reason");
|
||||
|
||||
private final StringTemplateUtils templateUtils;
|
||||
private final Template.Parser templateParser;
|
||||
private final EmailService emailService;
|
||||
|
||||
@Inject
|
||||
public Parser(Settings settings, EmailService emailService, StringTemplateUtils templateUtils) {
|
||||
public Parser(Settings settings, EmailService emailService, Template.Parser templateParser) {
|
||||
super(settings);
|
||||
this.emailService = emailService;
|
||||
this.templateUtils = templateUtils;
|
||||
this.templateParser = templateParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -147,9 +137,9 @@ public class EmailAction extends Action<EmailAction.Result> {
|
|||
String account = null;
|
||||
Profile profile = null;
|
||||
Email.Builder email = Email.builder();
|
||||
StringTemplateUtils.Template subject = null;
|
||||
StringTemplateUtils.Template textBody = null;
|
||||
StringTemplateUtils.Template htmlBody = null;
|
||||
Template subject = null;
|
||||
Template textBody = null;
|
||||
Template htmlBody = null;
|
||||
boolean attachPayload = false;
|
||||
|
||||
String currentFieldName = null;
|
||||
|
@ -169,15 +159,27 @@ public class EmailAction extends Action<EmailAction.Result> {
|
|||
} else if (Email.BCC_FIELD.match(currentFieldName)) {
|
||||
email.bcc(Email.AddressList.parse(currentFieldName, token, parser));
|
||||
} else if (Email.SUBJECT_FIELD.match(currentFieldName)) {
|
||||
subject = StringTemplateUtils.readTemplate(parser);
|
||||
try {
|
||||
subject = templateParser.parse(parser);
|
||||
} catch (Template.Parser.ParseException pe) {
|
||||
throw new ActionSettingsException("could not parse email [subject] template", pe);
|
||||
}
|
||||
} else if (Email.TEXT_BODY_FIELD.match(currentFieldName)) {
|
||||
textBody = StringTemplateUtils.readTemplate(parser);
|
||||
try {
|
||||
textBody = templateParser.parse(parser);
|
||||
} catch (Template.Parser.ParseException pe) {
|
||||
throw new ActionSettingsException("could not parse email [text_body] template", pe);
|
||||
}
|
||||
} else if (Email.HTML_BODY_FIELD.match(currentFieldName)) {
|
||||
try {
|
||||
htmlBody = templateParser.parse(parser);
|
||||
} catch (Template.Parser.ParseException pe) {
|
||||
throw new ActionSettingsException("could not parse email [html_body] template", pe);
|
||||
}
|
||||
} else if (token == XContentParser.Token.VALUE_STRING) {
|
||||
if (Email.PRIORITY_FIELD.match(currentFieldName)) {
|
||||
email.priority(Email.Priority.resolve(parser.text()));
|
||||
} else if (Email.HTML_BODY_FIELD.match(currentFieldName)) {
|
||||
htmlBody = StringTemplateUtils.readTemplate(parser);
|
||||
} else if (ACCOUNT_FIELD.match(currentFieldName)) {
|
||||
} else if (ACCOUNT_FIELD.match(currentFieldName)) {
|
||||
account = parser.text();
|
||||
} else if (USER_FIELD.match(currentFieldName)) {
|
||||
user = parser.text();
|
||||
|
@ -186,26 +188,26 @@ public class EmailAction extends Action<EmailAction.Result> {
|
|||
} else if (PROFILE_FIELD.match(currentFieldName)) {
|
||||
profile = Profile.resolve(parser.text());
|
||||
} else {
|
||||
throw new EmailException("could not parse email action. unrecognized string field [" + currentFieldName + "]");
|
||||
throw new ActionSettingsException("could not parse email action. unrecognized string field [" + currentFieldName + "]");
|
||||
}
|
||||
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
|
||||
if (ATTACH_PAYLOAD_FIELD.match(currentFieldName)) {
|
||||
attachPayload = parser.booleanValue();
|
||||
} else {
|
||||
throw new EmailException("could not parse email action. unrecognized boolean field [" + currentFieldName + "]");
|
||||
throw new ActionSettingsException("could not parse email action. unrecognized boolean field [" + currentFieldName + "]");
|
||||
}
|
||||
} else {
|
||||
throw new EmailException("could not parse email action. unexpected token [" + token + "]");
|
||||
throw new ActionSettingsException("could not parse email action. unexpected token [" + token + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (email.to() == null || email.to().isEmpty()) {
|
||||
throw new EmailException("could not parse email action. [to] was not found or was empty");
|
||||
throw new ActionSettingsException("could not parse email action. [to] was not found or was empty");
|
||||
}
|
||||
|
||||
Authentication auth = new Authentication(user, password);
|
||||
return new EmailAction(logger, emailService, templateUtils, email, auth, profile, account, subject, textBody, htmlBody, attachPayload);
|
||||
return new EmailAction(logger, emailService, email, auth, profile, account, subject, textBody, htmlBody, attachPayload);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.elasticsearch.alerts.ExecutionContext;
|
|||
import org.elasticsearch.alerts.Payload;
|
||||
import org.elasticsearch.alerts.actions.Action;
|
||||
import org.elasticsearch.alerts.actions.ActionException;
|
||||
import org.elasticsearch.alerts.actions.ActionSettingsException;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
|
@ -90,6 +91,26 @@ public class IndexAction extends Action<IndexAction.Result> {
|
|||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
IndexAction that = (IndexAction) o;
|
||||
|
||||
if (index != null ? !index.equals(that.index) : that.index != null) return false;
|
||||
if (type != null ? !type.equals(that.type) : that.type != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = index != null ? index.hashCode() : 0;
|
||||
result = 31 * result + (type != null ? type.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class Parser extends AbstractComponent implements Action.Parser<Result, IndexAction> {
|
||||
|
||||
public static final ParseField INDEX_FIELD = new ParseField("index");
|
||||
|
@ -127,19 +148,19 @@ public class IndexAction extends Action<IndexAction.Result> {
|
|||
} else if (TYPE_FIELD.match(currentFieldName)) {
|
||||
type = parser.text();
|
||||
} else {
|
||||
throw new ActionException("could not parse index action. unexpected field [" + currentFieldName + "]");
|
||||
throw new ActionSettingsException("could not parse index action. unexpected field [" + currentFieldName + "]");
|
||||
}
|
||||
} else {
|
||||
throw new ActionException("could not parse index action. unexpected token [" + token + "]");
|
||||
throw new ActionSettingsException("could not parse index action. unexpected token [" + token + "]");
|
||||
}
|
||||
}
|
||||
|
||||
if (index == null) {
|
||||
throw new ActionException("could not parse index action [index] is required");
|
||||
throw new ActionSettingsException("could not parse index action [index] is required");
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
throw new ActionException("could not parse index action [type] is required");
|
||||
throw new ActionSettingsException("could not parse index action [type] is required");
|
||||
}
|
||||
|
||||
return new IndexAction(logger, client, index, type);
|
||||
|
@ -215,24 +236,4 @@ public class IndexAction extends Action<IndexAction.Result> {
|
|||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
IndexAction that = (IndexAction) o;
|
||||
|
||||
if (index != null ? !index.equals(that.index) : that.index != null) return false;
|
||||
if (type != null ? !type.equals(that.type) : that.type != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = index != null ? index.hashCode() : 0;
|
||||
result = 31 * result + (type != null ? type.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,17 @@
|
|||
*/
|
||||
package org.elasticsearch.alerts.actions.webhook;
|
||||
|
||||
import org.elasticsearch.alerts.AlertsSettingsException;
|
||||
import org.elasticsearch.alerts.ExecutionContext;
|
||||
import org.elasticsearch.alerts.Payload;
|
||||
import org.elasticsearch.alerts.actions.Action;
|
||||
import org.elasticsearch.alerts.actions.ActionException;
|
||||
import org.elasticsearch.alerts.support.StringTemplateUtils;
|
||||
import org.elasticsearch.alerts.actions.ActionSettingsException;
|
||||
import org.elasticsearch.alerts.support.template.Template;
|
||||
import org.elasticsearch.alerts.support.template.XContentTemplate;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
|
@ -21,8 +25,6 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*/
|
||||
|
@ -30,27 +32,18 @@ public class WebhookAction extends Action<WebhookAction.Result> {
|
|||
|
||||
public static final String TYPE = "webhook";
|
||||
|
||||
static final StringTemplateUtils.Template DEFAULT_BODY_TEMPLATE = new StringTemplateUtils.Template(
|
||||
"{ 'alertname' : '{{alert_name}}', 'response' : {{response}} }");
|
||||
|
||||
private final StringTemplateUtils templateUtils;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private final StringTemplateUtils.Template urlTemplate;
|
||||
private final HttpMethod method;
|
||||
private final Template url;
|
||||
private final @Nullable Template body;
|
||||
|
||||
//Optional, default will be used if not provided
|
||||
private final StringTemplateUtils.Template bodyTemplate;
|
||||
|
||||
public WebhookAction(ESLogger logger, StringTemplateUtils templateUtils, HttpClient httpClient,
|
||||
@Nullable StringTemplateUtils.Template bodyTemplate,
|
||||
StringTemplateUtils.Template urlTemplate, HttpMethod method) {
|
||||
public WebhookAction(ESLogger logger, HttpClient httpClient, HttpMethod method, Template url, Template body) {
|
||||
super(logger);
|
||||
this.templateUtils = templateUtils;
|
||||
this.httpClient = httpClient;
|
||||
this.bodyTemplate = bodyTemplate;
|
||||
this.urlTemplate = urlTemplate;
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,44 +53,62 @@ public class WebhookAction extends Action<WebhookAction.Result> {
|
|||
|
||||
@Override
|
||||
public Result execute(ExecutionContext ctx, Payload payload) throws IOException {
|
||||
Map<String, Object> data = payload.data();
|
||||
String renderedUrl = applyTemplate(templateUtils, urlTemplate, ctx.alert().name(), data);
|
||||
String body = applyTemplate(templateUtils, bodyTemplate != null ? bodyTemplate : DEFAULT_BODY_TEMPLATE, ctx.alert().name(), data);
|
||||
ImmutableMap<String, Object> model = ImmutableMap.<String, Object>builder()
|
||||
.put(ALERT_NAME_VARIABLE, ctx.alert().name())
|
||||
.put(PAYLOAD_VARIABLE, payload.data())
|
||||
.build();
|
||||
String urlText = url.render(model);
|
||||
String bodyText = body != null ? body.render(model) : XContentTemplate.YAML.render(model);
|
||||
try {
|
||||
|
||||
int status = httpClient.execute(method, renderedUrl, body);
|
||||
int status = httpClient.execute(method, urlText, bodyText);
|
||||
if (status >= 400) {
|
||||
logger.warn("got status [" + status + "] when connecting to [" + renderedUrl + "]");
|
||||
logger.warn("got status [" + status + "] when connecting to [" + urlText + "]");
|
||||
} else {
|
||||
if (status >= 300) {
|
||||
logger.warn("a 200 range return code was expected, but got [" + status + "]");
|
||||
}
|
||||
}
|
||||
return new Result.Executed(status, renderedUrl, body);
|
||||
return new Result.Executed(status, urlText, bodyText);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
logger.error("failed to connect to [{}] for alert [{}]", ioe, renderedUrl, ctx.alert().name());
|
||||
logger.error("failed to connect to [{}] for alert [{}]", ioe, urlText, ctx.alert().name());
|
||||
return new Result.Failure("failed to send http request. " + ioe.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static String applyTemplate(StringTemplateUtils templateUtils, StringTemplateUtils.Template template, String alertName, Map<String, Object> data) {
|
||||
Map<String, Object> webHookParams = new HashMap<>();
|
||||
webHookParams.put(ALERT_NAME_VARIABLE_NAME, alertName);
|
||||
webHookParams.put(RESPONSE_VARIABLE_NAME, data);
|
||||
return templateUtils.executeTemplate(template, webHookParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
builder.field(Parser.METHOD_FIELD.getPreferredName(), method.getName());
|
||||
if (bodyTemplate != null) {
|
||||
StringTemplateUtils.writeTemplate(Parser.BODY_TEMPLATE_FIELD.getPreferredName(), bodyTemplate, builder, params);
|
||||
builder.field(Parser.URL_FIELD.getPreferredName(), url);
|
||||
if (body != null) {
|
||||
builder.field(Parser.BODY_FIELD.getPreferredName(), body);
|
||||
}
|
||||
StringTemplateUtils.writeTemplate(Parser.URL_TEMPLATE_FIELD.getPreferredName(), urlTemplate, builder, params);
|
||||
builder.endObject();
|
||||
return builder;
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
WebhookAction that = (WebhookAction) o;
|
||||
|
||||
if (!body.equals(that.body)) return false;
|
||||
if (!method.equals(that.method)) return false;
|
||||
if (!url.equals(that.url)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = method.hashCode();
|
||||
result = 31 * result + url.hashCode();
|
||||
result = 31 * result + body.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public abstract static class Result extends Action.Result {
|
||||
|
@ -108,19 +119,19 @@ public class WebhookAction extends Action<WebhookAction.Result> {
|
|||
|
||||
public static class Executed extends Result {
|
||||
|
||||
private final int httpStatusCode;
|
||||
private final int httpStatus;
|
||||
private final String url;
|
||||
private final String body;
|
||||
|
||||
public Executed(int httpStatusCode, String url, String body) {
|
||||
super(TYPE, httpStatusCode < 400);
|
||||
this.httpStatusCode = httpStatusCode;
|
||||
public Executed(int httpStatus, String url, String body) {
|
||||
super(TYPE, httpStatus < 400);
|
||||
this.httpStatus = httpStatus;
|
||||
this.url = url;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public int httpStatusCode() {
|
||||
return httpStatusCode;
|
||||
public int httpStatus() {
|
||||
return httpStatus;
|
||||
}
|
||||
|
||||
public String url() {
|
||||
|
@ -134,9 +145,9 @@ public class WebhookAction extends Action<WebhookAction.Result> {
|
|||
@Override
|
||||
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.field("success", success())
|
||||
.field(Parser.HTTP_STATUS_FIELD.getPreferredName(), httpStatusCode)
|
||||
.field(Parser.URL_FIELD.getPreferredName(), url)
|
||||
.field(Parser.BODY_FIELD.getPreferredName(), body);
|
||||
.field(WebhookAction.Parser.HTTP_STATUS_FIELD.getPreferredName(), httpStatus)
|
||||
.field(WebhookAction.Parser.URL_FIELD.getPreferredName(), url)
|
||||
.field(WebhookAction.Parser.BODY_FIELD.getPreferredName(), body);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,7 +162,7 @@ public class WebhookAction extends Action<WebhookAction.Result> {
|
|||
|
||||
@Override
|
||||
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.field(Parser.REASON_FIELD.getPreferredName(), reason);
|
||||
return builder.field(WebhookAction.Parser.REASON_FIELD.getPreferredName(), reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,21 +171,18 @@ public class WebhookAction extends Action<WebhookAction.Result> {
|
|||
public static class Parser extends AbstractComponent implements Action.Parser<Result, WebhookAction> {
|
||||
|
||||
public static final ParseField METHOD_FIELD = new ParseField("method");
|
||||
public static final ParseField URL_TEMPLATE_FIELD = new ParseField("url_template");
|
||||
public static final ParseField BODY_TEMPLATE_FIELD = new ParseField("body_template");
|
||||
|
||||
public static final ParseField BODY_FIELD = new ParseField("body");
|
||||
public static final ParseField URL_FIELD = new ParseField("url");
|
||||
public static final ParseField BODY_FIELD = new ParseField("body");
|
||||
public static final ParseField HTTP_STATUS_FIELD = new ParseField("http_status");
|
||||
public static final ParseField REASON_FIELD = new ParseField("reason");
|
||||
|
||||
private final StringTemplateUtils templateUtils;
|
||||
private final Template.Parser templateParser;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Inject
|
||||
public Parser(Settings settings, StringTemplateUtils templateUtils, HttpClient httpClient) {
|
||||
public Parser(Settings settings, Template.Parser templateParser, HttpClient httpClient) {
|
||||
super(settings);
|
||||
this.templateUtils = templateUtils;
|
||||
this.templateParser = templateParser;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
|
@ -186,8 +194,8 @@ public class WebhookAction extends Action<WebhookAction.Result> {
|
|||
@Override
|
||||
public WebhookAction parse(XContentParser parser) throws IOException {
|
||||
HttpMethod method = HttpMethod.POST;
|
||||
StringTemplateUtils.Template urlTemplate = null;
|
||||
StringTemplateUtils.Template bodyTemplate = null;
|
||||
Template urlTemplate = null;
|
||||
Template bodyTemplate = null;
|
||||
|
||||
String currentFieldName = null;
|
||||
XContentParser.Token token;
|
||||
|
@ -199,26 +207,33 @@ public class WebhookAction extends Action<WebhookAction.Result> {
|
|||
if (METHOD_FIELD.match(currentFieldName)) {
|
||||
method = HttpMethod.valueOf(parser.text());
|
||||
if (method != HttpMethod.POST && method != HttpMethod.GET && method != HttpMethod.PUT) {
|
||||
throw new ActionException("could not parse webhook action. unsupported http method ["
|
||||
+ method.getName() + "]");
|
||||
throw new ActionSettingsException("could not parse webhook action. unsupported http method [" + method.getName() + "]");
|
||||
}
|
||||
} else if (URL_FIELD.match(currentFieldName)) {
|
||||
try {
|
||||
urlTemplate = templateParser.parse(parser);
|
||||
} catch (Template.Parser.ParseException pe) {
|
||||
throw new AlertsSettingsException("could not parse webhook action [url] template", pe);
|
||||
}
|
||||
} else if (BODY_FIELD.match(currentFieldName)) {
|
||||
try {
|
||||
bodyTemplate = templateParser.parse(parser);
|
||||
} catch (Template.Parser.ParseException pe) {
|
||||
throw new ActionSettingsException("could not parse webhook action [body] template", pe);
|
||||
}
|
||||
} else if (URL_TEMPLATE_FIELD.match(currentFieldName)) {
|
||||
urlTemplate = StringTemplateUtils.readTemplate(parser);
|
||||
} else if (BODY_TEMPLATE_FIELD.match(currentFieldName)) {
|
||||
bodyTemplate = StringTemplateUtils.readTemplate(parser);
|
||||
} else {
|
||||
throw new ActionException("could not parse webhook action. unexpected field [" + currentFieldName + "]");
|
||||
throw new ActionSettingsException("could not parse webhook action. unexpected field [" + currentFieldName + "]");
|
||||
}
|
||||
} else {
|
||||
throw new ActionException("could not parse webhook action. unexpected token [" + token + "]");
|
||||
throw new ActionSettingsException("could not parse webhook action. unexpected token [" + token + "]");
|
||||
}
|
||||
}
|
||||
|
||||
if (urlTemplate == null) {
|
||||
throw new ActionException("could not parse webhook action. [url_template] is required");
|
||||
throw new ActionSettingsException("could not parse webhook action. [url_template] is required");
|
||||
}
|
||||
|
||||
return new WebhookAction(logger, templateUtils, httpClient, bodyTemplate, urlTemplate, method);
|
||||
return new WebhookAction(logger, httpClient, method, urlTemplate, bodyTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -264,25 +279,4 @@ public class WebhookAction extends Action<WebhookAction.Result> {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
WebhookAction that = (WebhookAction) o;
|
||||
|
||||
if (bodyTemplate != null ? !bodyTemplate.equals(that.bodyTemplate) : that.bodyTemplate != null) return false;
|
||||
if (method != null ? !method.equals(that.method) : that.method != null) return false;
|
||||
if (urlTemplate != null ? !urlTemplate.equals(that.urlTemplate) : that.urlTemplate != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = urlTemplate != null ? urlTemplate.hashCode() : 0;
|
||||
result = 31 * result + (method != null ? method.hashCode() : 0);
|
||||
result = 31 * result + (bodyTemplate != null ? bodyTemplate.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ public final class AlertUtils {
|
|||
searchRequest.templateName(parser.textOrNull());
|
||||
break;
|
||||
case "template_type":
|
||||
searchRequest.templateType(readScriptType(parser.textOrNull()));
|
||||
searchRequest.templateType(ScriptService.ScriptType.valueOf(parser.text().toUpperCase(Locale.ROOT)));
|
||||
break;
|
||||
case "search_type":
|
||||
searchType = SearchType.fromString(parser.text());
|
||||
|
@ -169,7 +169,7 @@ public final class AlertUtils {
|
|||
builder.field("template_name", searchRequest.templateName());
|
||||
}
|
||||
if (searchRequest.templateType() != null) {
|
||||
builder.field("template_type", writeScriptType(searchRequest.templateType()));
|
||||
builder.field("template_type", searchRequest.templateType().name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
builder.startArray("indices");
|
||||
for (String index : searchRequest.indices()) {
|
||||
|
@ -200,30 +200,4 @@ public final class AlertUtils {
|
|||
builder.endObject();
|
||||
}
|
||||
|
||||
static ScriptService.ScriptType readScriptType(String value) {
|
||||
switch (value) {
|
||||
case "indexed":
|
||||
return ScriptService.ScriptType.INDEXED;
|
||||
case "inline":
|
||||
return ScriptService.ScriptType.INLINE;
|
||||
case "file":
|
||||
return ScriptService.ScriptType.FILE;
|
||||
default:
|
||||
throw new ElasticsearchIllegalArgumentException("Unknown script_type value [" + value + "]");
|
||||
}
|
||||
}
|
||||
|
||||
static String writeScriptType(ScriptService.ScriptType value) {
|
||||
switch (value) {
|
||||
case INDEXED:
|
||||
return "indexed";
|
||||
case INLINE:
|
||||
return "inline";
|
||||
case FILE:
|
||||
return "file";
|
||||
default:
|
||||
throw new ElasticsearchIllegalArgumentException("Illegal script_type value [" + value + "]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.alerts.support;
|
||||
|
||||
|
||||
import org.elasticsearch.ElasticsearchIllegalArgumentException;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class StringTemplateUtils extends AbstractComponent {
|
||||
|
||||
private final ScriptServiceProxy scriptService;
|
||||
|
||||
@Inject
|
||||
public StringTemplateUtils(Settings settings, ScriptServiceProxy scriptService) {
|
||||
super(settings);
|
||||
this.scriptService = scriptService;
|
||||
}
|
||||
|
||||
public String executeTemplate(Template template) {
|
||||
return executeTemplate(template, Collections.<String, Object>emptyMap());
|
||||
}
|
||||
|
||||
public String executeTemplate(Template template, Map<String, Object> additionalParams) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.putAll(template.getParams());
|
||||
params.putAll(additionalParams);
|
||||
ExecutableScript script = scriptService.executable(template.getLanguage(), template.getTemplate(), template.getScriptType(), params);
|
||||
Object result = script.run();
|
||||
if (result instanceof String) {
|
||||
return (String) result;
|
||||
} else if (result instanceof BytesReference) {
|
||||
return ((BytesReference) script.run()).toUtf8();
|
||||
} else {
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
public static Template readTemplate(XContentParser parser) throws IOException {
|
||||
assert parser.currentToken() == XContentParser.Token.START_OBJECT : "Expected START_OBJECT, but was " + parser.currentToken();
|
||||
Map<String, String> params = null;
|
||||
String script = null;
|
||||
ScriptService.ScriptType type = ScriptService.ScriptType.INLINE;
|
||||
String language = "mustache";
|
||||
String fieldName = parser.currentName();
|
||||
for (XContentParser.Token token = parser.nextToken(); token != XContentParser.Token.END_OBJECT; token = parser.nextToken()) {
|
||||
switch (token) {
|
||||
case FIELD_NAME:
|
||||
fieldName = parser.currentName();
|
||||
break;
|
||||
case START_OBJECT:
|
||||
switch (fieldName) {
|
||||
case "params":
|
||||
params = (Map) parser.map();
|
||||
break;
|
||||
default:
|
||||
throw new ElasticsearchIllegalArgumentException("Unexpected field [" + fieldName + "]");
|
||||
}
|
||||
break;
|
||||
case VALUE_STRING:
|
||||
switch (fieldName) {
|
||||
case "script":
|
||||
script = parser.text();
|
||||
break;
|
||||
case "language":
|
||||
language = parser.text();
|
||||
break;
|
||||
case "type":
|
||||
type = AlertUtils.readScriptType(parser.text());
|
||||
break;
|
||||
default:
|
||||
throw new ElasticsearchIllegalArgumentException("Unexpected field [" + fieldName + "]");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ElasticsearchIllegalArgumentException("Unexpected json token [" + token + "]");
|
||||
}
|
||||
}
|
||||
return new Template(script, params, language, type);
|
||||
}
|
||||
|
||||
public static void writeTemplate(String objectName, Template template, XContentBuilder builder, ToXContent.Params params) throws IOException {
|
||||
builder.startObject(objectName);
|
||||
builder.field("script", template.getTemplate());
|
||||
builder.field("type", AlertUtils.writeScriptType(template.getScriptType()));
|
||||
builder.field("language", template.getLanguage());
|
||||
if (template.getParams() != null && !template.getParams().isEmpty()) {
|
||||
builder.field("params", template.getParams());
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
|
||||
public static class Template {
|
||||
private final String template;
|
||||
private final Map<String, String> params;
|
||||
private final String language;
|
||||
private final ScriptService.ScriptType scriptType;
|
||||
public Template(String template) {
|
||||
this.template = template;
|
||||
this.params = Collections.emptyMap();
|
||||
this.language = "mustache";
|
||||
this.scriptType = ScriptService.ScriptType.INLINE;
|
||||
}
|
||||
public Template(String template, Map<String, String> params, String language, ScriptService.ScriptType scriptType) {
|
||||
this.template = template;
|
||||
this.params = params;
|
||||
this.language = language;
|
||||
this.scriptType = scriptType;
|
||||
}
|
||||
public ScriptService.ScriptType getScriptType() {
|
||||
return scriptType;
|
||||
}
|
||||
public String getTemplate() {
|
||||
return template;
|
||||
}
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
public Map<String, String> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Template template1 = (Template) o;
|
||||
|
||||
if (language != null ? !language.equals(template1.language) : template1.language != null) return false;
|
||||
if (params != null ? !params.equals(template1.params) : template1.params != null) return false;
|
||||
if (scriptType != template1.scriptType) return false;
|
||||
if (template != null ? !template.equals(template1.template) : template1.template != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = template != null ? template.hashCode() : 0;
|
||||
result = 31 * result + (params != null ? params.hashCode() : 0);
|
||||
result = 31 * result + (language != null ? language.hashCode() : 0);
|
||||
result = 31 * result + (scriptType != null ? scriptType.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.alerts.support.template;
|
||||
|
||||
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.base.Function;
|
||||
import org.elasticsearch.common.base.Joiner;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.collect.Collections2;
|
||||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ScriptTemplate implements ToXContent, Template {
|
||||
|
||||
static final ParseField TEXT_FIELD = new ParseField("script", "text");
|
||||
static final ParseField LANG_FIELD = new ParseField("lang", "language", "script_lang");
|
||||
static final ParseField TYPE_FIELD = new ParseField("type", "script_type");
|
||||
static final ParseField PARAMS_FIELD = new ParseField("model", "params");
|
||||
|
||||
public static final String DEFAULT_LANG = "mustache";
|
||||
|
||||
private final String text;
|
||||
private final String lang;
|
||||
private final ScriptService.ScriptType type;
|
||||
private final Map<String, Object> params;
|
||||
private final ScriptServiceProxy service;
|
||||
|
||||
public ScriptTemplate(ScriptServiceProxy service, String text) {
|
||||
this(service, text, DEFAULT_LANG, ScriptService.ScriptType.INLINE, Collections.<String, Object>emptyMap());
|
||||
}
|
||||
|
||||
public ScriptTemplate(ScriptServiceProxy service, String text, String lang, ScriptService.ScriptType type, Map<String, Object> params) {
|
||||
this.service = service;
|
||||
this.text = text;
|
||||
this.lang = lang;
|
||||
this.type = type;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public String text() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public ScriptService.ScriptType type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String lang() {
|
||||
return lang;
|
||||
}
|
||||
|
||||
public Map<String, Object> params() {
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String render(Map<String, Object> model) {
|
||||
Map<String, Object> mergedModel = new HashMap<>();
|
||||
mergedModel.putAll(params);
|
||||
mergedModel.putAll(model);
|
||||
ExecutableScript script = service.executable(lang, text, type, mergedModel);
|
||||
Object result = script.run();
|
||||
if (result instanceof BytesReference) {
|
||||
return ((BytesReference) script.run()).toUtf8();
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ScriptTemplate template = (ScriptTemplate) o;
|
||||
|
||||
if (!lang.equals(template.lang)) return false;
|
||||
if (!params.equals(template.params)) return false;
|
||||
if (!text.equals(template.text)) return false;
|
||||
if (type != template.type) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = text.hashCode();
|
||||
result = 31 * result + lang.hashCode();
|
||||
result = 31 * result + type.hashCode();
|
||||
result = 31 * result + params.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.startObject()
|
||||
.field(TEXT_FIELD.getPreferredName(), text)
|
||||
.field(TYPE_FIELD.getPreferredName(), type.name().toLowerCase(Locale.ROOT))
|
||||
.field(LANG_FIELD.getPreferredName(), lang)
|
||||
.field(PARAMS_FIELD.getPreferredName(), this.params)
|
||||
.endObject();
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public static class Parser extends AbstractComponent implements Template.Parser<ScriptTemplate> {
|
||||
|
||||
private final ScriptServiceProxy scriptService;
|
||||
|
||||
@Inject
|
||||
public Parser(Settings settings, ScriptServiceProxy scriptService) {
|
||||
super(settings);
|
||||
this.scriptService = scriptService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptTemplate parse(XContentParser parser) throws IOException {
|
||||
assert parser.currentToken() == XContentParser.Token.START_OBJECT : "Expected START_OBJECT, but was " + parser.currentToken();
|
||||
|
||||
String text = null;
|
||||
ScriptService.ScriptType type = ScriptService.ScriptType.INLINE;
|
||||
String lang = DEFAULT_LANG;
|
||||
ImmutableMap.Builder<String, Object> params = ImmutableMap.builder();
|
||||
|
||||
String currentFieldName = null;
|
||||
XContentParser.Token token;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if (TEXT_FIELD.match(currentFieldName)) {
|
||||
if (token.isValue()) {
|
||||
text = parser.text();
|
||||
} else {
|
||||
throw new ParseException("expected a string value for [" + currentFieldName + "], but found [" + token + "] instead");
|
||||
}
|
||||
} else if (LANG_FIELD.match(currentFieldName)) {
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
lang = parser.text();
|
||||
} else {
|
||||
throw new ParseException("expected a string value for [" + currentFieldName + "], but found [" + token + "] instead");
|
||||
}
|
||||
} else if (TYPE_FIELD.match(currentFieldName)) {
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
String value = parser.text();
|
||||
try {
|
||||
type = ScriptService.ScriptType.valueOf(value.toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
String typeOptions = Joiner.on(",").join(Collections2.transform(ImmutableList.copyOf(ScriptService.ScriptType.values()), new Function<ScriptService.ScriptType, String>() {
|
||||
@Override
|
||||
public String apply(ScriptService.ScriptType scriptType) {
|
||||
return scriptType.name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}));
|
||||
throw new ParseException("unknown template script type [" + currentFieldName + "]. type can only be on of: [" + typeOptions + "]");
|
||||
}
|
||||
}
|
||||
} else if (PARAMS_FIELD.match(currentFieldName)) {
|
||||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new ParseException("expected an object for [" + currentFieldName + "], but found [" + token + "]");
|
||||
}
|
||||
params.putAll(parser.map());
|
||||
} else {
|
||||
throw new ParseException("unexpected field [" + currentFieldName + "]");
|
||||
}
|
||||
}
|
||||
if (text == null) {
|
||||
throw new ParseException("missing required field [" + TEXT_FIELD.getPreferredName() + "]");
|
||||
}
|
||||
return new ScriptTemplate(scriptService, text, lang, type, params.build());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.alerts.support.template;
|
||||
|
||||
import org.elasticsearch.alerts.AlertsException;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface Template extends ToXContent {
|
||||
|
||||
String render(Map<String, Object> model);
|
||||
|
||||
interface Parser<T extends Template> {
|
||||
|
||||
T parse(XContentParser parser) throws IOException, ParseException;
|
||||
|
||||
public static class ParseException extends AlertsException {
|
||||
|
||||
public ParseException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.alerts.support.template;
|
||||
|
||||
import org.elasticsearch.alerts.AlertsException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class TemplateException extends AlertsException {
|
||||
|
||||
public TemplateException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public TemplateException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.alerts.support.template;
|
||||
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class TemplateModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(Template.Parser.class).to(ScriptTemplate.Parser.class).asEagerSingleton();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.alerts.support.template;
|
||||
|
||||
import org.elasticsearch.common.xcontent.XContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.yaml.YamlXContent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class XContentTemplate implements Template {
|
||||
|
||||
public static XContentTemplate YAML = new XContentTemplate(YamlXContent.yamlXContent);
|
||||
|
||||
private final XContent xContent;
|
||||
|
||||
private XContentTemplate(XContent xContent) {
|
||||
this.xContent = xContent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.startObject().endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String render(Map<String, Object> model) {
|
||||
try {
|
||||
return XContentBuilder.builder(xContent).map(model).bytes().toUtf8();
|
||||
} catch (IOException ioe) {
|
||||
throw new TemplateException("could not render [" + xContent.type().name() + "] xcontent template", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -24,9 +24,10 @@ import org.elasticsearch.alerts.history.FiredAlert;
|
|||
import org.elasticsearch.alerts.history.HistoryStore;
|
||||
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
|
||||
import org.elasticsearch.alerts.support.AlertUtils;
|
||||
import org.elasticsearch.alerts.support.StringTemplateUtils;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
|
||||
import org.elasticsearch.alerts.support.template.ScriptTemplate;
|
||||
import org.elasticsearch.alerts.support.template.Template;
|
||||
import org.elasticsearch.alerts.transform.SearchTransform;
|
||||
import org.elasticsearch.alerts.transport.actions.stats.AlertsStatsResponse;
|
||||
import org.elasticsearch.client.Client;
|
||||
|
@ -162,10 +163,10 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
|
|||
|
||||
List<Action> actions = new ArrayList<>();
|
||||
|
||||
StringTemplateUtils.Template template =
|
||||
new StringTemplateUtils.Template("{{alert_name}} executed with {{response.hits.total}} hits");
|
||||
Template url = new ScriptTemplate(scriptService(), "http://localhost/foobarbaz/{{alert_name}}");
|
||||
Template body = new ScriptTemplate(scriptService(), "{{alert_name}} executed with {{response.hits.total}} hits");
|
||||
|
||||
actions.add(new WebhookAction(logger, stringTemplateUtils(), httpClient(), template, new StringTemplateUtils.Template("http://localhost/foobarbaz/{{alert_name}}"), HttpMethod.GET));
|
||||
actions.add(new WebhookAction(logger, httpClient(), HttpMethod.GET, url, body));
|
||||
|
||||
Email.Address from = new Email.Address("from@test.com");
|
||||
List<Email.Address> emailAddressList = new ArrayList<>();
|
||||
|
@ -178,8 +179,8 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
|
|||
emailBuilder.to(to);
|
||||
|
||||
|
||||
EmailAction emailAction = new EmailAction(logger, noopEmailService(), stringTemplateUtils(), emailBuilder,
|
||||
new Authentication("testname", "testpassword"), Profile.STANDARD, "testaccount", template, template, null, true);
|
||||
EmailAction emailAction = new EmailAction(logger, noopEmailService(), emailBuilder,
|
||||
new Authentication("testname", "testpassword"), Profile.STANDARD, "testaccount", body, body, null, true);
|
||||
|
||||
actions.add(emailAction);
|
||||
|
||||
|
@ -189,9 +190,9 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
|
|||
return new Alert(
|
||||
alertName,
|
||||
new CronSchedule("0/5 * * * * ? *"),
|
||||
new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
|
||||
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
|
||||
conditionRequest,"return true", ScriptService.ScriptType.INLINE, "groovy"),
|
||||
new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), transformRequest),
|
||||
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), transformRequest),
|
||||
new TimeValue(0),
|
||||
new Actions(actions),
|
||||
metadata,
|
||||
|
@ -204,8 +205,12 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
|
|||
return internalTestCluster().getInstance(AlertsClient.class);
|
||||
}
|
||||
|
||||
protected ScriptService scriptService() {
|
||||
return internalTestCluster().getInstance(ScriptService.class);
|
||||
protected ScriptServiceProxy scriptService() {
|
||||
return internalTestCluster().getInstance(ScriptServiceProxy.class);
|
||||
}
|
||||
|
||||
protected Template.Parser templateParser() {
|
||||
return internalTestCluster().getInstance(Template.Parser.class);
|
||||
}
|
||||
|
||||
protected HttpClient httpClient() {
|
||||
|
@ -216,10 +221,6 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
|
|||
return new NoopEmailService();
|
||||
}
|
||||
|
||||
protected StringTemplateUtils stringTemplateUtils() {
|
||||
return internalTestCluster().getInstance(StringTemplateUtils.class);
|
||||
}
|
||||
|
||||
protected FiredAlert.Parser firedAlertParser() {
|
||||
return internalTestCluster().getInstance(FiredAlert.Parser.class);
|
||||
}
|
||||
|
|
|
@ -15,16 +15,15 @@ import org.elasticsearch.alerts.actions.Action;
|
|||
import org.elasticsearch.alerts.actions.Actions;
|
||||
import org.elasticsearch.alerts.actions.index.IndexAction;
|
||||
import org.elasticsearch.alerts.client.AlertsClient;
|
||||
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
|
||||
import org.elasticsearch.alerts.history.FiredAlert;
|
||||
import org.elasticsearch.alerts.history.HistoryStore;
|
||||
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
|
||||
import org.elasticsearch.alerts.transform.SearchTransform;
|
||||
import org.elasticsearch.alerts.transport.actions.ack.AckAlertResponse;
|
||||
import org.elasticsearch.alerts.transport.actions.get.GetAlertResponse;
|
||||
import org.elasticsearch.alerts.transport.actions.put.PutAlertResponse;
|
||||
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
|
@ -40,9 +39,7 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
|
||||
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
|
||||
/**
|
||||
|
@ -69,9 +66,9 @@ public class AlertThrottleTests extends AbstractAlertingTests {
|
|||
Alert alert = new Alert(
|
||||
"test-serialization",
|
||||
new CronSchedule("0/5 * * * * ? *"),
|
||||
new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
|
||||
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
|
||||
request, "hits.total > 0", ScriptService.ScriptType.INLINE, "groovy"),
|
||||
new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), request),
|
||||
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), request),
|
||||
new TimeValue(0),
|
||||
new Actions(actions),
|
||||
null,
|
||||
|
@ -152,9 +149,9 @@ public class AlertThrottleTests extends AbstractAlertingTests {
|
|||
Alert alert = new Alert(
|
||||
"test-time-throttle",
|
||||
new CronSchedule("0/5 * * * * ? *"),
|
||||
new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
|
||||
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
|
||||
request, "hits.total > 0", ScriptService.ScriptType.INLINE, "groovy"),
|
||||
new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), request),
|
||||
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), request),
|
||||
new TimeValue(10, TimeUnit.SECONDS),
|
||||
new Actions(actions),
|
||||
null,
|
||||
|
|
|
@ -16,7 +16,6 @@ import org.elasticsearch.alerts.history.FiredAlert;
|
|||
import org.elasticsearch.alerts.history.HistoryStore;
|
||||
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
|
||||
import org.elasticsearch.alerts.transform.SearchTransform;
|
||||
import org.elasticsearch.alerts.transport.actions.put.PutAlertResponse;
|
||||
import org.elasticsearch.alerts.transport.actions.stats.AlertsStatsResponse;
|
||||
|
@ -80,9 +79,9 @@ public class BootStrapTest extends AbstractAlertingTests {
|
|||
Alert alert = new Alert(
|
||||
"test-serialization",
|
||||
new CronSchedule("0/5 * * * * ? 2035"),
|
||||
new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
|
||||
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
|
||||
searchRequest, "return true", ScriptService.ScriptType.INLINE, "groovy"),
|
||||
new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), searchRequest),
|
||||
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), searchRequest),
|
||||
new TimeValue(0),
|
||||
new Actions(new ArrayList<Action>()),
|
||||
null,
|
||||
|
@ -140,9 +139,9 @@ public class BootStrapTest extends AbstractAlertingTests {
|
|||
Alert alert = new Alert(
|
||||
"action-test-"+ i + " " + j,
|
||||
new CronSchedule("0/5 * * * * ? 2035"), //Set a cron schedule far into the future so this alert is never scheduled
|
||||
new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
|
||||
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
|
||||
searchRequest, "return true", ScriptService.ScriptType.INLINE, "groovy"),
|
||||
new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), searchRequest),
|
||||
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), searchRequest),
|
||||
new TimeValue(0),
|
||||
new Actions(new ArrayList<Action>()),
|
||||
null,
|
||||
|
|
|
@ -13,7 +13,6 @@ import org.elasticsearch.alerts.actions.index.IndexAction;
|
|||
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
|
||||
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
|
||||
import org.elasticsearch.alerts.transform.SearchTransform;
|
||||
import org.elasticsearch.alerts.transport.actions.put.PutAlertResponse;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
|
@ -59,9 +58,9 @@ public class TransformSearchTest extends AbstractAlertingTests {
|
|||
Alert alert = new Alert(
|
||||
"test-serialization",
|
||||
new CronSchedule("0/5 * * * * ? *"),
|
||||
new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
|
||||
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
|
||||
conditionRequest,"return true", ScriptService.ScriptType.INLINE, "groovy"),
|
||||
new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), transformRequest),
|
||||
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), transformRequest),
|
||||
new TimeValue(0),
|
||||
new Actions(actions),
|
||||
metadata,
|
||||
|
|
|
@ -9,9 +9,10 @@ package org.elasticsearch.alerts.actions;
|
|||
import org.elasticsearch.alerts.AbstractAlertingTests;
|
||||
import org.elasticsearch.alerts.Alert;
|
||||
import org.elasticsearch.alerts.actions.index.IndexAction;
|
||||
import org.elasticsearch.alerts.condition.Condition;
|
||||
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
|
||||
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
|
||||
import org.elasticsearch.alerts.transform.SearchTransform;
|
||||
import org.elasticsearch.alerts.transport.actions.delete.DeleteAlertRequest;
|
||||
import org.elasticsearch.alerts.transport.actions.delete.DeleteAlertResponse;
|
||||
|
@ -19,8 +20,6 @@ import org.elasticsearch.alerts.transport.actions.get.GetAlertRequest;
|
|||
import org.elasticsearch.alerts.transport.actions.get.GetAlertResponse;
|
||||
import org.elasticsearch.alerts.transport.actions.put.PutAlertRequest;
|
||||
import org.elasticsearch.alerts.transport.actions.put.PutAlertResponse;
|
||||
import org.elasticsearch.alerts.condition.Condition;
|
||||
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
|
@ -57,7 +56,7 @@ public class ActionsTest extends AbstractAlertingTests {
|
|||
final List<Action> actionList = new ArrayList<>();
|
||||
actionList.add(alertAction);
|
||||
|
||||
Condition alertCondition = new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()),
|
||||
Condition alertCondition = new ScriptSearchCondition(logger, scriptService(),
|
||||
ClientProxy.of(client()), createConditionSearchRequest(), "return true", ScriptService.ScriptType.INLINE, "groovy");
|
||||
|
||||
|
||||
|
@ -65,7 +64,7 @@ public class ActionsTest extends AbstractAlertingTests {
|
|||
"my-first-alert",
|
||||
new CronSchedule("0/5 * * * * ? *"),
|
||||
alertCondition,
|
||||
new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), createConditionSearchRequest()),
|
||||
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), createConditionSearchRequest()),
|
||||
new TimeValue(0),
|
||||
new Actions(actionList),
|
||||
null,
|
||||
|
|
|
@ -13,8 +13,8 @@ import org.elasticsearch.alerts.actions.email.service.Email;
|
|||
import org.elasticsearch.alerts.actions.email.service.EmailService;
|
||||
import org.elasticsearch.alerts.actions.email.service.Profile;
|
||||
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
|
||||
import org.elasticsearch.alerts.support.StringTemplateUtils;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
|
||||
import org.elasticsearch.alerts.support.template.ScriptTemplate;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.common.joda.time.DateTime;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
|
@ -40,8 +40,6 @@ import java.util.Set;
|
|||
public class EmailActionTest extends ElasticsearchTestCase {
|
||||
|
||||
public void testEmailTemplateRender() throws IOException, MessagingException {
|
||||
StringTemplateUtils.Template template =
|
||||
new StringTemplateUtils.Template("{{alert_name}} executed with {{response.hits.total}} hits");
|
||||
|
||||
Settings settings = ImmutableSettings.settingsBuilder().build();
|
||||
MustacheScriptEngineService mustacheScriptEngineService = new MustacheScriptEngineService(settings);
|
||||
|
@ -49,8 +47,9 @@ public class EmailActionTest extends ElasticsearchTestCase {
|
|||
Set<ScriptEngineService> engineServiceSet = new HashSet<>();
|
||||
engineServiceSet.add(mustacheScriptEngineService);
|
||||
|
||||
ScriptService scriptService = new ScriptService(settings, new Environment(), engineServiceSet, new ResourceWatcherService(settings, threadPool));
|
||||
StringTemplateUtils stringTemplateUtils = new StringTemplateUtils(settings, ScriptServiceProxy.of(scriptService));
|
||||
ScriptServiceProxy scriptService = ScriptServiceProxy.of(new ScriptService(settings, new Environment(), engineServiceSet, new ResourceWatcherService(settings, threadPool)));
|
||||
|
||||
ScriptTemplate template = new ScriptTemplate(scriptService, "{{alert_name}} executed with {{response.hits.total}} hits");
|
||||
|
||||
EmailService emailService = new EmailServiceMock();
|
||||
|
||||
|
@ -64,7 +63,7 @@ public class EmailActionTest extends ElasticsearchTestCase {
|
|||
emailBuilder.from(from);
|
||||
emailBuilder.to(to);
|
||||
|
||||
EmailAction emailAction = new EmailAction(logger, emailService, stringTemplateUtils, emailBuilder,
|
||||
EmailAction emailAction = new EmailAction(logger, emailService, emailBuilder,
|
||||
new Authentication("testname", "testpassword"), Profile.STANDARD, "testaccount", template, template, null, true);
|
||||
|
||||
//This is ok since the execution of the action only relies on the alert name
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.alerts.actions.webhook;
|
||||
|
||||
import org.elasticsearch.alerts.support.StringTemplateUtils;
|
||||
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.script.ScriptEngineService;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.script.mustache.MustacheScriptEngineService;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class WebhookTest extends ElasticsearchTestCase {
|
||||
|
||||
public void testRequestParameterSerialization() throws Exception {
|
||||
|
||||
Map<String, Object> responseMap = new HashMap<>();
|
||||
responseMap.put("hits",0);
|
||||
|
||||
Settings settings = ImmutableSettings.settingsBuilder().build();
|
||||
MustacheScriptEngineService mustacheScriptEngineService = new MustacheScriptEngineService(settings);
|
||||
ThreadPool tp;
|
||||
tp = new ThreadPool(ThreadPool.Names.SAME);
|
||||
Set<ScriptEngineService> engineServiceSet = new HashSet<>();
|
||||
engineServiceSet.add(mustacheScriptEngineService);
|
||||
ScriptService scriptService = new ScriptService(settings, new Environment(), engineServiceSet, new ResourceWatcherService(settings, tp));
|
||||
|
||||
StringTemplateUtils.Template testTemplate = new StringTemplateUtils.Template("{ 'alertname' : '{{alert_name}}', 'response' : { 'hits' : {{response.hits}} } }");
|
||||
String testBody = WebhookAction.applyTemplate(new StringTemplateUtils(settings, ScriptServiceProxy.of(scriptService)), testTemplate, "foobar", responseMap);
|
||||
|
||||
tp.shutdownNow();
|
||||
assertEquals("{ 'alertname' : 'foobar', 'response' : { 'hits' : 0 } }", testBody);
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.alerts.support.template;
|
||||
|
||||
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ScriptTemplateTests extends ElasticsearchTestCase {
|
||||
|
||||
private ScriptServiceProxy proxy;
|
||||
private ExecutableScript script;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
proxy = mock(ScriptServiceProxy.class);
|
||||
script = mock(ExecutableScript.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender() throws Exception {
|
||||
String lang = "_lang";
|
||||
String templateText = "_template";
|
||||
Map<String, Object> params = ImmutableMap.<String, Object>of("param_key", "param_val");
|
||||
Map<String, Object> model = ImmutableMap.<String, Object>of("model_key", "model_val");
|
||||
Map<String, Object> merged = ImmutableMap.<String, Object>builder().putAll(params).putAll(model).build();
|
||||
ScriptService.ScriptType scriptType = ScriptService.ScriptType.values()[randomIntBetween(0, ScriptService.ScriptType.values().length - 1)];
|
||||
|
||||
when(script.run()).thenReturn("rendered_text");
|
||||
when(proxy.executable(lang, templateText, scriptType, merged)).thenReturn(script);
|
||||
|
||||
ScriptTemplate template = new ScriptTemplate(proxy, templateText, lang, scriptType, params);
|
||||
assertThat(template.render(model), is("rendered_text"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_OverridingModel() throws Exception {
|
||||
String lang = "_lang";
|
||||
String templateText = "_template";
|
||||
Map<String, Object> params = ImmutableMap.<String, Object>of("key", "param_val");
|
||||
Map<String, Object> model = ImmutableMap.<String, Object>of("key", "model_val");
|
||||
ScriptService.ScriptType scriptType = randomScriptType();
|
||||
|
||||
|
||||
when(script.run()).thenReturn("rendered_text");
|
||||
when(proxy.executable(lang, templateText, scriptType, model)).thenReturn(script);
|
||||
|
||||
ScriptTemplate template = new ScriptTemplate(proxy, templateText, lang, scriptType, params);
|
||||
assertThat(template.render(model), is("rendered_text"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_Defaults() throws Exception {
|
||||
String templateText = "_template";
|
||||
Map<String, Object> model = ImmutableMap.<String, Object>of("key", "model_val");
|
||||
|
||||
when(script.run()).thenReturn("rendered_text");
|
||||
when(proxy.executable(ScriptTemplate.DEFAULT_LANG, templateText, ScriptService.ScriptType.INLINE, model)).thenReturn(script);
|
||||
|
||||
ScriptTemplate template = new ScriptTemplate(proxy, templateText);
|
||||
assertThat(template.render(model), is("rendered_text"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParser() throws Exception {
|
||||
ScriptTemplate.Parser templateParser = new ScriptTemplate.Parser(ImmutableSettings.EMPTY, proxy);
|
||||
|
||||
ScriptTemplate template = new ScriptTemplate(proxy, "_template", "_lang", randomScriptType(), ImmutableMap.<String, Object>of("param_key", "param_val"));
|
||||
|
||||
XContentBuilder builder = jsonBuilder().startObject()
|
||||
.field(randomFrom("lang", "script_lang"), template.lang())
|
||||
.field(randomFrom("script", "text"), template.text())
|
||||
.field(randomFrom("type", "script_type"), template.type().name())
|
||||
.field(randomFrom("params", "model"), template.params())
|
||||
.endObject();
|
||||
BytesReference bytes = builder.bytes();
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
|
||||
parser.nextToken();
|
||||
ScriptTemplate parsed = templateParser.parse(parser);
|
||||
assertThat(parsed, notNullValue());
|
||||
assertThat(parsed, equalTo(template));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParser_ParserSelfGenerated() throws Exception {
|
||||
ScriptTemplate.Parser templateParser = new ScriptTemplate.Parser(ImmutableSettings.EMPTY, proxy);
|
||||
|
||||
ScriptTemplate template = new ScriptTemplate(proxy, "_template", "_lang", randomScriptType(), ImmutableMap.<String, Object>of("param_key", "param_val"));
|
||||
|
||||
XContentBuilder builder = jsonBuilder().value(template);
|
||||
BytesReference bytes = builder.bytes();
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
|
||||
parser.nextToken();
|
||||
ScriptTemplate parsed = templateParser.parse(parser);
|
||||
assertThat(parsed, notNullValue());
|
||||
assertThat(parsed, equalTo(template));
|
||||
}
|
||||
|
||||
@Test(expected = Template.Parser.ParseException.class)
|
||||
public void testParser_Invalid_UnexpectedField() throws Exception {
|
||||
ScriptTemplate.Parser templateParser = new ScriptTemplate.Parser(ImmutableSettings.EMPTY, proxy);
|
||||
|
||||
XContentBuilder builder = jsonBuilder().startObject()
|
||||
.field("unknown_field", "value")
|
||||
.endObject();
|
||||
BytesReference bytes = builder.bytes();
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
|
||||
parser.nextToken();
|
||||
templateParser.parse(parser);
|
||||
fail("expected parse exception when encountering an unknown field");
|
||||
}
|
||||
|
||||
@Test(expected = Template.Parser.ParseException.class)
|
||||
public void testParser_Invalid_UnknownScriptType() throws Exception {
|
||||
ScriptTemplate.Parser templateParser = new ScriptTemplate.Parser(ImmutableSettings.EMPTY, proxy);
|
||||
|
||||
XContentBuilder builder = jsonBuilder().startObject()
|
||||
.field("lang", ScriptTemplate.DEFAULT_LANG)
|
||||
.field("script", "_template")
|
||||
.field("type", "unknown_type")
|
||||
.startObject("params").endObject()
|
||||
.endObject();
|
||||
BytesReference bytes = builder.bytes();
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
|
||||
parser.nextToken();
|
||||
templateParser.parse(parser);
|
||||
fail("expected parse exception when script type is unknown");
|
||||
}
|
||||
|
||||
@Test(expected = Template.Parser.ParseException.class)
|
||||
public void testParser_Invalid_MissingScript() throws Exception {
|
||||
ScriptTemplate.Parser templateParser = new ScriptTemplate.Parser(ImmutableSettings.EMPTY, proxy);
|
||||
|
||||
XContentBuilder builder = jsonBuilder().startObject()
|
||||
.field("lang", ScriptTemplate.DEFAULT_LANG)
|
||||
.field("type", ScriptService.ScriptType.INDEXED)
|
||||
.startObject("params").endObject()
|
||||
.endObject();
|
||||
BytesReference bytes = builder.bytes();
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
|
||||
parser.nextToken();
|
||||
templateParser.parse(parser);
|
||||
fail("expected parse exception when template text is missing");
|
||||
}
|
||||
|
||||
private static ScriptService.ScriptType randomScriptType() {
|
||||
return randomFrom(ScriptService.ScriptType.values());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.alerts.support.template;
|
||||
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.yaml.YamlXContent;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class XContentTemplateTests extends ElasticsearchTestCase {
|
||||
|
||||
@Test
|
||||
public void testYaml() throws Exception {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
Map<String, Object> a = new HashMap<>();
|
||||
a.put("aa", "aa");
|
||||
a.put("ab", "ab");
|
||||
model.put("a", a);
|
||||
Map<String, Object> b = new HashMap<>();
|
||||
b.put("ba", 21);
|
||||
model.put("b", b);
|
||||
model.put("c", "c");
|
||||
|
||||
String text = XContentTemplate.YAML.render(model);
|
||||
|
||||
// now lets read it as xcontent and make sure it has all the pieces
|
||||
XContentParser parser = YamlXContent.yamlXContent.createParser(new BytesArray(text));
|
||||
XContentParser.Token token = parser.nextToken();
|
||||
assertThat(token, is(XContentParser.Token.START_OBJECT));
|
||||
String currentFieldName = null;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if ("a".equals(currentFieldName)) {
|
||||
assertThat(token, is(XContentParser.Token.START_OBJECT));
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else {
|
||||
assertThat(token, is(XContentParser.Token.VALUE_STRING));
|
||||
if ("aa".equals(currentFieldName)) {
|
||||
assertThat(parser.text(), equalTo("aa"));
|
||||
} else if ("ab".equals(currentFieldName)) {
|
||||
assertThat(parser.text(), equalTo("ab"));
|
||||
} else {
|
||||
fail("unexpected field name [" + currentFieldName + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("b".equals(currentFieldName)) {
|
||||
assertThat(token, is(XContentParser.Token.START_OBJECT));
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else {
|
||||
assertThat(token, is(XContentParser.Token.VALUE_NUMBER));
|
||||
assertThat(currentFieldName, equalTo("ba"));
|
||||
assertThat(parser.intValue(), is(21));
|
||||
}
|
||||
}
|
||||
} else if ("c".equals(currentFieldName)) {
|
||||
assertThat(token, is(XContentParser.Token.VALUE_STRING));
|
||||
assertThat(parser.text(), equalTo("c"));
|
||||
} else {
|
||||
fail("unexpected field [" + currentFieldName + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue