diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationFactory.java
index 1e0dddf3074..9c51f00af9a 100644
--- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationFactory.java
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationFactory.java
@@ -34,6 +34,9 @@ private ConfigurationFactory() {}
* Parse the {@code inputStream} YAML to {@link OpenTelemetryConfiguration} and interpret the
* model to create {@link OpenTelemetrySdk} instance corresponding to the configuration.
*
+ *
Before parsing, environment variable substitution is performed as described in {@link
+ * ConfigurationReader.EnvSubstitutionConstructor}.
+ *
* @param inputStream the configuration YAML
* @return the {@link OpenTelemetrySdk}
*/
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationReader.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationReader.java
index 4fe765ee344..731bfee1770 100644
--- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationReader.java
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigurationReader.java
@@ -10,23 +10,120 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration;
import java.io.InputStream;
+import java.util.Map;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.snakeyaml.engine.v2.api.Load;
import org.snakeyaml.engine.v2.api.LoadSettings;
+import org.snakeyaml.engine.v2.constructor.StandardConstructor;
+import org.snakeyaml.engine.v2.nodes.MappingNode;
+import org.yaml.snakeyaml.Yaml;
final class ConfigurationReader {
- private static final ObjectMapper MAPPER =
- new ObjectMapper()
- // Create empty object instances for keys which are present but have null values
- .setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
+ private static final Pattern ENV_VARIABLE_REFERENCE =
+ Pattern.compile("\\$\\{env:([a-zA-Z_]+[a-zA-Z0-9_]*)}");
+
+ private static final ObjectMapper MAPPER;
+
+ static {
+ MAPPER =
+ new ObjectMapper()
+ // Create empty object instances for keys which are present but have null values
+ .setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
+ // Boxed primitives which are present but have null values should be set to null, rather than
+ // empty instances
+ MAPPER.configOverride(String.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET));
+ MAPPER.configOverride(Integer.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET));
+ MAPPER.configOverride(Double.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET));
+ MAPPER.configOverride(Boolean.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET));
+ }
private ConfigurationReader() {}
- /** Parse the {@code configuration} YAML and return the {@link OpenTelemetryConfiguration}. */
+ /**
+ * Parse the {@code configuration} YAML and return the {@link OpenTelemetryConfiguration}.
+ *
+ *
Environment variables follow the syntax {@code ${env:VARIABLE}}, where {@code VARIABLE} is
+ * an environment variable matching the regular expression {@code [a-zA-Z_]+[a-zA-Z0-9_]*}.
+ *
+ *
Environment variable substitution only takes place on scalar values of maps. References to
+ * environment variables in keys or sets are ignored.
+ *
+ *
If a referenced environment variable is not defined, it is replaced with {@code ""}.
+ */
+ static final class EnvSubstitutionConstructor extends StandardConstructor {
+
+ // Yaml is not thread safe but this instance is always used on the same thread
+ private final Yaml yaml = new Yaml();
+ private final Map environmentVariables;
+
+ private EnvSubstitutionConstructor(
+ LoadSettings loadSettings, Map environmentVariables) {
+ super(loadSettings);
+ this.environmentVariables = environmentVariables;
+ }
+
+ @Override
+ protected Map