CFEngine uses classes (also know as groups) to associate groups of systems with configuration actions. This page outlines the setup and usage of classes in a heterogeneous environment. Under this setup, the CFEngine configuration files are organized into different files by Operating System (cf.macosx, cf.redhat), application (cf.app_clamav, cf.app_squid), and other logical groups. CFEngine import statements are used to join these files together from cfagent.conf. Most classes are defined in the cf.groups file.
Keep in mind classes under CFEngine 2 share a single, flat namespace. For reference, run the cfagent -pv command to show the built-in and calculated classes.
Consult the book Automating Linux and Unix System Administration for another opinion on how CFEngine classes and file imports should be managed.
UnderscoreClasses
The UnderscoreClasses variable only affects a small list of builtin classes. Consider instead setting all local classes to begin with an underscore, or differentiate between hostname and builtin classes by using unique prefixes on classes, such as restart_ or role_. Be aware that without UnderscoreClasses set, a hostname such as darwin will conflict with the darwin hard class on Mac OS X.
Descriptive Classes
I do not recommend configuration based on hostname, as services and roles change over time. Instead, group hosts into descriptive class names, and use the custom class to configure services. That is, instead of using darwin:: for the spool directory cleanup shown above, create a class of systems where the spool directory cleanup must be done, then add darwin to that class. If additional systems need the spool cleanup, simply add them to the custom class.
classes:
any::
needs_spool_cleanup = ( darwin evolution )
tidy:
needs_spool_cleanup::
/var/spool/MD-Quarantine pattern=* age=7 r=inf ifelapsed=1439
A better name instead of needs_spool_cleanup might be one named for the application (in this case MIMEDefang) or the role the hosts are providing (a mail proxy versus a mail client system). If unsure, consult with your peers or online for class naming conventions. I favor using underscores in custom class names, to avoid clashes with hostnames, such as app_mimedefang and mail_proxy.
Naming Conventions
Each workplace should negotiate class naming conventions, to improve configuration consistency and to avoid namespace conflicts. One convention would be the use of UnderscoreClasses, covered above. Others are outlined in the following list.
- app_applicationname
- group_*
- restart_*
- sys_*
Use app_applicationname for configuration specific to a particular application, and app_applicationname_whatever for any additional classes an application requires. This avoids a generic class name shared by two different things from triggering unintended actions. To spread configuration among different filenames, define app_applicationname in a top-level cf.groups file, and import the cf.applicationname only if the required class is set.
# main cfagent.conf - only imports
import:
any::
cf.site
cf.groups
cf.applications
…
# cf.applications - import application-specific files
import:
app_mimedefang::
cf.app_mimedefang
…
Different groups can be designated by the lead faculty member or the task of the group in question (development, accounting). This way, systems for each group can be targeted. For laptop systems, using a FileExists statement to assign group membership can workaround the case where no dynamic mapping of a hostname to a roaming client is possible. On the other hand, relying on a file may cause problems if the wrong file is touched by accident!
classes:
any::
group_devel = ( darwin evolution FileExists(/etc/classes/group_devel) )
For service restarts, use a generic class name, so copy and other configuration can define the restart class, and each operating system carry out the processses or shellcommands actions required to restart the service.
control:
any::
AddInstallable = (
restart_ntpd
)
processes:
openbsd.restart_ntpd::
"ntpd" signal=term restart "/usr/local/sbin/ntpd -x"
shellcommands:
redhat.restart_ntpd::
"/sbin/service ntpd restart >/dev/null 2>&1" useshell=true umask=022
Create sys_* to account for system related conditions, such as sys_startup for a special cfagent run at system startup time, or sys_server and sys_desktop to distinguish major system types. If running cfagent at startup from rc.local or some other startup script, be sure to background cfagent to prevent startup delays.
cfagent --no-splay --define sys_startup &
Services & Roles
Use service classes for global configuration, and role classes for specific configuration of a particular service. For example, all sys_nfs_server systems will need portmap and other processes running, while different Network File System (NFS) server configuration (/etc/exports and so forth) should be assigned to a role class.
classes:
any::
sys_nfs_server = ( darwin evolution )
role_nfs_development = ( darwin )
role_nfs_homedirs = ( evolution )
control:
role_nfs_development::
nfs_exports_config = ( ${masterfiles}/role/nfs/development/exports )
role_nfs_homedirs::
nfs_exports_config = ( ${masterfiles}/role/nfs/homedirs/exports )
copy:
sys_nfs_server::
${nfs_exports_config}
dest=/etc/exports
define=restart_nfs
processes:
redhat.sys_nfs_server::
"nfsd" restart "/sbin/service nfs start"
"portmap" restart "/sbin/service portmap start"
"rpc.statd" restart "/sbin/service nfslock start"
shellcommands:
redhat.restart_nfs::
"/usr/sbin/exportfs -ar"
Class Scope
Some groups of classes will account for all systems in an organization, while other class groups will only cover a subset of the systems. The subset classes may have logic problems, as !backup_server covers both backup_client systems and other systems outside of the backup_* umbrella. Why is this important? Logic errors can result in unintended actions being carried out, such as cfexecd being killed on all the Windows systems. Sorry, Alex! :)
classes:
any::
ntp_primary = ( tick tock )
ntp_client = ( any -ntp_primary )
backup_server = ( hindsight )
backup_client = ( darwin evolution tick )
To target systems not being backed up, the unwieldy expression !(backup_server|backup_client) would be required. If a new, exclusive backup class was added (for example backup_proxy), all the negative rules would need to be changed to !(backup_server|backup_client|backup_proxy). On the other hand, to add a ntp_secondary class, the addition need only be done under the classes section:
classes:
any::
ntp_primary = ( tick tock )
ntp_secondary = ( darwin )
ntp_client = ( any -ntp_primary -ntp_secondary )
Another option entails using SingleCopy nirvana to workaround copy blocks with complicated someclass.!anotherclass logic.
The following example shows configuration for the Network Time Protocol (NTP) daemon. Either the system is a server, or a client, and each class requires a different ntp.conf. If Operating Systems use different locations for ntp.conf, account for this in the control section with various ntp_config_dest definitions.
control:
any::
ntp_config_dest = ( /etc/ntp.conf )
ntp_client::
ntp_config_src = ( ${masterfiles}/conf/ntp/client/ntp.conf )
ntp_primary::
ntp_config_src = ( ${masterfiles}/conf/ntp/server/ntp.conf )
copy:
any::
${ntp_config_src}
dest=${ntp_config_dest}
mode=0444
define=restart_ntpd
More CFEngine Examples.