Skip to content
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

Enable programmatic overriding of environment variables #797

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 5 additions & 5 deletions config/src/main/java/com/typesafe/config/ConfigFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -1137,13 +1137,13 @@ public static java.util.Optional<Config> parseApplicationReplacement(ConfigParse

// override application.conf with config.file, config.resource,
// config.url if requested.
String resource = System.getProperty("config.resource");
String resource = SystemOverride.getProperty("config.resource");
if (resource != null)
specified += 1;
String file = System.getProperty("config.file");
String file = SystemOverride.getProperty("config.file");
if (file != null)
specified += 1;
String url = System.getProperty("config.url");
String url = SystemOverride.getProperty("config.url");
if (url != null)
specified += 1;

Expand Down Expand Up @@ -1238,7 +1238,7 @@ public static Config parseMap(Map<String, ? extends Object> values) {
}

private static ConfigLoadingStrategy getConfigLoadingStrategy() {
String className = System.getProperties().getProperty(STRATEGY_PROPERTY_NAME);
String className = SystemOverride.getProperties().getProperty(STRATEGY_PROPERTY_NAME);

if (className != null) {
try {
Expand All @@ -1256,7 +1256,7 @@ private static ConfigLoadingStrategy getConfigLoadingStrategy() {
}

private static Boolean getOverrideWithEnv() {
String overrideWithEnv = System.getProperties().getProperty(OVERRIDE_WITH_ENV_PROPERTY_NAME);
String overrideWithEnv = SystemOverride.getProperties().getProperty(OVERRIDE_WITH_ENV_PROPERTY_NAME);

return Boolean.parseBoolean(overrideWithEnv);
}
Expand Down
138 changes: 138 additions & 0 deletions config/src/main/java/com/typesafe/config/SystemOverride.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.typesafe.config;

import java.io.PrintStream;
import java.util.Map;
import java.util.Properties;
import java.util.function.Supplier;

public class SystemOverride {

/** Runs the specified synchronous (blocking) operation
* guaranteeing that all SystemOverride methods return the specified values
* while the operation is being executed.
* <p>
* The configuration library only ever accesses System through SystemOverride,
* so this method is useful whenever you want to influence the configuration resolution
* through altering the environment variables (also system properties and standard error stream) programmatically.
*
* @param systemProperties replacement for the system properties
* @param environmentVariables replacement for the environment variables
* @param errorStream replacement for the standard error stream
*
* @return T the result of the specified operation
* @throws RuntimeException in case the specified operation throws
* */
public static <T> T withSystemOverride(
Map<String, String> systemProperties,
Map<String, String> environmentVariables,
PrintStream errorStream,
Supplier<T> configurationAccessOperation) {
SystemImplementation overridden = new OverriddenSystemImplementation(
systemProperties,
environmentVariables,
errorStream);
try {
current.set(overridden);
return configurationAccessOperation.get();
} finally {
current.set(real);
}
}

public static Properties getProperties() {
return current.get().getProperties();
}

public static String getProperty(String propertyKey) {
return current.get().getProperty(propertyKey);
}

public static String getenv(String environmentVariableName) {
return current.get().getenv(environmentVariableName);
}

public static Map<String, String> getenv() {
return current.get().getenv();
}

public static PrintStream err() {
return current.get().err();
}

private static interface SystemImplementation {
Properties getProperties();

String getProperty(String propertyKey);

String getenv(String environmentVariableName);

Map<String, String> getenv();

PrintStream err();

}

private static class LiveSystemImplementation implements SystemImplementation {
public Properties getProperties() {
return System.getProperties();
}

public String getProperty(String propertyKey) {
return System.getProperty(propertyKey);
}

public String getenv(String environmentVariableName) {
return System.getenv(environmentVariableName);
}

public Map<String, String> getenv() {
return System.getenv();
}

public PrintStream err() {
return System.err;
}


}

private static class OverriddenSystemImplementation implements SystemImplementation {

private final Map<String, String> systemProperties;
private final Map<String, String> environmentVariables;
private final PrintStream errorStream;

private OverriddenSystemImplementation(Map<String, String> systemProperties, Map<String, String> environmentVariables, PrintStream errorStream) {
this.systemProperties = systemProperties;
this.environmentVariables = environmentVariables;
this.errorStream = errorStream;
}

public Properties getProperties() {
Properties result = new Properties();
result.putAll(systemProperties);
return result;
}

public String getProperty(String propertyKey) {
return systemProperties.get(propertyKey);
}

public String getenv(String environmentVariableName) {
return environmentVariables.get(environmentVariableName);
}

public Map<String, String> getenv() {
return environmentVariables;
}

public PrintStream err() {
return errorStream;
}
}

private static final SystemImplementation real = new LiveSystemImplementation();

private static final ThreadLocal<SystemImplementation> current = ThreadLocal.withInitial(() -> real);

}
19 changes: 10 additions & 9 deletions config/src/main/java/com/typesafe/config/impl/ConfigImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigParseable;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.SystemOverride;
import com.typesafe.config.impl.SimpleIncluder.NameSource;

/**
Expand Down Expand Up @@ -299,7 +300,7 @@ static ConfigIncluder defaultIncluder() {

private static Properties getSystemProperties() {
// Avoid ConcurrentModificationException due to parallel setting of system properties by copying properties
final Properties systemProperties = System.getProperties();
final Properties systemProperties = SystemOverride.getProperties();
final Properties systemPropertiesCopy = new Properties();
synchronized (systemProperties) {
for (Map.Entry<Object, Object> entry: systemProperties.entrySet()) {
Expand Down Expand Up @@ -342,7 +343,7 @@ public static void reloadSystemPropertiesConfig() {
}

private static AbstractConfigObject loadEnvVariables() {
return PropertiesParser.fromStringMap(newEnvVariable("env variables"), System.getenv());
return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), SystemOverride.getenv());
}

private static class EnvVariablesHolder {
Expand Down Expand Up @@ -370,8 +371,8 @@ public static void reloadEnvVariablesConfig() {


private static AbstractConfigObject loadEnvVariablesOverrides() {
Map<String, String> env = new HashMap(System.getenv());
Map<String, String> result = new HashMap();
Map<String, String> env = new HashMap<>(SystemOverride.getenv());
Map<String, String> result = new HashMap<>();

for (String key : env.keySet()) {
if (key.startsWith(ENV_VAR_OVERRIDE_PREFIX)) {
Expand Down Expand Up @@ -453,7 +454,7 @@ private static Map<String, Boolean> loadDiagnostics() {
result.put(SUBSTITUTIONS, false);

// People do -Dconfig.trace=foo,bar to enable tracing of different things
String s = System.getProperty("config.trace");
String s = SystemOverride.getProperty("config.trace");
if (s == null) {
return result;
} else {
Expand All @@ -464,7 +465,7 @@ private static Map<String, Boolean> loadDiagnostics() {
} else if (k.equals(SUBSTITUTIONS)) {
result.put(SUBSTITUTIONS, true);
} else {
System.err.println("config.trace property contains unknown trace topic '"
SystemOverride.err().println("config.trace property contains unknown trace topic '"
+ k + "'");
}
}
Expand Down Expand Up @@ -503,15 +504,15 @@ public static boolean traceSubstitutionsEnabled() {
}

public static void trace(String message) {
System.err.println(message);
SystemOverride.err().println(message);
}

public static void trace(int indentLevel, String message) {
while (indentLevel > 0) {
System.err.print(" ");
SystemOverride.err().print(" ");
indentLevel -= 1;
}
System.err.println(message);
SystemOverride.err().println(message);
}

// the basic idea here is to add the "what" and have a canonical
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigResolveOptions
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.SystemOverride
import scala.collection.JavaConverters._

class ConfigSubstitutionTest extends TestUtils {
Expand Down Expand Up @@ -731,11 +732,20 @@ class ConfigSubstitutionTest extends TestUtils {
|"a": ${testList}
""".stripMargin)

System.setProperty("testList.0", "0")
System.setProperty("testList.1", "1")
ConfigImpl.reloadSystemPropertiesConfig()

val resolved = resolve(ConfigFactory.systemProperties().withFallback(props).root.asInstanceOf[AbstractConfigObject])
val systemProperties = Map(
"testList.0" -> "0",
"testList.1" -> "1"
).asJava

val resolved = SystemOverride.withSystemOverride(
systemProperties,
new java.util.HashMap[String, String](),
System.err,
() => {
ConfigImpl.reloadSystemPropertiesConfig()
resolve(ConfigFactory.systemProperties().withFallback(props).root.asInstanceOf[AbstractConfigObject])
}
)

assertEquals(List("0", "1"), resolved.getList("a").unwrapped().asScala)
}
Expand Down