In our current project we had multiple web applications that has to be deployed and tested across several test environments. Each of these web applications are highly configurable using property file entries. Also, we have load balancing clusters as well. The property file values are on 2 different classes. First class of property file values which needs to change on a per-environment basis(E.g, log.level... needs to be DEBUG on all test environments and INFO on production environment). There is another class of property file values which needs to change on a per-load-balanced-server basis within an environment (E.g, server.host, server.port).
Our development and deployment approach follows the steps:
- During development, templatize the property values (i.e., a property file entry would look like server.host=@server.host@) which needs to change, be it across environments or servers.
- Maintain environment specific property files - one '.env' file per environment (i.e., for env1 -> server.host=172.24.1.2 and for env2 -> server.host=172.24.1.3 etc.)
- During the build, replace each templatized value with an actual value from the specific environment file. Then deploy the generated property files.
This works for templatizing property values across multiple environments, but not for multiple servers within the same environment. What we used initially was we had a .env file per server per environment, e.g., test.server1.env, test.server2.env etc. The problem with this approach is that 98% of the values in both these server specific env files are same. So, when something changes, we had to go and modify all these redundant server-specific files which was painful as well as error-prone.
So, we have now decided to use 2 levels of env files. For an environment, there will be 1 env file which will contain all the templatization keys that is common to all load balancing servers across the test environment. There will be another server specific environment file which will contain JUST the templatization keys that are server specific. Then we use ANT to synthesize the final property file from both these env files on a server by server basis.
To illustrate by an example...
Let's say we have a property file which needs replacement called
app.properties.template
log.level=@level@
log.file=@filepath@
throttle=5
host=@host.ip@
port=@host.port@
In the above, I have 5 property key/value pairs. One of which is static (throttle). Another 2 (log.level, log.file) are supposed to be same for all servers in an environment and final two (host, port) which are supposed to be different for each server in an environment.
We have the
replace.env which replaces takes care of the properties which are same across all servers.
level=DEBUG
filepath=/usr/home/tomcat/app/logs/
Then, we have the server specific replacement env files:
replace.env.server1
host.ip=172.24.1.1
host.port=61009
replace.env.server2
host.ip=172.24.1.2
host.port=61010
And, finally the ANT
build.xml
If we run
ant change -Denv=server1, the
app.properties gets generated, which looks like
log.level=DEBUG
log.file=/usr/home/tomcat/app/logs/
throttle=5
host=172.24.1.1
port=61009
And, if we run
ant change -Denv=server2, the
app.properties gets generated, which looks like
log.level=DEBUG
log.file=/usr/home/tomcat/app/logs/
throttle=5
host=172.24.1.2
port=61010
What is happening here is that the
copy task finds all files ending in
.template and uses the
filterset task to replace the templatization tokens. We are using 2
filtersfile, the first one being the env file which contains environment specific templatization keys and the second one being the server specific templatization keys within the environment.
This approach helps to keep the environment files more modular and clean.