diff --git a/components/yaml/build.gradle.kts b/components/yaml/build.gradle.kts index 1ad2b8e1f01..f2297262236 100644 --- a/components/yaml/build.gradle.kts +++ b/components/yaml/build.gradle.kts @@ -4,7 +4,6 @@ plugins { apply(from = "$rootDir/gradle/java.gradle") -// https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.4/snakeyaml-2.4.pom dependencies { - implementation("org.yaml", "snakeyaml", "2.4") + implementation("org.snakeyaml", "snakeyaml-engine", "2.9") } diff --git a/components/yaml/src/main/java/datadog/yaml/YamlParser.java b/components/yaml/src/main/java/datadog/yaml/YamlParser.java index 7af6d22303b..1284ef0833a 100644 --- a/components/yaml/src/main/java/datadog/yaml/YamlParser.java +++ b/components/yaml/src/main/java/datadog/yaml/YamlParser.java @@ -1,15 +1,18 @@ package datadog.yaml; -import org.yaml.snakeyaml.Yaml; +import org.snakeyaml.engine.v2.api.Load; +import org.snakeyaml.engine.v2.api.LoadSettings; public class YamlParser { - // Supports clazz == null for default yaml parsing - public static T parse(String content, Class clazz) { - Yaml yaml = new Yaml(); - if (clazz == null) { - return yaml.load(content); - } else { - return yaml.loadAs(content, clazz); - } + /** + * Parses YAML content. Duplicate keys are not allowed and will result in a runtime exception.. + * + * @param content - text context to be parsed as YAML + * @return - a parsed representation as a composition of map and list objects. + */ + public static Object parse(String content) { + LoadSettings settings = LoadSettings.builder().build(); + Load yaml = new Load(settings); + return yaml.loadFromString(content); } } diff --git a/dd-java-agent/build.gradle b/dd-java-agent/build.gradle index 1b2c3804acb..4413097cc79 100644 --- a/dd-java-agent/build.gradle +++ b/dd-java-agent/build.gradle @@ -38,7 +38,7 @@ ext.generalShadowJarConfig = { // used to report our own dependencies, but we should remove the top-level metadata // of vendored packages because those could trigger unwanted framework checks. exclude '/META-INF/maven/org.slf4j/**' - exclude '/META-INF/maven/org.yaml/**' + exclude '/META-INF/maven/org.snakeyaml/**' exclude '**/META-INF/maven/**/pom.xml' exclude '**/META-INF/proguard/' exclude '**/META-INF/*.kotlin_module' @@ -66,7 +66,7 @@ ext.generalShadowJarConfig = { // Prevents conflict with other instances, but doesn't relocate instrumentation if (!projectName.equals('instrumentation')) { - relocate 'org.yaml.snakeyaml', 'datadog.snakeyaml' + relocate 'org.snakeyaml.engine', 'datadog.snakeyaml.engine' relocate 'okhttp3', 'datadog.okhttp3' relocate 'okio', 'datadog.okio' } diff --git a/dd-java-agent/instrumentation/build.gradle b/dd-java-agent/instrumentation/build.gradle index 2d19b5f5b6f..e7d93411ed7 100644 --- a/dd-java-agent/instrumentation/build.gradle +++ b/dd-java-agent/instrumentation/build.gradle @@ -60,7 +60,7 @@ subprojects { Project subProj -> jdkCompile = "main_${name}Implementation" } configurations.muzzleBootstrap { - exclude group: 'org.yaml', module : 'snakeyaml' // we vendor this in the agent jar + exclude group: 'org.snakeyaml', module : 'snakeyaml-engine' // we vendor this in the agent jar } dependencies { // Apply common dependencies for instrumentation. diff --git a/dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/ResourcesFeatureInstrumentation.java b/dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/ResourcesFeatureInstrumentation.java index bf546d0b97f..47e5a2a62b1 100644 --- a/dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/ResourcesFeatureInstrumentation.java +++ b/dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/ResourcesFeatureInstrumentation.java @@ -50,8 +50,7 @@ public static void onExit() { tracerResources.add("profiling/jfr/overrides/minimal.jfp"); // jmxfetch configs - tracerResources.add( - "metrics/project.properties"); // org.datadog.jmxfetch.AppConfig reads its version + tracerResources.add("metrics/project.properties"); tracerResources.add("metrics/org/datadog/jmxfetch/default-jmx-metrics.yaml"); tracerResources.add("metrics/org/datadog/jmxfetch/new-gc-default-jmx-metrics.yaml"); tracerResources.add("metrics/org/datadog/jmxfetch/old-gc-default-jmx-metrics.yaml"); diff --git a/dd-java-agent/testing/build.gradle b/dd-java-agent/testing/build.gradle index 00394d6f480..df2430f9433 100644 --- a/dd-java-agent/testing/build.gradle +++ b/dd-java-agent/testing/build.gradle @@ -39,7 +39,7 @@ excludedClassesCoverage += [ ] configurations.api { - exclude group: 'org.yaml', module: 'snakeyaml' // we vendor this in the agent jar + exclude group: 'org.snakeyaml', module: 'snakeyaml-engine' // we vendor this in the agent jar } dependencies { diff --git a/dd-smoke-tests/spring-boot-3.0-native/application/build.gradle b/dd-smoke-tests/spring-boot-3.0-native/application/build.gradle index 26f88cf0a49..441b0ee29f9 100644 --- a/dd-smoke-tests/spring-boot-3.0-native/application/build.gradle +++ b/dd-smoke-tests/spring-boot-3.0-native/application/build.gradle @@ -39,7 +39,6 @@ if (hasProperty('agentPath')) { if (withProfiler && property('profiler') == 'true') { buildArgs.add("-J-Ddd.profiling.enabled=true") } - buildArgs.add("--enable-monitoring=jmxserver") jvmArgs.add("-Xmx3072M") } } diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 4fbdac06696..cb46e079d9a 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -51,8 +51,8 @@ final class CachedData { // cafe_crypto and its transitives exclude(dependency('cafe.cryptography::')) - // snakeyaml and its transitives - exclude(dependency('org.yaml:snakeyaml')) + // snakeyaml-engine and its transitives + exclude(dependency('org.snakeyaml:snakeyaml-engine')) } ] } diff --git a/internal-api/build.gradle b/internal-api/build.gradle index fd3fe3ddb9c..fbf1916a4af 100644 --- a/internal-api/build.gradle +++ b/internal-api/build.gradle @@ -252,7 +252,7 @@ dependencies { // it contains annotations that are also present in the instrumented application classes api "com.datadoghq:dd-javac-plugin-client:0.2.2" - testImplementation("org.yaml:snakeyaml:2.4") + testImplementation("org.snakeyaml:snakeyaml-engine:2.9") testImplementation project(":utils:test-utils") testImplementation("org.assertj:assertj-core:3.20.2") testImplementation libs.bundles.junit5 diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/StableConfigParser.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/StableConfigParser.java index 3721528f81f..47f3ea53a7d 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/StableConfigParser.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/StableConfigParser.java @@ -1,17 +1,17 @@ package datadog.trace.bootstrap.config.provider; -import datadog.trace.bootstrap.config.provider.stableconfigyaml.ConfigurationMap; -import datadog.trace.bootstrap.config.provider.stableconfigyaml.Rule; -import datadog.trace.bootstrap.config.provider.stableconfigyaml.Selector; -import datadog.trace.bootstrap.config.provider.stableconfigyaml.StableConfigYaml; +import datadog.trace.bootstrap.config.provider.stableconfig.Rule; +import datadog.trace.bootstrap.config.provider.stableconfig.Selector; +import datadog.trace.bootstrap.config.provider.stableconfig.StableConfig; import datadog.yaml.YamlParser; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.function.BiPredicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,11 +41,12 @@ public static StableConfigSource.StableConfig parse(String filePath) throws IOEx try { String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8); String processedContent = processTemplate(content); - StableConfigYaml data = YamlParser.parse(processedContent, StableConfigYaml.class); + Object parsedYaml = YamlParser.parse(processedContent); + StableConfig data = new StableConfig(parsedYaml); - String configId = data.getConfig_id(); - ConfigurationMap configMap = data.getApm_configuration_default(); - List rules = data.getApm_configuration_rules(); + String configId = data.getConfigId(); + Map configMap = data.getApmConfigurationDefault(); + List rules = data.getApmConfigurationRules(); if (!rules.isEmpty()) { for (Rule rule : rules) { @@ -53,14 +54,15 @@ public static StableConfigSource.StableConfig parse(String filePath) throws IOEx if (doesRuleMatch(rule)) { // Merge configs found in apm_configuration_rules with those found in // apm_configuration_default - configMap.putAll(rule.getConfiguration()); - return createStableConfig(configId, configMap); + Map mergedConfigMap = new LinkedHashMap<>(configMap); + mergedConfigMap.putAll(rule.getConfiguration()); + return new StableConfigSource.StableConfig(configId, mergedConfigMap); } } } // If configs were found in apm_configuration_default, use them if (!configMap.isEmpty()) { - return createStableConfig(configId, configMap); + return new StableConfigSource.StableConfig(configId, configMap); } // If there's a configId but no configMap, use configId but return an empty map @@ -69,10 +71,7 @@ public static StableConfigSource.StableConfig parse(String filePath) throws IOEx } } catch (IOException e) { - log.debug( - "Stable configuration file either not found or not readable at filepath {}. Error: {}", - filePath, - e.getMessage()); + log.debug("Failed to read the stable configuration file: {}", filePath, e); } return StableConfigSource.StableConfig.EMPTY; } @@ -91,12 +90,6 @@ private static boolean doesRuleMatch(Rule rule) { return true; // Return true if all selectors match } - /** Creates a StableConfig object from the provided configId and configMap. */ - private static StableConfigSource.StableConfig createStableConfig( - String configId, ConfigurationMap configMap) { - return new StableConfigSource.StableConfig(configId, new HashMap<>(configMap)); - } - private static boolean validOperatorForLanguageOrigin(String operator) { operator = operator.toLowerCase(); // "exists" is not valid diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/Rule.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/Rule.java new file mode 100644 index 00000000000..2d464b41ed1 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/Rule.java @@ -0,0 +1,44 @@ +package datadog.trace.bootstrap.config.provider.stableconfig; + +import static java.util.Collections.*; +import static java.util.stream.Collectors.toList; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Rule represents a set of selectors and their corresponding configurations found in stable + * configuration files + */ +public final class Rule { + private final List selectors; + private final Map configuration; + + public Rule() { + this.selectors = emptyList(); + this.configuration = emptyMap(); + } + + public Rule(List selectors, Map configuration) { + this.selectors = selectors; + this.configuration = configuration; + } + + public Rule(Object yaml) { + Map map = (Map) yaml; + selectors = + unmodifiableList( + ((List) map.get("selectors")) + .stream().filter(Objects::nonNull).map(Selector::new).collect(toList())); + configuration = unmodifiableMap((Map) map.get("configuration")); + } + + public List getSelectors() { + return selectors; + } + + public Map getConfiguration() { + return configuration; + } +} diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/Selector.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/Selector.java new file mode 100644 index 00000000000..acf396053fd --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/Selector.java @@ -0,0 +1,43 @@ +package datadog.trace.bootstrap.config.provider.stableconfig; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public final class Selector { + private final String origin; + private final String key; + private final List matches; + private final String operator; + + public Selector(String origin, String key, List matches, String operator) { + this.origin = origin; + this.key = key; + this.matches = matches; + this.operator = operator; + } + + public Selector(Object yaml) { + Map map = (Map) yaml; + origin = (String) map.get("origin"); + key = (String) map.get("key"); + matches = Collections.unmodifiableList((List) map.get("matches")); + operator = (String) map.get("operator"); + } + + public String getOrigin() { + return origin; + } + + public String getKey() { + return key; + } + + public List getMatches() { + return matches; + } + + public String getOperator() { + return operator; + } +} 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 new file mode 100644 index 00000000000..58fa3c4f826 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfig/StableConfig.java @@ -0,0 +1,48 @@ +package datadog.trace.bootstrap.config.provider.stableconfig; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableMap; +import static java.util.stream.Collectors.toList; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public final class StableConfig { + private final String configId; + private final Map apmConfigurationDefault; + private final List apmConfigurationRules; + + public StableConfig(Object yaml) { + Map map = (Map) yaml; + this.configId = String.valueOf(map.get("config_id")); + this.apmConfigurationDefault = + unmodifiableMap( + (Map) map.getOrDefault("apm_configuration_default", emptyMap())); + this.apmConfigurationRules = + unmodifiableList( + ((List) map.getOrDefault("apm_configuration_rules", emptyList())) + .stream().map(Rule::new).collect(toList())); + } + + // test only + private StableConfig(String configId, Map apmConfigurationDefault) { + this.configId = configId; + this.apmConfigurationDefault = apmConfigurationDefault; + this.apmConfigurationRules = new ArrayList<>(); + } + + public String getConfigId() { + return configId; + } + + public Map getApmConfigurationDefault() { + return apmConfigurationDefault; + } + + public List getApmConfigurationRules() { + return apmConfigurationRules; + } +} diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/ConfigurationMap.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/ConfigurationMap.java deleted file mode 100644 index 20940733fb2..00000000000 --- a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/ConfigurationMap.java +++ /dev/null @@ -1,23 +0,0 @@ -package datadog.trace.bootstrap.config.provider.stableconfigyaml; - -import java.util.HashMap; -import java.util.Map; - -// ConfigurationMap represents configuration key-values found in stable configuration files -public class ConfigurationMap extends HashMap { - public ConfigurationMap() { - super(); - } - - public ConfigurationMap(Map map) { - super(map); - } -} - -class ConfigurationValue { - private final String value; - - public ConfigurationValue(String value) { - this.value = value; - } -} diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/Rule.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/Rule.java deleted file mode 100644 index dbcca5792e3..00000000000 --- a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/Rule.java +++ /dev/null @@ -1,38 +0,0 @@ -package datadog.trace.bootstrap.config.provider.stableconfigyaml; - -import java.util.ArrayList; -import java.util.List; - -// Rule represents a set of selectors and their corresponding configurations found in stable -// configuration files -public class Rule { - private List selectors; - private ConfigurationMap configuration; - - public Rule() { - this.selectors = new ArrayList<>(); - this.configuration = new ConfigurationMap(); - } - - public Rule(List selectors, ConfigurationMap configuration) { - this.selectors = selectors; - this.configuration = configuration; - } - - // Getters and setters - public List getSelectors() { - return selectors; - } - - public void setSelectors(List selectors) { - this.selectors = selectors; - } - - public ConfigurationMap getConfiguration() { - return configuration; - } - - public void setConfiguration(ConfigurationMap configuration) { - this.configuration = configuration; - } -} diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/Selector.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/Selector.java deleted file mode 100644 index 05ce5cb9c6b..00000000000 --- a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/Selector.java +++ /dev/null @@ -1,58 +0,0 @@ -package datadog.trace.bootstrap.config.provider.stableconfigyaml; - -import java.util.ArrayList; -import java.util.List; - -public class Selector { - private String origin; - private String key; - private List matches; - private String operator; - - public Selector() { - this.origin = null; - this.key = null; - this.matches = new ArrayList<>(); - this.operator = null; - } - - public Selector(String origin, String key, List matches, String operator) { - this.origin = origin; - this.key = key; - this.matches = matches; - this.operator = operator; - } - - // Getters and setters - public String getOrigin() { - return origin; - } - - public void setOrigin(String origin) { - this.origin = origin; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public List getMatches() { - return matches; - } - - public void setMatches(List matches) { - this.matches = matches; - } - - public String getOperator() { - return operator; - } - - public void setOperator(String operator) { - this.operator = operator; - } -} diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/StableConfigYaml.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/StableConfigYaml.java deleted file mode 100644 index ec1a2816ed1..00000000000 --- a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/stableconfigyaml/StableConfigYaml.java +++ /dev/null @@ -1,41 +0,0 @@ -package datadog.trace.bootstrap.config.provider.stableconfigyaml; - -import java.util.ArrayList; -import java.util.List; - -public class StableConfigYaml { - private String config_id; // optional - private ConfigurationMap apm_configuration_default; - private List apm_configuration_rules; // optional - - public StableConfigYaml() { - this.config_id = null; - this.apm_configuration_default = new ConfigurationMap(); - this.apm_configuration_rules = new ArrayList<>(); - } - - // Getters and setters - public String getConfig_id() { - return config_id; - } - - public void setConfig_id(String config_id) { - this.config_id = config_id; - } - - public ConfigurationMap getApm_configuration_default() { - return apm_configuration_default; - } - - public void setApm_configuration_default(ConfigurationMap apm_configuration_default) { - this.apm_configuration_default = apm_configuration_default; - } - - public List getApm_configuration_rules() { - return apm_configuration_rules; - } - - public void setApm_configuration_rules(List apm_configuration_rules) { - this.apm_configuration_rules = apm_configuration_rules; - } -} 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 686648471a1..4a0bbdbf39f 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 @@ -4,12 +4,16 @@ import java.nio.file.Files import java.nio.file.Path class StableConfigParserTest extends DDSpecification { + def "test parse valid"() { when: Path filePath = Files.createTempFile("testFile_", ".yaml") + then: if (filePath == null) { - throw new AssertionError("Failed to create test file") + throw new AssertionError("Failed to create: " + filePath) } + + when: injectEnvConfig("DD_SERVICE", "mysvc") // From the below yaml, only apm_configuration_default and the second selector should be applied: We use the first matching rule and discard the rest String yaml = """ @@ -39,18 +43,8 @@ apm_configuration_rules: configuration: KEY_FOUR: "ignored" """ - try { - Files.write(filePath, yaml.getBytes()) - } catch (IOException e) { - throw new AssertionError("Failed to write to file: ${e.message}") - } - - StableConfigSource.StableConfig cfg - try { - cfg = StableConfigParser.parse(filePath.toString()) - } catch (Exception e) { - throw new AssertionError("Failed to parse the file: ${e.message}") - } + Files.write(filePath, yaml.getBytes()) + StableConfigSource.StableConfig cfg = StableConfigParser.parse(filePath.toString()) then: def keys = cfg.getKeys() @@ -74,86 +68,67 @@ apm_configuration_rules: match == expectMatch where: - origin | matches | operator | key | expectMatch - "language" | ["java"] | "equals" | "" | true - "LANGUAGE" | ["JaVa"] | "EQUALS" | "" | true // check case insensitivity - "language" | ["java", "golang"] | "equals" | "" | true - "language" | ["java"] | "starts_with" | "" | true - "language" | ["golang"] | "equals" | "" | false - "language" | ["java"] | "exists" | "" | false - "language" | ["java"] | "something unexpected" | "" | false - "environment_variables" | [] | "exists" | "DD_TAGS" | true - "environment_variables" | ["team:apm"] | "contains" | "DD_TAGS" | true - "ENVIRONMENT_VARIABLES" | ["TeAm:ApM"] | "CoNtAiNs" | "Dd_TaGs" | true // check case insensitivity - "environment_variables" | ["team:apm"] | "equals" | "DD_TAGS" | false - "environment_variables" | ["team:apm"] | "starts_with" | "DD_TAGS" | true - "environment_variables" | ["true"] | "equals" | "DD_PROFILING_ENABLED" | true - "environment_variables" | ["abcdefg"] | "equals" | "DD_PROFILING_ENABLED" | false - "environment_variables" | ["true"] | "equals" | "DD_PROFILING_ENABLED" | true - "environment_variables" | ["mysvc", "othersvc"] | "equals" | "DD_SERVICE" | true - "environment_variables" | ["my"] | "starts_with" | "DD_SERVICE" | true - "environment_variables" | ["svc"] | "ends_with" | "DD_SERVICE" | true - "environment_variables" | ["svc"] | "contains" | "DD_SERVICE" | true - "environment_variables" | ["other"] | "contains" | "DD_SERVICE" | false - "environment_variables" | [null] | "contains" | "DD_SERVICE" | false + origin | matches | operator | key | expectMatch + "language" | ["java"] | "equals" | "" | true + "LANGUAGE" | ["JaVa"] | "EQUALS" | "" | true // check case insensitivity + "language" | ["java", "golang"] | "equals" | "" | true + "language" | ["java"] | "starts_with" | "" | true + "language" | ["golang"] | "equals" | "" | false + "language" | ["java"] | "exists" | "" | false + "language" | ["java"] | "something unexpected" | "" | false + "environment_variables" | [] | "exists" | "DD_TAGS" | true + "environment_variables" | ["team:apm"] | "contains" | "DD_TAGS" | true + "ENVIRONMENT_VARIABLES" | ["TeAm:ApM"] | "CoNtAiNs" | "Dd_TaGs" | true // check case insensitivity + "environment_variables" | ["team:apm"] | "equals" | "DD_TAGS" | false + "environment_variables" | ["team:apm"] | "starts_with" | "DD_TAGS" | true + "environment_variables" | ["true"] | "equals" | "DD_PROFILING_ENABLED" | true + "environment_variables" | ["abcdefg"] | "equals" | "DD_PROFILING_ENABLED" | false + "environment_variables" | ["true"] | "equals" | "DD_PROFILING_ENABLED" | true + "environment_variables" | ["mysvc", "othersvc"] | "equals" | "DD_SERVICE" | true + "environment_variables" | ["my"] | "starts_with" | "DD_SERVICE" | true + "environment_variables" | ["svc"] | "ends_with" | "DD_SERVICE" | true + "environment_variables" | ["svc"] | "contains" | "DD_SERVICE" | true + "environment_variables" | ["other"] | "contains" | "DD_SERVICE" | false + "environment_variables" | [null] | "contains" | "DD_SERVICE" | false } - def "test duplicate entries"() { - // When duplicate keys are encountered, snakeyaml preserves the last value by default + def "test duplicate entries not allowed"() { when: Path filePath = Files.createTempFile("testFile_", ".yaml") + then: if (filePath == null) { - throw new AssertionError("Failed to create test file") + throw new AssertionError("Failed to create: " + filePath) } + + when: String yaml = """ config_id: 12345 config_id: 67890 - apm_configuration_default: - DD_KEY: value_1 - apm_configuration_default: - DD_KEY: value_2 """ - - try { - Files.write(filePath, yaml.getBytes()) - } catch (IOException e) { - throw new AssertionError("Failed to write to file: ${e.message}") - } - - StableConfigSource.StableConfig cfg - try { - cfg = StableConfigParser.parse(filePath.toString()) - } catch (Exception e) { - throw new AssertionError("Failed to parse the file: ${e.message}") - } + Files.write(filePath, yaml.getBytes()) + StableConfigParser.parse(filePath.toString()) then: - cfg != null - cfg.getConfigId() == "67890" - cfg.get("DD_KEY") == "value_2" + def ex = thrown(RuntimeException) + + and: + ex.message.contains "found duplicate key config_id" } def "test config_id only"() { when: Path filePath = Files.createTempFile("testFile_", ".yaml") + then: if (filePath == null) { - throw new AssertionError("Failed to create test file") + throw new AssertionError("Failed to create: " + filePath) } + + when: String yaml = """ config_id: 12345 """ - try { - Files.write(filePath, yaml.getBytes()) - } catch (IOException e) { - throw new AssertionError("Failed to write to file: ${e.message}") - } - - StableConfigSource.StableConfig cfg - try { - cfg = StableConfigParser.parse(filePath.toString()) - } catch (Exception e) { - throw new AssertionError("Failed to parse the file: ${e.message}") - } + Files.write(filePath, yaml.getBytes()) + StableConfigSource.StableConfig cfg = StableConfigParser.parse(filePath.toString()) then: cfg != null @@ -165,9 +140,12 @@ apm_configuration_rules: // If any piece of the file is invalid, the whole file is rendered invalid and an exception is thrown when: Path filePath = Files.createTempFile("testFile_", ".yaml") + then: if (filePath == null) { - throw new AssertionError("Failed to create test file") + throw new AssertionError("Failed to create: " + filePath) } + + when: String yaml = """ something-irrelevant: "" config_id: 12345 @@ -185,13 +163,8 @@ apm_configuration_rules: KEY_FIVE: [a,b,c,d] something-else-irrelevant: value-irrelevant """ - try { - Files.write(filePath, yaml.getBytes()) - } catch (IOException e) { - throw new AssertionError("Failed to write to file: ${e.message}") - } - - StableConfigSource.StableConfig cfg + Files.write(filePath, yaml.getBytes()) + StableConfigSource.StableConfig cfg = null Exception exception = null try { cfg = StableConfigParser.parse(filePath.toString()) @@ -215,14 +188,14 @@ apm_configuration_rules: StableConfigParser.processTemplate(templateVar) == expect where: - templateVar | envKey | envVal | expect - "{{environment_variables['DD_KEY']}}" | "DD_KEY" | "value" | "value" - "{{environment_variables['DD_KEY']}}" | null | null | "UNDEFINED" - "{{}}" | null | null | "UNDEFINED" - "{}" | null | null | "{}" - "{{environment_variables['dd_key']}}" | "DD_KEY" | "value" | "value" - "{{environment_variables['DD_KEY}}" | "DD_KEY" | "value" | "UNDEFINED" - "header-{{environment_variables['DD_KEY']}}-footer" | "DD_KEY" | "value" | "header-value-footer" + templateVar | envKey | envVal | expect + "{{environment_variables['DD_KEY']}}" | "DD_KEY" | "value" | "value" + "{{environment_variables['DD_KEY']}}" | null | null | "UNDEFINED" + "{{}}" | null | null | "UNDEFINED" + "{}" | null | null | "{}" + "{{environment_variables['dd_key']}}" | "DD_KEY" | "value" | "value" + "{{environment_variables['DD_KEY}}" | "DD_KEY" | "value" | "UNDEFINED" + "header-{{environment_variables['DD_KEY']}}-footer" | "DD_KEY" | "value" | "header-value-footer" "{{environment_variables['HEADER']}}{{environment_variables['DD_KEY']}}{{environment_variables['FOOTER']}}" | "DD_KEY" | "value" | "UNDEFINEDvalueUNDEFINED" } @@ -235,8 +208,8 @@ apm_configuration_rules: e.message == expect where: - templateVar | expect - "{{environment_variables['']}}" | "Empty environment variable name in template" + templateVar | expect + "{{environment_variables['']}}" | "Empty environment variable name in template" "{{environment_variables['DD_KEY']}" | "Unterminated template in config" } } 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 f9a6281f337..b3ec755ddc1 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 @@ -3,18 +3,12 @@ package datadog.trace.bootstrap.config.provider import static java.util.Collections.singletonMap import datadog.trace.api.ConfigOrigin -import datadog.trace.bootstrap.config.provider.stableconfigyaml.ConfigurationMap -import datadog.trace.bootstrap.config.provider.stableconfigyaml.ConfigurationValue -import datadog.trace.bootstrap.config.provider.stableconfigyaml.Rule -import datadog.trace.bootstrap.config.provider.stableconfigyaml.Selector -import datadog.trace.bootstrap.config.provider.stableconfigyaml.StableConfigYaml +import datadog.trace.bootstrap.config.provider.stableconfig.Rule +import datadog.trace.bootstrap.config.provider.stableconfig.Selector +import datadog.trace.bootstrap.config.provider.stableconfig.StableConfig import datadog.trace.test.util.DDSpecification -import org.yaml.snakeyaml.DumperOptions -import org.yaml.snakeyaml.Yaml -import org.yaml.snakeyaml.introspector.Property -import org.yaml.snakeyaml.nodes.NodeTuple -import org.yaml.snakeyaml.nodes.Tag -import org.yaml.snakeyaml.representer.Representer +import org.snakeyaml.engine.v2.api.Dump +import org.snakeyaml.engine.v2.api.DumpSettings import java.nio.file.Files import java.nio.file.Path @@ -34,11 +28,13 @@ class StableConfigSourceTest extends DDSpecification { def "test empty file"() { when: Path filePath = Files.createTempFile("testFile_", ".yaml") + then: if (filePath == null) { - throw new AssertionError("Failed to create test file") + throw new AssertionError("Failed to create: " + filePath) } - StableConfigSource config = new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG) + when: + StableConfigSource config = new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG) then: config.getKeys().size() == 0 config.getConfigId() == null @@ -48,16 +44,13 @@ class StableConfigSourceTest extends DDSpecification { // StableConfigSource must handle the exception thrown by StableConfigParser.parse(filePath) gracefully when: Path filePath = Files.createTempFile("testFile_", ".yaml") + then: if (filePath == null) { - throw new AssertionError("Failed to create test file") - } - - try { - writeFileRaw(filePath, configId, data) - } catch (IOException e) { - throw new AssertionError("Failed to write to file: ${e.message}") + throw new AssertionError("Failed to create: " + filePath) } + when: + writeFileRaw(filePath, configId, data) StableConfigSource stableCfg = new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG) then: @@ -74,19 +67,14 @@ class StableConfigSourceTest extends DDSpecification { def "test file valid format"() { when: Path filePath = Files.createTempFile("testFile_", ".yaml") + then: if (filePath == null) { - throw new AssertionError("Failed to create test file") - } - StableConfigYaml stableConfigYaml = new StableConfigYaml() - stableConfigYaml.setConfig_id(configId) - stableConfigYaml.setApm_configuration_default(defaultConfigs as ConfigurationMap) - - try { - writeFileYaml(filePath, stableConfigYaml) - } catch (IOException e) { - throw new AssertionError("Failed to write to file: ${e.message}") + throw new AssertionError("Failed to create: " + filePath) } + when: + StableConfig stableConfigYaml = new StableConfig(configId, defaultConfigs) + writeFileYaml(filePath, stableConfigYaml) StableConfigSource stableCfg = new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG) then: @@ -115,9 +103,9 @@ class StableConfigSourceTest extends DDSpecification { Files.delete(filePath) where: - configId | defaultConfigs | ruleConfigs - "" | new HashMap<>() | Arrays.asList(new Rule()) - "12345" | new HashMap<>() << ["DD_KEY_ONE": "one", "DD_KEY_TWO": "two"] | Arrays.asList(sampleMatchingRule, sampleNonMatchingRule) + configId | defaultConfigs | ruleConfigs + "" | [:] | Arrays.asList(new Rule()) + "12345" | ["DD_KEY_ONE": "one", "DD_KEY_TWO": "two"] | Arrays.asList(sampleMatchingRule, sampleNonMatchingRule) } // Corrupt YAML string variable used for testing, defined outside the 'where' block for readability @@ -129,37 +117,26 @@ class StableConfigSourceTest extends DDSpecification { ''' // Matching and non-matching Rules used for testing, defined outside the 'where' block for readability - def static sampleMatchingRule = new Rule(Arrays.asList(new Selector("origin", "language", Arrays.asList("Java"), null)), new ConfigurationMap(singletonMap("DD_KEY_THREE", new ConfigurationValue("three")))) - def static sampleNonMatchingRule = new Rule(Arrays.asList(new Selector("origin", "language", Arrays.asList("Golang"), null)), new ConfigurationMap(singletonMap("DD_KEY_FOUR", new ConfigurationValue("four")))) - - // Helper functions - def stableConfigYamlWriter = getStableConfigYamlWriter() - - Yaml getStableConfigYamlWriter() { - DumperOptions options = new DumperOptions() - // Create the Representer, configure it to omit nulls - Representer representer = new Representer(options) { - @Override - protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) { - if (propertyValue == null) { - return null // Skip null values - } else { - return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag) - } - } - } - // Exclude class tag from the resulting yaml string - representer.addClassTag(StableConfigYaml.class, Tag.MAP) + def static sampleMatchingRule = new Rule(Arrays.asList(new Selector("origin", "language", Arrays.asList("Java"), null)), singletonMap("DD_KEY_THREE", "three")) + def static sampleNonMatchingRule = new Rule(Arrays.asList(new Selector("origin", "language", Arrays.asList("Golang"), null)), singletonMap("DD_KEY_FOUR", "four")) - // YAML instance with custom Representer - return new Yaml(representer, options) - } + def writeFileYaml(Path filePath, StableConfig stableConfigs) { + Map yamlData = new HashMap<>() + + if (stableConfigs.getConfigId() != null && !stableConfigs.getConfigId().isEmpty()) { + yamlData.put("config_id", stableConfigs.getConfigId()) + } + + if (stableConfigs.getApmConfigurationDefault() != null && !stableConfigs.getApmConfigurationDefault().isEmpty()) { + yamlData.put("apm_configuration_default", stableConfigs.getApmConfigurationDefault()) + } + + DumpSettings settings = DumpSettings.builder().build() + Dump dump = new Dump(settings) + String yamlContent = dump.dumpToString(yamlData) - def writeFileYaml(Path filePath, StableConfigYaml stableConfigs) { - try (FileWriter writer = new FileWriter(filePath.toString())) { - stableConfigYamlWriter.dump(stableConfigs, writer) - } catch (IOException e) { - e.printStackTrace() + try (FileWriter writer = new FileWriter(filePath.toFile())) { + writer.write(yamlContent) } }