Added painless execute api. (#29164)
Added an api that allows to execute an arbitrary script and a result to be returned. ``` POST /_scripts/painless/_execute { "script": { "source": "params.var1 / params.var2", "params": { "var1": 1, "var2": 1 } } } ``` Relates to #27875
This commit is contained in:
parent
621a1935b8
commit
8afa7c174f
|
@ -0,0 +1,53 @@
|
|||
[[painless-execute-api]]
|
||||
=== Painless execute API
|
||||
|
||||
The Painless execute API allows an arbitrary script to be executed and a result to be returned.
|
||||
|
||||
[[painless-execute-api-parameters]]
|
||||
.Parameters
|
||||
[options="header"]
|
||||
|======
|
||||
| Name | Required | Default | Description
|
||||
| `script` | yes | - | The script to execute
|
||||
| `context` | no | `execute_api_script` | The context the script should be executed in.
|
||||
|======
|
||||
|
||||
==== Contexts
|
||||
|
||||
Contexts control how scripts are executed, what variables are available at runtime and what the return type is.
|
||||
|
||||
===== Painless test script context
|
||||
|
||||
The `painless_test` context executes scripts as is and do not add any special parameters.
|
||||
The only variable that is available is `params`, which can be used to access user defined values.
|
||||
The result of the script is always converted to a string.
|
||||
If no context is specified then this context is used by default.
|
||||
|
||||
==== Example
|
||||
|
||||
Request:
|
||||
|
||||
[source,js]
|
||||
----------------------------------------------------------------
|
||||
POST /_scripts/painless/_execute
|
||||
{
|
||||
"script": {
|
||||
"source": "params.count / params.total",
|
||||
"params": {
|
||||
"count": 100.0,
|
||||
"total": 1000.0
|
||||
}
|
||||
}
|
||||
}
|
||||
----------------------------------------------------------------
|
||||
// CONSOLE
|
||||
|
||||
Response:
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
{
|
||||
"result": "0.1"
|
||||
}
|
||||
--------------------------------------------------
|
||||
// TESTRESPONSE
|
|
@ -389,3 +389,5 @@ dispatch *feels* like it'd add a ton of complexity which'd make maintenance and
|
|||
other improvements much more difficult.
|
||||
|
||||
include::painless-debugging.asciidoc[]
|
||||
|
||||
include::painless-execute-script.asciidoc[]
|
||||
|
|
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.elasticsearch.painless;
|
||||
|
||||
import org.elasticsearch.action.Action;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.action.support.ActionFilters;
|
||||
import org.elasticsearch.action.support.HandledTransportAction;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
import org.elasticsearch.client.node.NodeClient;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.rest.BaseRestHandler;
|
||||
import org.elasticsearch.rest.BytesRestResponse;
|
||||
import org.elasticsearch.rest.RestController;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.rest.RestResponse;
|
||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||
import static org.elasticsearch.rest.RestRequest.Method.GET;
|
||||
import static org.elasticsearch.rest.RestRequest.Method.POST;
|
||||
import static org.elasticsearch.rest.RestStatus.OK;
|
||||
|
||||
public class PainlessExecuteAction extends Action<PainlessExecuteAction.Request, PainlessExecuteAction.Response,
|
||||
PainlessExecuteAction.RequestBuilder> {
|
||||
|
||||
static final PainlessExecuteAction INSTANCE = new PainlessExecuteAction();
|
||||
private static final String NAME = "cluster:admin/scripts/painless/execute";
|
||||
|
||||
private PainlessExecuteAction() {
|
||||
super(NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
||||
return new RequestBuilder(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response newResponse() {
|
||||
return new Response();
|
||||
}
|
||||
|
||||
public static class Request extends ActionRequest implements ToXContent {
|
||||
|
||||
private static final ParseField SCRIPT_FIELD = new ParseField("script");
|
||||
private static final ParseField CONTEXT_FIELD = new ParseField("context");
|
||||
private static final ConstructingObjectParser<Request, Void> PARSER = new ConstructingObjectParser<>(
|
||||
"painless_execute_request", args -> new Request((Script) args[0], (SupportedContext) args[1]));
|
||||
|
||||
static {
|
||||
PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, c) -> Script.parse(p), SCRIPT_FIELD);
|
||||
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> {
|
||||
// For now only accept an empty json object:
|
||||
XContentParser.Token token = p.nextToken();
|
||||
assert token == XContentParser.Token.FIELD_NAME;
|
||||
String contextType = p.currentName();
|
||||
token = p.nextToken();
|
||||
assert token == XContentParser.Token.START_OBJECT;
|
||||
token = p.nextToken();
|
||||
assert token == XContentParser.Token.END_OBJECT;
|
||||
token = p.nextToken();
|
||||
assert token == XContentParser.Token.END_OBJECT;
|
||||
return SupportedContext.valueOf(contextType.toUpperCase(Locale.ROOT));
|
||||
}, CONTEXT_FIELD);
|
||||
}
|
||||
|
||||
private Script script;
|
||||
private SupportedContext context;
|
||||
|
||||
static Request parse(XContentParser parser) throws IOException {
|
||||
return PARSER.parse(parser, null);
|
||||
}
|
||||
|
||||
Request(Script script, SupportedContext context) {
|
||||
this.script = Objects.requireNonNull(script);
|
||||
this.context = context != null ? context : SupportedContext.PAINLESS_TEST;
|
||||
}
|
||||
|
||||
Request() {
|
||||
}
|
||||
|
||||
public Script getScript() {
|
||||
return script;
|
||||
}
|
||||
|
||||
public SupportedContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
ActionRequestValidationException validationException = null;
|
||||
if (script.getType() != ScriptType.INLINE) {
|
||||
validationException = addValidationError("only inline scripts are supported", validationException);
|
||||
}
|
||||
return validationException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
script = new Script(in);
|
||||
context = SupportedContext.fromId(in.readByte());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
script.writeTo(out);
|
||||
out.writeByte(context.id);
|
||||
}
|
||||
|
||||
// For testing only:
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.field(SCRIPT_FIELD.getPreferredName(), script);
|
||||
builder.startObject(CONTEXT_FIELD.getPreferredName());
|
||||
{
|
||||
builder.startObject(context.name());
|
||||
builder.endObject();
|
||||
}
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Request request = (Request) o;
|
||||
return Objects.equals(script, request.script) &&
|
||||
context == request.context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(script, context);
|
||||
}
|
||||
|
||||
public enum SupportedContext {
|
||||
|
||||
PAINLESS_TEST((byte) 0);
|
||||
|
||||
private final byte id;
|
||||
|
||||
SupportedContext(byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static SupportedContext fromId(byte id) {
|
||||
switch (id) {
|
||||
case 0:
|
||||
return PAINLESS_TEST;
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown context [" + id + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder> {
|
||||
|
||||
RequestBuilder(ElasticsearchClient client) {
|
||||
super(client, INSTANCE, new Request());
|
||||
}
|
||||
}
|
||||
|
||||
public static class Response extends ActionResponse implements ToXContentObject {
|
||||
|
||||
private Object result;
|
||||
|
||||
Response() {}
|
||||
|
||||
Response(Object result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public Object getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
result = in.readGenericValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeGenericValue(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
builder.field("result", result);
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Response response = (Response) o;
|
||||
return Objects.equals(result, response.result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(result);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class PainlessTestScript {
|
||||
|
||||
private final Map<String, Object> params;
|
||||
|
||||
public PainlessTestScript(Map<String, Object> params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
/** Return the parameters for this script. */
|
||||
public Map<String, Object> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public abstract Object execute();
|
||||
|
||||
public interface Factory {
|
||||
|
||||
PainlessTestScript newInstance(Map<String, Object> params);
|
||||
|
||||
}
|
||||
|
||||
public static final String[] PARAMETERS = {};
|
||||
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("painless_test", Factory.class);
|
||||
|
||||
}
|
||||
|
||||
public static class TransportAction extends HandledTransportAction<Request, Response> {
|
||||
|
||||
|
||||
private final ScriptService scriptService;
|
||||
|
||||
@Inject
|
||||
public TransportAction(Settings settings, ThreadPool threadPool, TransportService transportService,
|
||||
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
|
||||
ScriptService scriptService) {
|
||||
super(settings, NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver, Request::new);
|
||||
this.scriptService = scriptService;
|
||||
}
|
||||
@Override
|
||||
protected void doExecute(Request request, ActionListener<Response> listener) {
|
||||
switch (request.context) {
|
||||
case PAINLESS_TEST:
|
||||
PainlessTestScript.Factory factory = scriptService.compile(request.script, PainlessTestScript.CONTEXT);
|
||||
PainlessTestScript painlessTestScript = factory.newInstance(request.script.getParams());
|
||||
String result = Objects.toString(painlessTestScript.execute());
|
||||
listener.onResponse(new Response(result));
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("unsupported context [" + request.context + "]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class RestAction extends BaseRestHandler {
|
||||
|
||||
RestAction(Settings settings, RestController controller) {
|
||||
super(settings);
|
||||
controller.registerHandler(GET, "/_scripts/painless/_execute", this);
|
||||
controller.registerHandler(POST, "/_scripts/painless/_execute", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "_scripts_painless_execute";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException {
|
||||
final Request request = Request.parse(restRequest.contentOrSourceParamParser());
|
||||
return channel -> client.executeLocally(INSTANCE, request, new RestBuilderListener<Response>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(Response response, XContentBuilder builder) throws Exception {
|
||||
response.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
return new BytesRestResponse(OK, builder);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -20,28 +20,40 @@
|
|||
package org.elasticsearch.painless;
|
||||
|
||||
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.cluster.node.DiscoveryNodes;
|
||||
import org.elasticsearch.common.settings.ClusterSettings;
|
||||
import org.elasticsearch.common.settings.IndexScopedSettings;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsFilter;
|
||||
import org.elasticsearch.painless.spi.PainlessExtension;
|
||||
import org.elasticsearch.painless.spi.Whitelist;
|
||||
import org.elasticsearch.plugins.ActionPlugin;
|
||||
import org.elasticsearch.plugins.ExtensiblePlugin;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.plugins.ScriptPlugin;
|
||||
import org.elasticsearch.rest.RestController;
|
||||
import org.elasticsearch.rest.RestHandler;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptEngine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Registers Painless as a plugin.
|
||||
*/
|
||||
public final class PainlessPlugin extends Plugin implements ScriptPlugin, ExtensiblePlugin {
|
||||
public final class PainlessPlugin extends Plugin implements ScriptPlugin, ExtensiblePlugin, ActionPlugin {
|
||||
|
||||
private final Map<ScriptContext<?>, List<Whitelist>> extendedWhitelists = new HashMap<>();
|
||||
|
||||
|
@ -74,4 +86,24 @@ public final class PainlessPlugin extends Plugin implements ScriptPlugin, Extens
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public List<ScriptContext> getContexts() {
|
||||
return Collections.singletonList(PainlessExecuteAction.PainlessTestScript.CONTEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
|
||||
return Collections.singletonList(
|
||||
new ActionHandler<>(PainlessExecuteAction.INSTANCE, PainlessExecuteAction.TransportAction.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RestHandler> getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings,
|
||||
IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter,
|
||||
IndexNameExpressionResolver indexNameExpressionResolver,
|
||||
Supplier<DiscoveryNodes> nodesInCluster) {
|
||||
return Collections.singletonList(new PainlessExecuteAction.RestAction(settings, restController));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.elasticsearch.painless;
|
||||
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
public class PainlessExecuteRequestTests extends AbstractStreamableXContentTestCase<PainlessExecuteAction.Request> {
|
||||
|
||||
@Override
|
||||
protected PainlessExecuteAction.Request createTestInstance() {
|
||||
Script script = new Script(randomAlphaOfLength(10));
|
||||
PainlessExecuteAction.Request.SupportedContext context = randomBoolean() ?
|
||||
PainlessExecuteAction.Request.SupportedContext.PAINLESS_TEST : null;
|
||||
return new PainlessExecuteAction.Request(script, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PainlessExecuteAction.Request createBlankInstance() {
|
||||
return new PainlessExecuteAction.Request();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PainlessExecuteAction.Request doParseInstance(XContentParser parser) throws IOException {
|
||||
return PainlessExecuteAction.Request.parse(parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsUnknownFields() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void testValidate() {
|
||||
Script script = new Script(ScriptType.STORED, null, randomAlphaOfLength(10), Collections.emptyMap());
|
||||
PainlessExecuteAction.Request request = new PainlessExecuteAction.Request(script, null);
|
||||
Exception e = request.validate();
|
||||
assertNotNull(e);
|
||||
assertEquals("Validation Failed: 1: only inline scripts are supported;", e.getMessage());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.elasticsearch.painless;
|
||||
|
||||
import org.elasticsearch.test.AbstractStreamableTestCase;
|
||||
|
||||
public class PainlessExecuteResponseTests extends AbstractStreamableTestCase<PainlessExecuteAction.Response> {
|
||||
|
||||
@Override
|
||||
protected PainlessExecuteAction.Response createBlankInstance() {
|
||||
return new PainlessExecuteAction.Response();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PainlessExecuteAction.Response createTestInstance() {
|
||||
return new PainlessExecuteAction.Response(randomAlphaOfLength(10));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
"Execute with defaults":
|
||||
- do:
|
||||
scripts_painless_execute:
|
||||
body:
|
||||
script:
|
||||
source: "params.count / params.total"
|
||||
params:
|
||||
count: 100.0
|
||||
total: 1000.0
|
||||
- match: { result: "0.1" }
|
||||
|
||||
---
|
||||
"Execute with execute_api_script context":
|
||||
- do:
|
||||
scripts_painless_execute:
|
||||
body:
|
||||
script:
|
||||
source: "params.var1 - params.var2"
|
||||
params:
|
||||
var1: 10
|
||||
var2: 100
|
||||
context:
|
||||
painless_test: {}
|
||||
- match: { result: "-90" }
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"scripts_painless_execute": {
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html",
|
||||
"methods": ["GET", "POST"],
|
||||
"url": {
|
||||
"path": "/_scripts/painless/_execute",
|
||||
"paths": ["/_scripts/painless/_execute"],
|
||||
"parts": {
|
||||
},
|
||||
"params": {
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"description": "The script to execute"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue