CFEngine is a program meant to automate the job of system administrators. Instead of writing adhoc custom scripts, cfengine gives you the easy way of expressing the solution with simple and straight forward declarative language. The declarative language allows you to specify a policy for cfengine to implement. For example as a policy, an administrator may want tmp directory to be emptied every three days, may want the password file to always have the permission 644, ensure that apache is up all the time etc. The policies such as these and many more can easily be written for cfengine to implement. For example a simple code such as
files: c:\filename mode=644 checksum=md5 action=fixall
will enable cfengine to implement a file policy without any complex script. CFEngine makes the policy implementation easy and simple.
As beginners we will first concentrate on how to use cfagent to implement simple policies since it is the most important component or agent of the cfengine configuration management software. Don’t rush to setup client/server configuration since it is not really needed to run cfengine except for pulling operations. The CFEngine client/server setup is only for update or copying or pulling of files from cfengine server to cfengine client. The actual configuration management is done by cfagent running on a host. By understanding how to run cfengine configuration script on your local host is more than 60% of what is required to implement cfengine network environment.
c:\var\cfengine directory.
The structure of cfengine declarative language is given below
control: variable_1 = ( value1 value2 ... ) variable_2 = ( value1 value2 ... ) ... variable_n = ( value1 value2 ... ) stanza1: [condition or class]:: target attribute1=value1, attribue2=value2 ... stanza2: [condition or class]:: target attribute1=value1, attribue2=value2 ...
The control stanza is a special stanza where all the global variables that will be used in the policy file are defined. The control could be used to specify the order of execution, access control etc.
variable1
... are the variable names, they can be
reserved or predefined variables or user defined variables. The values
of the variable are specified in the parenthesis. The values can be a
stanza or action type, a class or Boolean, a path or any appropriate
user defined value. The stanzas define the rule type or action type,
for example files rule type or action type, disk rule type or action
type etc.
Each stanza consists of optional condition for which the policy should
be implemented. The condition or class ( as called by cfengine ) must
be followed by double colon. Within every stanza or rule type (action
type) we have target, attributes or options, values and
action. Usually, such attributes are defined by cfengine. However, one
can define their own attribute using the cfengine "defined"
keyword. For example the simple files stanza can have the following
attributes or options: owner
, mode
, checksum
.
The values of the attributes are either predefined or user
defined. For example the value for the mode attribute 644
is user
defined but the value for checksum is md5
which is predefined. The
target defines the object to which cfengine will manage or operate on.
A target can be a file, a directory, a process etc. action defines
the operation we want cfengine to perform on a given target. We also
have the target which specifies the object we want to apply the
policy.
In summary cfengine declarative language consist of
Example 1
We are going to test cfengine by simple files rule type example. We
will check if a file has mode 644
if not we change it to 000
.
#cf_ex1.conf control: actionsequence = ( files ) files: /cygdrive/c:/cfengine/testfile.txt mode=000 action=fixall #end
This policy specification allows cfengine to check if the target
(testfile.txt) has permission 000
, if not cfengine will change
the permission to 000
. This check can be done every minute, hour
depending on what you want. CFEngine will automate the administrator's
job. The permission 000
means no one can read, write or execute the
file.
We will run cfagent to test if this policy will be implemented. We first have to create the file testfile.txt and make it readable, writable and executable by all. To do that right click the file and go to properties -> security and change the permissions. Now create a file called cf_ex1.conf and type in the file policy in example 1. Save the file in c:\var\cfengine\inputs folder. CFEngine by default look for all the configurations files from c:\var\cfengine\inputs.
Type the following command for cfengine to implement the policy:
c:\var\cfengine\bin>cfagent -vKf cf_ex1.conf
The directory c:\var\cfengine\bin is where the cfengine agent is located and -v is for verbose output, -K is a command to force the implementation of policy and -f is used to specify the file which contains the policy configuration. The policy configuration file is found in cf_ex1.conf. By default cfagent runs every 1 minute and if that time is not elapsed since the last run the current configuration will not be executed so -K option is used to override this default option. In example 1 the actionsequence variable (directive or command) has the value files. The value of the actionsequenece variable is placed in parenthesis. There should be a space before and after the variable value ( files ). The values of actionsequance represent the rule type and the sequence of execution.
The sequence of execution is simply first come first serve. E.g
actionsequenece = ( files shellcommands )
means the files command must be executed before the shellcommnds. The files stanza or rule type has the target testfiles.txt (the full path of the file should be specified) and attributes or options of the files are mode and action and the values are 000 and fixall respectively. The action is a special attribute. It is used to specify operation to be performed or action to be taken. The cygdrive is more reliable way of specifying cfengine path. The path /cygdrive/c/cfengine/testfile.txt will be interpreted as c:\cfengine\testfiles.txt in windows.
This example adds shell commands to example 1 and executes it from cfengine
#cf_ex2.conf control: actionsequence = ( files shellcommands ) files: /cygdrive/c/cfengine/testfile.txt mode=644 action=fixall shellcommands: "/bin/echo ...second cfengine script ..."
This example shows that cfengine configuration file may contain more than one action type. The files action type just fix the file permission of the testfile.txt and the shellcommands action type just echoes or displays a message `second cfengine script'.
This section helps us to understand some simple errors that we may encounter when creating cfengine configuration file. Whenever an error occurs in cfengine configuration files, cfengine will tell which line in the configuration contains the error. Using the –v option in cfagent execution helps us spot the error.
Example 2b
One of the common errors a beginner could make is the space around the actionsequence. In this example we try to remove the space around the action sequence value, files, and examine the response from cfengine.
#cf_ex2b.conf control: actionsequence = (files) #no space around the files rule type files: /cygdrive/c/cfengine/testfile.txt mode=000 action=fixall #end
Now run the following command c:\var\cfengine\bin>cfagent -vKf cf_ex1b.conf
The message you will see is: /var/cfengine/inputs/cf_ex1b.conf:2: syntax error
This means you should examine line 2 of the cf_ex1b.conf file. Usually fengine does not suggest the kind of error. As a general rule it is good to always run cfengine with the verbose option. Also any time you have an error cfengine will be able to tell the line number in which the error occurred.
The order in which you write the rule types in cfengine doesn't affect the sequence of execution. For example, example 2 can be rewritten as
#cf_ex2a.conf control: actionsequence = ( files shellcommands ) shellcommands: "/bin/echo ...second cfengine script ..." files: /cygdrive/c/cfengine/testfile.txt mode=644 action=fixall #end
The results will be the same. The files rule type will be executed first followed by shellcommands since the actionsequence specifies so. So the sequence is specified by the actionsequence but the order of rules does not matter.
Variables allow us to store data in memory for processing. It enable a programmer to reference a certain memory location. Variables are declared in cfengine as follows: name = ( value1, value2 ... )
The name can be any name that follows the general programming standard. The name can also be predefined name. Values are either given or user supplied. A variable can hold one or more data. Example 3 shows how to work with cfengine variables.
Example 3 #cf_ex3.conf control: actionsequence = ( shellcommands ) hello = ( "hello cfengine " ) #hello variable shellcommands: "/bin/echo ..$(hello).."
This code declares the hello variable and assigns the value "hello
cfengine". We then use shellcommands stanza to display the value of
the variables. The code $(hello) shows how cfengine references
variables. There are two ways of referencing cfengine variables. We
can reference a variable with parenthesis or curly bracket. For
example $(hello)
can be rewritten as ${hello}
.
#cf_ex4.conf control: actionsequence = ( shellcommands ) hello = ( "hello cfengine " ) #hello variable shellcommands: "/bin/echo ..${hello}.."
Example 4 will produce the same result as example 3. The only
difference is that the parenthesis is changed to curly brace i.e.
${hello}
.
The values of a variable can be specified in the cfengine configuration file or read from a file. Example 5 read data from a file and stores it in a variable.
Example 5 #cf_ex5.conf control: actionsequence = ( shellcommands ) data = ( ReadFile(/cygdrive/c/cfengine/data.txt,100) ) #reading from a file shellcommands: "/bin/echo ..$(data).."
This example reads data from the file data.txt using the cfengine
ReadFile method. The output is stored in the variable data and the
result displayed by shellcommands. The ReadFile()
method has two
parameters, the file name and the number of characters to be read.
Special keys such as tab, newline etc can also be represented in cfengine to format output. The following are the some of the escape sequence.
Example 5a
#cf_ex5a.conf control: actionsequence = ( shellcommands ) alerts: compiled_on_cygwin:: "The car cost $(n) $(dollar)20,000 "
The output will be:
The car cost $20,000
Write a sentence and put it in double quote
Selection or classes allow a program to decide which code to
execute. In cfengine such commands are called classes. There are no if
statement in cfengine, only classes. By default cfengine classifies
the system environment and used the results to make decisions. The
classes are Boolean with true or false values. We can see the default
or defined classes by running cfagent -vp
command. My system has the
following define classes
Defined Classes = ( 192_168_157 192_168_157_1 192_168_189 192_168_189_1 32_bit A ugust Day7 Hr11 Hr11_Q4 IBM Min55_00 Min58 Q4 Thursday Yr2008 any cfengine_2_2 c fengine_2_2_8 cfengine_2_2_8_pwin compiled_on_cygwin cygwin_nt_6_0_1_5_25_0_156_ 4_2_ cygwin_nt_6_0_i686 cygwin_nt_6_0_i686_1_5_25_0_156_4_2_ cygwin_nt_6_0_i686_ 1_5_25_0_156_4_2__2008_06_12_19_34 i686 ibm ipv4_192 ipv4_192_168 ipv4_192_168_1 57 ipv4_192_168_157_1 ipv4_192_168_189 ipv4_192_168_189_1 net_iface_eth0 net_ifa ce_eth1 net_iface_lo nt undefined_domain )
This means I can use any of these values to make a decision. For example, this configuration
files: August:: /cygdrive/c/cfengine/testfile mode=644 action=fixall
Means the file operation should be performed if the month is August. Each of the names in the Defined Classes can be used to write a conditional statement. Thus they are meant for decision making.
Users can also define their own classes which can also be used for decision making.
Example 6 This example displays the message "...cfengine classes in action.." if the compiled_on_cygwin classes is found on your system. CFEngine will check if " compiled_on_cygwin" is part of the defined classes and displays the message accordingly.
#cf_ex6.conf control: alerts: compiled_on_cygwin:: "..cfengine classes in action.."
We can have two or more classes as shown in example 7
#cf_ex7.conf control: alerts: compiled_on_cygwin:: "..cfengine classes in action..windows" SuSe:: "..cfengine classes in action..suse"
CFEngine will display only the first message since there is no
SuSE
classes in my defined classes above
Logical operators are used to combine conditional statements or classes. For example we can check if a our operating system is windows or linux, the operating system is windows and the time is quarter pass two and so on.
CFEngine has the following logical operators:
Example 8 #cf_ex8.conf control: alerts: redhat.SuSE:: "..cfengine classes found.."
The output is nothing.
Example 8 shows the combination of two classes redhat and SuSe. redhat.SuSe means if the distribution is redhat and suse display the "cfengine classes found". Since a distribution cannot be both redhat and suse or the two classes are not found in the defined classes above nothing is displayed.
#cf_ex9.conf control: alerts: compile_on_cygwin|SuSe:: "..at least on class is found.."
This means if either of the classes compile_on_cygwin
or
SuSE
if found on the system, display the message.
#cf_ex10.conf control: alerts: !redhat|Hr10:: "..cfengine classes in action.."
This means if distribution is not redhat or the hour is 10 am display the results.
It is possible to define your own class using the define keyword or classes rule or action type.
#cf_ex11.conf User define classes and sequence control: classes: Check = ( FileExists(/cygdrive/c/cfengine/cf.txt) ) alerts: Check:: "yes"
This example defines the class Check
using the classes rule type. The
method FileExists()
is cfengine method which check whether or not a
file exists. The value of the class Check
is true if the file cf.txt
is found c:\cfengine in directory. The output of the policy will be
yes if the file is found.
Sometimes you may want cfengine to be aware of your user defined class before it is used. We do this by using the AddInstallable method.
#cf_ex12.conf control: #add class that might become define at run time AddInstallable = ( Check ) classes: Check = ( FileExists(/cygdrive/c/cfengine/cf.txt) ) alerts: Check:: "yes"
User defined classes can be define at runtime.
#cf_ex13.conf #User define classes and sequence control: actionsequence = ( files ) #add class that might become define at run time #the order of usage is not important AddInstallable = ( RCheck ) files: RCheck:: #RCheck is true /cygdrive/c/cfengine/test mode=700 action=fixall files: #define stores the outcome of execution #if mode is not 777 then change to 777 and set RCheck to true /cygdrive/c/cfengine/test1 mode=777 action=fixall define=RCheck
This example uses the outcome of results in the second stanza (test1)
to fix the file permission for the test file. The entire policy
reads like this, if the file permission for test1 file is not 777
and the action is taken to change it to 777 then change the permission
of the test file to 700
. The order is not important; the right
instruction will be executed.
#User define classes control: #add class that might become define at run time actionsequence = ( files ) AddInstallable = ( WinXP Indigo ) classes: #list of classes of pcs WinXP = ( pc121 pc122 linux ) #-box2 and -box4 not part Indigo = ( irix -box2 -box4 ) #if file exist RCheck = ( FileExists(/cygdrive/c/cfengine/sequence.txt) ) files: RCheck.WinXP:: #RCheck and pc121 or RCheck and p122 or RCheck and linux is true /cygdrive/c/cfengine/test.txt mode=600 action=fixall
CFEngine list allows you to specify a list of items which can be treated as a single variable. A list can be any set of names, file names etc. The example 22 shows how to create a list.
control: alist = ( "one:two:three:four:five" ) alerts: compiled_on_cygwin:: " $(alist)"
By default a list should be separated by colon. This can be changed using the Split option.
control: Split = ( , ) alist = ( "one,two,three,four,five" ) alerts: compiled_on_cygwin:: " $(alist)"
The Split
method allows us to change the default colon to comma. The
power of a list is made clear when it is supplied as a parameter to a
method.
control: Split = ( " " ) mylist = ( "mark ricky bad-dude" ) tidy: /cygdrive/c/$(mylist) pattern=*.cfsaved age=1
In this example cfengine will iterate and substitute mylist with each of the elements of in the list. It will then delete all .cfsaved file which is a day old in each of the directory.
Recursion is used to iterate a directory of files and
subdirectories. The keyword for recursion is recurse. The value of
recurse is from 0
to inf
(infinity). Where 0
means stay in the current directory and infinity means everything in
the current directory including subdirectories.
control: actionsequence = ( files ) files: /cygdrive/c/cfengineDoc mode=644 action=fixall recurse=inf
This example means change the mode of everything in the directory tree to 644.
control: actionsequence = ( files ) files: /cygdrive/c/cfengineDoc mode=644 action=fixall recurse=1
This example means change the mode of current directory and subdirectory to 644.
A method is a set of instructions that achieve a specific task. It is possible to perform a task using cfengine method. Methods in cfengine is given by
control MethodName = ( name ) MethodParameters = ( p1 p2 ... ) method-body
The MethodName
directive is use to specify the method name and the
MethodParameters
specifies the parameters. The number of parameters
can zero or more. The method body is the usual cfengine policy
code. It is not different from what we know so far. We begin with a
simple method that displays a message.
#cf_ex26.conf User define classes and sequence control: MethodName = ( UserClass ) #add class that might become define at run time AddInstallable = ( Check ) classes: Check = ( FileExists(/cygdrive/c/cfengine/cf.txt) ) alerts: Check:: "Yes the cf exist" !Check:: "No, cf does not exist"
This method checks if the file cf exist and display a message. The
method is called UserClass
and has no parameter. In cfengine it is
required that a method is placed in a special directory called
modules. The default location of this directory is
c:\var\cfengine\modules. You should copy the cf_ex26.conf file into
the modules folder. The next step is to create a method call
configuration file to make use of the method as in example 27.
#cf_ex27.conf control: actionsequence = ( methods ) methods: UserClass(void) action=cf_ex26.conf #include the method here returnvars=null #no return value server=localhost #run the method on local machine
The method call has the methods rule type. It is used to call the
method. Under methods rule type we call the method by name. Since our
method has no parameters we supply void as parameter entry. The action
directive now specifies which file contains the method. The returnvars
allows the return value to be store and reuse it later. Our method has
no return type so the value of return type is null. The server
attribute or option allows you to specify which host will run the
method. In this example the local host will run the method.
The next example shows how to use parameter in methods.
Example 28
#cf_ex28.conf User define classes and sequence control: MethodName = ( UserClass ) MethodParameters = ( filename ) #add class that might become define at run time AddInstallable = ( Check ) classes: Check = ( FileExists("/cygdrive/c/cfengine/$(filename)") ) alerts: Check:: "Yes the cf exists" !Check:: "No, cf does not exists"
This method performs the same task as example 26 however the file name will not be fix but vary. The name will now be supplied at method call. Example 29 is the method call for example 28. Note that the file path is in double quote.
control: actionsequence = ( methods ) methods: UserClass(cf) action=cf_ex28.conf returnvars=null server=localhost
This example calls the method UserClass and supply the parameter cf. The cf will replace the variable or parameter filename so that the file path will c:\cfengine\cf. As usual the action option specifies the file which contains the method. The method file must be located in the modules directory. Note that the method call configuration file should not be in the modules directory.
A method can return a value which could be used for further operation. In this case the return value will not be null. Example 30 illustrates how this could be done.
#cf_ex30.conf User define classes and sequence control: MethodName = ( ReturnName ) MethodParameters = ( filename ) classes: every = ( any ) alerts: every:: ReturnVariables("$(filename)")
This method just accepts a file name and returns the name of the file. The alerts rule type is needed to return the results. The method call for example 30 is shown below.
control: actionsequence = ( methods ) methods: ReturnName(cf) action=cf_ex30.conf returnvars=results server=localhost classes: every = ( any ) alerts: every:: "The file name is $(ReturnName.results)"
Here the returnvars value is results. This means the file name being returned by the method will be stored in the variable results. We then use the alerts command to display the file name. The results variable is accessed using the name of the method name. Example 32 This example shows how to use more than one parameters in cfengine method.
#cf_ex32.conf User define classes and sequence control: actionsequence = ( editfiles ) MethodName = ( EditMethod ) MethodParameters = ( filename data ) editfiles: { /cygdrive/c/cfengine/$(filename) AppendIfNoSuchLine "$(data)" } classes: every = ( any ) alerts: every:: ReturnVariables("The file name is $(filename) and was editted with $(data)")
Example 33 calls the method in example 32.
Example 33
control: actionsequence = ( methods ) methods: EditMethod(cf,"insert data here") action=cf_ex32.conf returnvars=results server=localhost classes: every = ( any ) alerts: every:: "The file name is $(EditMethod.results)"
CFEngine provides a number of built-in functions for evaluating classes, based on file tests and perform other functions. Using these built-in functions is quicker than calling the shell test function.
classes: userdef = ( ClassMatch(.*linux.*) )
IsDefined(${var})
)
Example control: actionsequence = ( files ) a = ( 2.12 ) b = ( 2.11 ) classes: lt = ( IsLessThan(${a},${b}) ) gt = ( IsGreaterThan(${a},${b}) ) alerts: lt:: "$(a) LESS THAN $(b)" gt:: "$(a) GREATER THAN $(b)"
What makes cfengine method powerful is the iteration. It is possible to iterate over a list using method. When a list is passed to a method cfengine will automatically iterate over the list.
Example 34
#cf_ex34.conf control: MethodName = ( ReturnName ) MethodParameters = ( filename ) classes: every = ( any ) alerts: every:: ReturnVariables("$(filename)")
We call the above method with example 35
Example 35
control: actionsequence = ( methods ) alist = ( one:two:three:four:five ) methods: ReturnName($(alist)) action=cf_ex24.conf returnvars=results server=localhost classes: every = ( any ) alerts: every:: "The file name is $(ReturnName.results)"
Table of Contents