..

Using Ansible to Template Java .properties Files

Introduction

This post should teach you some jinja tricks to make templating out the java .properties file format as painless as possible. For those unfamiliar they generally look something like the following example which was taken from the apache commons config guide. You can find more information and the original here

# Properties definining the GUI
colors.background = #FFFFFF
colors.foreground = #000080

window.width = 500
window.height = 300

Notice that they follow a dictionary like structure, if we were to represent this using yaml it would look something like this.

# Properties definining the GUI
colors:
 background: "#FFFFFF"
 foreground: "#000080"

window:
  width: 500
  height: 300

If we were to then create a template using the dictionary above to recreate the properties file originally posted it would look something like this.

# Ansible Managed
colors.background = {{ colors.background }}
colors.foreground = {{ colors.foreground }}

window.width = {{ window.width }}
window.height = {{ window.height }}

This is probably the route most people take when templating things out with ansible and for small files it’s perfectly fine. However there are 2 problems that will creep up when scaling or attempting to use the above solution in a role and I’m going to show you how to tackle each one.

The Problems

Adding Variables

We are going to tackle adding variables first because it makes solving the 2nd issue a cake walk. You may have noticed already, that in order to add variables we will need to update both the template and dictionary above. In fact, putting the variables in a dictionary didn’t help us save much typing because we still need to type out the full path in the template. We can tackle this problem by updating our template and restructuring our dictionary a little. Lets first tackle our dictionary, assuming the file we want to template out if going to be called usergui.properties we can structure our new dictionary like so.

usergui_properties:
  colors:
   background: "#FFFFFF"
   foreground: "#000080"
  window:
    width: 500
    height: 300

Now lets take a look at the template, we can user some jinja2 magic to automatically populate both the name and value based on the dictionary we pass.

{%- macro f(parent, dict) -%}
{%- for k, v in dict.items() -%}
{%- if v is mapping -%}
{%- if parent == "" -%}
{{ f(k, v) }}
{%- else -%}
{{ f(parent + "." + k, v) }}
{%- endif -%}
{%- else -%}
{%- if parent == "" -%}
{{k}} = {{v}}
{%- else -%}
{{parent}}.{{k}} = {{v}}
{% endif -%}
{%- endif -%}
{%- endfor -%}
{%- endmacro -%}

{{ f("", usergui_properties) }}

Wow! There’s a lot going on right there, basically we have defined a recursive function f that we can call to traverse through a dictionary and build us out a .properties file. The nice thing about this being recursive instead of iterative is that we can nest dictionaries as deep as we want. Without the need to add another loop.

If you want to test this out yourself you can create a vars.yml file with the dictionary above along with the template named usergui.properties.j2 and run the following command to see the output.

ansible all -i localhost, -c local -m template -a "src=usergui.properties.j2 dest=./usergui.properties" [email protected]

You can also try adding some variables to the dictionary and see that they are automatically added to the template. Nice!

You can use the jinja2 snippet above as-is or as a starting point for your own work.

Allowing User Overrides

So this is great, we can now allow users to define a dictionary with whatever structure they want and we can create a properties file though. Suppose we package this all up in a role but the defaults are usually good enough? The user of said role will now have to define the entire dictionary just to modify a single variable. There is an elegant solution to this problem using the jinja combine filter. Take a look at our dictionary yet again redefined.

usergui_properties:
  colors:
   background: "#000000"
   primary: "#FFFFFF"

default_usergui_properties:
  colors:
   background: "#FFFFFF"
   foreground: "#000080"
  window:
    width: 500
    height: 300

We now have 2 dictionaries, a default_usergui_properties and a usergui_properties, as you have probably guess the default_usergui_properties contains the defined defaults and the usergui_properties is for our users to override. We can then modify our template as show below to combine our 2 dictionaries.

{%- macro f(parent, dict) -%}
{%- for k, v in dict.items() -%}
{%- if v is mapping -%}
{%- if parent == "" -%}
{{ f(k, v) }}
{%- else -%}
{{ f(parent + "." + k, v) }}
{%- endif -%}
{%- else -%}
{%- if parent == "" -%}
{{k}} = {{v}}
{%- else -%}
{{parent}}.{{k}} = {{v}}
{% endif -%}
{%- endif -%}
{%- endfor -%}
{%- endmacro -%}

{{ f("", default_usergui_properties | combine(usergui_properties, recursive=True)) }}

If you rerun the ansible ad-hoc command above you should now have a file in your current directory called usergui.properties with content similar to the following (order my differ)

colors.foreground = #000080
colors.primary = #FFFFFF
colors.background = #000000
window.width = 500
window.height = 300

As a final pointer, I would recommend making it clear in your documentation which variables should be considered ‘private’ and which ones the user can overload. Hopefully you got something useful out of this, a similar thing can be done to template out ini files check back soon for a how-to on this as well.


comments powered by Disqus