Style is a very personal choice and the contents of this guide should only be considered suggestions. We invite you to contribute to the growth of this guide.

Style Summary

  • one indent = 2 spaces
  • avoid letting line length surpass 80 characters.
  • vertically align opening and closing curly braces unless on same line
  • promise type = 1 indent
  • context class expression = 2 indents
  • promiser = 3 indents, to allow for adding class guards without changing indent
  • promise attributes = (we suggest 3 or 4 indents)

Promise Ordering

There are two common styles that are used when writing policy. The Normal Order style dictates that promises should be written in in the Normal Order that the agent evaluates promises in. The other is reader optimized where promises are written in the order they make sense to the reader. Both styles have their merits, but there seems to be a trend toward the reader optimized style.

1) Normal Order

Here is an example of a policy written in the Normal Order. Note how packages are listed after files. This could confuse a novice who thinks that it is necessary for the files promise to only be attempted after the package promise is kept. However this style can be useful to a policy expert who is familiar with Normal Ordering.

bundle agent main
{
  vars:

      "sshd_config"
        string => "/etc/ssh/sshd_config";

  files:

      "$(sshd_config)"
        edit_line => insert_lines("PermitRootLogin no"),
        classes => results("bundle", "sshd_config");

  packages:

      "ssh"
        policy => "present";
        package_module => apt_get;

  services:

    sshd_config_repaired::

        "ssh"
          service_policy => "restart",
          comment => "After the sshd config file has been repaired, the
                      service must be reloaded in order for the new
                      settings to take effect.";

}

2) Reader Optimized

Here is an example of a policy written to be optimized for the reader. Note how packages are listed before files in the order which users think about taking imperitive action. This style can make it significantly easier for a novice to understand the desired state, but it is important to remember that Normal Ordering still applies and that the promises will not be actuated in the order they are written.

bundle agent main
{
  vars:

      "sshd_config"
        string => "/etc/ssh/sshd_config";

  packages:

      "ssh"
        policy => "present";
        package_module => apt_get;


  files:

      "$(sshd_config)"
        edit_line => insert_lines("PermitRootLogin no"),
        classes => results("bundle", "sshd_config");

  services:

    sshd_config_repaired::

        "ssh"
          service_policy => "restart",
          comment => "After the sshd config file has been repaired, the
                      service must be reloaded in order for the new
                      settings to take effect.";

}

Whitespace and Line Length

Spaces are preferred to tab characters. Lines should not have trailing whitespace. Generally line length should not surpass 80 characters.

Curly brace alignment

Generally if opening and closing braces are not on a single line they should be aligned vertically. Content inside braces can be indented one level over instead of to the right of the braces.

Example:

bundle agent example
{
  vars:
      "people" slist => {
          "Obi-Wan Kenobi",
          "Luke Skywalker",
          "Chewbacca",
          "Yoda",
          "Darth Vader",
      };

      "cuddly" slist => { "Chewbacca", "Yoda" };
}

Promise types

Promise types should have 1 indent and each promise type after the first listed should have a blank line before the next promise type.

This example illustrates the blank line before the "classes" type.

bundle agent example
{
  vars:
      "policyhost" string => "MyPolicyServerHostname";

  classes:
      "EL5" or => { "centos_5", "redhat_5" };
      "EL6" or => { "centos_6", "redhat_6" };
}

Context class expressions

Context class expressions should have 2 indents and each context class expression after the first listed within a given promise type should have a blank line preceding it.

This example illustrates the blank line before the second context class expression (solaris) in the files type promise section:

bundle agent example
{
  files:
    any::
      "/var/cfengine/inputs/"
        copy_from    => update_policy( "/var/cfengine/masterfiles","$(policyhost)" ),
        classes      => policy_updated( "policy_updated" ),
        depth_search => recurse("inf");

    solaris::
      "/var/cfengine/inputs"
        copy_from => update_policy( "/var/cfengine/masterfiles", "$(policyhost" ),
        classes   => policy_updated( "policy_updated" );
}

Policy Comments

