CFEngine is a suite of programs for integrated autonomic management of either individual or networked computers. It has existed a This document represents cfengine versions 3.0.0 and later, which are a radical departure from earlier versions.
CFEngine 3 has been changed so as to be both a more powerful and much simpler. CFEngine 3 is not backwards compatible with the cfengine 2 configuration language, but it interoperates with cfengine 2 so that it is "run-time compatible". This means that you can change over to version 3 slowly, with low risk and at your own speed.
With cfengine 3 you can install, configure and maintain computers using powerful hands-free tools. You can also integrate knowledge management and diagnosis into the processes.
CFEngine differs from most management systems in being
CFEngine 3 consists of a number of components:
cf-agent
cf-execd
cf-graph
cf-know
cf-monitord
cf-promises
cf-runAgent
cf-serverd
cf-show
The starred components are new. The daemon formally called
cfenvd
i previous versions of cfengine is now called
cf-monitord
.
Unlike previous versions of cfengine, which had no consistent model for its features, you can recognize everything in cfengine 3 from just a few concepts.
Concept Quick description Promise A statement about the state we desire to maintain</td> Promise bundles A collection of promises</td> Promise bodies A part of a promise which details and constrains its nature</td> Data types An interpretation of a scalar value: string, integer or real number</td> Variables An association of the form "LVALUE represents RVALUE", where rval may be a scalar value or a list of scalar values Functions Built-in parameterized rvalues Classes Cfengine's boolean classifiers that describe context
If you have used cfengine before then the most visible part of cfengine 3 will be its new language interface. Although it has been clear for a long time that the organically grown language used in cfengines 1 and 2 developed many problems, it was not immediately clear exactly what would be better. It has taken years of research to simplify the successful features of cfengine to a single overarching model. To understand the new cfengine, it is best to set aside any preconceptions about what cfengine is today. CFEngine 3 is a genuine "next generation" effort, which is will be a springboard into the future of system management.
Many attempts at improving the user interface of cfengine have been proposed but none of them have been sufficiently impressive to make the change worthwhile before now. Some have gone in for an Object Oriented approach, but this imposes a hierarchical model that does not fit cfengine's autonomous peer model. The main goal in changing the language is to simplify and improve the robustness and functionality without sacrificing the basic freedoms and concepts. Concepts such as explicit loops and and tests have long been banished from cfengine and proposals to reintroduce them have been dismissed — something better is needed. The difficulty, of course is to provide a genuine simplification and improvement that is robust and lasting: this requires a deep understanding of the problem.
CFEngine 3's new language is a direct implementation of a model developed at Oslo University College over the past four years, known colloquially as "promise theory". Promises were originally introduced by Mark Burgess as a way to talk about cfengine's model of autonomy and have since become a powerful way of modelling cooperative systems. CFEngine 3 is a generic implementation of the language of promises that allows all of the aspects of configuration management to be unified under a single umbrella.
Why talk about promises instead of simply talking about changes? After all, the trend in business and IT management today is to talk about Change Management, e.g. in the IT Infrastructure Library (ITIL) terminology. This comes from a long history of process management thinking. But we are not really interested in change – we are interested in being in a state where we don't need to make any changes. In other words we want to be able to promise that the system is correct, verify this and only make changes if our promises are not kept.
To put it another way, cfengine is not really a change management system, it is a maintenance system. Maintenance is the process of making small changes or corrections to a model. A "model" is just another word for a template or a specification of how we want the system to work. CFEngine's model is based on the idea of promises, which means that it focuses on what is stable and lasting about a system – not about what is changing.
This is an important philosophical shift. It means we are focused mainly on what is right and not on what is wrong. By saying what "right" is (the ideal state of our system) we are focussed on the actual behaviour. If we focus too much on the changes, i.e. the differences between now and the future, we might forget to verify that what we assume is working now in fact works.
Models that talk about change management tend to forget that after every change there is a litany of incidents during which it is necessary to repair the system or return it to its intended state. But if we know what we have promised, it is easy to verify whether the promise is kept. This means that it is the promises about how the system should be that are most important, not the actual changes that are made in order to keep them.
One of the practical advantages of cfengine is that you can test it without the need for root or administrator privileges. This is recommended for all new users of cfengine 3.
CFEngine operates with the notion of a work-directory. The default
work directory for the root
user is /var/cfengine
(except on Debian Linux and various derivatives which prefer
/var/lib/cfengine). For any other user, the work directory
lies in the user's home directory, named ~/.cfagent. CFEngine
prefers you to keep certain files here. You should not resist this
too strongly or you will make unnecessary trouble for yourself. The
decision to have this `known directory' was made to simplify a lot of
configuration.
To test cfengine as an ordinary user, do the following:
host$ mkdir -p ~/.cfagent/inputs host$ mkdir -p ~/.cfagent/bin host$ cd src host$ cp cf-* ~/.cfagent/bin host$ cd ../inputs host$ cp *.cf ~/.cfagent/inputs
You can test the software and play with configuration files by editing the basic get-started files directly in the ~/.cfagent/inputs directory. For example, try the following:
host$ ~/.cfagent/bin/cf-promises host$ ~/.cfagent/bin/cf-promises --verbose
This is always the way to start checking a configuration in cfengine 3. If a configuration does not pass this check/test, you will not be allowed to use it, and cf-agent will look for the file failsafe.cf.
Notice that the cfengine 3 binaries have slightly different names than the cfengine 2 binaries. They all start with the cf- prefix.
host$ ~/.cfagent/bin/cf-agent
####################################################### # # The starting point for every configuration # ####################################################### body common control { any:: bundlesequence => { "testbundle" }; } ####################################################### bundle agent testbundle { }
If you try to process this using the cf-promises
command, you will
see something like this:
atlas$ ~/LapTop/Cfengine3/trunk/src/cf-promises -f ./unit_null_config.cf cf3:./unit_null_config.cf:21,1: syntax error, near token '}' Summarizing promises as text to ./unit_null_config.cf.txt Summarizing promises as html to ./unit_null_config.cf.html
Examine the two files produced:
cat ./unit_null_config.cf.txt firefox ./unit_null_config.cf.html
To familiarize yourself with cfengine 3, type or paste in the following example text:
######################################################## # # Simple test execution # ######################################################## body common control { bundlesequence => { "testbundle" }; } ######################################################## bundle agent testbundle { vars: "size" int => "46k"; "rand" int => randomint("33","$(size)"); commands: "/bin/echo Hello" # additional args that do not become path of $(this.promiser) args => "world - $(size)/$(rand)", contain => standard, classes => mydefine("followup","alert"); followup:: "/bin/ls" contain => standard; reports: alert:: "What happened?"; } ###################################################################### body contain standard { exec_owner => "mark"; useshell => "true"; } ###################################################################### body classes mydefine(class,alert) { on_change => { "$(class)" }; on_failure => { "$(alert)" }; }
If you are familiar with cfengine's history, this will look quite strange to you, but fear not.
This example shows all of the main features of cfengine: bundles, bodies, control, variables, and promises. To the casual eye it might look complex, but that is because it is explicit about all of the details. Fortunately it is easy to hide many of these details to make the example simpler without sacrificing any functionality.
The first thing to try with this example is to verify it – did we
make any mistakes? Are there any inconsistencies? To do this we use
the new cfengine program cf-promises
. Let's assume that you
typed this into a file called test.cf in the current directory.
cf-promises -f ./test.cf
If all is well, typing this command shows no output. Try now running the command with verbose output.
cf-agent -f ./test.cf -v
Now you see a lot of information
Reference time set to Sat Aug 2 11:26:06 2008 cf3 CFEngine - 3.0.0 Free Software Foundation 1994- Donated by Mark Burgess, Oslo University College, Norway cf3 ------------------------------------------------------------------------ cf3 Host name is: atlas cf3 Operating System Type is linux cf3 Operating System Release is 2.6.22.18-0.2-default cf3 Architecture = x86_64 cf3 Using internal soft-class linux for host linux cf3 The time is now Sat Aug 2 11:26:06 2008 cf3 ------------------------------------------------------------------------ cf3 Additional hard class defined as: 64_bit cf3 Additional hard class defined as: linux_2_6_22_18_0_2_default cf3 Additional hard class defined as: linux_x86_64 cf3 Additional hard class defined as: linux_x86_64_2_6_22_18_0_2_default cf3 GNU autoconf class from compile time: compiled_on_linux_gnu cf3 Interface 1: lo cf3 Trying to locate my IPv6 address cf3 Looking for environment from cfenvd... cf3 Unable to detect environment from cfMonitord --------------------------------------------------------------------- Loading persistent classes --------------------------------------------------------------------- --------------------------------------------------------------------- Loaded persistent memory --------------------------------------------------------------------- cf3 > Parsing file ./test.cf --------------------------------------------------------------------- Agent's basic classified context --------------------------------------------------------------------- Defined Classes = ( any Saturday Hr11 Min26 Min25_30 Q2 Hr11_Q2 Day2 August Yr2008 linux atlas 64_bit linux_2_6_22_18_0_2_default x86_64 linux_x86_64 linux_x86_64_2_6_22_18_0_2_default linux_x86_64_2_6_22_18_0_2_default__1_SMP_2008_06_09_13_53_20__0200 compiled_on_linux_gnu net_iface_lo ) Negated Classes = ( ) Installable classes = ( ) cf3 Wrote expansion summary to promise_output_common.html cf3 Inputs are valid
The last two lines of this are of interest. Each time a component of
cfengine 3 parses a number of promises, it summarizes the information
in an HTML file called generically promise_output_
component-type.html
.
In this case the cf-promises
command represents all possible promises,
by the type "common". You can view this output file in a suitable web browser
to see exactly what cfengine has understood by the configuration.
The output looks something like this:
Everything in cfengine 3 can be interpreted as a promise. Promises can be made about all kinds of different subjects, from file attributes, to the execution of commands, to access control decisions and knowledge relationships.
This simple but powerful idea allows a very practical uniformity in cfengine syntax. There is only one grammatical form for statements in the language that you need to know and it looks generically like this:
type: classes:: "promiser" -> { "promisee1", "promisee2", ... } attribute_1 => value_1, attribute_2 => value_2, ... attribute_n => value_n;
We speak of a promiser (the abstract oject making the promise), the promisee is the abstract object to whom the promise is made, and them there is a list of associations that we call the `body' of the promise, which together with the promiser-type tells us what it is all about.
Not all of these elements are necessary every time. Some promises contain a lot of implicit behaviour. In other cases we might want to be much more explicit. For example, the simplest promise looks like this:
commands: "/bin/echo hello world";
This promise has default attributes for everything except the `promiser', i.e. the command string that promises to execute. A more complex promise contains many attributes:
files: "/home/mark/tmp/test_plain" -> "system blue team", comment => "This comment follows the rule for knowledge integration", perms => users("@(usernames)"), create => "true";
The list of promisees is not used by cfengine except for documentation, just as the comment attribute (which can be added to any promise) has no actual function other than to provide more information to the user in error tracing and auditing.
You see several kinds of object in this example. All literal strings
(e.g. "true"
) in cfengine 3 must be quoted. This provides
absolute consistency and makes type-checking easy and error-correction
powerful. All function-like objects (e.g. users("..")
) are either builtin
special functions or parameterized templates which contain the `meat' of the right hand
side.
In addition to statements (which we call simply promises), cfengine allows you to bundle things into containers. A container for promises is called simply a bundle.
bundle agent identifier { commands: "/bin/echo These commands are a silly way to use cfengine"; "/bin/ls -l"; "/bin/echo But they illustrate a point"; }
Bundles serve two purposes: they allow us to collect related promises under a single heading, like a subroutine, and they allow us to mix configuration for different parts of cfengine in the same file. The type of a bundle is the name of the component of cfengine for which it is intended.
For instance, we can make a self-contained example agent-server configuration by labelling the bundles:
# # Not a complete example # bundle agent testbundle { files: "/home/mark/tmp/testcopy" copy_from => mycopy("/home/mark/LapTop/words","127.0.0.1"), perms => system, depth_search => recurse("inf"); } # bundle server access_rules { access: "/home/mark/LapTop" admit => { "127.0.0.1" }; }
Another type of container in cfengine 3 is a `body' part. Body parts exist to hide complex parameter information in reusable containers. The right hand side of some attribute assignments use body containers to reduce the amount of in-line information and preserve readability. You cannot choose where to use bodies: either they are used or they are not used for a particular kind of attribute. What you can choose, however, is the name and number of parameters for the body; and you can make as many of them as you like: For example:
body copy_from mycopy(from,server) { source => "$(from)"; servers => { "$(server)" }; copy_backup => "true"; special_class:: purge => "true"; }
Notice also that classes can be used in bodies as well as parameters so that you can hide environmental adaptations in these bodies also. The classes used here are effectively ANDed with the classes under which the calling promise is defined.
A key difference in cfengine 3 compared to earlier versions is the presence of data types. Data types are a mechanism for associating values and checking consistency in a language. Once again, there is a simple pattern to types in cfengine.
The principle is very simple: types exist in order to match like a plug-socket relationship. In the examples above, you can see two places where types are used to match templates:
bundle TYPE name # matches TYPE to running agent { }
lvalues => rvalue
constraints:
body TYPE name # matches TYPE => name in promise { }
Check these by identifying the words `agent' and `copy_from' in the examples above. Types are there to make configuration more robust.
These instructions assume that you have all of your configuration in a single test file, such as the example in the distriution directory tests/units.
cf-promises
. This require no privileges
an cf-agent
will not execute a configuration that has not passed this test.
host$ cf-promises -f ./inputfile.cf
host$ make host$ src/cf-promises -f ./tests/units/unit_server_copy_localhost.cf host$ src/cf-serverd -f ./tests/units/unit_server_copy_localhost.cf host$ src/cf-agent -f ./tests/units/unit_server_copy_localhost.cf