Respect environment variables in jvm.options (#16834)

JvmOptionsParser adds support for ${VAR:default} syntax when parsing jvm.options
- allow dynamic resolution of environment variables in the jvm.options file
- enables fallback to default value when the environment variable is not set
This commit is contained in:
kaisecheng 2025-01-03 23:04:28 +00:00 committed by GitHub
parent de6a6c5b0f
commit ef36df6b81
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 97 additions and 5 deletions

View file

@ -30,7 +30,7 @@
## basic ## basic
# set the I/O temp directory # set the I/O temp directory
#-Djava.io.tmpdir=$HOME #-Djava.io.tmpdir=${HOME}
# set to headless, just in case # set to headless, just in case
-Djava.awt.headless=true -Djava.awt.headless=true

View file

@ -32,7 +32,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -283,7 +282,6 @@ public class JvmOptionsParser {
} }
private static final Pattern OPTION_DEFINITION = Pattern.compile("((?<start>\\d+)(?<range>-)?(?<end>\\d+)?:)?(?<option>-.*)$"); private static final Pattern OPTION_DEFINITION = Pattern.compile("((?<start>\\d+)(?<range>-)?(?<end>\\d+)?:)?(?<option>-.*)$");
/** /**
* *
* If the version syntax specified on a line matches the specified JVM options, the JVM option callback will be invoked with the JVM * If the version syntax specified on a line matches the specified JVM options, the JVM option callback will be invoked with the JVM
@ -372,13 +370,16 @@ public class JvmOptionsParser {
// Skip comments and blank lines // Skip comments and blank lines
return Optional.empty(); return Optional.empty();
} }
final Matcher matcher = OPTION_DEFINITION.matcher(line);
String subbedLine = resolveEnvVar(line, System.getenv());
final Matcher matcher = OPTION_DEFINITION.matcher(subbedLine);
if (matcher.matches()) { if (matcher.matches()) {
final String start = matcher.group("start"); final String start = matcher.group("start");
final String end = matcher.group("end"); final String end = matcher.group("end");
if (start == null) { if (start == null) {
// no range present, unconditionally apply the JVM option // no range present, unconditionally apply the JVM option
return Optional.of(line); return Optional.of(subbedLine);
} else { } else {
final int lower = Integer.parseInt(start); final int lower = Integer.parseInt(start);
final int upper; final int upper;
@ -404,6 +405,23 @@ public class JvmOptionsParser {
return Optional.empty(); return Optional.empty();
} }
private static final Pattern ENV_VAR_PATTERN = Pattern.compile("\\$\\{([a-zA-Z_.][a-zA-Z0-9_.]*)(?::([^}]*))?\\}");
static String resolveEnvVar(String line, Map<String,String> env) {
Matcher matcher = ENV_VAR_PATTERN.matcher(line);
StringBuilder sb = new StringBuilder();
while (matcher.find()) {
String varName = matcher.group(1);
String defaultValue = Optional.ofNullable(matcher.group(2)).orElse("");
String replacement = env.getOrDefault(varName, defaultValue);
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(sb);
return sb.toString();
}
private static final Pattern JAVA_VERSION = Pattern.compile("^(?:1\\.)?(?<javaMajorVersion>\\d+)(?:\\.\\d+)?$"); private static final Pattern JAVA_VERSION = Pattern.compile("^(?:1\\.)?(?<javaMajorVersion>\\d+)(?:\\.\\d+)?$");
private static int javaMajorVersion() { private static int javaMajorVersion() {

View file

@ -198,4 +198,78 @@ public class JvmOptionsParserTest {
return new BufferedReader(new StringReader(s)); return new BufferedReader(new StringReader(s));
} }
@Test
public void testSingleEnvSub() throws IOException {
String result = JvmOptionsParser.resolveEnvVar("-XX:HeapDumpPath=${LOGSTASH_HOME}/heapdump.hprof",
Map.of("LOGSTASH_HOME", "/path/to/ls_home"));
assertEquals("-XX:HeapDumpPath=/path/to/ls_home/heapdump.hprof", result);
}
@Test
public void testMultipleEnvSub() throws IOException {
String result = JvmOptionsParser.resolveEnvVar("-XX:HeapDumpPath=${LOGSTASH_HOME}/${DATA}/heapdump.hprof",
Map.of("LOGSTASH_HOME", "/path/to/ls_home", "DATA", "data"));
assertEquals("-XX:HeapDumpPath=/path/to/ls_home/data/heapdump.hprof", result);
}
@Test
public void testPeriodEnvSub() throws IOException {
String result = JvmOptionsParser.resolveEnvVar("-XX:HeapDumpPath=${.HOME}/heapdump.hprof",
Map.of(".HOME", "/path/to/.home"));
assertEquals("-XX:HeapDumpPath=/path/to/.home/heapdump.hprof", result);
}
@Test
public void testEmptyEnvSub() throws IOException {
String result = JvmOptionsParser.resolveEnvVar("-XX:HeapDumpPath=${NOT_VALID}/heapdump.hprof", Map.of());
assertEquals("-XX:HeapDumpPath=/heapdump.hprof", result);
}
@Test
public void testNoSub() throws IOException {
String result = JvmOptionsParser.resolveEnvVar(" ", Map.of());
assertEquals(" ", result);
}
@Test
public void testEnvSubWithDefault() throws IOException {
String result = JvmOptionsParser.resolveEnvVar("-XX:HeapDumpPath=${LOGSTASH_HOME:/usr/share/logstash}/${DATA:data}/heapdump.hprof",
Map.of());
assertEquals("-XX:HeapDumpPath=/usr/share/logstash/data/heapdump.hprof", result);
}
@Test
public void testEnvSubWithDefaultSpecialChar() throws IOException {
String result = JvmOptionsParser.resolveEnvVar("-XX:HeapDumpPath=${LOGSTASH_HOME:/usr/share/logstash}/${DATA:{$crazy!enough?'bless'@[you]}/heapdump.hprof",
Map.of());
assertEquals("-XX:HeapDumpPath=/usr/share/logstash/{$crazy!enough?'bless'@[you]/heapdump.hprof", result);
}
@Test
public void testEnvSubWithDefaultOverwritten() throws IOException {
String result = JvmOptionsParser.resolveEnvVar("-XX:HeapDumpPath=${LOGSTASH_HOME:/usr/share/logstash}/${DATA:data}/heapdump.hprof",
Map.of("DATA", "data2"));
assertEquals("-XX:HeapDumpPath=/usr/share/logstash/data2/heapdump.hprof", result);
}
@Test
public void testEnvSubInFile() throws IOException {
File optionsFile = writeIntoTempOptionsFile(
writer -> writer.println("-Xlog:gc*,gc+age=trace,safepoint:file=${UNKNOWN}:"));
JvmOptionsParser.handleJvmOptions(new String[] {"/path/to/ls_home", optionsFile.toString()}, "-Dcli.opts=something");
final String output = outputStreamCaptor.toString();
assertTrue("env variable should be substituted ", output.contains("-Xlog:gc*,gc+age=trace,safepoint:file=:"));
}
@Test
public void testCommentedEnvSub() throws IOException {
final BufferedReader options = asReader("# -Xlog:gc*,gc+age=trace,safepoint:file=${UNKNOWN}:");
final JvmOptionsParser.ParseResult res = JvmOptionsParser.parse(11, options);
assertTrue("no invalid lines can be present", res.getInvalidLines().isEmpty());
assertFalse(String.join(System.lineSeparator(), res.getJvmOptions()).contains("-Xlog:gc*,gc+age=trace,safepoint"));
}
} }