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.
Comments
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
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))
}
in build.gradle , I added these:
war{
rootSpec.exclude('application.yml')
}
It seems to work for me.
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....
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