Cfengine automatically sets a large number of classes at runtime based on attributes of the system. These are classes based on the IP address of the system, the operating system (e.g., linux
or solaris
), the date and time, and many other attributes. Many predefined cfengine classes are shown and explained in Chapter 4.
Cfengine modules are designed for the definition of custom classes. Modules allow you to write code to extend cfengine, so that it can detect new situations and site-specific conditions. We say "designed for" because it's possible to use modules to implement system changes as well. We'll focus on what modules are designed for and then briefly touch on other uses. We'll explain the requirements for using modules and then show you how to create a simple module to get you started. Once you know how to create and use a module, you'll be able to build on the example in your own environment.
Before we discuss modules in any detail, we'll lay out the requirements for using them:
moduledirectory
in cfagent.conf
(or a file imported from cfagent.conf
).module
:mymodule
.cfagent
will execute only modules
+
sign are interpreted as classes to be defined.-
sign are interpreted as classes to be undefined.=
are interpreted as variables to be defined.cfagent
, so modules should generally be silent.Classes are used by cfengine to determine the appropriate actions to take, if any. During the development of our example environment used throughout this book, we only needed classes based on simple tests. For example, the following shellcommands
section will only be run if the niagara_t1_proc
class is set:
classes:
sunos_sun4v::
niagara_t1_proc =
( "/usr/platform/sun4v/sbin/prtdiag | grep UltraSPARC-T1 >/dev/null" )
shellcommands:
niagara_t1_proc::
"/bin/echo hello world"
Sun hardware classified as sun4v
has the processor class that we're looking for but not all systems of that class run a particular CPU called the Niagara T1 processor. In the classes
section, we ran the prtdiag
command and piped the output into the grep
command looking for the string "UltraSPARC-T1". Running the prtdiag
and grep
commands enabled us to find the sun4v
systems that are running the Niagara T1 processor and then to set the niagara_t1_proc
class.
This very simple example of setting a custom class is well suited to the classes
section because of its simplicity. At some point, you may need to set classes based on much more complex criteria. If you can write code in any language supported on your systems, you can write a cfengine module to set your custom classes. We will use Bourne shell scripting for our example module.
Making use of a module to set the niagara_t1_proc
class is a good way to get familiar with creating your own modules. We will implement this simple module in our example environment.
First, we created a module called module:detect_niagara
, with these contents:
1. #!/bin/sh
2. PATH=/bin:/usr/bin
3.
4. if /usr/platform/sun4v/sbin/prtdiag | grep UltraSPARC-T1 >/dev/null
5. then
6. THREAD='/usr/platform/sun4v/sbin/prtdiag | grep UltraSPARC-T1
7. | wc -l | sed 's/^[ ]*//' '
8. echo "+niagara_t1_proc"
9. echo "=num_cores=$THREAD"
10. fi
This script is very simple. It executes a grep
command against the output of the prtdiag
command on line 4, and if a match is found, three things happen:
prtdiag
command is run again—this time to capture the total number of CPU threads present on the system's processor—using the wc
command. The sed
command in the pipeline removes any leading whitespace placed in the output by the wc
command.niagara_t1_proc
class is set using an echo statement on lines 7 and 8, so now, the cfagent
process running this module will have the class defined.num_cores
will be passed back to the cfagent
process running the module, with the value set to the number of threads on the system from line 6.We placed the file in the PROD/inputs/modules
directory, which should exist if you followed along with this book (this relative path convention has also been used throughout this book; the full path on the cfengine master in our example environment is /var/lib/cfengine2/masterfiles/PROD/inputs/modules
). If not, you may need to create the directory. We added this line to PROD/inputs/control/cf.control_cfagent_conf
so our module could be found by cfagent
at runtime (make sure that it applies to the any
class):
moduledirectory = ( $(client_cfinput)/modules )
We then created a task at PROD/inputs/tasks/misc/cf.detect_niagara_proc
with these contents:
control:
sunos_sun4v::
addinstallable = ( niagara_t1_proc )
actionsequence = ( module:detect_niagara )
shellcommands:
sunos_sun4v.niagara_t1_proc::
"/bin/echo hello world - I have a Niagara proc with $(num_cores) threads"
We needed the addinstallable
line so that cfengine knew that a custom class might be defined. We set the actionsequence
to include this module on any hosts running the sun4v
architecture—recall that modules are always called via the cfengine actionsequence
. We run the command in the shellcommands
section when a host is a sun4v
system and when the niagara_t1_proc
class is set. When the command is run, the variable containing the number of threads on the processor is returned.
To put the task into use, we added it to the PROD/inputs/hostgroups/cf.any
file with this entry:
tasks/misc/cf.detect_niagara_proc
Check the files into Subversion, and check them out onto your cfengine master, if applicable and if you've followed along with the book to this point (if you're not sure what we mean, don't worry about it). We don't need take any extra measures in our example environment to set the ownership or permissions on the files in our modules
directory, because the update.conf
in our example environment copies all files inside the PROD/inputs
directory with root
(user and group) ownership and other permissions completely absent (file permission mode 700 and owned by root:root
). If you haven't followed along with this book, here's the pertinent section from update.conf
:
copy:
$(master_cfinput)/
dest=$(workdir)/inputs/
r=inf
mode=700
type=checksum
ignore=RCS
ignore=.svn
owner=root
group=root
ignore=*,v
purge=true
server=$(policyhost)
trustkey=true
encrypt=true
Our modules
directory is under the inputs
directory (which is copied via update.conf
), and this copy
action recursively copies all files and directories beneath the inputs
directory. The variables used aren't pertinent to this section. What's important is that the module files are owned by root
, since we only run cfengine as root
at our example site.
On systems running a Niagara processor (such as a Sun T2000 system), you'll see output from cfagent
like this:
cfengine:sunbox:/bin/echo hello: hello world - I have a Niagara proc with 32 threads
This simple example puts all the pieces in place for you to successfully use cfengine modules. You can use it to build a much more complicated module that sets classes and variables that you can then use to take actions in a cfengine task file (as we did with the echo
command in the shellcommands
section of cf.detect_niagara_proc
).
Using modules, you can extend cfengine in ways never imagined by the author of cfengine.
Cfengine provides the shellcommands
section so you have an easy way to perform custom actions. The commands defined in a shellcommands
section can be standard operating system utilities or custom scripts. Cfengine makes every attempt to be as generic as possible, and it directly supports only the most basic system administration actions (e.g., file copies, permission fixes, link creation, file editing, etc).
Nothing prevents the code in a module from making changes on a system. The entire list of classes defined by cfengine on the host is passed to scripts or programs run by shellcommands
as well as to scripts or programs run as a module, so there is no technical barrier to using a module instead of a shellcommands
section.
We don't like to use modules this way, because they weren't designed to replace shellcommands
. We think that some sites choose to use modules in place of shellcommands
since it's easy to automate the copy of the modules
directory and use that as a single location for cfengine-specific scripts. In our example environment, we automated the copy of an administrative script directory, so we have an easy location to place sitewide scripts for execution by administrators, cfengine, or both.
Modules are sometimes recommended on Internet mailing lists when the quotes used in shellcommands
actions get too complicated for the cfengine parser, resulting in errors. Consider a shellcommands
section such as this:
shellcommands:
debian.i686::
"/bin/grep 'foo bar' /etc/foobar | /bin/sed 's/^[ ]*//'
| /usr/bin/mail -s"file contents from 'hostname'" [email protected]"
This code is difficult to read and needs some escaping of double quotes, and sometimes, cfengine can get confused when parsing commands like this. However, we don't consider this a reason to put the commands in a cfengine module. Instead, use a shell script, in a location such as our example site's /opt/admin-scripts
directory.
We could create PROD/repl/admin-scripts/mail-foobar
with these contents:
#!/bin/sh
PATH=/bin:/usr/bin
if [ -f /etc/foobar ]
then
/bin/grep 'foo bar' /etc/foobar | /bin/sed 's/^[ ]*//' |
/usr/bin/mail -s"file contents from 'hostname'" [email protected]
fi
We could then create a new shellcommands
section like this:
shellcommands:
debian.i686::
"/opt/admin-scripts/mail-foobar"
Now, the cfengine configuration is easy to read, and the script can be a simple shell script with no special escaping rules, making it easy to read too. You can also easily add extra niceties to the shell script, like a PATH
statement and a test for the /etc/foobar
file's existence before running grep
against it. At our example site, the contents of PROD/repl/admin-scripts
on our cfengine master are copied to /opt/admin-scripts
on all hosts, so once we place a script into the central directory, the copy is automatic.
We definitely recommend using shell scripts—not modules—for complicated shellcommands
sections.
18.219.132.107