compile ScriptProcessor inline scripts when creating ingest pipelines (#21858)
Inline scripts defined in Ingest Pipelines are now compiled at creation time to preemptively catch errors on initialization of the pipeline. Fixes #21842.
This commit is contained in:
parent
f0c0f571bf
commit
eaf82a6e7e
|
@ -28,21 +28,21 @@ import org.elasticsearch.ingest.Processor;
|
|||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptException;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static org.elasticsearch.common.Strings.hasLength;
|
||||
import static org.elasticsearch.ingest.ConfigurationUtils.newConfigurationException;
|
||||
import static org.elasticsearch.ingest.ConfigurationUtils.readOptionalMap;
|
||||
import static org.elasticsearch.ingest.ConfigurationUtils.readOptionalStringProperty;
|
||||
import static org.elasticsearch.ingest.ConfigurationUtils.readStringProperty;
|
||||
import static org.elasticsearch.script.ScriptType.FILE;
|
||||
import static org.elasticsearch.script.ScriptType.INLINE;
|
||||
import static org.elasticsearch.script.ScriptType.STORED;
|
||||
|
||||
/**
|
||||
* Processor that adds new fields with their corresponding values. If the field is already present, its value
|
||||
* will be replaced with the provided one.
|
||||
* Processor that evaluates a script with an ingest document in its context.
|
||||
*/
|
||||
public final class ScriptProcessor extends AbstractProcessor {
|
||||
|
||||
|
@ -51,12 +51,24 @@ public final class ScriptProcessor extends AbstractProcessor {
|
|||
private final Script script;
|
||||
private final ScriptService scriptService;
|
||||
|
||||
/**
|
||||
* Processor that evaluates a script with an ingest document in its context
|
||||
*
|
||||
* @param tag The processor's tag.
|
||||
* @param script The {@link Script} to execute.
|
||||
* @param scriptService The {@link ScriptService} used to execute the script.
|
||||
*/
|
||||
ScriptProcessor(String tag, Script script, ScriptService scriptService) {
|
||||
super(tag);
|
||||
this.script = script;
|
||||
this.scriptService = scriptService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the script with the Ingest document in context.
|
||||
*
|
||||
* @param document The Ingest document passed into the script context under the "ctx" object.
|
||||
*/
|
||||
@Override
|
||||
public void execute(IngestDocument document) {
|
||||
ExecutableScript executableScript = scriptService.executable(script, ScriptContext.Standard.INGEST);
|
||||
|
@ -111,16 +123,27 @@ public final class ScriptProcessor extends AbstractProcessor {
|
|||
}
|
||||
|
||||
final Script script;
|
||||
String scriptPropertyUsed;
|
||||
if (Strings.hasLength(file)) {
|
||||
script = new Script(FILE, lang, file, (Map<String, Object>)params);
|
||||
scriptPropertyUsed = "file";
|
||||
} else if (Strings.hasLength(inline)) {
|
||||
script = new Script(INLINE, lang, inline, (Map<String, Object>)params);
|
||||
scriptPropertyUsed = "inline";
|
||||
} else if (Strings.hasLength(id)) {
|
||||
script = new Script(STORED, lang, id, (Map<String, Object>)params);
|
||||
scriptPropertyUsed = "id";
|
||||
} else {
|
||||
throw newConfigurationException(TYPE, processorTag, null, "Could not initialize script");
|
||||
}
|
||||
|
||||
// verify script is able to be compiled before successfully creating processor.
|
||||
try {
|
||||
scriptService.compile(script, ScriptContext.Standard.INGEST, script.getOptions());
|
||||
} catch (ScriptException e) {
|
||||
throw newConfigurationException(TYPE, processorTag, scriptPropertyUsed, e);
|
||||
}
|
||||
|
||||
return new ScriptProcessor(processorTag, script, scriptService);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.elasticsearch.ingest.common;
|
|||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptException;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Before;
|
||||
|
@ -31,7 +32,9 @@ import java.util.Map;
|
|||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ScriptProcessorFactoryTests extends ESTestCase {
|
||||
|
||||
|
@ -98,4 +101,22 @@ public class ScriptProcessorFactoryTests extends ESTestCase {
|
|||
|
||||
assertThat(exception.getMessage(), is("Need [file], [id], or [inline] parameter to refer to scripts"));
|
||||
}
|
||||
|
||||
public void testFactoryInvalidateWithInvalidCompiledScript() throws Exception {
|
||||
String randomType = randomFrom("inline", "file", "id");
|
||||
ScriptService mockedScriptService = mock(ScriptService.class);
|
||||
ScriptException thrownException = new ScriptException("compile-time exception", new RuntimeException(),
|
||||
Collections.emptyList(), "script", "mockscript");
|
||||
when(mockedScriptService.compile(any(), any(), any())).thenThrow(thrownException);
|
||||
factory = new ScriptProcessor.Factory(mockedScriptService);
|
||||
|
||||
Map<String, Object> configMap = new HashMap<>();
|
||||
configMap.put("lang", "mockscript");
|
||||
configMap.put(randomType, "my_script");
|
||||
|
||||
ElasticsearchException exception = expectThrows(ElasticsearchException.class,
|
||||
() -> factory.create(null, randomAsciiOfLength(10), configMap));
|
||||
|
||||
assertThat(exception.getMessage(), is("compile-time exception"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,10 @@ import java.util.Map;
|
|||
|
||||
import org.elasticsearch.ingest.IngestDocument;
|
||||
import org.elasticsearch.ingest.RandomDocumentPicks;
|
||||
import org.elasticsearch.script.CompiledScript;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import static org.hamcrest.Matchers.hasKey;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
|
|
@ -115,3 +115,26 @@
|
|||
- match: { _source.bytes_in: 1234 }
|
||||
- match: { _source.bytes_out: 4321 }
|
||||
- match: { _source.bytes_total: 5555 }
|
||||
|
||||
---
|
||||
"Test script processor with syntax error in inline script":
|
||||
- do:
|
||||
catch: request
|
||||
ingest.put_pipeline:
|
||||
id: "my_pipeline"
|
||||
body: >
|
||||
{
|
||||
"description": "_description",
|
||||
"processors": [
|
||||
{
|
||||
"script" : {
|
||||
"inline": "invalid painless, hear me roar!"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
- match: { error.header.processor_type: "script" }
|
||||
- match: { error.header.property_name: "inline" }
|
||||
- match: { error.type: "script_exception" }
|
||||
- match: { error.reason: "compile error" }
|
||||
|
||||
|
|
Loading…
Reference in New Issue