The right order in cfengine

NOTE: the code in this post has been revisited, be sure to read The right order in cfengine, revisited after this one.


I've been playing a bit with cfEngine recently, with different degrees of happiness and frustration. After reading the early release of the super-awesome O'Reilly's Learning Cfengine book, I decided to revise my experiments once again.

One thing I had trouble doing was to push out changes in a configuration file in the exact order I wanted them. Before reading the book it looked too odd and difficult; now it is still looks a bit odd, but doable. Let's see how it works. …Basically, cfengine runs three passes through the policies in a certain order (the so called natural ordering). If you want to impose a different order in which operations should take place, you set classes at different times of execution, so that a change done at the first pass will then trigger other changes in the following passes. I only had a doubt though: will a large set of individual, cascading changes, be able to converge in three passes?

To check that, I prepared a test policy:

body common control
{
  bundlesequence => { "test" } ;
  inputs         => { "cfengine_stdlib.cf" } ;
  version        => "Slow convergence";
}

body classes always(x)

# Define a class no matter what the outcome of the promise is

{
  promise_repaired => { "$(x)" };
  promise_kept => { "$(x)" };
  repair_failed => { "$(x)" };
  repair_denied => { "$(x)" };
  repair_timeout => { "$(x)" };
}

bundle agent test {
  vars:
      "index" slist => { "a", "b", "c", "d" } ;

  classes:
      "has_$(index)" expression => "trigger" ;

  files:
      "/tmp/t2.txt"
	comment => "This should trigger following changes",
	edit_defaults => empty,
	create => "true",
	edit_line => append_if_no_line("### header ###"),
	classes => always("trigger") ;

    trigger::
      "/tmp/t2.txt"
	comment => "This should be the second",
	edit_line => append_if_no_line("this should be the second"),
	classes => always("step_a") ;

    has_a.step_a::
      "/tmp/t2.txt"
	comment => "this should be third",
	edit_line => append_if_no_line("third"),
	classes => always("step_b") ;

    has_b.step_b::
      "/tmp/t2.txt"
	comment => "this should be fourth",
	edit_line => append_if_no_line("fourth"),
	classes => always("step_c") ;

    has_c.step_c::
      "/tmp/t2.txt"
	comment => "this should be fifth",
	edit_line => append_if_no_line("fifth"),
	classes => always("step_d") ;

    has_d.step_d::
      "/tmp/t2.txt"
	comment => "this should be the last",
	edit_line => append_if_no_line("last") ;
}

The first body in this policy will just dictate which bundle will be executed (the test bundle), and that the standard library should be loaded.

The second body defines an always() function that will return a class named after the variable $(x), no matter the result of the promise using it. So always("boo") will always set a global class called boo.

The meat, of course, comes in the test bundle.

In the vars: section I defined an array of four elements called index. Using index, I define (in the classes: section) one class for each of the array elements (they will be called "has_a", "has_b" and so on); each of this class will be equivalent to the "trigger" class which is currently undefined, so all these classes are "false" at creation time.

Then comes the files: section. The first change is unconditionally applied, while the others all depend on classes that, as of now, are undefined. So, only this first one will be applied now: starting from an empty file (see edit_defaults) it will add a header line and define the trigger class, which will also "activate" all has_* classes in the second pass (at this pass, they started as undefined and so they will stay).

Having the trigger class activated will immediately enable the second change, which will add a new line to the file, and define a class called "step_a". This will end the first pass as all the other promises would require "has_*" classes to be active, but they currently aren't.

When the second pass starts, it will see that the trigger class is now active, and will activate all of the has_* classes. Besides, the step_a class is also active from previous pass, so the third change is activated; in turn, this will define the step_b class, which will allow the fourth change to take place, and so on, up to the last one in a cascading fashion.

At the end of the second pass, all changes are applied, and no further change will take place at the third pass: voilà! The final version of the file /tmp/t2.txt is the following:

### header ###
this should be the second
third
fourth
fifth
last

As you can see, all the lines are there in the right order. I like that 🙂

Advertisements

2 thoughts on “The right order in cfengine

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s