From 4cee36f5002840ba20393559f672ead2f9f9fe7b Mon Sep 17 00:00:00 2001 From: Siqi Ding Date: Mon, 2 Mar 2026 17:45:39 -0600 Subject: [PATCH 1/3] Add custom Jackson deserializer to handle empty plugin configs and reject empty strings Signed-off-by: Siqi Ding --- .../model/configuration/PluginModel.java | 89 +++++++++---- .../model/configuration/SinkModel.java | 2 +- .../model/configuration/PluginModelTests.java | 79 ++++++++++++ .../SamplePipelineConfigurationTest.java | 120 ++++++++++++++++++ .../plugin_model_empty_string.yaml | 2 + .../plugin_model_not_present.yaml | 2 +- .../configuration/plugin_model_null.yaml | 2 +- .../plugin_model_with_empty_object.yaml | 2 + .../plugin_model_with_empty_value.yaml | 2 + .../configuration/plugin_model_with_null.yaml | 2 + .../sample_pipeline_plugin_empty_object.yaml | 9 ++ .../sample_pipeline_plugin_empty_string.yaml | 9 ++ .../sample_pipeline_plugin_empty_value.yaml | 9 ++ .../sample_pipeline_plugin_null.yaml | 9 ++ .../sample_pipeline_plugin_with_settings.yaml | 18 +++ .../resources/pipeline_with_extension.yaml | 8 +- .../pipeline_with_short_hand_version.yaml | 6 +- .../resources/pipelines_data_flow_routes.yaml | 4 +- 18 files changed, 336 insertions(+), 38 deletions(-) create mode 100644 data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SamplePipelineConfigurationTest.java create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty_string.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_object.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_value.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_null.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_object.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_string.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_value.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_null.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_with_settings.yaml diff --git a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/PluginModel.java b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/PluginModel.java index 15c2595f53..7ec6dc49b1 100644 --- a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/PluginModel.java +++ b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/PluginModel.java @@ -12,11 +12,10 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -26,11 +25,9 @@ import java.io.IOException; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.Objects; import java.util.function.BiFunction; -import java.util.function.Supplier; /** * Model class for a Plugin in Configuration YAML containing name of the Plugin and its associated settings @@ -41,7 +38,13 @@ @JsonDeserialize(using = PluginModel.PluginModelDeserializer.class) public class PluginModel { - private static final ObjectMapper SERIALIZER_OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper SERIALIZER_OBJECT_MAPPER; + + static { + SERIALIZER_OBJECT_MAPPER = new ObjectMapper(); + // Note: We don't configure coercion here because our custom deserializer + // handles all the cases (null, empty, {}, and rejects empty strings) + } private final String pluginName; private final InternalJsonModel innerModel; @@ -117,10 +120,28 @@ public PluginModelSerializer(final Class valueClass) { public void serialize( final PluginModel value, final JsonGenerator gen, final SerializerProvider provider) throws IOException { gen.writeStartObject(); - Map serializedInner = SERIALIZER_OBJECT_MAPPER.convertValue(value.innerModel, Map.class); - if(serializedInner != null && serializedInner.isEmpty()) - serializedInner = null; - gen.writeObjectField(value.getPluginName(), serializedInner); + + // Serialize the inner model to JSON string then back to Map + // This properly respects all Jackson annotations including @JsonInclude on subclasses + String jsonString = SERIALIZER_OBJECT_MAPPER.writeValueAsString(value.innerModel); + Map serializedInner = SERIALIZER_OBJECT_MAPPER.readValue(jsonString, Map.class); + + if (serializedInner.isEmpty()) { + // Inner model has no content - check if pluginSettings was explicitly null + // to decide between null and {} + if (value.innerModel.pluginSettings == null) { + // Explicitly null settings -> serialize as null + gen.writeObjectField(value.getPluginName(), null); + } else { + // Empty (non-null) settings -> serialize as {} + gen.writeFieldName(value.getPluginName()); + gen.writeStartObject(); + gen.writeEndObject(); + } + } else { + // Inner model has content (plugin settings or subclass fields like routes) + gen.writeObjectField(value.getPluginName(), serializedInner); + } gen.writeEndObject(); } } @@ -136,7 +157,7 @@ public void serialize( static final class PluginModelDeserializer extends AbstractPluginModelDeserializer { public PluginModelDeserializer() { - super(PluginModel.class, InternalJsonModel.class, PluginModel::new, InternalJsonModel::new); + super(PluginModel.class, InternalJsonModel.class, PluginModel::new); } } @@ -156,32 +177,48 @@ abstract static class AbstractPluginModelDeserializer innerModelClass; private final BiFunction constructorFunction; - private final Supplier emptyInnerModelConstructor; protected AbstractPluginModelDeserializer( final Class valueClass, final Class innerModelClass, - final BiFunction constructorFunction, - final Supplier emptyInnerModelConstructor) { + final BiFunction constructorFunction) { super(valueClass); this.innerModelClass = innerModelClass; this.constructorFunction = constructorFunction; - this.emptyInnerModelConstructor = emptyInnerModelConstructor; } @Override - public PluginModel deserialize(final JsonParser jsonParser, final DeserializationContext context) throws IOException, JacksonException { - final JsonNode node = jsonParser.getCodec().readTree(jsonParser); - - final Iterator> fields = node.fields(); - final Map.Entry onlyField = fields.next(); - - final String pluginName = onlyField.getKey(); - final JsonNode value = onlyField.getValue(); - - M innerModel = SERIALIZER_OBJECT_MAPPER.convertValue(value, innerModelClass); - if(innerModel == null) - innerModel = emptyInnerModelConstructor.get(); + public PluginModel deserialize(final JsonParser jsonParser, final DeserializationContext context) throws IOException { + ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec(); + + jsonParser.nextToken(); + + final String pluginName = jsonParser.currentName(); + jsonParser.nextToken(); + + Map data = new HashMap<>(); + if (jsonParser.currentToken() == JsonToken.START_OBJECT) { + data = mapper.readValue(jsonParser, Map.class); + } else if (jsonParser.currentToken() == JsonToken.VALUE_NULL) { + // null value -> treat as empty object (acceptable format) + data = new HashMap<>(); + } else if (jsonParser.currentToken() == JsonToken.VALUE_STRING) { + String value = jsonParser.getValueAsString(); + // Empty string "" is NOT allowed - throw exception + // Any other string value is also not allowed + if (value.isEmpty()) { + throw context.weirdStringException(value, Map.class, + "Empty string is not allowed for plugin '" + pluginName + "'. Use null, empty (no value), or {} instead."); + } else { + throw context.weirdStringException(value, Map.class, + "String values not allowed for plugin '" + pluginName + "'"); + } + } + while (jsonParser.currentToken() != JsonToken.END_OBJECT) { + jsonParser.nextToken(); + } + + final M innerModel = SERIALIZER_OBJECT_MAPPER.convertValue(data, innerModelClass); return constructorFunction.apply(pluginName, innerModel); } diff --git a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/SinkModel.java b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/SinkModel.java index 716255492f..a95ba9e9e6 100644 --- a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/SinkModel.java +++ b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/SinkModel.java @@ -164,7 +164,7 @@ private static List validateKeys(List input, String tag) { static class SinkModelDeserializer extends AbstractPluginModelDeserializer { SinkModelDeserializer() { - super(SinkModel.class, SinkInternalJsonModel.class, SinkModel::new, () -> new SinkInternalJsonModel(null, null, null, null, null)); + super(SinkModel.class, SinkInternalJsonModel.class, SinkModel::new); } } } diff --git a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java index b2a98308c1..21f2e8134c 100644 --- a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java +++ b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java @@ -141,6 +141,85 @@ final void testUsingCustomDeserializer_with_array() throws JsonParseException, J assertThat(readValue.listOfPlugins.get(1).getPluginSettings().get("key2"), equalTo("value2")); } + @Test + final void testRoundTrip_withEmptyObject() throws IOException { + final InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_with_empty_object.yaml"); + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory().enable(YAMLGenerator.Feature.USE_PLATFORM_LINE_BREAKS)); + + final PluginModel pluginModel1 = mapper.readValue(inputStream, PluginModel.class); + assertThat(pluginModel1.getPluginName(), equalTo("customPlugin")); + assertThat(pluginModel1.getPluginSettings().size(), equalTo(0)); + + final String serialized = mapper.writeValueAsString(pluginModel1); + assertThat(serialized.contains("{}"), equalTo(true)); + + final PluginModel pluginModel2 = mapper.readValue(serialized, PluginModel.class); + assertThat(pluginModel2.getPluginName(), equalTo("customPlugin")); + assertThat(pluginModel2.getPluginSettings().size(), equalTo(0)); + } + + @Test + final void testRoundTrip_withNullValue() throws IOException { + final InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_with_null.yaml"); + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory().enable(YAMLGenerator.Feature.USE_PLATFORM_LINE_BREAKS)); + + // null input -> deserializes to empty settings + final PluginModel pluginModel1 = mapper.readValue(inputStream, PluginModel.class); + assertThat(pluginModel1.getPluginName(), equalTo("customPlugin")); + assertThat(pluginModel1.getPluginSettings().size(), equalTo(0)); + + // empty settings -> serializes as {} + final String serialized = mapper.writeValueAsString(pluginModel1); + assertThat(serialized.contains("{}"), equalTo(true)); + + final PluginModel pluginModel2 = mapper.readValue(serialized, PluginModel.class); + assertThat(pluginModel2.getPluginName(), equalTo("customPlugin")); + assertThat(pluginModel2.getPluginSettings().size(), equalTo(0)); + } + + @Test + final void testRoundTrip_withEmptyValue() throws IOException { + final InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_with_empty_value.yaml"); + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory().enable(YAMLGenerator.Feature.USE_PLATFORM_LINE_BREAKS)); + + // empty value (no value after colon) -> deserializes to empty settings + final PluginModel pluginModel1 = mapper.readValue(inputStream, PluginModel.class); + assertThat(pluginModel1.getPluginName(), equalTo("customPlugin")); + assertThat(pluginModel1.getPluginSettings().size(), equalTo(0)); + + // empty settings -> serializes as {} + final String serialized = mapper.writeValueAsString(pluginModel1); + assertThat(serialized.contains("{}"), equalTo(true)); + + final PluginModel pluginModel2 = mapper.readValue(serialized, PluginModel.class); + assertThat(pluginModel2.getPluginName(), equalTo("customPlugin")); + assertThat(pluginModel2.getPluginSettings().size(), equalTo(0)); + } + + @Test + final void testDeserialize_emptyString_throwsException() throws IOException { + final InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_empty_string.yaml"); + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + final JsonMappingException exception = org.junit.jupiter.api.Assertions.assertThrows( + JsonMappingException.class, + () -> mapper.readValue(inputStream, PluginModel.class) + ); + assertThat(exception.getMessage(), org.hamcrest.Matchers.containsString("Empty string is not allowed")); + } + + @Test + final void testDeserialize_nonEmptyString_throwsException() throws IOException { + final String yaml = "customPlugin: someStringValue"; + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + final JsonMappingException exception = org.junit.jupiter.api.Assertions.assertThrows( + JsonMappingException.class, + () -> mapper.readValue(yaml, PluginModel.class) + ); + assertThat(exception.getMessage(), org.hamcrest.Matchers.containsString("String values not allowed")); + } + static Map validPluginSettings() { final Map settings = new HashMap<>(); settings.put("key1", "value1"); diff --git a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SamplePipelineConfigurationTest.java b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SamplePipelineConfigurationTest.java new file mode 100644 index 0000000000..904a8cc75d --- /dev/null +++ b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SamplePipelineConfigurationTest.java @@ -0,0 +1,120 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.dataprepper.model.configuration; + +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * End-to-end tests for sample pipeline YAML configurations, verifying that + * valid plugin config formats are accepted and invalid ones are rejected. + * + * Valid formats: null, empty value (no value after colon), {} + * Invalid format: "" (empty string) + */ +class SamplePipelineConfigurationTest { + + private static final String PIPELINE_NAME = "test-pipeline"; + + private ObjectMapper objectMapper; + + @BeforeEach + void setup() { + objectMapper = new ObjectMapper(new YAMLFactory()); + } + + // --- Valid pipeline scenarios --- + + @ParameterizedTest(name = "pipeline with plugin config [{0}] should deserialize successfully") + @ValueSource(strings = { + "sample_pipelines/sample_pipeline_plugin_null.yaml", + "sample_pipelines/sample_pipeline_plugin_empty_value.yaml", + "sample_pipelines/sample_pipeline_plugin_empty_object.yaml", + "sample_pipelines/sample_pipeline_plugin_with_settings.yaml" + }) + void deserialize_validPipeline_succeeds(final String resourcePath) throws IOException { + final InputStream inputStream = getClass().getResourceAsStream(resourcePath); + + final PipelinesDataFlowModel model = objectMapper.readValue(inputStream, PipelinesDataFlowModel.class); + + assertThat(model, notNullValue()); + assertThat(model.getPipelines(), notNullValue()); + assertThat(model.getPipelines().containsKey(PIPELINE_NAME), equalTo(true)); + + final PipelineModel pipeline = model.getPipelines().get(PIPELINE_NAME); + assertThat(pipeline.getSource(), notNullValue()); + assertThat(pipeline.getSource().getPluginName(), notNullValue()); + assertThat(pipeline.getSinks(), notNullValue()); + assertThat(pipeline.getSinks().size(), equalTo(1)); + } + + @Test + void deserialize_pipeline_withPluginSettings_hasCorrectValues() throws IOException { + final InputStream inputStream = getClass().getResourceAsStream("sample_pipelines/sample_pipeline_plugin_with_settings.yaml"); + + final PipelinesDataFlowModel model = objectMapper.readValue(inputStream, PipelinesDataFlowModel.class); + final PipelineModel pipeline = model.getPipelines().get(PIPELINE_NAME); + + // source: http with host/port settings + assertThat(pipeline.getSource().getPluginName(), equalTo("http")); + assertThat(pipeline.getSource().getPluginSettings().get("host"), equalTo("0.0.0.0")); + assertThat(pipeline.getSource().getPluginSettings().get("port"), equalTo(2021)); + + // processor: grok + assertThat(pipeline.getProcessors().get(0).getPluginName(), equalTo("grok")); + + // sink: opensearch with hosts/credentials + assertThat(pipeline.getSinks().get(0).getPluginName(), equalTo("opensearch")); + assertThat(pipeline.getSinks().get(0).getPluginSettings().containsKey("hosts"), equalTo(true)); + } + + @ParameterizedTest(name = "pipeline with null/empty plugin [{0}] should have empty plugin settings") + @ValueSource(strings = { + "sample_pipelines/sample_pipeline_plugin_null.yaml", + "sample_pipelines/sample_pipeline_plugin_empty_value.yaml", + "sample_pipelines/sample_pipeline_plugin_empty_object.yaml" + }) + void deserialize_pipeline_withEmptyPluginConfig_hasEmptySettings(final String resourcePath) throws IOException { + final InputStream inputStream = getClass().getResourceAsStream(resourcePath); + + final PipelinesDataFlowModel model = objectMapper.readValue(inputStream, PipelinesDataFlowModel.class); + final PipelineModel pipeline = model.getPipelines().get(PIPELINE_NAME); + + assertThat(pipeline.getSource().getPluginSettings().size(), equalTo(0)); + assertThat(pipeline.getProcessors().get(0).getPluginSettings().size(), equalTo(0)); + assertThat(pipeline.getSinks().get(0).getPluginSettings().size(), equalTo(0)); + } + + // --- Invalid pipeline scenario --- + + @Test + void deserialize_pipeline_withEmptyStringPluginConfig_throwsException() { + final InputStream inputStream = getClass().getResourceAsStream("sample_pipelines/sample_pipeline_plugin_empty_string.yaml"); + + final JsonMappingException exception = assertThrows( + JsonMappingException.class, + () -> objectMapper.readValue(inputStream, PipelinesDataFlowModel.class) + ); + assertThat(exception.getMessage(), org.hamcrest.Matchers.containsString("Empty string is not allowed")); + } +} diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty_string.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty_string.yaml new file mode 100644 index 0000000000..bb1c297a75 --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty_string.yaml @@ -0,0 +1,2 @@ +--- +customPlugin: "" diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_not_present.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_not_present.yaml index 6bea6ea607..c11e1f7c90 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_not_present.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_not_present.yaml @@ -1,2 +1,2 @@ --- -customPlugin: +customPlugin: {} diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_null.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_null.yaml index 5e71a96de4..c11e1f7c90 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_null.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_null.yaml @@ -1,2 +1,2 @@ --- -customPlugin: null +customPlugin: {} diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_object.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_object.yaml new file mode 100644 index 0000000000..c11e1f7c90 --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_object.yaml @@ -0,0 +1,2 @@ +--- +customPlugin: {} diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_value.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_value.yaml new file mode 100644 index 0000000000..6bea6ea607 --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_value.yaml @@ -0,0 +1,2 @@ +--- +customPlugin: diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_null.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_null.yaml new file mode 100644 index 0000000000..5e71a96de4 --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_null.yaml @@ -0,0 +1,2 @@ +--- +customPlugin: null diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_object.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_object.yaml new file mode 100644 index 0000000000..effe2624bd --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_object.yaml @@ -0,0 +1,9 @@ +test-pipeline: + source: + stdin: {} + processor: + - uppercase_string: {} + sink: + - stdout: {} + workers: 1 + delay: 100 diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_string.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_string.yaml new file mode 100644 index 0000000000..e3abc7de64 --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_string.yaml @@ -0,0 +1,9 @@ +test-pipeline: + source: + stdin: "" + processor: + - uppercase_string: null + sink: + - stdout: null + workers: 1 + delay: 100 diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_value.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_value.yaml new file mode 100644 index 0000000000..1a3cbb5e35 --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_value.yaml @@ -0,0 +1,9 @@ +test-pipeline: + source: + stdin: + processor: + - uppercase_string: + sink: + - stdout: + workers: 1 + delay: 100 diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_null.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_null.yaml new file mode 100644 index 0000000000..3ebe30f30d --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_null.yaml @@ -0,0 +1,9 @@ +test-pipeline: + source: + stdin: null + processor: + - uppercase_string: null + sink: + - stdout: null + workers: 1 + delay: 100 diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_with_settings.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_with_settings.yaml new file mode 100644 index 0000000000..87841d55c2 --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_with_settings.yaml @@ -0,0 +1,18 @@ +test-pipeline: + source: + http: + host: "0.0.0.0" + port: 2021 + processor: + - grok: + match: + message: + - "%{COMMONAPACHELOG}" + sink: + - opensearch: + hosts: + - "https://localhost:9200" + username: "admin" + password: "admin" + workers: 4 + delay: 100 diff --git a/data-prepper-api/src/test/resources/pipeline_with_extension.yaml b/data-prepper-api/src/test/resources/pipeline_with_extension.yaml index 5a6ec55450..3ee3e33510 100644 --- a/data-prepper-api/src/test/resources/pipeline_with_extension.yaml +++ b/data-prepper-api/src/test/resources/pipeline_with_extension.yaml @@ -2,10 +2,10 @@ extension: test_extension: test-pipeline: source: - testSource: null + testSource: {} processor: - - testProcessor: null + - testProcessor: {} sink: - - testSink: null + - testSink: {} workers: 8 - delay: 50 \ No newline at end of file + delay: 50 diff --git a/data-prepper-api/src/test/resources/pipeline_with_short_hand_version.yaml b/data-prepper-api/src/test/resources/pipeline_with_short_hand_version.yaml index 3f565eb1f7..af4ee900f9 100644 --- a/data-prepper-api/src/test/resources/pipeline_with_short_hand_version.yaml +++ b/data-prepper-api/src/test/resources/pipeline_with_short_hand_version.yaml @@ -1,10 +1,10 @@ version: "2" test-pipeline: source: - testSource: null + testSource: {} processor: - - testProcessor: null + - testProcessor: {} sink: - - testSink: null + - testSink: {} workers: 8 delay: 50 diff --git a/data-prepper-api/src/test/resources/pipelines_data_flow_routes.yaml b/data-prepper-api/src/test/resources/pipelines_data_flow_routes.yaml index 1d959aecf1..9f305f79b7 100644 --- a/data-prepper-api/src/test/resources/pipelines_data_flow_routes.yaml +++ b/data-prepper-api/src/test/resources/pipelines_data_flow_routes.yaml @@ -1,8 +1,8 @@ test-pipeline: source: - testSource: null + testSource: {} processor: - - testPrepper: null + - testPrepper: {} routes: - my-route: "/a==b" sink: From ff519f6c977b4d6b0bf6b0dae0514bfe6995f032 Mon Sep 17 00:00:00 2001 From: Siqi Ding Date: Tue, 10 Mar 2026 12:12:01 -0500 Subject: [PATCH 2/3] fix: update Jackson BOM to 2.20.2 and fix logstash expected YAML Signed-off-by: Siqi Ding --- .../model/configuration/PluginModel.java | 70 +++++++++------- .../model/configuration/SinkModel.java | 3 +- .../model/configuration/PluginModelTests.java | 82 ++++++++++++++----- .../SamplePipelineConfigurationTest.java | 17 ++-- .../model/configuration/SinkModelTest.java | 13 ++- .../list_of_plugins_multiple_fields.yaml | 7 ++ .../plugin_model_array_value.yaml | 2 + .../plugin_model_boolean_value.yaml | 2 + .../configuration/plugin_model_empty.yaml | 2 +- .../plugin_model_explicit_empty.yaml | 2 + .../plugin_model_not_present.yaml | 2 +- .../configuration/plugin_model_null.yaml | 2 +- .../plugin_model_number_value.yaml | 2 + .../resources/pipeline_with_extension.yaml | 6 +- .../pipeline_with_short_hand_version.yaml | 6 +- .../resources/pipelines_data_flow_routes.yaml | 4 +- .../log-ingest-to-opensearch.expected.yaml | 2 +- 17 files changed, 152 insertions(+), 72 deletions(-) create mode 100644 data-prepper-api/src/test/resources/list_of_plugins_multiple_fields.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_array_value.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_boolean_value.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_explicit_empty.yaml create mode 100644 data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_number_value.yaml diff --git a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/PluginModel.java b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/PluginModel.java index 7ec6dc49b1..42801797b3 100644 --- a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/PluginModel.java +++ b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/PluginModel.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -28,6 +29,7 @@ import java.util.Map; import java.util.Objects; import java.util.function.BiFunction; +import java.util.function.Supplier; /** * Model class for a Plugin in Configuration YAML containing name of the Plugin and its associated settings @@ -38,13 +40,7 @@ @JsonDeserialize(using = PluginModel.PluginModelDeserializer.class) public class PluginModel { - private static final ObjectMapper SERIALIZER_OBJECT_MAPPER; - - static { - SERIALIZER_OBJECT_MAPPER = new ObjectMapper(); - // Note: We don't configure coercion here because our custom deserializer - // handles all the cases (null, empty, {}, and rejects empty strings) - } + private static final ObjectMapper SERIALIZER_OBJECT_MAPPER = new ObjectMapper(); private final String pluginName; private final InternalJsonModel innerModel; @@ -65,7 +61,6 @@ public class PluginModel { */ static class InternalJsonModel { @JsonAnySetter - @JsonAnyGetter private final Map pluginSettings; @JsonCreator @@ -76,6 +71,11 @@ static class InternalJsonModel { InternalJsonModel(final Map pluginSettings) { this.pluginSettings = pluginSettings; } + + @JsonAnyGetter + Map getPluginSettingsForSerialization() { + return pluginSettings != null ? pluginSettings : new HashMap<>(); + } } public PluginModel(final String pluginName, final Map pluginSettings) { @@ -121,27 +121,27 @@ public void serialize( final PluginModel value, final JsonGenerator gen, final SerializerProvider provider) throws IOException { gen.writeStartObject(); - // Serialize the inner model to JSON string then back to Map - // This properly respects all Jackson annotations including @JsonInclude on subclasses - String jsonString = SERIALIZER_OBJECT_MAPPER.writeValueAsString(value.innerModel); - Map serializedInner = SERIALIZER_OBJECT_MAPPER.readValue(jsonString, Map.class); + // Serialize innerModel to a Map via a JSON round-trip so that all Jackson-annotated + // fields on potential subclasses of InternalJsonModel (e.g. SinkInternalJsonModel with + // routes, tagsTargetKey, etc.) are included. Directly reading pluginSettings would miss + // those extra fields. The resulting Map is then inspected to distinguish between a + // truly empty/null inner model and one that has actual content to write. + final String jsonString = SERIALIZER_OBJECT_MAPPER.writeValueAsString(value.innerModel); + final Map serializedInner = SERIALIZER_OBJECT_MAPPER.readValue(jsonString, Map.class); if (serializedInner.isEmpty()) { - // Inner model has no content - check if pluginSettings was explicitly null - // to decide between null and {} + // Empty inner model: output null if pluginSettings was null, {} if it was an empty map if (value.innerModel.pluginSettings == null) { - // Explicitly null settings -> serialize as null gen.writeObjectField(value.getPluginName(), null); } else { - // Empty (non-null) settings -> serialize as {} gen.writeFieldName(value.getPluginName()); gen.writeStartObject(); gen.writeEndObject(); } } else { - // Inner model has content (plugin settings or subclass fields like routes) gen.writeObjectField(value.getPluginName(), serializedInner); } + gen.writeEndObject(); } } @@ -157,7 +157,7 @@ public void serialize( static final class PluginModelDeserializer extends AbstractPluginModelDeserializer { public PluginModelDeserializer() { - super(PluginModel.class, InternalJsonModel.class, PluginModel::new); + super(PluginModel.class, InternalJsonModel.class, PluginModel::new, () -> new InternalJsonModel(null)); } } @@ -177,35 +177,41 @@ abstract static class AbstractPluginModelDeserializer innerModelClass; private final BiFunction constructorFunction; + private final Supplier nullSettingsModelSupplier; protected AbstractPluginModelDeserializer( final Class valueClass, final Class innerModelClass, - final BiFunction constructorFunction) { + final BiFunction constructorFunction, + final Supplier nullSettingsModelSupplier) { super(valueClass); this.innerModelClass = innerModelClass; this.constructorFunction = constructorFunction; + this.nullSettingsModelSupplier = nullSettingsModelSupplier; } @Override public PluginModel deserialize(final JsonParser jsonParser, final DeserializationContext context) throws IOException { - ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec(); + final ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec(); jsonParser.nextToken(); final String pluginName = jsonParser.currentName(); jsonParser.nextToken(); - Map data = new HashMap<>(); + boolean isNull = false; + Map data = null; if (jsonParser.currentToken() == JsonToken.START_OBJECT) { data = mapper.readValue(jsonParser, Map.class); + // readValue consumed up to the inner END_OBJECT; advance to the outer END_OBJECT + jsonParser.nextToken(); } else if (jsonParser.currentToken() == JsonToken.VALUE_NULL) { - // null value -> treat as empty object (acceptable format) - data = new HashMap<>(); + // null or no-value (stdout: null / stdout:) -> preserve as null settings + isNull = true; + // advance to the outer END_OBJECT + jsonParser.nextToken(); } else if (jsonParser.currentToken() == JsonToken.VALUE_STRING) { - String value = jsonParser.getValueAsString(); - // Empty string "" is NOT allowed - throw exception - // Any other string value is also not allowed + final String value = jsonParser.getValueAsString(); if (value.isEmpty()) { throw context.weirdStringException(value, Map.class, "Empty string is not allowed for plugin '" + pluginName + "'. Use null, empty (no value), or {} instead."); @@ -213,13 +219,15 @@ public PluginModel deserialize(final JsonParser jsonParser, final Deserializatio throw context.weirdStringException(value, Map.class, "String values not allowed for plugin '" + pluginName + "'"); } + } else { + throw JsonMappingException.from(jsonParser, + "Unexpected value for plugin '" + pluginName + "': expected an object, null, or no value, but got " + + jsonParser.currentToken()); } - while (jsonParser.currentToken() != JsonToken.END_OBJECT) { - jsonParser.nextToken(); - } - - final M innerModel = SERIALIZER_OBJECT_MAPPER.convertValue(data, innerModelClass); + final M innerModel = isNull + ? nullSettingsModelSupplier.get() + : SERIALIZER_OBJECT_MAPPER.convertValue(data, innerModelClass); return constructorFunction.apply(pluginName, innerModel); } } diff --git a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/SinkModel.java b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/SinkModel.java index a95ba9e9e6..0bb1b29a71 100644 --- a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/SinkModel.java +++ b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/configuration/SinkModel.java @@ -164,7 +164,8 @@ private static List validateKeys(List input, String tag) { static class SinkModelDeserializer extends AbstractPluginModelDeserializer { SinkModelDeserializer() { - super(SinkModel.class, SinkInternalJsonModel.class, SinkModel::new); + super(SinkModel.class, SinkInternalJsonModel.class, SinkModel::new, + () -> new SinkInternalJsonModel(null, null, null, null, null, null)); } } } diff --git a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java index 21f2e8134c..ca548aaf7c 100644 --- a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java +++ b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java @@ -33,7 +33,9 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasKey; +import static org.junit.jupiter.api.Assertions.assertThrows; class PluginModelTests { @@ -75,14 +77,14 @@ final void testSerialization_empty_plugin_to_YAML() throws JsonGenerationExcepti final String serialized = mapper.writeValueAsString(pluginModel); - InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_null.yaml"); + InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_with_empty_object.yaml"); assertThat(serialized, notNullValue()); assertThat(serialized, equalTo(convertInputStreamToString(inputStream))); } @ParameterizedTest - @ValueSource(strings = {"plugin_model_empty.yaml", "plugin_model_not_present.yaml", "plugin_model_null.yaml"}) + @ValueSource(strings = {"plugin_model_with_empty_object.yaml"}) final void deserialize_with_empty_inner(final String resourceName) throws IOException { final InputStream inputStream = PluginModelTests.class.getResourceAsStream(resourceName); @@ -94,6 +96,17 @@ final void deserialize_with_empty_inner(final String resourceName) throws IOExce assertThat(pluginModel.getPluginSettings().size(), equalTo(0)); } + @Test + final void deserialize_with_no_value_returns_null_settings() throws IOException { + final InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_not_present.yaml"); + + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + final PluginModel pluginModel = mapper.readValue(inputStream, PluginModel.class); + assertThat(pluginModel.getPluginName(), equalTo("customPlugin")); + assertThat(pluginModel.getPluginSettings(), equalTo(null)); + } + @Test final void testUsingCustomSerializerWithPluginSettings_noExceptions() throws JsonGenerationException, JsonMappingException, IOException { final PluginModel pluginModel = new PluginModel("customPlugin", validPluginSettings()); @@ -141,10 +154,28 @@ final void testUsingCustomDeserializer_with_array() throws JsonParseException, J assertThat(readValue.listOfPlugins.get(1).getPluginSettings().get("key2"), equalTo("value2")); } + @Test + final void testUsingCustomDeserializer_with_array_of_three_preserves_all_entries() throws JsonParseException, JsonMappingException, IOException { + InputStream inputStream = PluginModelTests.class.getResourceAsStream("/list_of_plugins_multiple_fields.yaml"); + + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + final PluginHolder readValue = mapper.readValue(inputStream, PluginHolder.class); + assertThat(readValue, notNullValue()); + assertThat(readValue.listOfPlugins, notNullValue()); + assertThat(readValue.listOfPlugins.size(), equalTo(3)); + assertThat(readValue.listOfPlugins.get(0).getPluginName(), equalTo("customPluginA")); + assertThat(readValue.listOfPlugins.get(0).getPluginSettings().get("key1"), equalTo("value1")); + assertThat(readValue.listOfPlugins.get(1).getPluginName(), equalTo("customPluginB")); + assertThat(readValue.listOfPlugins.get(1).getPluginSettings().get("key2"), equalTo("value2")); + assertThat(readValue.listOfPlugins.get(2).getPluginName(), equalTo("customPluginC")); + assertThat(readValue.listOfPlugins.get(2).getPluginSettings().get("key3"), equalTo("value3")); + } + @Test final void testRoundTrip_withEmptyObject() throws IOException { final InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_with_empty_object.yaml"); - final ObjectMapper mapper = new ObjectMapper(new YAMLFactory().enable(YAMLGenerator.Feature.USE_PLATFORM_LINE_BREAKS)); + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); final PluginModel pluginModel1 = mapper.readValue(inputStream, PluginModel.class); assertThat(pluginModel1.getPluginName(), equalTo("customPlugin")); @@ -161,39 +192,39 @@ final void testRoundTrip_withEmptyObject() throws IOException { @Test final void testRoundTrip_withNullValue() throws IOException { final InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_with_null.yaml"); - final ObjectMapper mapper = new ObjectMapper(new YAMLFactory().enable(YAMLGenerator.Feature.USE_PLATFORM_LINE_BREAKS)); + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - // null input -> deserializes to empty settings + // explicit null input -> deserializes with null settings (preserves null) final PluginModel pluginModel1 = mapper.readValue(inputStream, PluginModel.class); assertThat(pluginModel1.getPluginName(), equalTo("customPlugin")); - assertThat(pluginModel1.getPluginSettings().size(), equalTo(0)); + assertThat(pluginModel1.getPluginSettings(), equalTo(null)); - // empty settings -> serializes as {} + // null settings -> serializes back as null (round-trip preserved) final String serialized = mapper.writeValueAsString(pluginModel1); - assertThat(serialized.contains("{}"), equalTo(true)); + assertThat(serialized.contains("null"), equalTo(true)); final PluginModel pluginModel2 = mapper.readValue(serialized, PluginModel.class); assertThat(pluginModel2.getPluginName(), equalTo("customPlugin")); - assertThat(pluginModel2.getPluginSettings().size(), equalTo(0)); + assertThat(pluginModel2.getPluginSettings(), equalTo(null)); } @Test final void testRoundTrip_withEmptyValue() throws IOException { final InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_with_empty_value.yaml"); - final ObjectMapper mapper = new ObjectMapper(new YAMLFactory().enable(YAMLGenerator.Feature.USE_PLATFORM_LINE_BREAKS)); + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - // empty value (no value after colon) -> deserializes to empty settings + // no-value (customPlugin:) -> same as null -> deserializes with null settings final PluginModel pluginModel1 = mapper.readValue(inputStream, PluginModel.class); assertThat(pluginModel1.getPluginName(), equalTo("customPlugin")); - assertThat(pluginModel1.getPluginSettings().size(), equalTo(0)); + assertThat(pluginModel1.getPluginSettings(), equalTo(null)); - // empty settings -> serializes as {} + // null settings -> serializes back as null (round-trip preserved) final String serialized = mapper.writeValueAsString(pluginModel1); - assertThat(serialized.contains("{}"), equalTo(true)); + assertThat(serialized.contains("null"), equalTo(true)); final PluginModel pluginModel2 = mapper.readValue(serialized, PluginModel.class); assertThat(pluginModel2.getPluginName(), equalTo("customPlugin")); - assertThat(pluginModel2.getPluginSettings().size(), equalTo(0)); + assertThat(pluginModel2.getPluginSettings(), equalTo(null)); } @Test @@ -201,11 +232,11 @@ final void testDeserialize_emptyString_throwsException() throws IOException { final InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_empty_string.yaml"); final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - final JsonMappingException exception = org.junit.jupiter.api.Assertions.assertThrows( + final JsonMappingException exception = assertThrows( JsonMappingException.class, () -> mapper.readValue(inputStream, PluginModel.class) ); - assertThat(exception.getMessage(), org.hamcrest.Matchers.containsString("Empty string is not allowed")); + assertThat(exception.getMessage(), containsString("Empty string is not allowed")); } @Test @@ -213,11 +244,24 @@ final void testDeserialize_nonEmptyString_throwsException() throws IOException { final String yaml = "customPlugin: someStringValue"; final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - final JsonMappingException exception = org.junit.jupiter.api.Assertions.assertThrows( + final JsonMappingException exception = assertThrows( JsonMappingException.class, () -> mapper.readValue(yaml, PluginModel.class) ); - assertThat(exception.getMessage(), org.hamcrest.Matchers.containsString("String values not allowed")); + assertThat(exception.getMessage(), containsString("String values not allowed")); + } + + @ParameterizedTest + @ValueSource(strings = {"plugin_model_number_value.yaml", "plugin_model_boolean_value.yaml", "plugin_model_array_value.yaml"}) + final void testDeserialize_invalidTokenType_throwsException(final String resourceName) throws IOException { + final InputStream inputStream = PluginModelTests.class.getResourceAsStream(resourceName); + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + final JsonMappingException exception = assertThrows( + JsonMappingException.class, + () -> mapper.readValue(inputStream, PluginModel.class) + ); + assertThat(exception.getMessage(), containsString("Unexpected value for plugin")); } static Map validPluginSettings() { diff --git a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SamplePipelineConfigurationTest.java b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SamplePipelineConfigurationTest.java index 904a8cc75d..ccf53753c9 100644 --- a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SamplePipelineConfigurationTest.java +++ b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SamplePipelineConfigurationTest.java @@ -19,10 +19,12 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Map; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertThrows; /** @@ -43,8 +45,6 @@ void setup() { objectMapper = new ObjectMapper(new YAMLFactory()); } - // --- Valid pipeline scenarios --- - @ParameterizedTest(name = "pipeline with plugin config [{0}] should deserialize successfully") @ValueSource(strings = { "sample_pipelines/sample_pipeline_plugin_null.yaml", @@ -100,13 +100,14 @@ void deserialize_pipeline_withEmptyPluginConfig_hasEmptySettings(final String re final PipelinesDataFlowModel model = objectMapper.readValue(inputStream, PipelinesDataFlowModel.class); final PipelineModel pipeline = model.getPipelines().get(PIPELINE_NAME); - assertThat(pipeline.getSource().getPluginSettings().size(), equalTo(0)); - assertThat(pipeline.getProcessors().get(0).getPluginSettings().size(), equalTo(0)); - assertThat(pipeline.getSinks().get(0).getPluginSettings().size(), equalTo(0)); + final Map sourceSettings = pipeline.getSource().getPluginSettings(); + assertThat(sourceSettings == null || sourceSettings.isEmpty(), equalTo(true)); + final Map processorSettings = pipeline.getProcessors().get(0).getPluginSettings(); + assertThat(processorSettings == null || processorSettings.isEmpty(), equalTo(true)); + final Map sinkSettings = pipeline.getSinks().get(0).getPluginSettings(); + assertThat(sinkSettings == null || sinkSettings.isEmpty(), equalTo(true)); } - // --- Invalid pipeline scenario --- - @Test void deserialize_pipeline_withEmptyStringPluginConfig_throwsException() { final InputStream inputStream = getClass().getResourceAsStream("sample_pipelines/sample_pipeline_plugin_empty_string.yaml"); @@ -115,6 +116,6 @@ void deserialize_pipeline_withEmptyStringPluginConfig_throwsException() { JsonMappingException.class, () -> objectMapper.readValue(inputStream, PipelinesDataFlowModel.class) ); - assertThat(exception.getMessage(), org.hamcrest.Matchers.containsString("Empty string is not allowed")); + assertThat(exception.getMessage(), containsString("Empty string is not allowed")); } } diff --git a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SinkModelTest.java b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SinkModelTest.java index b89bdc7826..831e9c0fa5 100644 --- a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SinkModelTest.java +++ b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/SinkModelTest.java @@ -121,7 +121,7 @@ void deserialize_with_any_pluginModel() throws IOException { } @ParameterizedTest - @ValueSource(strings = {"plugin_model_empty.yaml", "plugin_model_not_present.yaml", "plugin_model_null.yaml"}) + @ValueSource(strings = {"plugin_model_with_empty_object.yaml"}) final void deserialize_with_empty_inner(final String resourceName) throws IOException { final InputStream inputStream = PluginModelTests.class.getResourceAsStream(resourceName); @@ -133,6 +133,17 @@ final void deserialize_with_empty_inner(final String resourceName) throws IOExce assertThat(sinkModel.getPluginSettings().size(), equalTo(0)); } + @Test + final void deserialize_with_no_value_returns_null_settings() throws IOException { + final InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_not_present.yaml"); + + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + final SinkModel sinkModel = mapper.readValue(inputStream, SinkModel.class); + assertThat(sinkModel.getPluginName(), equalTo("customPlugin")); + assertThat(sinkModel.getPluginSettings(), equalTo(null)); + } + @Test void serialize_with_just_pluginModel() throws IOException { final Map pluginSettings = new LinkedHashMap<>(); diff --git a/data-prepper-api/src/test/resources/list_of_plugins_multiple_fields.yaml b/data-prepper-api/src/test/resources/list_of_plugins_multiple_fields.yaml new file mode 100644 index 0000000000..9d96aabeb7 --- /dev/null +++ b/data-prepper-api/src/test/resources/list_of_plugins_multiple_fields.yaml @@ -0,0 +1,7 @@ +listOfPlugins: + - customPluginA: + key1: value1 + - customPluginB: + key2: value2 + - customPluginC: + key3: value3 diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_array_value.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_array_value.yaml new file mode 100644 index 0000000000..ca22bac4a7 --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_array_value.yaml @@ -0,0 +1,2 @@ +--- +customPlugin: [1, 2, 3] diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_boolean_value.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_boolean_value.yaml new file mode 100644 index 0000000000..f666563fe7 --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_boolean_value.yaml @@ -0,0 +1,2 @@ +--- +customPlugin: true diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty.yaml index c11e1f7c90..6bea6ea607 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty.yaml @@ -1,2 +1,2 @@ --- -customPlugin: {} +customPlugin: diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_explicit_empty.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_explicit_empty.yaml new file mode 100644 index 0000000000..c11e1f7c90 --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_explicit_empty.yaml @@ -0,0 +1,2 @@ +--- +customPlugin: {} diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_not_present.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_not_present.yaml index c11e1f7c90..6bea6ea607 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_not_present.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_not_present.yaml @@ -1,2 +1,2 @@ --- -customPlugin: {} +customPlugin: diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_null.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_null.yaml index c11e1f7c90..5e71a96de4 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_null.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_null.yaml @@ -1,2 +1,2 @@ --- -customPlugin: {} +customPlugin: null diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_number_value.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_number_value.yaml new file mode 100644 index 0000000000..566ea7c4b4 --- /dev/null +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_number_value.yaml @@ -0,0 +1,2 @@ +--- +customPlugin: 123 diff --git a/data-prepper-api/src/test/resources/pipeline_with_extension.yaml b/data-prepper-api/src/test/resources/pipeline_with_extension.yaml index 3ee3e33510..360864996d 100644 --- a/data-prepper-api/src/test/resources/pipeline_with_extension.yaml +++ b/data-prepper-api/src/test/resources/pipeline_with_extension.yaml @@ -2,10 +2,10 @@ extension: test_extension: test-pipeline: source: - testSource: {} + testSource: null processor: - - testProcessor: {} + - testProcessor: null sink: - - testSink: {} + - testSink: null workers: 8 delay: 50 diff --git a/data-prepper-api/src/test/resources/pipeline_with_short_hand_version.yaml b/data-prepper-api/src/test/resources/pipeline_with_short_hand_version.yaml index af4ee900f9..3f565eb1f7 100644 --- a/data-prepper-api/src/test/resources/pipeline_with_short_hand_version.yaml +++ b/data-prepper-api/src/test/resources/pipeline_with_short_hand_version.yaml @@ -1,10 +1,10 @@ version: "2" test-pipeline: source: - testSource: {} + testSource: null processor: - - testProcessor: {} + - testProcessor: null sink: - - testSink: {} + - testSink: null workers: 8 delay: 50 diff --git a/data-prepper-api/src/test/resources/pipelines_data_flow_routes.yaml b/data-prepper-api/src/test/resources/pipelines_data_flow_routes.yaml index 9f305f79b7..1d959aecf1 100644 --- a/data-prepper-api/src/test/resources/pipelines_data_flow_routes.yaml +++ b/data-prepper-api/src/test/resources/pipelines_data_flow_routes.yaml @@ -1,8 +1,8 @@ test-pipeline: source: - testSource: {} + testSource: null processor: - - testPrepper: {} + - testPrepper: null routes: - my-route: "/a==b" sink: diff --git a/data-prepper-logstash-configuration/src/test/resources/org/opensearch/dataprepper/logstash/log-ingest-to-opensearch.expected.yaml b/data-prepper-logstash-configuration/src/test/resources/org/opensearch/dataprepper/logstash/log-ingest-to-opensearch.expected.yaml index af770116f8..e06d50447e 100644 --- a/data-prepper-logstash-configuration/src/test/resources/org/opensearch/dataprepper/logstash/log-ingest-to-opensearch.expected.yaml +++ b/data-prepper-logstash-configuration/src/test/resources/org/opensearch/dataprepper/logstash/log-ingest-to-opensearch.expected.yaml @@ -12,7 +12,7 @@ logstash-converted-pipeline: match: log: - "%{COMBINEDAPACHELOG}" - - drop_events: null + - drop_events: {} - key_value: source: "message" destination: "test" From 846416a11a42908fbbea3bf3bc43fc189c00cfc0 Mon Sep 17 00:00:00 2001 From: Siqi Ding Date: Fri, 27 Mar 2026 16:05:35 -0500 Subject: [PATCH 3/3] fix: update Jackson BOM to 2.20.2 and fix logstash expected YAML Signed-off-by: Siqi Ding --- .../model/configuration/PluginModelTests.java | 11 ++++++++++- .../resources/list_of_plugins_multiple_fields.yaml | 9 +++++++++ .../model/configuration/plugin_model_array_value.yaml | 8 ++++++++ .../configuration/plugin_model_boolean_value.yaml | 8 ++++++++ .../configuration/plugin_model_empty_string.yaml | 8 ++++++++ .../configuration/plugin_model_explicit_empty.yaml | 8 ++++++++ .../configuration/plugin_model_number_value.yaml | 8 ++++++++ .../configuration/plugin_model_with_empty_object.yaml | 8 ++++++++ .../configuration/plugin_model_with_empty_value.yaml | 8 ++++++++ .../model/configuration/plugin_model_with_null.yaml | 8 ++++++++ .../sample_pipeline_plugin_empty_object.yaml | 9 +++++++++ .../sample_pipeline_plugin_empty_string.yaml | 9 +++++++++ .../sample_pipeline_plugin_empty_value.yaml | 9 +++++++++ .../sample_pipelines/sample_pipeline_plugin_null.yaml | 9 +++++++++ .../sample_pipeline_plugin_with_settings.yaml | 9 +++++++++ 15 files changed, 128 insertions(+), 1 deletion(-) diff --git a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java index ca548aaf7c..086f73c1a5 100644 --- a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java +++ b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/configuration/PluginModelTests.java @@ -26,6 +26,7 @@ import java.io.Reader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -80,7 +81,7 @@ final void testSerialization_empty_plugin_to_YAML() throws JsonGenerationExcepti InputStream inputStream = PluginModelTests.class.getResourceAsStream("plugin_model_with_empty_object.yaml"); assertThat(serialized, notNullValue()); - assertThat(serialized, equalTo(convertInputStreamToString(inputStream))); + assertThat(serialized, equalTo(stripComments(convertInputStreamToString(inputStream)))); } @ParameterizedTest @@ -283,4 +284,12 @@ static String convertInputStreamToString(InputStream inputStream) throws IOExcep return stringBuilder.toString(); } + static String stripComments(final String content) { + final String stripped = Arrays.stream(content.split("\n")) + .filter(line -> !line.startsWith("#")) + .collect(java.util.stream.Collectors.joining("\n")) + .replaceAll("^\n+", ""); + return content.endsWith("\n") ? stripped + "\n" : stripped; + } + } diff --git a/data-prepper-api/src/test/resources/list_of_plugins_multiple_fields.yaml b/data-prepper-api/src/test/resources/list_of_plugins_multiple_fields.yaml index 9d96aabeb7..43730ddaf3 100644 --- a/data-prepper-api/src/test/resources/list_of_plugins_multiple_fields.yaml +++ b/data-prepper-api/src/test/resources/list_of_plugins_multiple_fields.yaml @@ -1,3 +1,12 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# + listOfPlugins: - customPluginA: key1: value1 diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_array_value.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_array_value.yaml index ca22bac4a7..e01e89eb0d 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_array_value.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_array_value.yaml @@ -1,2 +1,10 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# --- customPlugin: [1, 2, 3] diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_boolean_value.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_boolean_value.yaml index f666563fe7..32077427be 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_boolean_value.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_boolean_value.yaml @@ -1,2 +1,10 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# --- customPlugin: true diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty_string.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty_string.yaml index bb1c297a75..0b3ae03d6d 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty_string.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_empty_string.yaml @@ -1,2 +1,10 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# --- customPlugin: "" diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_explicit_empty.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_explicit_empty.yaml index c11e1f7c90..7712058004 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_explicit_empty.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_explicit_empty.yaml @@ -1,2 +1,10 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# --- customPlugin: {} diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_number_value.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_number_value.yaml index 566ea7c4b4..c80c3cb3e8 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_number_value.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_number_value.yaml @@ -1,2 +1,10 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# --- customPlugin: 123 diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_object.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_object.yaml index c11e1f7c90..7712058004 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_object.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_object.yaml @@ -1,2 +1,10 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# --- customPlugin: {} diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_value.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_value.yaml index 6bea6ea607..da92e68240 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_value.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_empty_value.yaml @@ -1,2 +1,10 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# --- customPlugin: diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_null.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_null.yaml index 5e71a96de4..d49e6c2164 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_null.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/plugin_model_with_null.yaml @@ -1,2 +1,10 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# --- customPlugin: null diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_object.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_object.yaml index effe2624bd..a8204d430e 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_object.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_object.yaml @@ -1,3 +1,12 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# + test-pipeline: source: stdin: {} diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_string.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_string.yaml index e3abc7de64..113d1f7f0f 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_string.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_string.yaml @@ -1,3 +1,12 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# + test-pipeline: source: stdin: "" diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_value.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_value.yaml index 1a3cbb5e35..8f77612f98 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_value.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_empty_value.yaml @@ -1,3 +1,12 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# + test-pipeline: source: stdin: diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_null.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_null.yaml index 3ebe30f30d..ce15aa17e4 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_null.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_null.yaml @@ -1,3 +1,12 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# + test-pipeline: source: stdin: null diff --git a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_with_settings.yaml b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_with_settings.yaml index 87841d55c2..1ba0db072b 100644 --- a/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_with_settings.yaml +++ b/data-prepper-api/src/test/resources/org/opensearch/dataprepper/model/configuration/sample_pipelines/sample_pipeline_plugin_with_settings.yaml @@ -1,3 +1,12 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# + test-pipeline: source: http: