Skip to content

Commit dc0b4ac

Browse files
authored
Add support for escaping declarative config env var substitution (#7033)
1 parent 2d1c14e commit dc0b4ac

File tree

2 files changed

+75
-11
lines changed

2 files changed

+75
-11
lines changed

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

Lines changed: 49 additions & 10 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;
@@ -308,6 +309,10 @@ protected Map<Object, Object> constructMapping(MappingNode node) {
308309
return mapping;
309310
}
310311

312+
private static final String ESCAPE_SEQUENCE = "$$";
313+
private static final int ESCAPE_SEQUENCE_LENGTH = ESCAPE_SEQUENCE.length();
314+
private static final char ESCAPE_SEQUENCE_REPLACEMENT = '$';
315+
311316
private Object constructValueObject(Node node) {
312317
Object value = constructObject(node);
313318
if (!(node instanceof ScalarNode)) {
@@ -318,14 +323,53 @@ private Object constructValueObject(Node node) {
318323
}
319324

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

326372
int offset = 0;
327-
StringBuilder newVal = new StringBuilder();
328-
ScalarStyle scalarStyle = ((ScalarNode) node).getScalarStyle();
329373
do {
330374
MatchResult matchResult = matcher.toMatchResult();
331375
String envVarKey = matcher.group(1);
@@ -340,13 +384,8 @@ private Object constructValueObject(Node node) {
340384
if (offset != val.length()) {
341385
newVal.append(val, offset, val.length());
342386
}
343-
// If the value was double quoted, retain the double quotes so we don't change a value
344-
// intended to be a string to a different type after environment variable substitution
345-
if (scalarStyle == ScalarStyle.DOUBLE_QUOTED) {
346-
newVal.insert(0, "\"");
347-
newVal.append("\"");
348-
}
349-
return load.loadFromString(newVal.toString());
387+
388+
return newVal;
350389
}
351390
}
352391
}

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,8 +593,10 @@ private static java.util.stream.Stream<Arguments> coreSchemaValuesArgs() {
593593
@MethodSource("envVarSubstitutionArgs")
594594
void envSubstituteAndLoadYaml(String rawYaml, Object expectedYamlResult) {
595595
Map<String, String> environmentVariables = new HashMap<>();
596+
environmentVariables.put("FOO", "BAR");
596597
environmentVariables.put("STR_1", "value1");
597598
environmentVariables.put("STR_2", "value2");
599+
environmentVariables.put("VALUE_WITH_ESCAPE", "value$$");
598600
environmentVariables.put("EMPTY_STR", "");
599601
environmentVariables.put("BOOL", "true");
600602
environmentVariables.put("INT", "1");
@@ -657,7 +659,30 @@ private static java.util.stream.Stream<Arguments> envVarSubstitutionArgs() {
657659
Arguments.of("key1: \"${EMPTY_STR}\"\n", mapOf(entry("key1", ""))),
658660
Arguments.of("key1: \"${BOOL}\"\n", mapOf(entry("key1", "true"))),
659661
Arguments.of("key1: \"${INT}\"\n", mapOf(entry("key1", "1"))),
660-
Arguments.of("key1: \"${FLOAT}\"\n", mapOf(entry("key1", "1.1"))));
662+
Arguments.of("key1: \"${FLOAT}\"\n", mapOf(entry("key1", "1.1"))),
663+
// Escaped
664+
Arguments.of("key1: ${FOO}\n", mapOf(entry("key1", "BAR"))),
665+
Arguments.of("key1: $${FOO}\n", mapOf(entry("key1", "${FOO}"))),
666+
Arguments.of("key1: $$${FOO}\n", mapOf(entry("key1", "$BAR"))),
667+
Arguments.of("key1: $$$${FOO}\n", mapOf(entry("key1", "$${FOO}"))),
668+
Arguments.of("key1: a $$ b\n", mapOf(entry("key1", "a $ b"))),
669+
Arguments.of("key1: $$ b\n", mapOf(entry("key1", "$ b"))),
670+
Arguments.of("key1: a $$\n", mapOf(entry("key1", "a $"))),
671+
Arguments.of("key1: a $ b\n", mapOf(entry("key1", "a $ b"))),
672+
Arguments.of("key1: $${STR_1}\n", mapOf(entry("key1", "${STR_1}"))),
673+
Arguments.of("key1: $${STR_1}$${STR_1}\n", mapOf(entry("key1", "${STR_1}${STR_1}"))),
674+
Arguments.of("key1: $${STR_1}$$\n", mapOf(entry("key1", "${STR_1}$"))),
675+
Arguments.of("key1: $$${STR_1}\n", mapOf(entry("key1", "$value1"))),
676+
Arguments.of("key1: \"$${STR_1}\"\n", mapOf(entry("key1", "${STR_1}"))),
677+
Arguments.of("key1: $${STR_1} ${STR_2}\n", mapOf(entry("key1", "${STR_1} value2"))),
678+
Arguments.of("key1: $${STR_1} $${STR_2}\n", mapOf(entry("key1", "${STR_1} ${STR_2}"))),
679+
Arguments.of("key1: $${NOT_SET:-value1}\n", mapOf(entry("key1", "${NOT_SET:-value1}"))),
680+
Arguments.of("key1: $${STR_1:-fallback}\n", mapOf(entry("key1", "${STR_1:-fallback}"))),
681+
Arguments.of("key1: $${STR_1:-${STR_1}}\n", mapOf(entry("key1", "${STR_1:-value1}"))),
682+
Arguments.of("key1: ${NOT_SET:-${FALLBACK}}\n", mapOf(entry("key1", "${FALLBACK}"))),
683+
Arguments.of(
684+
"key1: ${NOT_SET:-$${FALLBACK}}\n", mapOf(entry("key1", "${NOT_SET:-${FALLBACK}}"))),
685+
Arguments.of("key1: ${VALUE_WITH_ESCAPE}\n", mapOf(entry("key1", "value$$"))));
661686
}
662687

663688
private static <K, V> Map.Entry<K, V> entry(K key, @Nullable V value) {

0 commit comments

Comments
 (0)