Skip to content

Commit 37d6bbb

Browse files
committed
Adjust algorithm to match marcalff's proposal
1 parent 940dc1a commit 37d6bbb

File tree

2 files changed

+68
-27
lines changed

2 files changed

+68
-27
lines changed

sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfiguration.java

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.regex.MatchResult;
3030
import java.util.regex.Matcher;
3131
import java.util.regex.Pattern;
32+
import javax.annotation.Nullable;
3233
import org.snakeyaml.engine.v2.api.Load;
3334
import org.snakeyaml.engine.v2.api.LoadSettings;
3435
import org.snakeyaml.engine.v2.common.ScalarStyle;
@@ -51,7 +52,7 @@ public final class FileConfiguration {
5152

5253
private static final Logger logger = Logger.getLogger(FileConfiguration.class.getName());
5354
private static final Pattern ENV_VARIABLE_REFERENCE =
54-
Pattern.compile("\\${1,2}\\{([a-zA-Z_][a-zA-Z0-9_]*)(:-([^\n}]*))?}");
55+
Pattern.compile("\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)(:-([^\n}]*))?}");
5556
private static final ComponentLoader DEFAULT_COMPONENT_LOADER =
5657
SpiHelper.serviceComponentLoader(FileConfiguration.class.getClassLoader());
5758

@@ -297,6 +298,10 @@ protected Map<Object, Object> constructMapping(MappingNode node) {
297298
return mapping;
298299
}
299300

301+
private static final String ESCAPE_SEQUENCE = "$$";
302+
private static final int ESCAPE_SEQUENCE_LENGTH = ESCAPE_SEQUENCE.length();
303+
private static final char ESCAPE_SEQUENCE_REPLACEMENT = '$';
304+
300305
private Object constructValueObject(Node node) {
301306
Object value = constructObject(node);
302307
if (!(node instanceof ScalarNode)) {
@@ -307,44 +312,69 @@ private Object constructValueObject(Node node) {
307312
}
308313

309314
String val = (String) value;
315+
ScalarStyle scalarStyle = ((ScalarNode) node).getScalarStyle();
316+
317+
// Iterate through val left to right, search for escape sequence "$$"
318+
// For the substring of val between the last escape sequence and the next found, perform
319+
// environment variable substitution
320+
// Add the escape replacement character '$' in place of each escape sequence found
321+
322+
int lastEscapeIndexEnd = 0;
323+
StringBuilder newVal = null;
324+
while (true) {
325+
int escapeIndex = val.indexOf(ESCAPE_SEQUENCE, lastEscapeIndexEnd);
326+
int substitutionEndIndex = escapeIndex == -1 ? val.length() : escapeIndex;
327+
newVal = envVarSubstitution(newVal, val, lastEscapeIndexEnd, substitutionEndIndex);
328+
if (escapeIndex == -1) {
329+
break;
330+
} else {
331+
newVal.append(ESCAPE_SEQUENCE_REPLACEMENT);
332+
}
333+
lastEscapeIndexEnd = escapeIndex + ESCAPE_SEQUENCE_LENGTH;
334+
if (lastEscapeIndexEnd >= val.length()) {
335+
break;
336+
}
337+
}
338+
339+
// If the value was double quoted, retain the double quotes so we don't change a value
340+
// intended to be a string to a different type after environment variable substitution
341+
if (scalarStyle == ScalarStyle.DOUBLE_QUOTED) {
342+
newVal.insert(0, "\"");
343+
newVal.append("\"");
344+
}
345+
return load.loadFromString(newVal.toString());
346+
}
347+
348+
private StringBuilder envVarSubstitution(
349+
@Nullable StringBuilder newVal, String source, int startIndex, int endIndex) {
350+
String val = source.substring(startIndex, endIndex);
310351
Matcher matcher = ENV_VARIABLE_REFERENCE.matcher(val);
352+
311353
if (!matcher.find()) {
312-
return value;
354+
return newVal == null ? new StringBuilder(val) : newVal.append(val);
355+
}
356+
357+
if (newVal == null) {
358+
newVal = new StringBuilder();
313359
}
314360

315361
int offset = 0;
316-
StringBuilder newVal = new StringBuilder();
317-
ScalarStyle scalarStyle = ((ScalarNode) node).getScalarStyle();
318362
do {
319363
MatchResult matchResult = matcher.toMatchResult();
320-
newVal.append(val, offset, matchResult.start());
321-
322-
String ref = val.substring(matchResult.start(), matchResult.end());
323-
// $$ indicates that env var reference is escaped. Strip the leading $.
324-
if (ref.startsWith("$$")) {
325-
newVal.append(ref.substring(1));
326-
} else {
327-
String envVarKey = matcher.group(1);
328-
String defaultValue = matcher.group(3);
329-
if (defaultValue == null) {
330-
defaultValue = "";
331-
}
332-
String replacement = environmentVariables.getOrDefault(envVarKey, defaultValue);
333-
newVal.append(replacement);
364+
String envVarKey = matcher.group(1);
365+
String defaultValue = matcher.group(3);
366+
if (defaultValue == null) {
367+
defaultValue = "";
334368
}
335-
369+
String replacement = environmentVariables.getOrDefault(envVarKey, defaultValue);
370+
newVal.append(val, offset, matchResult.start()).append(replacement);
336371
offset = matchResult.end();
337372
} while (matcher.find());
338373
if (offset != val.length()) {
339374
newVal.append(val, offset, val.length());
340375
}
341-
// If the value was double quoted, retain the double quotes so we don't change a value
342-
// intended to be a string to a different type after environment variable substitution
343-
if (scalarStyle == ScalarStyle.DOUBLE_QUOTED) {
344-
newVal.insert(0, "\"");
345-
newVal.append("\"");
346-
}
347-
return load.loadFromString(newVal.toString());
376+
377+
return newVal;
348378
}
349379
}
350380
}

sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigurationParseTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ private static java.util.stream.Stream<Arguments> coreSchemaValuesArgs() {
592592
@MethodSource("envVarSubstitutionArgs")
593593
void envSubstituteAndLoadYaml(String rawYaml, Object expectedYamlResult) {
594594
Map<String, String> environmentVariables = new HashMap<>();
595+
environmentVariables.put("FOO", "BAR");
595596
environmentVariables.put("STR_1", "value1");
596597
environmentVariables.put("STR_2", "value2");
597598
environmentVariables.put("EMPTY_STR", "");
@@ -658,8 +659,18 @@ private static java.util.stream.Stream<Arguments> envVarSubstitutionArgs() {
658659
Arguments.of("key1: \"${INT}\"\n", mapOf(entry("key1", "1"))),
659660
Arguments.of("key1: \"${FLOAT}\"\n", mapOf(entry("key1", "1.1"))),
660661
// Escaped
662+
Arguments.of("key1: ${FOO}\n", mapOf(entry("key1", "BAR"))),
663+
Arguments.of("key1: $${FOO}\n", mapOf(entry("key1", "${FOO}"))),
664+
Arguments.of("key1: $$${FOO}\n", mapOf(entry("key1", "$BAR"))),
665+
Arguments.of("key1: $$$${FOO}\n", mapOf(entry("key1", "$${FOO}"))),
666+
Arguments.of("key1: a $$ b\n", mapOf(entry("key1", "a $ b"))),
667+
Arguments.of("key1: $$ b\n", mapOf(entry("key1", "$ b"))),
668+
Arguments.of("key1: a $$\n", mapOf(entry("key1", "a $"))),
669+
Arguments.of("key1: a $ b\n", mapOf(entry("key1", "a $ b"))),
661670
Arguments.of("key1: $${STR_1}\n", mapOf(entry("key1", "${STR_1}"))),
662-
Arguments.of("key1: $$${STR_1}\n", mapOf(entry("key1", "$${STR_1}"))),
671+
Arguments.of("key1: $${STR_1}$${STR_1}\n", mapOf(entry("key1", "${STR_1}${STR_1}"))),
672+
Arguments.of("key1: $${STR_1}$$\n", mapOf(entry("key1", "${STR_1}$"))),
673+
Arguments.of("key1: $$${STR_1}\n", mapOf(entry("key1", "$value1"))),
663674
Arguments.of("key1: \"$${STR_1}\"\n", mapOf(entry("key1", "${STR_1}"))),
664675
Arguments.of("key1: $${STR_1} ${STR_2}\n", mapOf(entry("key1", "${STR_1} value2"))),
665676
Arguments.of("key1: $${STR_1} $${STR_2}\n", mapOf(entry("key1", "${STR_1} ${STR_2}"))),

0 commit comments

Comments
 (0)