Home

Awesome

Dropwizard Template Config Circle CI

A Dropwizard Bundle that allows you to write your config.yaml as a Freemarker template. This is especially useful when you need to access environment variables or system properties. In fact, this project is the successor to the fabulous dropwizard-environment-config plugin.

Setup

First add the dependency to your POM:

<dependency>
    <groupId>de.thomaskrille</groupId>
    <artifactId>dropwizard-template-config</artifactId>
    <version>1.5.0</version>
</dependency>

Your project is assumed to separately pull in dropwizard-core version 0.7.1 or newer. Compatibility has been tested through Dropwizard 1.0.0.

To enable, simply add the TemplateConfigBundle to the Bootstrap object in your initialize method:

@Override
public void initialize(final Bootstrap<Configuration> bootstrap) {
    ...
    bootstrap.addBundle(new TemplateConfigBundle());
    ...
}

You can configure the bundle by passing an instance of TemplateConfigBundleConfiguration to the constructor:

@Override
public void initialize(final Bootstrap<Configuration> bootstrap) {
    ...
    bootstrap.addBundle(new TemplateConfigBundle(
            new TemplateConfigBundleConfiguration().charset(Charsets.US_ASCII)
    ));
    ...
}

You can add your own variables to the template by adding your own implementations of the TemplateConfigProvider to the constructor:

@Override
public void initialize(final Bootstrap<Configuration> bootstrap) {
    ...
    bootstrap.addBundle(new TemplateConfigBundle(
        new TemplateConfigBundleConfiguration()
            .addCustomProvider(myCustomProvider1)
            .addCustomProvider(myCustomProvider2)
    ));
    ...
}

Look at TemplateConfigBundleConfiguration's javadoc to see all available options.

Heads up: The Bundle gets the content of the config.yaml by wrapping any previously defined io.dropwizard.configuration.ConfigurationSourceProvider. So you must set any custom ConfigurationSourceProvider before adding this Bundle to the Bootstrap.

Quickstart

Environment variables and system properties can be specified in config.yaml by using the following Freemarker magic:

server:
  type: simple
  connector:
    type: http
    # replacing environment variables
    port: ${PORT}
logging:
  # with default values too
  level: ${LOG_LEVEL!'WARN'}
  appenders:
    # system properties also work
    - type: ${log_appender!'console'}

See Freemarker's Template Author's Guide for more information on how to write templates.

Tutorial

Using this bundle you can write your config.yaml as Freemarker template. Let's start with a simple example: replacing environment variables. For all the Heroku users out there, we will try to use Heroku's environment variables for the examples. Let's focus on a single piece of the configuration shown in the Quickstart:

server:
  type: simple
  connector:
    type: http
    port: ${PORT}

You can specify a default value in case the environment variable is missing. This is useful for local tests on your development machine:

server:
  type: simple
  connector:
    type: http
    port: ${PORT!8080}

Default values are separated from the variable name by a ! and follow more or less the well-known Java syntax for scalars. If there is no default value for a missing variable an exception will be thrown by Freemarker and wrapped in a RuntimeException. Java system properties (the contents of System.getProperties()) are available, too:

server:
  type: simple
  connector:
    type: http
    port: ${http_port}

