diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java index d631496d59..fc80e46a43 100644 --- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java +++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProvider.java @@ -100,8 +100,16 @@ private static Map load(Path path) { return Map.of(); } - try (InputStream in = Files.newInputStream(path)) { - Map result = MAPPER.readValue(in, Map.class); + try (InputStream in = Files.newInputStream(path); + com.fasterxml.jackson.core.JsonParser parser = MAPPER.getFactory().createParser(in)) { + if (parser.nextToken() == null) { + log.debug( + "YAML file contains no mappings (possibly empty or comments only). " + + "Returning empty config. Path: {}", + path); + return Map.of(); + } + Map result = MAPPER.readValue(parser, Map.class); return result != null ? result : Map.of(); } catch (IOException e) { throw new UncheckedIOException("Failed to load config YAML from " + path, e); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProviderTest.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProviderTest.java index 2362b85ea4..328ac6d83f 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProviderTest.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/config/loader/provider/YamlConfigProviderTest.java @@ -16,6 +16,7 @@ package io.javaoperatorsdk.operator.config.loader.provider; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; @@ -26,6 +27,7 @@ import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; class YamlConfigProviderTest { @@ -132,10 +134,60 @@ void loadsFromFile(@TempDir Path dir) throws IOException { assertThat(provider.getValue("josdk.test.integer", Integer.class)).hasValue(7); } + @Test + void emptyFile(@TempDir Path dir) throws IOException { + Path file = dir.resolve("empty.yaml"); + Files.writeString(file, ""); + + var provider = new YamlConfigProvider(file); + assertThat(provider.getValue("any.key", String.class)).isEmpty(); + } + + @Test + void fileWithCommentOnly(@TempDir Path dir) throws IOException { + Path file = dir.resolve("empty.yaml"); + Files.writeString( + file, + """ + # sample comment + """); + var provider = new YamlConfigProvider(file); + assertThat(provider.getValue("any.key", String.class)).isEmpty(); + } + @Test void returnsEmptyForNonExistingFile(@TempDir Path dir) { Path missing = dir.resolve("does-not-exist.yaml"); var provider = new YamlConfigProvider(missing); assertThat(provider.getValue("any.key", String.class)).isEmpty(); } + + @Test + void throwsForMalformedYaml(@TempDir Path dir) throws IOException { + Path file = dir.resolve("bad.yaml"); + // YAML list where a map is expected + Files.writeString( + file, + """ + - item1 + - item2 + """); + assertThatExceptionOfType(UncheckedIOException.class) + .isThrownBy(() -> new YamlConfigProvider(file)); + } + + @Test + void commentWithProperDocument(@TempDir Path dir) throws IOException { + Path file = dir.resolve("filewithcomment.yaml"); + // YAML comment followed by a proper mapping document + Files.writeString( + file, + """ + # comment + key: + subkey: val + """); + var provider = new YamlConfigProvider(file); + assertThat(provider.getValue("key.subkey", String.class)).contains("val"); + } }