In-line policy comments are useful for debugging and explaining why something is done a specific way. We encourage you to document your policy thoroughly.

Comments about general body and bundle behavior and parameters should be placed after the body or bundle definition, before the opening curly brace and should not be indented. Comments about specific promise behavior should be placed before the promise at the same indention level as the promiser or on the same line after the attribute.

bundle agent example(param1)
# This is an example bundle to illustrate comments
# param1 - string -
{
  vars:
      "copy_of_param1" string => "$(param1)";

      "jedi" slist => {
          "Obi-Wan Kenobi",
          "Luke Skywalker",
          "Yoda",
          "Darth Vader", # He used to be a Jedi, and since he
                         # tossed the emperor into the Death
                         # Star's reactor shaft we are including
                         # him.
      };
  classes:
      # Most of the time we don't need differentiation of redhat and centos
      "EL5" or => { "centos_5", "redhat_5" };
      "EL6" or => { "centos_6", "redhat_6" };
}

Policy Reports

It is common and useful to include reports in policy to get detailed information about what is going on. During a normal agent run the goal is to have 0 output so reports should always be guarded with a class. Carefully consider when your policy should generate report output. For policy degbugging type information (value of variables, classes that were set or not) the following style is recommended:

bundle agent example
{
  reports:
    DEBUG|DEBUG_example::
      "DEBUG $(this.bundle): Desired Report Output";
}

As of version 3.7 variables can be used in double colon class expressions. If your policy will only be parsed by 3.7 or newer agents the following style is recommended:

bundle agent example
{
  reports:
    "DEBUG|DEBUG_$(this.bundle)"::
      "DEBUG $(this.bundle): Desired Report Output";
}

Following this style keeps policy debug reports from spamming logs. It avoids polluting the inform_mode and verbose_mode output, and it allows you to get debug output for ALL policy or just a select bundle which is incredibly useful when debugging a large policy set.

Promise Handles

Promise handles uniquely identify a promise within a policy. We suggest a simple naming scheme of bundle_name_promise_type_class_restriction_promiser to keep handles unique and easily identifiable. Often it may be easier to omit the handle.

bundle agent example
{
  commands:
    dev::
      "/usr/bin/git"
        args    => "pull",
        contain => in_dir("/var/srv/myrepo"),
        if      => "redhat",
        handle  => "example_commands_dev_redhat_git_pull";
}

Hashrockets (=>)

You may align hash rockets within a promise body scope and for grouped single line promises.

Example:

bundle agent example
{
  files:
    any::
      "/var/cfengine/inputs/"
        copy_from    => update_policy( "/var/cfengine/masterfiles","$(policyhost)" ),
        classes      => policy_updated( "policy_updated" ),
        depth_search => recurse("inf");

      "/var/cfengine/modules"
        copy_from => update_policy( "/var/cfengine/modules", "$(policyhost" ),
        classes   => policy_updated( "modules_updated" );

  classes:
      "EL5" or => { "centos_5", "redhat_5" };
      "EL6" or => { "centos_6", "redhat_6" };
}

You may also simply leave them as they are:

bundle agent example
{
  files:
    any::
      "/var/cfengine/inputs/"
        copy_from => update_policy( "/var/cfengine/masterfiles","$(policyhost)" ),
        classes => policy_updated( "policy_updated" ),
        depth_search => recurse("inf");

      "/var/cfengine/modules"
        copy_from => update_policy( "/var/cfengine/modules", "$(policyhost" ),
        classes => policy_updated( "modules_updated" );

  classes:
      "EL5" or => { "centos_5", "redhat_5" };
      "EL6" or => { "centos_6", "redhat_6" };
}

Which one do you prefer?

Naming Conventions

Naming conventions can also help to provide clarity.

Snakecase

Words delimited by an underscore. This style is prevalant for variables, classes, bundle and body names in the Masterfiles Policy Framework.

