Pither.com / Simon
Development, systems administration, parenting and business

Grails 3 Externalized Configuration

Grails 2.x uses it's own configuration loader that includes the ability to read extra configuration from additional, external files. This is a great feature for reading production, deployment specific configuration values; while keeping them separate from a standard WAR file. I've found this very handy when the same application needs to be deployed to multiple machines with only some configuration differences.

To do this in Grails 2.x you can use a line similar to this in your Config.groovy file:

grails.config.locations = [ 'file:/etc/my_path/config_file.groovy' ]

I recently tried to do the same thing with a Grails 3.x application and discovered that the configuration loading is now powered by Spring Boot - the grails.config.locations option is no longer used at all.

However this is generally great for simplifying Grails and the Spring Boot config loader does actually include an awful lot of options, including some for loading external (from the WAR) configuration files. Unfortunately all of the locations it uses are very much within the Java deployment area and weren't really what I wanted.

With a little help from a kind soul on the Grails Slack channels I discovered that this problem had already been discussed on the Grails mailing list and a couple of possible code snippets were included for loading an external config file.

So given those examples and my specific requirement to load some configuration values from an external file, where the location of that file is pre-set within the application, I ended up making my Application.groovy class look like the code below. This code has a hard coded base file path (and supports overriding it) set to "/etc/my_path/config-file" and will look for both a .yml and .groovy version of that path (ie "/etc/my_path/config-file.yml" and "/etc/my_path/config-file.groovy"); if found they'll be loaded as configuration sources.

import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean
import org.springframework.context.EnvironmentAware
import org.springframework.core.env.Environment
import org.springframework.core.env.MapPropertySource
import org.springframework.core.env.PropertiesPropertySource
import org.springframework.core.io.FileSystemResource
import org.springframework.core.io.Resource

class Application extends GrailsAutoConfiguration implements EnvironmentAware {
    static void main(String[] args) {
        GrailsApp.run(Application, args)
    }

    @Override
    void setEnvironment(Environment environment) {
        def configBase = System.getenv('GRAILS_CONFIG_LOCATION') ?: System.getProperty('grails.config.location') ?: "/etc/my_path/config-file"
        boolean configLoaded = false

        def ymlConfig = new File(configBase + '.yml')
        if(ymlConfig.exists()) {
            println "Loading external configuration from YAML: ${ymlConfig.absolutePath}"
            Resource resourceConfig = new FileSystemResource(ymlConfig)
            YamlPropertiesFactoryBean ypfb = new YamlPropertiesFactoryBean()
            ypfb.setResources(resourceConfig)
            ypfb.afterPropertiesSet()
            Properties properties = ypfb.getObject()
            environment.propertySources.addFirst(new PropertiesPropertySource("externalYamlConfig", properties))
            println "External YAML configuration loaded."
            configLoaded = true
        }

        def groovyConfig = new File(configBase + '.groovy')
        if(groovyConfig.exists()) {
            println "Loading external configuration from Groovy: ${groovyConfig.absolutePath}"
            def config = new ConfigSlurper().parse(groovyConfig.toURL())
            environment.propertySources.addFirst(new MapPropertySource("externalGroovyConfig", config))
            println "External Groovy configuration loaded."
            configLoaded = true
        }

        if(!configLoaded) {
            println "External config could not be found, checked ${ymlConfig.absolutePath} and ${groovyConfig.absolutePath}"
        }
    }
}

This code attempts to make the external configuration values the highest priority, so that they override any values pre-set within the application. This seems to work generally but unfortunately doesn't for any internally specified values within environment blocks.

Tags:

Comments

On Jan. 28, 2016, 11:10 a.m. Leo Bartoloni said...

I tried similar solution,
groovy configuration parsed with ConfigSlurper needs enviroment name:

def config = new ConfigSlurper().parse(groovyConfig.toURL())

becomes (I changed toURL() to .toURI().toURL() because it's deprecated)

def config = new ConfigSlurper(grails.util.Environment.current.name).parse(groovyConfig.toURI().toURL())

now I see 'environment' configurations in config object,
but unfortunately grails3 ignores them.

One step further, but not yet really working

On March 9, 2016, 9:30 a.m. Eric Brayeur said...

I also add the problem where I was not able to override environment specific configuration using an external config ...

I ended up doing the following to force all properties set in the external config to be environment specific :

String envName = grails.util.Environment.current.name

Properties properties = ypfb.getObject();

properties.keys().toList().findAll { ! it.toString().startsWith("environments.") }.each { key ->

properties.put("environments." + envName + "." + key, properties.get(key))

}

On March 20, 2016, 12:31 p.m. Narathit said...

in build.gradle , I added these:

war{
rootSpec.exclude('application.yml')
}

It seems to work for me.

On May 22, 2016, 6:25 a.m. Salman said...

Hi, could u please tell me what is this { GRAILS_CONFIG_LOCATION }
in line System.getenv('GRAILS_CONFIG_LOCATION') .... from where it comes or where it to set....

On May 23, 2016, 3:40 p.m. John said...

You can set by setting the environment variable with that name

set GRAILS_CONFIG_LOCATION="C:\path\to\file"
for windows or

export GRAILS_CONFIG_LOCATION="C:\path\to\file"
for linux

Add a comment