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

Scripting: Add watcher script contexts #34059

Merged
merged 15 commits into from
Sep 28, 2018
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@

package org.elasticsearch.script;

import org.apache.logging.log4j.LogManager;
import org.elasticsearch.common.logging.DeprecationLogger;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.elasticsearch.common.logging.DeprecationLogger;

public final class ParameterMap implements Map<String, Object> {

Expand All @@ -34,7 +35,7 @@ public final class ParameterMap implements Map<String, Object> {

private final Map<String, String> deprecations;

ParameterMap(Map<String, Object> params, Map<String, String> deprecations) {
public ParameterMap(Map<String, Object> params, Map<String, String> deprecations) {
this.params = params;
this.deprecations = deprecations;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
import org.elasticsearch.plugins.ScriptPlugin;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.SearchScript;
Expand Down Expand Up @@ -106,6 +105,7 @@
import org.elasticsearch.xpack.watcher.condition.InternalAlwaysCondition;
import org.elasticsearch.xpack.watcher.condition.NeverCondition;
import org.elasticsearch.xpack.watcher.condition.ScriptCondition;
import org.elasticsearch.xpack.watcher.condition.WatcherConditionScript;
import org.elasticsearch.xpack.watcher.execution.AsyncTriggerEventConsumer;
import org.elasticsearch.xpack.watcher.execution.ExecutionService;
import org.elasticsearch.xpack.watcher.execution.InternalWatchExecutor;
Expand Down Expand Up @@ -152,6 +152,7 @@
import org.elasticsearch.xpack.watcher.support.search.WatcherSearchTemplateService;
import org.elasticsearch.xpack.watcher.transform.script.ScriptTransform;
import org.elasticsearch.xpack.watcher.transform.script.ScriptTransformFactory;
import org.elasticsearch.xpack.watcher.transform.script.WatcherTransformScript;
import org.elasticsearch.xpack.watcher.transform.search.SearchTransform;
import org.elasticsearch.xpack.watcher.transform.search.SearchTransformFactory;
import org.elasticsearch.xpack.watcher.transport.actions.ack.TransportAckWatchAction;
Expand Down Expand Up @@ -225,9 +226,6 @@ public class Watcher extends Plugin implements ActionPlugin, ScriptPlugin, Reloa

public static final ScriptContext<SearchScript.Factory> SCRIPT_SEARCH_CONTEXT =
new ScriptContext<>("xpack", SearchScript.Factory.class);
// TODO: remove this context when each xpack script use case has their own contexts
public static final ScriptContext<ExecutableScript.Factory> SCRIPT_EXECUTABLE_CONTEXT
= new ScriptContext<>("xpack_executable", ExecutableScript.Factory.class);
public static final ScriptContext<TemplateScript.Factory> SCRIPT_TEMPLATE_CONTEXT
= new ScriptContext<>("xpack_template", TemplateScript.Factory.class);

Expand Down Expand Up @@ -673,7 +671,8 @@ public List<BootstrapCheck> getBootstrapChecks() {

@Override
public List<ScriptContext<?>> getContexts() {
return Arrays.asList(Watcher.SCRIPT_SEARCH_CONTEXT, Watcher.SCRIPT_EXECUTABLE_CONTEXT, Watcher.SCRIPT_TEMPLATE_CONTEXT);
return Arrays.asList(Watcher.SCRIPT_SEARCH_CONTEXT, WatcherTransformScript.CONTEXT,
WatcherConditionScript.CONTEXT, Watcher.SCRIPT_TEMPLATE_CONTEXT);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public ExecutableEmailAction(EmailAction action, Logger logger, EmailService ema
}

public Action.Result execute(String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
Map<String, Object> model = Variables.createCtxParamsMap(ctx, payload);

Map<String, Attachment> attachments = new HashMap<>();
DataAttachment dataAttachment = action.getDataAttachment();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public Action.Result execute(final String actionId, WatchExecutionContext ctx, P
// watch/action were created.
account.validateParsedTemplate(ctx.id().watchId(), actionId, action.message);

Map<String, Object> model = Variables.createCtxModel(ctx, payload);
Map<String, Object> model = Variables.createCtxParamsMap(ctx, payload);
HipChatMessage message = account.render(ctx.id().watchId(), actionId, templateEngine, action.message, model);

if (ctx.simulateAction(actionId)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public Action.Result execute(final String actionId, WatchExecutionContext ctx, P
throw new IllegalStateException("account [" + action.account + "] was not found. perhaps it was deleted");
}

final Function<String, String> render = s -> engine.render(new TextTemplate(s), Variables.createCtxModel(ctx, payload));
final Function<String, String> render = s -> engine.render(new TextTemplate(s), Variables.createCtxParamsMap(ctx, payload));

Map<String, Object> fields = new HashMap<>();
// Apply action fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Logger textLogger() {

@Override
public Action.Result execute(String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
Map<String, Object> model = Variables.createCtxParamsMap(ctx, payload);

String loggedText = templateEngine.render(action.text, model);
if (ctx.simulateAction(actionId)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public Action.Result execute(final String actionId, WatchExecutionContext ctx, P
throw new IllegalStateException("account [" + action.event.account + "] was not found. perhaps it was deleted");
}

Map<String, Object> model = Variables.createCtxModel(ctx, payload);
Map<String, Object> model = Variables.createCtxParamsMap(ctx, payload);
IncidentEvent event = action.event.render(ctx.watch().id(), actionId, templateEngine, model, account.getDefaults());

if (ctx.simulateAction(actionId)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public Action.Result execute(final String actionId, WatchExecutionContext ctx, P
throw new IllegalStateException("account [" + action.account + "] was not found. perhaps it was deleted");
}

Map<String, Object> model = Variables.createCtxModel(ctx, payload);
Map<String, Object> model = Variables.createCtxParamsMap(ctx, payload);
SlackMessage message = action.message.render(ctx.id().watchId(), actionId, templateEngine, model, account.getMessageDefaults());

if (ctx.simulateAction(actionId)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public ExecutableWebhookAction(WebhookAction action, Logger logger, HttpClient h

@Override
public Action.Result execute(String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
Map<String, Object> model = Variables.createCtxParamsMap(ctx, payload);

HttpRequest request = action.requestTemplate.render(templateEngine, model);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ protected AbstractCompareCondition(String type, Clock clock) {
@Override
public final Result execute(WatchExecutionContext ctx) {
Map<String, Object> resolvedValues = new HashMap<>();
Map<String, Object> model = Variables.createCtxModel(ctx, ctx.payload());
Map<String, Object> model = Variables.createCtxParamsMap(ctx, ctx.payload());
return doExecute(model, resolvedValues);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,15 @@
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.xpack.core.watcher.condition.ExecutableCondition;
import org.elasticsearch.xpack.core.watcher.execution.WatchExecutionContext;
import org.elasticsearch.xpack.watcher.Watcher;
import org.elasticsearch.xpack.watcher.support.Variables;

import java.io.IOException;
import java.util.Map;

import static org.elasticsearch.xpack.core.watcher.support.Exceptions.illegalState;

/**
* This class executes a script against the ctx payload and returns a boolean
*/
Expand All @@ -30,16 +26,16 @@ public final class ScriptCondition implements ExecutableCondition {
private static final Result UNMET = new Result(null, TYPE, false);

private final Script script;
private final ExecutableScript.Factory scriptFactory;
private final WatcherConditionScript.Factory scriptFactory;

public ScriptCondition(Script script) {
this.script = script;
scriptFactory = null;
this.scriptFactory = null;
}

ScriptCondition(Script script, ExecutableScript.Factory scriptFactory) {
ScriptCondition(Script script, ScriptService scriptService) {
this.script = script;
this.scriptFactory = scriptFactory;
this.scriptFactory = scriptService.compile(script, WatcherConditionScript.CONTEXT);
}

public Script getScript() {
Expand All @@ -49,7 +45,7 @@ public Script getScript() {
public static ScriptCondition parse(ScriptService scriptService, String watchId, XContentParser parser) throws IOException {
try {
Script script = Script.parse(parser);
return new ScriptCondition(script, scriptService.compile(script, Watcher.SCRIPT_EXECUTABLE_CONTEXT));
return new ScriptCondition(script, scriptService);
} catch (ElasticsearchParseException pe) {
throw new ElasticsearchParseException("could not parse [{}] condition for watch [{}]. failed to parse script", pe, TYPE,
watchId);
Expand All @@ -62,17 +58,12 @@ public Result execute(WatchExecutionContext ctx) {
}

public Result doExecute(WatchExecutionContext ctx) {
Map<String, Object> parameters = Variables.createCtxModel(ctx, ctx.payload());
Map<String, Object> parameters = Variables.createCtxParamsMap(ctx, ctx.payload());
if (script.getParams() != null && !script.getParams().isEmpty()) {
parameters.putAll(script.getParams());
}
ExecutableScript executable = scriptFactory.newInstance(parameters);
Object value = executable.run();
if (value instanceof Boolean) {
return (Boolean) value ? MET : UNMET;
}
throw illegalState("condition [{}] must return a boolean value (true|false) but instead returned [{}]", type(), ctx.watch().id(),
script, value);
WatcherConditionScript conditionScript = scriptFactory.newInstance(script.getParams(), ctx);
return conditionScript.execute() ? MET : UNMET;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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.xpack.watcher.condition;

import org.elasticsearch.script.ParameterMap;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.xpack.core.watcher.execution.WatchExecutionContext;
import org.elasticsearch.xpack.watcher.support.Variables;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
* A script to determine whether a watch should be run.
*/
public abstract class WatcherConditionScript {
public static final String[] PARAMETERS = {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This constant must exist for all script classes, even if the execute method takes zero arguments.


private static final Map<String, String> DEPRECATIONS;

static {
Map<String, String> deprecations = new HashMap<>();
deprecations.put(
"ctx",
"Accessing variable [ctx] via [params.ctx] from within a watcher_condition script " +
"is deprecated in favor of directly accessing [ctx]."
);
DEPRECATIONS = Collections.unmodifiableMap(deprecations);
}

private final Map<String, Object> params;
// TODO: ctx should have its members extracted into execute parameters, but it needs to be a member for bwc access in params
private final Map<String, Object> ctx;

public WatcherConditionScript(Map<String, Object> params, WatchExecutionContext watcherContext) {
Map<String, Object> paramsWithCtx = new HashMap<>(params);
Map<String, Object> ctx = Variables.createCtx(watcherContext, watcherContext.payload());
paramsWithCtx.put("ctx", ctx);
this.params = new ParameterMap(Collections.unmodifiableMap(paramsWithCtx), DEPRECATIONS);
this.ctx = ctx;
}

public abstract boolean execute();

public Map<String, Object> getParams() {
return params;
}

public Map<String, Object> getCtx() {
return ctx;
}

public interface Factory {
WatcherConditionScript newInstance(Map<String, Object> params, WatchExecutionContext watcherContext);
}

public static ScriptContext<Factory> CONTEXT = new ScriptContext<>("watcher_condition", Factory.class);
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public ExecutableHttpInput(HttpInput input, Logger logger, HttpClient client, Te
public HttpInput.Result execute(WatchExecutionContext ctx, Payload payload) {
HttpRequest request = null;
try {
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
Map<String, Object> model = Variables.createCtxParamsMap(ctx, payload);
request = input.getRequest().render(templateEngine, model);
return doExecute(ctx, request);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public DataAttachment parse(String id, XContentParser parser) throws IOException

@Override
public Attachment toAttachment(WatchExecutionContext ctx, Payload payload, DataAttachment attachment) throws IOException {
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
Map<String, Object> model = Variables.createCtxParamsMap(ctx, payload);
return attachment.getDataAttachment().create(attachment.id(), model);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public HttpRequestAttachment parse(String id, XContentParser parser) throws IOEx
@Override
public Attachment toAttachment(WatchExecutionContext context, Payload payload,
HttpRequestAttachment attachment) throws IOException {
Map<String, Object> model = Variables.createCtxModel(context, payload);
Map<String, Object> model = Variables.createCtxParamsMap(context, payload);
HttpRequest httpRequest = attachment.getRequestTemplate().render(templateEngine, model);

HttpResponse response = httpClient.execute(httpRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public ReportingAttachment parse(String id, XContentParser parser) throws IOExce

@Override
public Attachment toAttachment(WatchExecutionContext context, Payload payload, ReportingAttachment attachment) throws IOException {
Map<String, Object> model = Variables.createCtxModel(context, payload);
Map<String, Object> model = Variables.createCtxParamsMap(context, payload);

String initialUrl = templateEngine.render(new TextTemplate(attachment.url()), model);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ public final class Variables {
public static final String METADATA = "metadata";
public static final String VARS = "vars";

public static Map<String, Object> createCtxModel(WatchExecutionContext ctx, Payload payload) {
/** Creates a ctx map and puts it into the returned map as "ctx". */
public static Map<String, Object> createCtxParamsMap(WatchExecutionContext ctx, Payload payload) {
Map<String, Object> model = new HashMap<>();
model.put(CTX, createCtx(ctx, payload));
return model;
}

/** Creates a ctx map. */
public static Map<String, Object> createCtx(WatchExecutionContext ctx, Payload payload) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need this? should it be @deprecated?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean. This was extracted from the previous method to allow only getting the actual ctx map, but not putting it into an outer params map (the new contexts need this so they can put it into the wrapped ParameterMap for deprecation warnings).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea i just mean if we dont want coders (me and @spinscale) accidentally using the createCtx method, u could private it, or if other things are still using it, mark it @deprecated in favor of using the other method createCtxParamsMap

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both are used for now. I will add a comment to try to make it clearer which should be used when.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you have just a second createCtxParamsMap method that takes another map as parameter? Which is an empty hash map by default used to fill and the parmaters in WatcherConditionScript?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having 2 methods with the same name would be more confusing. Do the java docs I added not make sense?

Map<String, Object> ctxModel = new HashMap<>();
ctxModel.put(ID, ctx.id().value());
ctxModel.put(WATCH_ID, ctx.id().watchId());
Expand All @@ -33,10 +41,6 @@ public static Map<String, Object> createCtxModel(WatchExecutionContext ctx, Payl
}
ctxModel.put(METADATA, ctx.watch().metadata());
ctxModel.put(VARS, ctx.vars());
Map<String, Object> model = new HashMap<>();
model.put(CTX, ctxModel);
return model;
return ctxModel;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public WatcherSearchTemplateService(Settings settings, ScriptService scriptServi
public String renderTemplate(Script source, WatchExecutionContext ctx, Payload payload) throws IOException {
// Due the inconsistency with templates in ES 1.x, we maintain our own template format.
// This template format we use now, will become the template structure in ES 2.0
Map<String, Object> watcherContextParams = Variables.createCtxModel(ctx, payload);
Map<String, Object> watcherContextParams = Variables.createCtxParamsMap(ctx, payload);
// Here we convert watcher template into a ES core templates. Due to the different format we use, we
// convert to the template format used in ES core
if (source.getParams() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.xpack.core.watcher.execution.WatchExecutionContext;
import org.elasticsearch.xpack.core.watcher.transform.ExecutableTransform;
import org.elasticsearch.xpack.core.watcher.watch.Payload;
import org.elasticsearch.xpack.watcher.Watcher;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static org.elasticsearch.xpack.watcher.support.Variables.createCtxModel;
import static org.elasticsearch.xpack.watcher.transform.script.ScriptTransform.TYPE;

public class ExecutableScriptTransform extends ExecutableTransform<ScriptTransform, ScriptTransform.Result> {
Expand All @@ -32,7 +29,7 @@ public ExecutableScriptTransform(ScriptTransform transform, Logger logger, Scrip
this.scriptService = scriptService;
Script script = transform.getScript();
// try to compile so we catch syntax errors early
scriptService.compile(script, Watcher.SCRIPT_EXECUTABLE_CONTEXT);
scriptService.compile(script, WatcherTransformScript.CONTEXT);
}

@Override
Expand All @@ -47,14 +44,9 @@ public ScriptTransform.Result execute(WatchExecutionContext ctx, Payload payload

ScriptTransform.Result doExecute(WatchExecutionContext ctx, Payload payload) throws IOException {
Script script = transform.getScript();
Map<String, Object> model = new HashMap<>();
if (script.getParams() != null) {
model.putAll(script.getParams());
}
model.putAll(createCtxModel(ctx, payload));
ExecutableScript.Factory factory = scriptService.compile(script, Watcher.SCRIPT_EXECUTABLE_CONTEXT);
ExecutableScript executable = factory.newInstance(model);
Object value = executable.run();
WatcherTransformScript.Factory factory = scriptService.compile(script, WatcherTransformScript.CONTEXT);
WatcherTransformScript transformScript = factory.newInstance(script.getParams(), ctx, payload);
Object value = transformScript.execute();
// TODO: deprecate one of these styles (returning a map or returning an opaque value below)
if (value instanceof Map) {
return new ScriptTransform.Result(new Payload.Simple((Map<String, Object>) value));
Expand Down
Loading