Add a config strategy to load from environment variables first.

This commit is contained in:
Andrea Peruffo 2019-02-27 12:37:37 +00:00
parent d6023a111d
commit d9d1f0a3fc
5 changed files with 157 additions and 2 deletions

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="SuppressionFilter">

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.1//EN"
"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<suppressions>
<!-- don't care about javadoc coverage of methods in impl -->
<suppress checks="JavadocMethod"

View File

@ -0,0 +1,79 @@
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

@ -1090,6 +1090,53 @@ class ConfigTest extends TestUtils {
assertEquals(10, resolved.getInt("bar.nested.a.q"))
}
@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)
try {
val loader02 = new TestClassLoader(this.getClass().getClassLoader(),
Map("reference.conf" -> resourceFile("test02.conf").toURI.toURL()))
val loader04 = new TestClassLoader(this.getClass().getClassLoader(),
Map("reference.conf" -> resourceFile("test04.conf").toURI.toURL()))
val conf02 = withContextClassLoader(loader02) {
ConfigFactory.load()
}
val conf04 = withContextClassLoader(loader04) {
ConfigFactory.load()
}
assertEquals(1, conf02.getInt("42_a"))
assertEquals(2, conf02.getInt("a.b.c"))
assertEquals(3, conf02.getInt("a-c"))
assertEquals(4, conf02.getInt("a_c"))
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")
}
}
@Test
def renderRoundTrip() {
val allBooleans = true :: false :: Nil

View File

@ -653,6 +653,28 @@ class PublicApiTest extends TestUtils {
}
}
@Test
def supportsEnvFirstConfigLoadingStrategy(): Unit = {
assertEquals("config.strategy is not set", null, System.getProperty("config.strategy"))
TestEnvFirstStrategy.putEnvVar("CONFIG_a", "5")
System.setProperty("config.strategy", classOf[EnvFirstConfigLoadingStrategy].getCanonicalName)
try {
val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(),
Map("reference.conf" -> resourceFile("a_1.conf").toURI.toURL()))
val configA1 = withContextClassLoader(loaderA1) {
ConfigFactory.load()
}
assertEquals(5, configA1.getInt("a"))
} finally {
System.clearProperty("config.strategy")
TestEnvFirstStrategy.removeEnvVar("CONFIG_a")
}
}
@Test
def usesContextClassLoaderForApplicationConf() {
val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(),
@ -1145,4 +1167,11 @@ object TestStrategy {
private var invocations = 0
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)
}