Skip to content

Commit

Permalink
Scripting: add available languages & contexts API (#49652) (#49815)
Browse files Browse the repository at this point in the history
Adds `GET /_script_language` to support Kibana dynamic scripting
language selection.

Response contains whether `inline` and/or `stored` scripts are
enabled as determined by the `script.allowed_types` settings.

For each scripting language registered, such as `painless`,
`expression`, `mustache` or custom, available contexts for the language
are included as determined by the `script.allowed_contexts` setting.

Response format:
```
{
  "types_allowed": [
    "inline",
    "stored"
  ],
  "language_contexts": [
    {
      "language": "expression",
      "contexts": [
        "aggregation_selector",
        "aggs"
        ...
      ]
    },
    {
      "language": "painless",
      "contexts": [
        "aggregation_selector",
        "aggs",
        "aggs_combine",
        ...
      ]
    }
...
  ]
}
```

Fixes: #49463 

**Backport**
  • Loading branch information
stu-elastic authored Dec 4, 2019
1 parent dbf6183 commit 426c7a5
Show file tree
Hide file tree
Showing 21 changed files with 866 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@ public void testApiNamingConventions() throws Exception {
"cluster.remote_info",
"create",
"get_script_context",
"get_script_languages",
"get_source",
"indices.exists_type",
"indices.get_upgrade",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,12 @@
import java.security.PrivilegedAction;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/**
* Provides the infrastructure for Lucene expressions as a scripting language for Elasticsearch.
Expand All @@ -65,6 +68,45 @@ public class ExpressionScriptEngine implements ScriptEngine {

public static final String NAME = "expression";

private static Map<ScriptContext<?>, Function<Expression,Object>> contexts;

static {
Map<ScriptContext<?>, Function<Expression,Object>> contexts = new HashMap<ScriptContext<?>, Function<Expression,Object>>();
contexts.put(BucketAggregationScript.CONTEXT,
ExpressionScriptEngine::newBucketAggregationScriptFactory);

contexts.put(BucketAggregationSelectorScript.CONTEXT,
(Expression expr) -> {
BucketAggregationScript.Factory factory = newBucketAggregationScriptFactory(expr);
BucketAggregationSelectorScript.Factory wrappedFactory = parameters -> new BucketAggregationSelectorScript(parameters) {
@Override
public boolean execute() {
return factory.newInstance(getParams()).execute().doubleValue() == 1.0;
}
};
return wrappedFactory; });

contexts.put(FilterScript.CONTEXT,
(Expression expr) -> (FilterScript.Factory) (p, lookup) -> newFilterScript(expr, lookup, p));

contexts.put(ScoreScript.CONTEXT,
(Expression expr) -> (ScoreScript.Factory) (p, lookup) -> newScoreScript(expr, lookup, p));

contexts.put(TermsSetQueryScript.CONTEXT,
(Expression expr) -> (TermsSetQueryScript.Factory) (p, lookup) -> newTermsSetQueryScript(expr, lookup, p));

contexts.put(AggregationScript.CONTEXT,
(Expression expr) -> (AggregationScript.Factory) (p, lookup) -> newAggregationScript(expr, lookup, p));

contexts.put(NumberSortScript.CONTEXT,
(Expression expr) -> (NumberSortScript.Factory) (p, lookup) -> newSortScript(expr, lookup, p));

contexts.put(FieldScript.CONTEXT,
(Expression expr) -> (FieldScript.Factory) (p, lookup) -> newFieldScript(expr, lookup, p));

ExpressionScriptEngine.contexts = Collections.unmodifiableMap(contexts);
}

@Override
public String getType() {
return NAME;
Expand Down Expand Up @@ -102,37 +144,15 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
}
}
});
if (context.instanceClazz.equals(BucketAggregationScript.class)) {
return context.factoryClazz.cast(newBucketAggregationScriptFactory(expr));
} else if (context.instanceClazz.equals(BucketAggregationSelectorScript.class)) {
BucketAggregationScript.Factory factory = newBucketAggregationScriptFactory(expr);
BucketAggregationSelectorScript.Factory wrappedFactory = parameters -> new BucketAggregationSelectorScript(parameters) {
@Override
public boolean execute() {
return factory.newInstance(getParams()).execute().doubleValue() == 1.0;
}
};
return context.factoryClazz.cast(wrappedFactory);
} else if (context.instanceClazz.equals(FilterScript.class)) {
FilterScript.Factory factory = (p, lookup) -> newFilterScript(expr, lookup, p);
return context.factoryClazz.cast(factory);
} else if (context.instanceClazz.equals(ScoreScript.class)) {
ScoreScript.Factory factory = (p, lookup) -> newScoreScript(expr, lookup, p);
return context.factoryClazz.cast(factory);
} else if (context.instanceClazz.equals(TermsSetQueryScript.class)) {
TermsSetQueryScript.Factory factory = (p, lookup) -> newTermsSetQueryScript(expr, lookup, p);
return context.factoryClazz.cast(factory);
} else if (context.instanceClazz.equals(AggregationScript.class)) {
AggregationScript.Factory factory = (p, lookup) -> newAggregationScript(expr, lookup, p);
return context.factoryClazz.cast(factory);
} else if (context.instanceClazz.equals(NumberSortScript.class)) {
NumberSortScript.Factory factory = (p, lookup) -> newSortScript(expr, lookup, p);
return context.factoryClazz.cast(factory);
} else if (context.instanceClazz.equals(FieldScript.class)) {
FieldScript.Factory factory = (p, lookup) -> newFieldScript(expr, lookup, p);
return context.factoryClazz.cast(factory);
if (contexts.containsKey(context) == false) {
throw new IllegalArgumentException("expression engine does not know how to handle script context [" + context.name + "]");
}
throw new IllegalArgumentException("expression engine does not know how to handle script context [" + context.name + "]");
return context.factoryClazz.cast(contexts.get(context).apply(expr));
}

@Override
public Set<ScriptContext<?>> getSupportedContexts() {
return contexts.keySet();
}

private static BucketAggregationScript.Factory newBucketAggregationScriptFactory(Expression expr) {
Expand Down Expand Up @@ -166,7 +186,7 @@ public Double execute() {
};
}

private NumberSortScript.LeafFactory newSortScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
private static NumberSortScript.LeafFactory newSortScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
// instead of complicating SimpleBindings (which should stay simple)
SimpleBindings bindings = new SimpleBindings();
Expand All @@ -193,7 +213,7 @@ private NumberSortScript.LeafFactory newSortScript(Expression expr, SearchLookup
return new ExpressionNumberSortScript(expr, bindings, needsScores);
}

private TermsSetQueryScript.LeafFactory newTermsSetQueryScript(Expression expr, SearchLookup lookup,
private static TermsSetQueryScript.LeafFactory newTermsSetQueryScript(Expression expr, SearchLookup lookup,
@Nullable Map<String, Object> vars) {
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
// instead of complicating SimpleBindings (which should stay simple)
Expand All @@ -216,7 +236,7 @@ private TermsSetQueryScript.LeafFactory newTermsSetQueryScript(Expression expr,
return new ExpressionTermSetQueryScript(expr, bindings);
}

private AggregationScript.LeafFactory newAggregationScript(Expression expr, SearchLookup lookup,
private static AggregationScript.LeafFactory newAggregationScript(Expression expr, SearchLookup lookup,
@Nullable Map<String, Object> vars) {
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
// instead of complicating SimpleBindings (which should stay simple)
Expand Down Expand Up @@ -252,7 +272,7 @@ private AggregationScript.LeafFactory newAggregationScript(Expression expr, Sear
return new ExpressionAggregationScript(expr, bindings, needsScores, specialValue);
}

private FieldScript.LeafFactory newFieldScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
private static FieldScript.LeafFactory newFieldScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
SimpleBindings bindings = new SimpleBindings();
for (String variable : expr.variables) {
try {
Expand All @@ -273,7 +293,7 @@ private FieldScript.LeafFactory newFieldScript(Expression expr, SearchLookup loo
* This is a hack for filter scripts, which must return booleans instead of doubles as expression do.
* See https://github.com/elastic/elasticsearch/issues/26429.
*/
private FilterScript.LeafFactory newFilterScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
private static FilterScript.LeafFactory newFilterScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
ScoreScript.LeafFactory searchLeafFactory = newScoreScript(expr, lookup, vars);
return ctx -> {
ScoreScript script = searchLeafFactory.newInstance(ctx);
Expand All @@ -290,7 +310,7 @@ public void setDocument(int docid) {
};
}

private ScoreScript.LeafFactory newScoreScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
private static ScoreScript.LeafFactory newScoreScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
// instead of complicating SimpleBindings (which should stay simple)
SimpleBindings bindings = new SimpleBindings();
Expand Down Expand Up @@ -327,7 +347,7 @@ private ScoreScript.LeafFactory newScoreScript(Expression expr, SearchLookup loo
/**
* converts a ParseException at compile-time or link-time to a ScriptException
*/
private ScriptException convertToScriptException(String message, String source, String portion, Throwable cause) {
private static ScriptException convertToScriptException(String message, String source, String portion, Throwable cause) {
List<String> stack = new ArrayList<>();
stack.add(portion);
StringBuilder pointer = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
* Main entry point handling template registration, compilation and
Expand Down Expand Up @@ -79,6 +80,11 @@ public <T> T compile(String templateName, String templateSource, ScriptContext<T

}

@Override
public Set<ScriptContext<?>> getSupportedContexts() {
return Collections.singleton(TemplateScript.CONTEXT);
}

private CustomMustacheFactory createMustacheFactory(Map<String, String> options) {
if (options == null || options.isEmpty() || options.containsKey(Script.CONTENT_TYPE_OPTION) == false) {
return new CustomMustacheFactory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ public Loader run() {
}
}

@Override
public Set<ScriptContext<?>> getSupportedContexts() {
return contextsToCompilers.keySet();
}

/**
* Generates a stateful factory class that will return script instances. Acts as a middle man between
* the {@link ScriptContext#factoryClazz} and the {@link ScriptContext#instanceClazz} when used so that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
* An example script plugin that adds a {@link ScriptEngine} implementing expert scoring.
Expand Down Expand Up @@ -76,6 +78,11 @@ public void close() {
// optionally close resources
}

@Override
public Set<ScriptContext<?>> getSupportedContexts() {
return Collections.singleton(ScoreScript.CONTEXT);
}

private static class PureDfLeafFactory implements LeafFactory {
private final Map<String, Object> params;
private final SearchLookup lookup;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"get_script_languages":{
"documentation":{
"description":"Returns available script types, languages and contexts"
},
"stability":"experimental",
"url":{
"paths":[
{
"path":"/_script_language",
"methods":[
"GET"
]
}
]
},
"params":{}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"Action to get script languages":
- skip:
version: " - 7.6.0"
reason: "get_script_languages introduced in 7.6.0"
- do:
get_script_languages: {}

- match: { types_allowed.0: "inline" }
- match: { types_allowed.1: "stored" }
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,12 @@
import org.elasticsearch.action.admin.cluster.stats.TransportClusterStatsAction;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptAction;
import org.elasticsearch.action.admin.cluster.storedscripts.GetScriptContextAction;
import org.elasticsearch.action.admin.cluster.storedscripts.GetScriptLanguageAction;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptAction;
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptAction;
import org.elasticsearch.action.admin.cluster.storedscripts.TransportDeleteStoredScriptAction;
import org.elasticsearch.action.admin.cluster.storedscripts.TransportGetScriptContextAction;
import org.elasticsearch.action.admin.cluster.storedscripts.TransportGetScriptLanguageAction;
import org.elasticsearch.action.admin.cluster.storedscripts.TransportGetStoredScriptAction;
import org.elasticsearch.action.admin.cluster.storedscripts.TransportPutStoredScriptAction;
import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksAction;
Expand Down Expand Up @@ -248,6 +250,7 @@
import org.elasticsearch.rest.action.admin.cluster.RestDeleteStoredScriptAction;
import org.elasticsearch.rest.action.admin.cluster.RestGetRepositoriesAction;
import org.elasticsearch.rest.action.admin.cluster.RestGetScriptContextAction;
import org.elasticsearch.rest.action.admin.cluster.RestGetScriptLanguageAction;
import org.elasticsearch.rest.action.admin.cluster.RestGetSnapshotsAction;
import org.elasticsearch.rest.action.admin.cluster.RestGetStoredScriptAction;
import org.elasticsearch.rest.action.admin.cluster.RestGetTaskAction;
Expand Down Expand Up @@ -531,6 +534,7 @@ public <Request extends ActionRequest, Response extends ActionResponse> void reg
actions.register(GetStoredScriptAction.INSTANCE, TransportGetStoredScriptAction.class);
actions.register(DeleteStoredScriptAction.INSTANCE, TransportDeleteStoredScriptAction.class);
actions.register(GetScriptContextAction.INSTANCE, TransportGetScriptContextAction.class);
actions.register(GetScriptLanguageAction.INSTANCE, TransportGetScriptLanguageAction.class);

actions.register(FieldCapabilitiesAction.INSTANCE, TransportFieldCapabilitiesAction.class,
TransportFieldCapabilitiesIndexAction.class);
Expand Down Expand Up @@ -661,6 +665,7 @@ public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster) {
registerHandler.accept(new RestPutStoredScriptAction(restController));
registerHandler.accept(new RestDeleteStoredScriptAction(restController));
registerHandler.accept(new RestGetScriptContextAction(restController));
registerHandler.accept(new RestGetScriptLanguageAction(restController));

registerHandler.accept(new RestFieldCapabilitiesAction(restController));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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.action.admin.cluster.storedscripts;

import org.elasticsearch.action.ActionType;

public class GetScriptLanguageAction extends ActionType<GetScriptLanguageResponse> {
public static final GetScriptLanguageAction INSTANCE = new GetScriptLanguageAction();
public static final String NAME = "cluster:admin/script_language/get";

private GetScriptLanguageAction() {
super(NAME, GetScriptLanguageResponse::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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.action.admin.cluster.storedscripts;

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.StreamInput;

import java.io.IOException;

public class GetScriptLanguageRequest extends ActionRequest {
public GetScriptLanguageRequest() {
super();
}

GetScriptLanguageRequest(StreamInput in) throws IOException {
super(in);
}

@Override
public ActionRequestValidationException validate() { return null; }

@Override
public String toString() { return "get script languages"; }
}
Loading

0 comments on commit 426c7a5

Please sign in to comment.