This site is now 100% read-only, and retired.

Dynamic definition of classes with CFEngine modules

Posted by Steve on Thu 28 Dec 2006 at 09:14

There are times when you'd like to conduct complex conditional actions within a CFEngine setup. Whilst it is possible to use built in classes, or dynamic tests for the existence of files, directories, or other things using an external plugin module gives you a lot of additional flexability.

CFEngine allows you to conduct different actions by "groups" and by "classses". Groups are generally defined in a static fashion such as this:

groups:
   internal = ( mine yours cfmaster )
   dhcp     = ( mine )

Here we define a group named "internal" to match the hosts "mine", "yours", and "cfmaster". We also define the hostname "mine" as being the value of the group "dhcp".

Classes can be a little bit more dynamic, since they can be set based upon tests such as these following examples:

classes:
    has_dupload  = ( FileExists(/usr/bin/dupload) )
    has_monit    = ( FileExists(/usr/sbin/monit) )
    has_php4     = ( FileExists(/etc/apache2/mods-available/php4.load) )

Here we have defined several classes based upon the existence of files. You can use many testing operations to define classes in this manner, although it can get a little tricky with complex tests.

(You can also define classes after a successful file copy, and at other points mid-execution.)

When your level of complexity starts to rise it is time to look at modules. Modules are essentially plugins which are executed on each node. When a module is executed CFEngine will process the output.

Module output which begin with a + sign is treated as a class to be defined, whilst lines which begin with a - sign are treated as classes to be undefined.

As an example the following module, when executed, will always define the class "Steve", and always unset the class "debian" (if present):

#!/bin/sh

echo "+Steve"
echo "-debian"
Installing Modules

For CFEngine to execute a module it must be located in the module-directory which is /var/lib/cfengine2/modules/ by default. It must also be named with a module: prefix.

For example the previous sample could have been named /var/lib/cfengine2/modules/module:sample.

As the modules must be copied to your managed clients you will probably need to add them to your cfagent.conf, or update.conf files, to make sure they are always up to date. For my setup I can copy all modules with a stanza such as this:

copy:
  $(host_base)/modules/ dest=/var/lib/cfengine2/modules/ server=$(server)
     recurse=inf mode=755 owner=root group=root

Once the modules are in place you need to cause them to be executed. Simply add their names to your actionsequence line:

  actionsequence  = ( module:sample copy ... )

The final thing you must do is tell CFEngine which classes the module will be defining/undefining:

  AddInstallable  = ( Steve debian )
Xen Detection

A simple example of a useful module would be the following script which attempts to detect whether the current host is either a Xen host, a Xen guest, or a Xen-free system:

#!/bin/sh
#  module:xen
#
#  Define one of "dom0", "domU", or "noxen".
#
test -d /proc/xen && test -x /usr/sbin/xm && echo "+dom0" && exit
test -d /proc/xen && echo "+domU" && exit
echo "+noxen"

Save this script as the file "module:xen" and copy it to all your CFEngine-managed nodes, into the modules directory. Now you can use it as follows:

control:
  # Allow these values to be dynamically set/updated
  AddInstallable  = ( dom0 domU noxen )

  Actionsequence = ( module:xen shellcommands .. )


#
#  Now we can run different commands based on the system type
#
shellcommands:
  domU::
    "/bin/echo This is domU"
  dom0::
   "/bin/echo This is dom0"
  noxen::
   "/bin/echo This system is not xen"
Detecting Debian Version

As another simple example the module module:release will detect the Debian release upon the current host, defining one of :

  • debian_sarge: For nodes running Sarge.
  • debian_etch: For nodes running Etch.
  • debian_sid: For nodes running Sid.

Arrange for the file to be copied to your CFEngine-managed nodes, in the file /var/lib/cfengine2/modules/module:release then add the following to cfagent.conf to ensure it will run:

control:
  AddInstallable  = ( debian_etch debian_sarge debian_sid )
  ActionSequence  = ( module:release ... )

Now you can use the result to control file copying, shell command execution, etc. For example we could copy a different sources.list file with a stanza such as:

#
#  Copy files from the master to the host.
#
copy:

 debian_etch::
  $(host_base)/etc/apt/sources.list.etch dest=/etc/apt/sources.list 
     server=$(server) mode=755 owner=root group=root

 debian_sarge::
  $(host_base)/etc/apt/sources.list.sarge dest=/etc/apt/sources.list
     server=$(server) mode=755 owner=root group=root

 debian_sid::
  $(host_base)/etc/apt/sources.list.sid dest=/etc/apt/sources.list 
     server=$(server) mode=755 owner=root group=root

 

 


Re: Dynamic definition of classes with CFEngine modules
Posted by Thorsten (84.58.xx.xx) on Sat 30 Dec 2006 at 03:48
Great article Steve - once more :)

many thanks for it!
Thorsten

[ Parent ]