Skip to content

Stable Config file: target system properties in process_arguments and support template variables in YamlParser #8690

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 42 additions & 8 deletions components/cli/src/main/java/datadog/cli/CLIHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,29 @@
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public final class CLIHelper {
private static final List<String> VM_ARGS = findVmArgs();
private static final Map<String, String> VM_ARGS = findVmArgs();

public static List<String> getVmArgs() {
public static Map<String, String> getVmArgs() {
return VM_ARGS;
}

@SuppressForbidden
private static List<String> findVmArgs() {
private static Map<String, String> findVmArgs() {
List<String> rawArgs;

// Try ProcFS on Linux
try {
if (isLinux()) {
Path cmdlinePath = Paths.get("/proc/self/cmdline");
if (Files.exists(cmdlinePath)) {
try (BufferedReader in = Files.newBufferedReader(cmdlinePath)) {
return Arrays.asList(in.readLine().split("\0"));
return parseVmArgs(Arrays.asList(in.readLine().split("\0")));
}
}
}
Expand Down Expand Up @@ -57,7 +61,8 @@ private static List<String> findVmArgs() {
}

//noinspection unchecked
return (List<String>) vmManagementClass.getMethod("getVmArguments").invoke(vmManagement);
rawArgs = (List<String>) vmManagementClass.getMethod("getVmArguments").invoke(vmManagement);
return parseVmArgs(rawArgs);
} catch (final ReflectiveOperationException | UnsatisfiedLinkError ignored) {
// Ignored exception
}
Expand All @@ -66,20 +71,49 @@ private static List<String> findVmArgs() {
try {
final Class<?> VMClass = Class.forName("com.ibm.oti.vm.VM");
final String[] argArray = (String[]) VMClass.getMethod("getVMArgs").invoke(null);
return Arrays.asList(argArray);
rawArgs = Arrays.asList(argArray);
return parseVmArgs(rawArgs);
} catch (final ReflectiveOperationException ignored) {
// Ignored exception
}

// Fallback to default
try {
return ManagementFactory.getRuntimeMXBean().getInputArguments();
rawArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
return parseVmArgs(rawArgs);
} catch (final Throwable t) {
// Throws InvocationTargetException on modularized applications
// with non-opened java.management module
System.err.println("WARNING: Unable to get VM args using managed beans");
}
return Collections.emptyList();
return Collections.emptyMap();
}

private static Map<String, String> parseVmArgs(List<String> args) {
Map<String, String> result = new HashMap<>();

// For now, we only support values on system properties (-D arguments)
for (String arg : args) {
if (arg.startsWith("-D")) {
// Handle system properties (-D arguments)
int equalsIndex = arg.indexOf('=');

if (equalsIndex >= 0) {
// Key-value pair
String key = arg.substring(0, equalsIndex);
String value = arg.substring(equalsIndex + 1);
result.put(key, value);
} else {
// Just a key with no value
result.put(arg, "");
}
} else {
// Any other type of VM argument
result.put(arg, "");
}
}

return result;
}

private static boolean isLinux() {
Expand Down
1 change: 1 addition & 0 deletions components/yaml/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ jmh {
// https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.4/snakeyaml-2.4.pom
dependencies {
implementation("org.yaml", "snakeyaml", "2.4")
implementation(project(":components:cli"))
}
82 changes: 75 additions & 7 deletions components/yaml/src/main/java/datadog/yaml/YamlParser.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,87 @@
package datadog.yaml;

import java.io.FileInputStream;
import datadog.cli.CLIHelper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import org.yaml.snakeyaml.Yaml;

public class YamlParser {
// Supports clazz == null for default yaml parsing
private static final Map<String, String> VM_ARGS = CLIHelper.getVmArgs();

public static <T> T parse(String filePath, Class<T> clazz) throws IOException {
Yaml yaml = new Yaml();
try (FileInputStream fis = new FileInputStream(filePath)) {
if (clazz == null) {
return yaml.load(fis);
} else {
return yaml.loadAs(fis, clazz);
String content = new String(Files.readAllBytes(Paths.get(filePath)));
String processedContent = processTemplate(content);

if (clazz == null) {
return yaml.load(processedContent);
} else {
return yaml.loadAs(processedContent, clazz);
}
}

private static String processTemplate(String content) throws IOException {
StringBuilder result = new StringBuilder(content.length());
String rest = content;

while (true) {
int openIndex = rest.indexOf("{{");
if (openIndex == -1) {
result.append(rest);
break;
}

// Add everything before the template
result.append(rest.substring(0, openIndex));

// Find the closing braces
int closeIndex = rest.indexOf("}}", openIndex);
if (closeIndex == -1) {
throw new IOException("Unterminated template in config");
}

// Extract the template variable
String templateVar = rest.substring(openIndex + 2, closeIndex).trim();

// Process the template variable and get its value
String value = processTemplateVar(templateVar);

// Add the processed value
result.append(value);

// Continue with the rest of the string
rest = rest.substring(closeIndex + 2);
}

return result.toString();
}

private static String processTemplateVar(String templateVar) throws IOException {
if (templateVar.startsWith("environment_variables[") && templateVar.endsWith("]")) {
String envVar =
templateVar.substring("environment_variables[".length(), templateVar.length() - 1).trim();
if (envVar.isEmpty()) {
throw new IOException("Empty environment variable name in template");
}
String value = System.getenv(envVar);
if (value == null || value.isEmpty()) {
return "UNDEFINED";
}
return value;
} else if (templateVar.startsWith("process_arguments[") && templateVar.endsWith("]")) {
String processArg =
templateVar.substring("process_arguments[".length(), templateVar.length() - 1).trim();
if (processArg.isEmpty()) {
throw new IOException("Empty process argument in template");
}
String value = VM_ARGS.get(processArg);
if (value == null || value.isEmpty()) {
return "UNDEFINED";
}
return value;
}
return "UNDEFINED";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;

Expand Down Expand Up @@ -381,8 +382,12 @@ private static List<File> getAgentFilesFromVMArguments() {
// - On IBM-based JDKs since at least 1.7
// This prevents custom log managers from working correctly
// Use reflection to bypass the loading of the class~
for (final String argument : CLIHelper.getVmArgs()) {
Map<String, String> vmArgs = CLIHelper.getVmArgs();
for (final Map.Entry<String, String> entry : vmArgs.entrySet()) {
final String argument = entry.getKey();
if (argument.startsWith(JAVA_AGENT_ARGUMENT)) {
// TODO: Modify CLIHelper to parse key-vals on JAVA_AGENT_ARGUMENT arguments, so we don't
// have to do the parsing here.
int index = argument.indexOf('=', JAVA_AGENT_ARGUMENT.length());
String agentPathname =
argument.substring(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.function.BiPredicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StableConfigParser {
private static final Logger log = LoggerFactory.getLogger(StableConfigParser.class);

private static final Set<String> VM_ARGS = new HashSet<>(CLIHelper.getVmArgs());
private static final Map<String, String> VM_ARGS = CLIHelper.getVmArgs();

/**
* Parses a configuration file and returns a stable configuration object.
Expand Down Expand Up @@ -66,7 +65,9 @@ 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 {}", filePath);
"Stable configuration file either not found or not readable at filepath {}. Error: {}",
filePath,
e.getMessage());
}
return StableConfigSource.StableConfig.EMPTY;
}
Expand Down Expand Up @@ -168,7 +169,7 @@ static boolean selectorMatch(String origin, List<String> matches, String operato
case "process_arguments":
// For now, always return true if `key` exists in the JVM Args
// TODO: flesh out the meaning of each operator for process_arguments
return VM_ARGS.contains(key);
return VM_ARGS.containsKey(key);
case "tags":
// TODO: Support this down the line (Must define the source of "tags" first)
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ apm_configuration_rules:
"environment_variables" | ["svc"] | "contains" | "DD_SERVICE" | true
"environment_variables" | ["other"] | "contains" | "DD_SERVICE" | false
"environment_variables" | [null] | "contains" | "DD_SERVICE" | false
// "process_arguments" | null | "equals" | "-DCustomKey" | true
}

def "test duplicate entries"() {
Expand Down