Templating Configuration Files with CFEngine

Varying Log Levels between Realms | Deterministic Variation

CFEngine version 2.1.17 introduced ExpandVariables in editfiles blocks. This allows macro expansion in configuration files:

  1. Define variables to expand in CFEngine policy.
  2. Copy the base configuration file from a CFEngine server to a preparation directory on the client.
  3. Edit the final location for the configuration file using the base data, and expand any variables set by the classes defined on the client.
  4. Optionally fix the permissions on the configuration file.

The runnable (with CFEngine) example copy-edit-test.cf policy file will help illustrate these steps:

$ chmod +x copy-edit-test.cf
$ cp copy-edit-test.cf /var/tmp/sourcefile
$ ./copy-edit-test.cf
$ cat /var/tmp/finalfile

If different classes need a different base configuration files, store the configuration under a version control repository, and use tags or branches to hold different configurations. This allows development systems to use a new version of a configuration file, and production systems tested configuration.

Varying Log Levels between Realms

One example use of templating: set different log verbosity levels between development and production systems. For example, Apache or log4j could write debug logs in development environments, but log less in production. On the other hand, OpenSSH could log more in critical production or ecommerce environments, and less on development systems.

Deterministic Variation

While the builtin strategies statement can set a random class, a script must be run to map even and odd hostnames or Internet addresses to CFEngine classes. The following example shows how to define either the class even_hostname or odd_hostname depending on the hostname of the system.

  1. Set mod_hostname variable.
  2. Use the modulo script to set the mod_hostname variable to 0 or 1 depending on the numbers in the hostname of the system. This assumes hostnames contain numbers: host001, host002, and so forth.

    control:
    any::
    mod_hostname = ( ExecResult(${cf_script_dir}/modulo 2 ${host}) )

  3. Define custom class.
  4. The mod_hostname variable now allows definition of an even_hostname or odd_hostname class:

    classes:
    any::
    even_hostname = ( Strcmp(0,${mod_hostname}) )
    odd_hostname = ( any -even_hostname )

In subsequent policy, use the classes to apply different configuration between otherwise identical systems. For example, half of the existing systems could log to a different log server in production environments, while development systems use the same loghost.

classes:
realm_dev = ( dev_example_org )
realm_prod = ( any -dev_example_org )

control:
realm_dev::
app_syslog_loghost = ( loghost.dev.example.org )
app_syslog_loglevel = ( debug )

realm_prod::
app_syslog_loglevel = ( info )

realm_prod.even_hostname::
app_syslog_loghost = ( loghost-1.example.org )

realm_prod.odd_hostname::
app_syslog_loghost = ( loghost-2.example.org )