bundle agent __main__
{
  methods:
      "ssh";
}
bundle agent ssh
{
  vars:
      "service_name" string => "ssh";
      "config_file" string => "/etc/ssh/sshd_config";
      "conf[Port]" string => "22";

  files:
      "$(config_file)"
        edit_line => default:set_line_based("$(this.bundle).conf",
                                            " ",
                                            "\s+",
                                            ".*",
                                            "\s*#\s*"),
        classes => default:results( "bundle", "$(config_file)");

  services:
    _etc_ssh_sshd_config_repaired::
      "$(service_name)"
        service_policy => "restart",
        classes => default:results( "bundle", "$(service_name)_restart");

  reports:
    ssh_restart_repaired._etc_ssh_sshd_config_repaired::
      "We restarted ssh because the config file was repaired";
}

This policy can be found in /var/cfengine/share/doc/examples/style_snake_case.cf and downloaded directly from github.

Pascalecase

Words delimited by capital Letters.

bundle agent __main__
{
  methods:
      "Ssh";
}
bundle agent Ssh
{
  vars:
      "ServiceName" string => "ssh";
      "ConfigFile" string => "/etc/ssh/sshd_config";
      "Conf[Port]" string => "22";

  files:
      "$(ConfigFile)"
        edit_line => default:set_line_based("$(this.bundle).Conf",
                                            " ",
                                            "\s+",
                                            ".*",
                                            "\s*#\s*"),
        classes => default:results( "bundle", "$(ConfigFile)");

  services:
    _etc_ssh_sshd_config_repaired::
      "$(ServiceName)"
        service_policy => "restart",
        classes => default:results( "bundle", "$(ServiceName)_restart");

  reports:
    ssh_restart_repaired._etc_ssh_sshd_config_repaired::
      "We restarted ssh because the config file was repaired";
}

This policy can be found in /var/cfengine/share/doc/examples/style_PascaleCase.cf and downloaded directly from github.

Camelcase

Words are delimited by capital letters, except the initial word.

bundle agent __main__
{
  methods:
      "Ssh";
}
bundle agent ssh
{
  vars:
      "serviceName" string => "ssh";
      "configFile" string => "/etc/ssh/sshd_config";
      "conf[Port]" string => "22";

  files:
      "$(configFile)"
        edit_line => default:set_line_based("$(this.bundle).conf",
                                            " ",
                                            "\s+",
                                            ".*",
                                            "\s*#\s*"),
        classes => default:results( "bundle", "$(configFile)");

  services:
    _etc_ssh_sshd_config_repaired::
      "$(serviceName)"
        service_policy => "restart",
        classes => default:results( "bundle", "$(serviceName)_restart");

  reports:
    ssh_restart_repaired._etc_ssh_sshd_config_repaired::
      "We restarted ssh because the config file was repaired";
}

This policy can be found in /var/cfengine/share/doc/examples/style_camelCase.cf and downloaded directly from github.

Hungarian notation

Hungarian notation can help improve the readability of policy, especially when working with lists and data containers where the use of @ or $ significantly affects the behavior of the policy.

bundle agent __main__
{
  vars:
      "s_one"   string => "one";
      "ITwo"    int => "2";
      "rThree"  real => "3.0";

      "lMyList" slist => { "$(s_one)", "$(ITwo)", "$(rThree)" };

  methods:
      "Iteration inside (bundle called once)"
        usebundle => dollar_vs_at( @(lMyList) );

      "Iteration outside (bundle called length(lMyList) times)"
        usebundle => dollar_vs_at( $(lMyList) );
}

bundle agent dollar_vs_at( myParam )
{
  vars:
    "myParamType" string => type( myParam );

   classes:
    "myParamType_slist" expression => strcmp( $(myParamType),  "slist" );
    "myParamType_string" expression => strcmp( $(myParamType),  "string" );

  reports:
      "Bundle promised by '$(with)'"
        with => nth( reverse( callstack_promisers() ), 0 );

    myParamType_slist::
      "myParam is of type '$(myParamType)' with value $(with)"
        with => join( ", ", @(myParam) );

    myParamType_string::
      "myParam is of type '$(myParamType)' with value $(myParam)";

}
R: Bundle promised by 'Iteration inside (bundle called once)'
R: myParam is of type 'slist' with value one, 2, 3.000000
R: Bundle promised by 'Iteration outside (bundle called length(lMyList) times)'
R: myParam is of type 'string' with value one
R: myParam is of type 'string' with value 2
R: myParam is of type 'string' with value 3.000000

