Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 :example-plugins:processor:yamlRestTest
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this comment actually true?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice catch, this was carelessly copied from other examples. It's probably worth fixing this for other examples as well, I will double check them and fix if necessary in another PR. Want to keep this PR self-contained.

* <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" } }