Mustache templating with ncf and CFEngine

Why use mustache template

When you start a configuration management project, you promptly discover that there are are two ways of managing your configuration files. You can either edit the existing ones or use templates to replace them all.

Editing pros and cons:

  • it is more complex and more bug prone
  • it is easier when you work work with isolated teams, but in this case you should really split your configuration file
  • it is a good choice when you want to smoothly migrate an existing platform where you don’t have a deep knowledge of the existing content

Templating pros and cons:

  • it is simple
  • it is easy to write when your platform is uniform
  • it is a good choice when you are starting a configuration from fresh

What if you want to have templates with ncf ?
Ncf provides two templating formats: the historic CFEngine format and the more widespread mustache format; the later being easier to learn.

Mustache has been made to handle templates in a uniform way across a lot of languages (see …). It was invented for HTML but can be used for any text based file.It is very simple since it is logic-less (if you don’t consider tests and iteration to be logic) and it is well suited for CFEngine structure.

How to handle a mustache template with ncf

To use mustache template within ncf, you need two things, first a ncf code to handle the template and the template file itself.

Let’s quickly go over what the code looks like:

# first copy the template from the server, you don’t need this if you have it on your local machine
# I usually use a temporary directory within /var/rudder to do store those kind of files but you can use /tmp too
file_copy_from_remote_source(“/var/rudder/configuration-repository/shared-files/my_file.tpl”, “/var/rudder/tmp/my_file.tpl”)
# then use this template to produce a finale file if the template has been properly copied
file_from_template_mustache(“/var/rudder/tmp/my_file.tpl”, “/etc/configuration.file”)

What does a mustache template look like ?

Now let’s dive into the subject
Mustache is called mustache because of similarity between a mustache and a curly brace ;-{)

That’s why the base mustache replacement has the form {{name}}.
But be careful, mustache was first thought for HTML, so this form will expand HTML entities such as / < > and &. If you are using mustache for a configuration file and not for a HTML file, you should always use variables in the {{{name}}} form that does not do this expansion. Or expect bugs …

However, almost every documentation use the double mustache form, ignoring that fact. Please be careful and replace them with triple mustache where they ought to be.

With CFEngine, you can put 4 kinds of things within these mustache:

  • classes
  • scalar variables
  • lists
  • container

Class

Classes are for conditional content:

  {{#classes.my_class}}
     content when class is defined
  {{/classes.my_class}}
  {{^classes.my_class}}
     content when class is *not* defined
  {{/classes.my_class}}

Notes:

  • You must not use the {{{ }}} triple mustache form for classes.
  • You cannot put class expression here.
  • People using ncf v1 should use the “Condition()” generic method to create classes based on expressions or on generic method outcome.

Scalar variable

Scalar variables are just replaced:

  {{{vars.my_bundle.my_variable}}}

Notes:

  • List variable cannot be put here
  • Integer variables are OK
  • Array elements are OK
  • Use {{{ }}} triple mustache to avoid html entities expansion
  • You have to put the bundle name where the variable has been defined
  • People using ncf v1 should use the “Variable from *” generic methods to create global variables

List

Lists can be used to iterate on them:

  {{#vars.my_bundle.my_list}}
      {{{.}}}
  {{/vars.my_bundle.my_list}}

Notes:

  • The syntax looks lithe the class one, so no triple mustache on the iterator
  • The current list item is named ‘.’

Container

Container usage depend on their content.

If the data is an object like this one:

  { “my_key”: “my_value” }

You can you them like a sub element of a variable:

  {{{vars.my_bundle.my_container.my_key}}}

If the data is a list like this one:

  [ “element1”, “element2” ]

You can iterate on it like any list:

  {{#vars.my_bundle.my_container}}
      {{{.}}}
  {{/vars.my_bundle.my_container}}

If the data is of mixed type, you have to take the right approach for the right level of hierarchy.
Ex for:

  [ { “key”: “value1” }, { “key”: “value2” } ]

You may use:

  {{#vars.my_bundle.my_container}}
      {{{.key}}}
  {{/vars.my_bundle.my_container}}

Conclusion

Mustache is a great templating format, easy to use and understand. It also is well adapted to CFEngine data types. Ncf already has everything needed to use it, so do not hesitate, start using mustache today !

Appendix

NCF technique:

file_copy_from_remote_source
source = “/var/rudder/configuration-repository/shared-files/my_file.tpl”
destination =  “/var/rudder/tmp/my_file.tpl”
file_from_template_mustache
source_template = “/var/rudder/tmp/my_file.tpl”
destination = “/etc/configuration.file”

my_file.tpl:

  {{#classes.debian}}
     key=debian_like
  {{/classes.debian}}
  {{^classes.debian}}
     key={{{vars.my_bundle.my_variable}}}
  {{/classes.debian}}