Skip to content

Commit

Permalink
Adds example plugin for custom ingest processor (#112282)
Browse files Browse the repository at this point in the history
* Adds example plugin for custom ingest processor

Adds an example for creating a plugin with a simple custom ingest
processor. The example processor repeats the value of an expected filed
in a document, or ignores it if the expected field does not exist.

Closes #111539
  • Loading branch information
samxbr authored Sep 6, 2024
1 parent 6ff0246 commit 7cd6de7
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 2 deletions.
6 changes: 6 additions & 0 deletions docs/changelog/112282.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 112282
summary: Adds example plugin for custom ingest processor
area: Ingest Node
type: enhancement
issues:
- 111539
5 changes: 3 additions & 2 deletions docs/plugins/development/creating-classic-plugins.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ for the plugin. If you need other resources, package them into a resources JAR.
The {es} repository contains {es-repo}tree/main/plugins/examples[examples of plugins]. Some of these include:

* a plugin with {es-repo}tree/main/plugins/examples/custom-settings[custom settings]
* a plugin with a {es-repo}tree/main/plugins/examples/custom-processor[custom ingest processor]
* adding {es-repo}tree/main/plugins/examples/rest-handler[custom rest endpoints]
* adding a {es-repo}tree/main/plugins/examples/rescore[custom rescorer]
* a script {es-repo}tree/main/plugins/examples/script-expert-scoring[implemented in Java]

These examples provide the bare bones needed to get started. For more
information about how to write a plugin, we recommend looking at the
information about how to write a plugin, we recommend looking at the
{es-repo}tree/main/plugins/[source code of existing plugins] for inspiration.

[discrete]
Expand Down Expand Up @@ -88,4 +89,4 @@ for more information.
[[plugin-descriptor-file-classic]]
==== The plugin descriptor file for classic plugins

include::plugin-descriptor-file.asciidoc[]
include::plugin-descriptor-file.asciidoc[]
21 changes: 21 additions & 0 deletions plugins/examples/custom-processor/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
apply plugin: 'elasticsearch.esplugin'
apply plugin: 'elasticsearch.yaml-rest-test'

esplugin {
name 'custom-processor'
description 'An example plugin showing how to register a custom ingest processor'
classname 'org.elasticsearch.example.customprocessor.ExampleProcessorPlugin'
licenseFile rootProject.file('SSPL-1.0+ELASTIC-LICENSE-2.0.txt')
noticeFile rootProject.file('NOTICE.txt')
}

dependencies {
yamlRestTestRuntimeOnly "org.apache.logging.log4j:log4j-core:${log4jVersion}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.example.customprocessor;

import org.elasticsearch.ingest.Processor;
import org.elasticsearch.plugins.IngestPlugin;
import org.elasticsearch.plugins.Plugin;

import java.util.Map;

public class ExampleProcessorPlugin extends Plugin implements IngestPlugin {

@Override
public Map<String, Processor.Factory> getProcessors(Processor.Parameters parameters) {
return Map.of(ExampleRepeatProcessor.TYPE, new ExampleRepeatProcessor.Factory());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.elasticsearch.example.customprocessor;

import org.elasticsearch.ingest.AbstractProcessor;
import org.elasticsearch.ingest.ConfigurationUtils;
import org.elasticsearch.ingest.IngestDocument;
import org.elasticsearch.ingest.Processor;

import java.util.Map;

/**
* Example of adding an ingest processor with a plugin.
*/
public class ExampleRepeatProcessor extends AbstractProcessor {
public static final String TYPE = "repeat";
public static final String FIELD_KEY_NAME = "field";

private final String field;

ExampleRepeatProcessor(String tag, String description, String field) {
super(tag, description);
this.field = field;
}

@Override
public IngestDocument execute(IngestDocument document) {
Object val = document.getFieldValue(field, Object.class, true);

if (val instanceof String string) {
String repeated = string.concat(string);
document.setFieldValue(field, repeated);
}
return document;
}

@Override
public String getType() {
return TYPE;
}

public static class Factory implements Processor.Factory {

@Override
public ExampleRepeatProcessor create(
Map<String, Processor.Factory> registry,
String tag,
String description,
Map<String, Object> config
) {
String field = ConfigurationUtils.readStringProperty(TYPE, tag, config, FIELD_KEY_NAME);
return new ExampleRepeatProcessor(tag, description, field);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.example.customprocessor;

import com.carrotsearch.randomizedtesting.annotations.Name;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;

/**
* {@link ExampleProcessorClientYamlTestSuiteIT} executes the plugin's REST API integration tests.
* <p>
* The tests can be executed using the command: ./gradlew :custom-processor:yamlRestTest
* <p>
* This class extends {@link ESClientYamlSuiteTestCase}, which takes care of parsing the YAML files
* located in the src/yamlRestTest/resources/rest-api-spec/test/ directory and validates them against the
* custom REST API definition files located in src/yamlRestTest/resources/rest-api-spec/api/.
* <p>
* Once validated, {@link ESClientYamlSuiteTestCase} executes the REST tests against a single node
* integration cluster which has the plugin already installed by the Gradle build script.
* </p>
*/
public class ExampleProcessorClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {

public ExampleProcessorClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
super(testCandidate);
}

@ParametersFactory
public static Iterable<Object[]> parameters() throws Exception {
// The test executes all the test candidates by default
// see ESClientYamlSuiteTestCase.REST_TESTS_SUITE
return ESClientYamlSuiteTestCase.createParameters();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"Custom processor is present":
- do:
ingest.put_pipeline:
id: pipeline1
body: >
{
"processors": [
{
"repeat" : {
"field": "test"
}
}
]
}
- match: { acknowledged: true }
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
setup:
- do:
ingest.put_pipeline:
id: pipeline1
body: >
{
"processors": [
{
"repeat" : {
"field": "to_repeat"
}
}
]
}
---
teardown:
- do:
ingest.delete_pipeline:
id: pipeline1
ignore: 404

- do:
indices.delete:
index: index1
ignore: 404
---
"Process document":
# index a document with field to be processed
- do:
index:
id: doc1
index: index1
pipeline: pipeline1
body: { to_repeat: "foo" }
- match: { result: "created" }

# validate document is processed
- do:
get:
index: index1
id: doc1
- match: { _source: { to_repeat: "foofoo" } }
---
"Does not process document without field":
# index a document without field to be processed
- do:
index:
id: doc1
index: index1
pipeline: pipeline1
body: { field1: "foo" }
- match: { result: "created" }

# validate document is not processed
- do:
get:
index: index1
id: doc1
- match: { _source: { field1: "foo" } }

0 comments on commit 7cd6de7

Please sign in to comment.