33

I am writing test code to validate a RESTful service. I want to be able to point it at any of our different environments by simply changing an environment variable before executing the tests.

I want to be able to merge three different config files:

  • conf/env/default.conf - the default configuration values for all environments
  • conf/env/<env>.conf - the environment-specific values
  • application.conf - the user's overrides of any of the above

The idea is that I don't want everything in a single config file, and run the risk of a bad edit causing configuration items to get lost. So instead, keep them separate and give the user the ability to override them.

Here's where it gets tricky: default.conf will include ${references} to things that are meant to be overridden in <env>.conf, and may be further overridden in application.conf.

I need to postpone resolving until all three are merged. How do I do that?

John Arrowwood
  • 2,370
  • 2
  • 21
  • 32
  • I have given a thumbs up to John's answer and its worth referring [https://github.com/lightbend/config#standard-behavior](https://github.com/lightbend/config#standard-behavior) before starting to merge your configurations. – Ravindra Jun 12 '19 at 04:33

2 Answers2

51

The answer is to use ConfigFactory.parseResource() in place of ConfigFactory.load().

Here is the finished result

private lazy val defaultConfig     = ConfigFactory.parseResources("conf/env/default.conf")
private lazy val environmentConfig = ConfigFactory.parseResources("conf/env/" + env + ".conf" )
private lazy val userConfig        = ConfigFactory.parseResources("application.conf")
private lazy val config = ConfigFactory.load()
                          .withFallback(userConfig)
                          .withFallback(environmentConfig)
                          .withFallback(defaultConfig)
                          .resolve()
John Arrowwood
  • 2,370
  • 2
  • 21
  • 32
0

I advice you to use userConfig.withFallback(ConfigFactory.load()).resolve(), not ConfigFactory.load().withFallback(userConfig).resolve(). parseResources does not change it for me, the same result (but in this test I don't use references)

Here my tests:

application1.conf

app {
  option1 = 323
  common-option = 64
}

application2.conf

app {
  option2 = 234
  common-option = 32
}
  val conf1 = ConfigFactory.load("application1.conf")
  val conf2 = ConfigFactory.load("application2.conf")

  // 1st variant: there is no option1
  println(conf1.withValue("app", conf2.getValue("app")).resolve().getConfig("app"))
  // {"common-option":32,"option2":234}

  // 2nd variant: common option from config1
  println(conf1.withFallback(conf2).resolve().getConfig("app"))
  // {"common-option":64,"option1":323,"option2":234}

  // 3nd variant: common option from config2
  println(conf2.withFallback(conf1).resolve().getConfig("app"))
  // {"common-option":32,"option1":323,"option2":234}

Documentation: https://github.com/lightbend/config/blob/main/config/src/main/java/com/typesafe/config/ConfigMergeable.java#L66

     /** ... Again, for details see the spec.
     * 
     * @param other
     *            an object whose keys should be used as fallbacks, if the keys
     *            are not present in this one
     * @return a new object (or the original one, if the fallback doesn't get
     *         used)
     */
    ConfigMergeable withFallback(ConfigMergeable other);
Mikhail Ionkin
  • 568
  • 4
  • 20
  • If memory serves, ConfigFactory.load() has the added benefit of populating things from the environment, which is why I do that first. – John Arrowwood Dec 01 '22 at 14:51