There are some limitations to the variables and properties you can access through these top-level variables. For one, environment variables and system properties with the same name will collide (environment variables will mask system properties in this case). For another, names containing [characters such as . and - that have special meaning to Freemarker] (http://freemarker.org/docs/dgui_template_exp.html#dgui_template_exp_var_toplevel) won't be available. To alleviate these problems, this bundle also provides Maps for the environment (env) and system properties (sys) at the top level:

# Use `sys` to access a system property masked by an environment variable
port: ${sys.http_port}

# Use bracket notation to access a system property with a `.` in its name
port: ${sys['my_app.http.port']}

# Names with a dash (`-`) can be accessed via brackets or backslash
port: ${sys['http-port']}
port: ${http\-port}

You can output variables inline in values. This is helpful to specify the database connection:

database:
  driverClass: org.postgresql.Driver
  user: ${DB_USER}
  password: ${DB_PASSWORD}
  url: jdbc:postgresql://${DB_HOST!'localhost'}:${DB_PORT}/my-app-db

In fact, you can output anything anywhere, because Freemarker doesn't know anything about YAML:

#
# Given the following environment:
#
# SERVER_TYPE_LINE='type: simple'
# SERVER_CONNECTOR_TYPE_LINE='type: http'
#
server:
  ${SERVER_TYPE_LINE}
  connector:
    ${SERVER_CONNECTOR_TYPE_LINE}
    port: 8080

Be careful though, not to mess up YAML's data structure. Of course, you can use any other Freemarker features beyond simple variable interpolation:

# Use with:
#
# ENABLE_SSL=true
# SSL_PORT=8443 (optional)
# SSL_KEYSTORE_PATH=/etc/my-app/keystore
# SSL_KEYSTORE_PASS=secret
#
# or
#
# ENABLE_SSL=false
#
server:
  applicationConnectors:
    - type: http
      port: ${PORT!8080}
<#if ENABLE_SSL == 'true'>
    - type: https
      port: ${SSL_PORT!8443}
      keyStorePath: ${SSL_KEYSTORE_PATH}
      keyStorePassword: ${SSL_KEYSTORE_PASS}
</#if>

The previous example conditionally enables HTTPS if the environment variable ENABLE_SSL is true. Comments are available too:

server:
  applicationConnectors:
    - type: http
      port: ${PORT!8080}
<#-- Un-comment to enable HTTPS
    - type: https
      port: ${SSL_PORT!8443}
      keyStorePath: ${SSL_KEYSTORE_PATH}
      keyStorePassword: ${SSL_KEYSTORE_PASS}
-->

Comments are writen between the <#-- --> Freemarker tags. They can span multiple lines. Another advanced use case might be introducing configuration profiles that can be switched by using an environment variable like this:

logging:
<#if PROFILE == 'production'>
  level: WARN
  loggers:
    com.example.my_app: INFO
    org.hibernate.SQL: OFF
  appenders:
    - type: syslog
      host: localhost
      facility: local0
<#elseif PROFILE == 'development'>
  level: INFO
  loggers:
    com.example.my_app: DEBUG
    org.hibernate.SQL: DEBUG
  appenders:
    - type: console
</#if>

You can include snippets from external config files via the <#include> tag. For this to work, you have to set an include directory based either on a resource in the classpath:

@Override
public void initialize(final Bootstrap<Configuration> bootstrap) {
    ...
    bootstrap.addBundle(new TemplateConfigBundle(
            new TemplateConfigBundleConfiguration().resourceIncludePath("/config")
    ));
    ...
}

or on the local filesystem:

@Override
public void initialize(final Bootstrap<Configuration> bootstrap) {
    ...
    bootstrap.addBundle(new TemplateConfigBundle(
            new TemplateConfigBundleConfiguration().fileIncludePath("./config")
    ));
    ...
}

Feel free to use sub-folders to organize your configurations. While the argument to fileIncludePath may be relative to the current working directory or absolute, the argument to resourceIncludePath will always be made absolute by prepending a slash (/) if not present. You can then include configuration templates from other files. To extract the database config, for example, create a config like this:

server:
  type: simple
  connector:
    type: http
    port: 8080

<#include "database.yaml">

logging:
  level: WARN

The database.yaml looks like this:

database:
  driverClass: org.postgresql.Driver
  user: my-app
  password: secret
  url: jdbc:postgresql://localhost:5432/my-app-db

The result will look like this:

server:
  type: simple
  connector:
    type: http
    port: 8080

database:
  driverClass: org.postgresql.Driver
  user: my-app
  password: secret
  url: jdbc:postgresql://localhost:5432/my-app-db

logging:
  level: WARN

Of course, you can also use any templating feature in the included file, like:

If you're not seeing the behavior you expect, it can be useful to inspect the rendered text of your template. Since 1.3.0, you can provide an outputPath to which the bundle will write the filled-out text of the config before passing it on to Dropwizard:

@Override
public void initialize(final Bootstrap<Configuration> bootstrap) {
    ...
    bootstrap.addBundle(new TemplateConfigBundle(
            new TemplateConfigBundleConfiguration().outputPath("/tmp/config.yml")
    ));
    ...
}

Be careful to not overuse all this stuff. In the end, a configuration file should stay as simple as possible and be easily readable. Extensively using advanced Freemarker features might get in the way of this principle.

See Freemarker's Template Author's Guide for more information on how to write templates.

Migration from Dropwizard Environment Config

TODO: write me!

Copyright Notice

This project is licensed under the Apache License, Version 2.0, January 2004, and uses the following 3rd party software:

See LICENSE-3RD-PARTY and NOTICE-3RD-PARTY for the individual 3rd parties.