From 6d60180ac1e0c50009a449e7a98038de1d0624d2 Mon Sep 17 00:00:00 2001 From: Baptiste Foy Date: Thu, 4 Sep 2025 11:02:08 +0200 Subject: [PATCH 1/3] fix(StableConfig): Support keys with no value in YAML --- .../provider/stableconfig/StableConfig.java | 8 ++-- .../provider/StableConfigParserTest.groovy | 42 +++++++++++++++++++ .../provider/StableConfigSourceTest.groovy | 25 +++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java index 472b8e845bb..74cfbbba4ae 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java @@ -18,9 +18,11 @@ public final class StableConfig { public StableConfig(Object yaml) { Map map = (Map) yaml; this.configId = map.get("config_id") == null ? null : String.valueOf(map.get("config_id")); - this.apmConfigurationDefault = - unmodifiableMap( - (Map) map.getOrDefault("apm_configuration_default", emptyMap())); + + // getOrDefault returns null if key exists with null value, so we need explicit null check + Map apmConfigDefault = (Map) map.getOrDefault("apm_configuration_default", emptyMap()); + this.apmConfigurationDefault = unmodifiableMap(apmConfigDefault != null ? apmConfigDefault : emptyMap()); + this.apmConfigurationRules = parseRules(map); } diff --git a/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigParserTest.groovy b/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigParserTest.groovy index cd36709ef7d..b3e1e2ef134 100644 --- a/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigParserTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigParserTest.groovy @@ -298,4 +298,46 @@ apm_configuration_rules: "{{environment_variables['']}}" | "Empty environment variable name in template" "{{environment_variables['DD_KEY']}" | "Unterminated template in config" } + + def "test null and empty values in YAML"() { + given: + Path filePath = Files.createTempFile("testFile_", ".yaml") + + when: + String yaml = """ +config_id: "12345" +apm_configuration_default: +apm_configuration_rules: +""" + Files.write(filePath, yaml.getBytes()) + StableConfigSource.StableConfig cfg = StableConfigParser.parse(filePath.toString()) + + then: + cfg.getConfigId() == "12345" + cfg.getKeys().isEmpty() + + cleanup: + Files.delete(filePath) + } + + def "test completely empty values in YAML"() { + given: + Path filePath = Files.createTempFile("testFile_", ".yaml") + + when: + String yaml = """ +config_id: "12345" +apm_configuration_default: +apm_configuration_rules: +""" + Files.write(filePath, yaml.getBytes()) + StableConfigSource.StableConfig cfg = StableConfigParser.parse(filePath.toString()) + + then: + cfg.getConfigId() == "12345" + cfg.getKeys().isEmpty() + + cleanup: + Files.delete(filePath) + } } diff --git a/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.groovy b/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.groovy index 575e87e7de2..08352da0dcc 100644 --- a/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.groovy @@ -71,6 +71,31 @@ class StableConfigSourceTest extends DDSpecification { "12345" | "this is not yaml format!" } + def "test null values in YAML"() { + when: + Path filePath = Files.createTempFile("testFile_", ".yaml") + then: + if (filePath == null) { + throw new AssertionError("Failed to create: " + filePath) + } + + when: + // Test the scenario where YAML contains null values for apm_configuration_default and apm_configuration_rules + String yaml = """ +config_id: "12345" +apm_configuration_default: +apm_configuration_rules: +""" + Files.write(filePath, yaml.getBytes()) + StableConfigSource stableCfg = new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG) + + then: + // Should not throw NullPointerException and should handle null values gracefully + stableCfg.getConfigId() == "12345" + stableCfg.getKeys().size() == 0 + Files.delete(filePath) + } + def "test file valid format"() { given: Path filePath = Files.createTempFile("testFile_", ".yaml") From 19178f87a442749861183805047cbf28bbb2d8aa Mon Sep 17 00:00:00 2001 From: Baptiste Foy Date: Thu, 4 Sep 2025 11:35:01 +0200 Subject: [PATCH 2/3] Update internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java Co-authored-by: Stuart McCulloch --- .../bootstrap/config/provider/stableconfig/StableConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java index 74cfbbba4ae..7ddc62c402e 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java @@ -20,7 +20,7 @@ public StableConfig(Object yaml) { this.configId = map.get("config_id") == null ? null : String.valueOf(map.get("config_id")); // getOrDefault returns null if key exists with null value, so we need explicit null check - Map apmConfigDefault = (Map) map.getOrDefault("apm_configuration_default", emptyMap()); + Map apmConfigDefault = (Map) map.get("apm_configuration_default"); this.apmConfigurationDefault = unmodifiableMap(apmConfigDefault != null ? apmConfigDefault : emptyMap()); this.apmConfigurationRules = parseRules(map); From 0c124390f6623309f826356aa21b62b01a59006f Mon Sep 17 00:00:00 2001 From: Baptiste Foy Date: Thu, 4 Sep 2025 13:23:26 +0200 Subject: [PATCH 3/3] spotless --- .../config/provider/stableconfig/StableConfig.java | 10 ++++++---- .../config/provider/StableConfigParserTest.groovy | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java index 7ddc62c402e..acac0da93dc 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java @@ -18,11 +18,13 @@ public final class StableConfig { public StableConfig(Object yaml) { Map map = (Map) yaml; this.configId = map.get("config_id") == null ? null : String.valueOf(map.get("config_id")); - + // getOrDefault returns null if key exists with null value, so we need explicit null check - Map apmConfigDefault = (Map) map.get("apm_configuration_default"); - this.apmConfigurationDefault = unmodifiableMap(apmConfigDefault != null ? apmConfigDefault : emptyMap()); - + Map apmConfigDefault = + (Map) map.get("apm_configuration_default"); + this.apmConfigurationDefault = + unmodifiableMap(apmConfigDefault != null ? apmConfigDefault : emptyMap()); + this.apmConfigurationRules = parseRules(map); } diff --git a/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigParserTest.groovy b/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigParserTest.groovy index b3e1e2ef134..5868c25467d 100644 --- a/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigParserTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigParserTest.groovy @@ -315,7 +315,7 @@ apm_configuration_rules: then: cfg.getConfigId() == "12345" cfg.getKeys().isEmpty() - + cleanup: Files.delete(filePath) } @@ -336,7 +336,7 @@ apm_configuration_rules: then: cfg.getConfigId() == "12345" cfg.getKeys().isEmpty() - + cleanup: Files.delete(filePath) }