The Message of the Day is displayed when you log in or connect to a server. It typically shows information about the operating system, license information, last login, etc.

It is often useful to customize the Message of the Day to inform your users about some specifics of the system they are connecting to. In this example we render a /etc/motd using a mustache template and add useful information as:

  • The role of the server ( staging / production )
  • The hostname of the server
  • The CFEngine version we are running on the host
  • The CFEngine role of the server ( client / hub )
  • The administrative contacts details conditionally to the environment
  • The primary Ipv4 IP address
  • The number of packages updates available for this host

The bundle is defined like this:

code
bundle agent env_classification
{
  vars:
      # We use presence of key files to know the hosts environment
      "environment_semaphores" slist => { "/etc/prod", "/etc/staging" };
      "environment"
        string => ifelse( filesexist( @(environment_semaphores) ), "incoherent",
                          fileexists("/etc/prod"), "production",
                          fileexists("/etc/staging"), "staging",
                          "unknown" );
      "env_issue_msg"
        string => ifelse( strcmp( "incoherent", $(environment) ),
                                  "WARNING: Environment incoherent (multiple environment semaphores)",
                          strcmp( "unknown", $(environment) ),
                                  "WARNING: Environment unknown (missing environment semaphores)",
                          "Host environment classified as $(environment)");

      # This is to extract the cfengine role ( hub or client )
      "cf_role"
        string => ifelse( "policy_server", "Policy Server", "Policy Client");

  classes:

      # We define a class for the selected environment. It's useful for making
      # decisions in other policies

      "env_$(environment)"
        expression => "any",
        scope => "namespace";

}
bundle agent env_info
{
  vars:
      ## Based on the environment we define different contacts.
      "admin_contact"
        slist => { "admin@acme.com", "oncall@acme.com" },
        if => strcmp( $(env_classification.environment), "production" );

      "admin_contact"
        slist => { "dev@acme.com" },
        if => strcmp( $(env_classification.environment), "staging" );

      "admin_contact"
        slist => { "root@localhost" },
        if => strcmp( $(env_classification.environment), "unknown" );

      ## This is to extract the available package updates status
      "updates_available"
        data => packageupdatesmatching(".*", ".*", ".*", ".*");
      "count_updates" int => length("updates_available");

  classes:

      # We define a class indicating there are updates available, it might be
      # useful for various different policies.

      "have_updates_available"
        expression => isgreaterthan( $(count_updates), 0),
        scope => "namespace";

}
bundle agent motd {

  vars:
      "motd_path" string => "/etc/motd";

      # It's considered best practice to prepare a data container holding the
      # information you need to render the template instead of relying on
      # current datastate()

      # First we extract currently defined classes from datastate(), then we
      # construct the template data.

      "_state" data => datastate(),
        if => not( isvariable ( $(this.promiser) ) ); # Limit recursive growth
                                                      # and multiple calls to
                                                      # datastate() over
                                                      # multiple passes.

      "template_data"
        data => mergedata('{ "fqhost": "$(sys.fqhost)",
                   "primary_ip": "$(sys.ipv4)",
                   "cf_version": "$(sys.cf_version)",
                   "issue_msg": "$(env_classification.env_issue_msg)",
                   "cf_role":  "$(env_classification.cf_role)",
                   "count_updates": "$(env_info.count_updates)",
                   "contacts": env_info.admin_contact,
                   "classes": _state[classes]
        }');

  files:
      "$(motd_path)"
        create => "true",
        template_method => "inline_mustache",
        template_data => @(template_data),
        edit_template_string => '# Managed by CFEngine
{{{issue_msg}}}
  ***
  ***    Welcome to {{{fqhost}}}
  ***

* *** *      CFEngine Role: {{{cf_role}}}
* *** *      CFEngine Version:{{{cf_version}}}
* *** *
*     *      Host IP: {{{primary_ip}}}
  ***        {{#classes.have_updates_available}}{{{count_updates}}} package updates available.{{/classes.have_updates_available}}{{^classes.have_updates_available}}No package updates available or status unknown.{{/classes.have_updates_available}}
  * *
  * *
  * *
             For support contact:{{#contacts}}
               - {{{.}}}{{/contacts}}$(const.n)';

}
bundle agent __main__
{
  methods:
      "env_classification";
      "env_info";
      "motd";
}

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

Example run:

command
cf-agent -KIf ./mustache_template_motd.cf; cat /etc/motd
output
    info: Updated rendering of '/etc/motd' from mustache template 'inline'
    info: files promise '/etc/motd' repaired
# Managed by CFEngine
WARNING: Environment unknown (missing environment semaphores)
  ***
  ***    Welcome to nickanderson-thinkpad-w550s
  ***

* *** *      CFEngine Role: Policy Client
* *** *      CFEngine Version:3.17.0
* *** *
*     *      Host IP: 192.168.42.189
  ***        No package updates available or status unknown.
  * *
  * *
  * *
             For support contact:
               - root@localhost