diff --git a/build.sbt b/build.sbt index 9d78b6ae..15260557 100644 --- a/build.sbt +++ b/build.sbt @@ -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") diff --git a/config/src/main/java/com/typesafe/config/ConfigFactory.java b/config/src/main/java/com/typesafe/config/ConfigFactory.java index 86d995e3..8405520f 100644 --- a/config/src/main/java/com/typesafe/config/ConfigFactory.java +++ b/config/src/main/java/com/typesafe/config/ConfigFactory.java @@ -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() { - return systemProperties(); + 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 Config containing the system's environment variables + * used to override configuration keys. + * Environment variables taken in considerations are starting with + * {@code CONFIG_FORCE_} + * + *

+ * Environment variables are mangled in the following way after stripping the prefix "CONFIG_FORCE_": + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Env VarConfig
_   [1 underscore]. [dot]
__  [2 underscore]- [dash]
___ [3 underscore]_ [underscore]
+ * + *

+ * A variable like: {@code CONFIG_FORCE_a_b__c___d} + * is translated to a config key: {@code a.b-c_d} + * + *

+ * This method can return a global immutable singleton, so it's preferred + * over parsing system properties yourself. + *

+ * {@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 Config + */ + public static Config systemEnvironmentOverrides() { + return ConfigImpl.envVariablesOverridesAsConfig(); + } + /** * Gets a Config 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); + } } diff --git a/config/src/main/java/com/typesafe/config/EnvFirstConfigLoadingStrategy.java b/config/src/main/java/com/typesafe/config/EnvFirstConfigLoadingStrategy.java deleted file mode 100644 index 0b094803..00000000 --- a/config/src/main/java/com/typesafe/config/EnvFirstConfigLoadingStrategy.java +++ /dev/null @@ -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} - * - *

- * Environment variables are mangled in the following way after stripping the prefix: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Env VarConfig
_   [1 underscore]. [dot]
__  [2 underscore]- [dash]
___ [3 underscore]_ [underscore]
- * - *

- * A variable like: {@code CONFIG_a_b__c___d} - * is translated to a config key: {@code a.b-c_d} - * - *

- * The prefix may be altered by defining the VM property {@code config.env_var_prefix} - */ -public class EnvFirstConfigLoadingStrategy extends DefaultConfigLoadingStrategy { - - protected static Map 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 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)); - } -} diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java index 9cf49913..f9057ede 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -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 env = new HashMap(System.getenv()); + Map 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() { @Override diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index f5084c79..853aa249 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -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") } } diff --git a/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala b/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala index 5eed422a..4c72643e 100644 --- a/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala @@ -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) -}