This policy can be found in /var/cfengine/share/doc/examples/style_hungarian.cf and downloaded directly from github.

Classes

Classes are intended to describe an aspect of the system, and they are combined in expressions to restrict when and where a promise should be actuated. To make this desired state easier to read classes should be named to describe the current state, not an action that should take place.

For example, here is a policy that uses a class that indicates an action that should be taken after having repaired the sshd config.

bundle agent main
{
  vars:
      "sshd_config" string => "/etc/ssh/sshd_config";

  files:
      "$(sshd_config)"
        edit_line => insert_lines("PermitRootLogin no"),
        classes => if_repaired("restart_sshd");

  services:

    !windows::

      "ssh"
        service_policy => "start",
        comment => "We always want ssh to be running so that we have
                    administrative access";

    restart_sshd::

      "ssh"
        service_policy => "restart",
        comment => "Here it's kind of hard to tell *why* we are
                    restarting sshd";
}

Here is a slightly improved version that shows using classes to describe the current state, or what happened as the result of the promise. Note how it's easier to determine why the ssh service should be restarted. Using the results, scoped_classes_generic, or classes_generic classes bodies can help improve class name consistency and are highly recommended.

bundle agent main
{
  vars:
      "sshd_config" string => "/etc/ssh/sshd_config";

  files:
      "$(sshd_config)"
        edit_line => insert_lines("PermitRootLogin no"),
        classes => results("bundle", "sshd_config");

  services:

    !windows::

      "ssh"
        service_policy => "start",
        comment => "We always want ssh to be running so that we have
                    administrative access";

    sshd_config_repaired::

      "ssh"
        service_policy => "restart",
        comment => "After the sshd config file has been repaired, the
                    service must be reloaded in order for the new
                    settings to take effect.";
}

Deprecating Bundles

As your policy library changes over time you may want to deprecate various bundles in favor of newer implimentations. To indicate that a bundle is deprecated we recommend the following style.

bundle agent old
{
  meta:
    "tags" slist => {
      "deprecated=3.6.0",
      "deprecation-reason=More feature rich implimentation",
      "replaced-by=newbundle",
    };
}

Tooling

Currently, there is no canonical policy linting or reformatting tool. There are a few different tools that can be useful apart from an editor with syntax support for achieving regular formatting.

cf-promises

cf-promises can output the parsed policy using the --policy-output-format option. Beware, this will strip macros as they are done during parse time.

Example policy:

bundle agent satellite_bootstrap_main
{

@if feature(this_is_not_the_feature_your_looking_for)
   Hello there.
@endif

  meta:

    (!ubuntu&!vvlan&!role_satellite)::
      "tags" slist => { "autorun" };

  methods:

      "bootstrap rhel7 servers to satellite every 24 hours"
        usebundle => satellite_bootstrap,
        action => if_elapsed(1440);

}

Output the parsed policy in cf format:

cf-promises -f /tmp/example.cf --policy-output-format cf

Formatted parsed policy:

bundle agent satellite_bootstrap_main()
{
meta:
  (!ubuntu&!vvlan&!sarcrole_satellite)::
    "tags"        slist =>  {"autorun"};

methods:
  any::
    "bootstrap rhel7 servers to satellite every 24 hours"        usebundle => satellite_bootstrap,
        action => if_elapsed("1440");


}

body file control()
{
  inputs =>  {"$(sys.libdir)/stdlib.cf"};

}

CFEngine beautifier

Written as a package for the Sublime Text editor, the CFEngine Beautifier can also be used from the command line as a stand-alone tool.

reindent.pl

reindent.pl is available from the contrib directory in the core repository. You can run reindent.pl FILE1.cf FILE2.c FILE3.h to reindent files, if you don't want to set up Emacs. It will rewrite them with the new indentation, using Emacs in batch mode.