CFEngine 3 is easier than you've heard

June 4, 2010

Psyching yourself up for an upgrade to CFEngine 3? You’ve probably convinced yourself that it is harder than it is. CFEngine allows you to decide between high and low level approaches. High level often means simplistic or inefficient, but low level can be overwhelming – there is a balance to be struck. In this article, we start from the bottom up and list some of the most basic low level idioms you’ll have used before in CFEngine 2, to show you how they can look in CFEngine 3. These are basic capabilities from which all high level approaches can be built, without the need for user-scripting. Then, using CFEngine 3’s “ACL paradigm” you will be able to scale these easily for most needs…

1, 2, 3, Nova, Constellation!

If you already have a large CFEngine 2 installation, you might be a little afraid of upgrading to CFEngine 3, but take it from those who already did – CFEngine 3 is simpler and more powerful than its predecessors. Don’t focus on the differences, focus on the similarities and you’ll be surprised how quickly it makes sense. The differences needn’t be exaggerated, because CFEngine always emphasizes principles – and the principles are the same.

Why upgrade at all if you are using CFEngine 2? Because CFEngine 3 supports:

  • Sophisticated pattern matching and iteration - to scale better.
  • Knowledge modelling - to make sure that the intentions are clear, and offer reporting.
  • Mapping into a Graphical User Interface - for fancy commercial upgrade paths.

Moreover, CFEngine 3 has a structure that is fully extensible for the future. So, how do others upgrade? Here are two approaches:

  • Low level: You can of course seek help from the CFEngine team to convert your old configuration into the new language (minus the new features and optimizations unique to CFEngine 3).
  • High level: Use the cloud? You could set up a green-field project using the Cloud as your testbed, and start with the Orion Cloud Pack to get a service oriented overview, which can then easily be transferred back to private hardware.

CFEngine Nova and Constellation customers have automatic access to the conversion tools.

So let’s get to it! Here’s how to achieve a few simple low level system repairs. Just put the following examples into the space provided, and this will be the skeleton for your configuration. The list is far from exhaustive, but it gives you some ideas.

body common control  {  bundlesequence => { "main" };  inputs => { "cfengine_stdlib.cf" };  }    bundle agent main  {  # insert examples here  }

These “boiler plate” parts of the code are there to provide new extensibility and to support the knowledge management that is new in CFEngine. We won’t mention those features here.

CFEngine now makes extensive use of a standard community library of “body parts”, or changable libraries of special words. Every blue word below can be changed by you to make the code easier to read and to hide decisions. The red words are part of the fixed syntax of CFEngine. The “rules” called “promises” have the generic form:

     promise_type:         "affected object"          detail/attribute => value/template;

Creating file objects

files:      "/home/mark/tmp/test_plain"     comment => "Promise that a plain file exists with stated permissions",       perms => mog("644", "root", "wheel"),       create => "true";      "/home/mark/tmp/test_dir/."       comment => "Make sure a directory exists",       perms => mog("o=rx", "root", "wheel"),       create => "true";

Notice a new feature in CFEngine 3: comments. Comments that you add in code are not just thrown away, but appear in log messages and reports to annotate the intention behind the promise. Why are we doing this? This is part of Knowledge Management.

Copying and synchronising files

files:      "/home/mark/tmp/test_plain"     comment => "Copy a plain file",      copy_from => secure_cp("/tmp/file","serverhost");     "/home/mark/tmp/test_dir"     comment => "Copy a directory and all of its contents",      copy_from => secure_cp("/tmp","serverhost"),   depth_search => recurse("inf");

In this last example, you could easily redefine a new word to write even more simply:

 "/home/mark/tmp/test_dir"      copy_from => secure_cp("/tmp","serverhost"),   depth_search => inf;

You have great freedom to make the code readable – and this is encouraged. Careful choice of terminology is good Knowledge Management.

Filling out a template file

files:      "/etc/special.conf"         comment => "Fill out a simple form containing variables",       edit_line => expand_template("/src/template.in"),   edit_defaults => empty;

If the template is small, you can also simply include in-line in the configuration.

Adding and promising user attributes

What about more high level issues? This doesn’t have to be any harder. Managing users is notoriously tricky, but not with CFEngine’s editing capabilities. These examples don’t just add users, they verify and repair them to make sure the promises are kept. Ensuring users are properly configured:

files:      "/home/mark/tmp/passwd"         comment => "Prevent this user from logging in",       edit_line => set_user_field("badwolf","7","/bin/false"); # Field 7 in /etc/passwd is the shell

Or groups:

vars:   "userset" slist => { "user1", "user2", "user3" };  files:    "/home/mark/tmp/group"         comment => "Make sure the listed users in this group",       edit_line => append_user_field("root","4","@(bundle.userset)");

Integrity “tripwires” - change detection

files:      "/home/mark/tmp" -> "me"         changes      => detect_all_change,         depth_search => recurse("inf"),         action       => background;

Embed commands safely, with sandboxes and scheduling

commands:      "/usr/bin/update_db"         action => ifelapsed("480"); # Every 8 hours      "/etc/mysql/start"        contain => setuid("mysql");

Process management

processes:      "snmpd"       signals => { "term", "kill" };

Partition management

storage:     "/home/mark/server_home"     mount => nfs("myserver","/home/mark");

Package management

vars:    "allpacks" slist => { "apache2", "texinfo", "php5", "mplayer" };    packages:    "$(allpacks)"       package_policy => "add",       package_method => generic;

Abstraction, abstraction, abstraction…

Critics sometimes claim that CFEngine is only about low level configuration, and that there is no abstraction capability. This is wrong of course, but that is the subject of a different article. (For a taste, see here.) You can make CFEngine as high level as you like – but watch out, as poor choices can lead to non-scalable strategy, or over simplification.

Just a taste

This is just a taste of what you’ll find in the CFEngine 3 family. Most configurations can be upgraded automatically, without the newer features, of course. We haven’t even begun to mention the benefits of using CFEngine Nova, and its graphical knowledge features.

The examples here look simple, because they are quite `ordinary’. Simplicity can be deceptive, when it is over-simplification. The true power of CFEngine if felt when you have truly special needs. That is when most systems let you down, because they are not generic, so you have to resort to shell commands or programming. The CFEngine language has many more tricks up its sleeves for the difficult cases, and you can always be sure that the final result will be predictable and constant no matter how many times you run CFEngine.