Env variables resolution moved to defaultOverrides

This commit is contained in:
Andrea Peruffo 2019-03-04 14:43:12 +00:00
parent 30b65568eb
commit 7fe47888d2
6 changed files with 130 additions and 117 deletions

View File

@ -85,7 +85,17 @@ lazy val configLib = Project("config", file("config"))
Test/ run / fork := true
//env vars for tests
Test / envVars ++= Map("testList.0" -> "0", "testList.1" -> "1")
Test / envVars ++= Map("testList.0" -> "0",
"testList.1" -> "1",
"CONFIG_FORCE_b" -> "5",
"CONFIG_FORCE_testList_0" -> "10",
"CONFIG_FORCE_testList_1" -> "11",
"CONFIG_FORCE_42___a" -> "1",
"CONFIG_FORCE_a_b_c" -> "2",
"CONFIG_FORCE_a__c" -> "3",
"CONFIG_FORCE_a___c" -> "4",
"CONFIG_FORCE_akka_version" -> "foo",
"CONFIG_FORCE_akka_event__handler__dispatcher_max__pool__size" -> "10")
OsgiKeys.exportPackage := Seq("com.typesafe.config", "com.typesafe.config.impl")
publish := sys.error("use publishSigned instead of plain publish")

View File

@ -36,6 +36,7 @@ import java.util.concurrent.Callable;
*/
public final class ConfigFactory {
private static final String STRATEGY_PROPERTY_NAME = "config.strategy";
private static final String OVERRIDE_WITH_ENV_PROPERTY_NAME = "config.override_with_env_vars";
private ConfigFactory() {
}
@ -383,7 +384,11 @@ public final class ConfigFactory {
* @return the default override configuration
*/
public static Config defaultOverrides() {
if (!getOverrideWithEnv()) {
return systemProperties();
} else {
return systemEnvironmentOverrides().withFallback(systemProperties());
}
}
/**
@ -394,7 +399,7 @@ public final class ConfigFactory {
* @return the default override configuration
*/
public static Config defaultOverrides(ClassLoader loader) {
return systemProperties();
return defaultOverrides();
}
/**
@ -549,6 +554,50 @@ public final class ConfigFactory {
return ConfigImpl.systemPropertiesAsConfig();
}
/**
* Gets a <code>Config</code> containing the system's environment variables
* used to override configuration keys.
* Environment variables taken in considerations are starting with
* {@code CONFIG_FORCE_}
*
* <p>
* Environment variables are mangled in the following way after stripping the prefix "CONFIG_FORCE_":
* <table border="1">
* <tr>
* <th bgcolor="silver">Env Var</th>
* <th bgcolor="silver">Config</th>
* </tr>
* <tr>
* <td>_&nbsp;&nbsp;&nbsp;[1 underscore]</td>
* <td>. [dot]</td>
* </tr>
* <tr>
* <td>__&nbsp;&nbsp;[2 underscore]</td>
* <td>- [dash]</td>
* </tr>
* <tr>
* <td>___&nbsp;[3 underscore]</td>
* <td>_ [underscore]</td>
* </tr>
* </table>
*
* <p>
* A variable like: {@code CONFIG_FORCE_a_b__c___d}
* is translated to a config key: {@code a.b-c_d}
*
* <p>
* This method can return a global immutable singleton, so it's preferred
* over parsing system properties yourself.
* <p>
* {@link #defaultOverrides} will include the system system environment variables as
* overrides if `config.override_with_env_vars` is set to `true`.
*
* @return system environment variable overrides parsed into a <code>Config</code>
*/
public static Config systemEnvironmentOverrides() {
return ConfigImpl.envVariablesOverridesAsConfig();
}
/**
* Gets a <code>Config</code> containing the system's environment variables.
* This method can return a global immutable singleton.
@ -1063,4 +1112,10 @@ public final class ConfigFactory {
return new DefaultConfigLoadingStrategy();
}
}
private static Boolean getOverrideWithEnv() {
String overrideWithEnv = System.getProperties().getProperty(OVERRIDE_WITH_ENV_PROPERTY_NAME);
return Boolean.parseBoolean(overrideWithEnv);
}
}

View File

@ -1,79 +0,0 @@
package com.typesafe.config;
import java.util.Map;
import java.util.HashMap;
/**
* Environment variables first config loading strategy. Able to load environment variables first and fallback to {@link DefaultConfigLoadingStrategy}
*
* <p>
* Environment variables are mangled in the following way after stripping the prefix:
* <table border="1">
* <tr>
* <th bgcolor="silver">Env Var</th>
* <th bgcolor="silver">Config</th>
* </tr>
* <tr>
* <td>_&nbsp;&nbsp;&nbsp;[1 underscore]</td>
* <td>. [dot]</td>
* </tr>
* <tr>
* <td>__&nbsp;&nbsp;[2 underscore]</td>
* <td>- [dash]</td>
* </tr>
* <tr>
* <td>___&nbsp;[3 underscore]</td>
* <td>_ [underscore]</td>
* </tr>
* </table>
*
* <p>
* A variable like: {@code CONFIG_a_b__c___d}
* is translated to a config key: {@code a.b-c_d}
*
* <p>
* The prefix may be altered by defining the VM property {@code config.env_var_prefix}
*/
public class EnvFirstConfigLoadingStrategy extends DefaultConfigLoadingStrategy {
protected static Map<String, String> env = new HashMap(System.getenv());
@Override
public Config parseApplicationConfig(ConfigParseOptions parseOptions) {
String envVarPrefix = System.getProperty("config.env_var_prefix");
if (envVarPrefix == null) // fallback to default
envVarPrefix = "CONFIG_";
Map<String, String> defaultsFromEnv = new HashMap();
for (String key : env.keySet()) {
if (key.startsWith(envVarPrefix)) {
StringBuilder builder = new StringBuilder();
String strippedPrefix = key.substring(envVarPrefix.length(), key.length());
int underscores = 0;
for (char c : strippedPrefix.toCharArray()) {
if (c == '_') {
underscores++;
} else {
switch (underscores) {
case 1: builder.append('.');
break;
case 2: builder.append('-');
break;
case 3: builder.append('_');
break;
}
underscores = 0;
builder.append(c);
}
}
String propertyKey = builder.toString();
defaultsFromEnv.put(propertyKey, env.get(key));
}
}
return ConfigFactory.parseMap(defaultsFromEnv).withFallback(super.parseApplicationConfig(parseOptions));
}
}

View File

@ -32,6 +32,7 @@ import com.typesafe.config.impl.SimpleIncluder.NameSource;
* For use only by the {@link com.typesafe.config} package.
*/
public class ConfigImpl {
private static final String ENV_VAR_OVERRIDE_PREFIX = "CONFIG_FORCE_";
private static class LoaderCache {
private Config currentSystemProperties;
@ -360,6 +361,57 @@ public class ConfigImpl {
EnvVariablesHolder.envVariables = loadEnvVariables();
}
private static AbstractConfigObject loadEnvVariablesOverrides() {
Map<String, String> env = new HashMap(System.getenv());
Map<String, String> result = new HashMap(System.getenv());
for (String key : env.keySet()) {
if (key.startsWith(ENV_VAR_OVERRIDE_PREFIX)) {
StringBuilder builder = new StringBuilder();
String strippedPrefix = key.substring(ENV_VAR_OVERRIDE_PREFIX.length(), key.length());
int underscores = 0;
for (char c : strippedPrefix.toCharArray()) {
if (c == '_') {
underscores++;
} else {
switch (underscores) {
case 1: builder.append('.');
break;
case 2: builder.append('-');
break;
case 3: builder.append('_');
break;
}
underscores = 0;
builder.append(c);
}
}
String propertyKey = builder.toString();
result.put(propertyKey, env.get(key));
}
}
return PropertiesParser.fromStringMap(newSimpleOrigin("env variables overrides"), result);
}
private static class EnvVariablesOverridesHolder {
static volatile AbstractConfigObject envVariables = loadEnvVariablesOverrides();
}
static AbstractConfigObject envVariablesOverridesAsConfigObject() {
try {
return EnvVariablesOverridesHolder.envVariables;
} catch (ExceptionInInitializerError e) {
throw ConfigImplUtil.extractInitializerError(e);
}
}
public static Config envVariablesOverridesAsConfig() {
return envVariablesOverridesAsConfigObject().toConfig();
}
public static Config defaultReference(final ClassLoader loader) {
return computeCachedConfig(loader, "defaultReference", new Callable<Config>() {
@Override

View File

@ -1092,15 +1092,7 @@ class ConfigTest extends TestUtils {
@Test
def testLoadWithEnvSubstitutions() {
TestEnvFirstStrategy.putEnvVar("CONFIG_42___a", "1")
TestEnvFirstStrategy.putEnvVar("CONFIG_a_b_c", "2")
TestEnvFirstStrategy.putEnvVar("CONFIG_a__c", "3")
TestEnvFirstStrategy.putEnvVar("CONFIG_a___c", "4")
TestEnvFirstStrategy.putEnvVar("CONFIG_akka_version", "foo")
TestEnvFirstStrategy.putEnvVar("CONFIG_akka_event__handler__dispatcher_max__pool__size", "10")
System.setProperty("config.strategy", classOf[EnvFirstConfigLoadingStrategy].getCanonicalName)
System.setProperty("config.override_with_env_vars", "true")
try {
val loader02 = new TestClassLoader(this.getClass().getClassLoader(),
@ -1125,15 +1117,7 @@ class ConfigTest extends TestUtils {
assertEquals("foo", conf04.getString("akka.version"))
assertEquals(10, conf04.getInt("akka.event-handler-dispatcher.max-pool-size"))
} finally {
System.clearProperty("config.strategy")
TestEnvFirstStrategy.removeEnvVar("CONFIG_42___a")
TestEnvFirstStrategy.removeEnvVar("CONFIG_a_b_c")
TestEnvFirstStrategy.removeEnvVar("CONFIG_a__c")
TestEnvFirstStrategy.removeEnvVar("CONFIG_a___c")
TestEnvFirstStrategy.removeEnvVar("CONFIG_akka_version")
TestEnvFirstStrategy.removeEnvVar("CONFIG_akka_event__handler__dispatcher_max__pool__size")
System.clearProperty("config.override_with_env_vars")
}
}

View File

@ -654,24 +654,22 @@ class PublicApiTest extends TestUtils {
}
@Test
def supportsEnvFirstConfigLoadingStrategy(): Unit = {
assertEquals("config.strategy is not set", null, System.getProperty("config.strategy"))
def loadEnvironmentVariablesOverridesIfConfigured(): Unit = {
assertEquals("config.override_with_env_vars is not set", null, System.getProperty("config.override_with_env_vars"))
TestEnvFirstStrategy.putEnvVar("CONFIG_a", "5")
System.setProperty("config.strategy", classOf[EnvFirstConfigLoadingStrategy].getCanonicalName)
System.setProperty("config.override_with_env_vars", "true")
try {
val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(),
Map("reference.conf" -> resourceFile("a_1.conf").toURI.toURL()))
val loaderB2 = new TestClassLoader(this.getClass().getClassLoader(),
Map("reference.conf" -> resourceFile("b_2.conf").toURI.toURL()))
val configA1 = withContextClassLoader(loaderA1) {
val configB2 = withContextClassLoader(loaderB2) {
ConfigFactory.load()
}
assertEquals(5, configA1.getInt("a"))
assertEquals(5, configB2.getInt("b"))
} finally {
System.clearProperty("config.strategy")
TestEnvFirstStrategy.removeEnvVar("CONFIG_a")
System.clearProperty("config.override_with_env_vars")
}
}
@ -1168,10 +1166,3 @@ object TestStrategy {
def getIncovations() = invocations
def increment() = invocations += 1
}
object TestEnvFirstStrategy extends EnvFirstConfigLoadingStrategy {
def putEnvVar(key: String, value: String) =
EnvFirstConfigLoadingStrategy.env.put(key, value)
def removeEnvVar(key: String) =
EnvFirstConfigLoadingStrategy.env.remove(key)
}