CFengine 3 Tutorial — Part 3 — Hello World

So up to this point, we’ve had a high level 10,000ft introduction to how CFEngine works. Hopefully we’ve gotten the needed bits built and packaged up to bootstrap our infrastructure. As any other programming language begins, lets look at the most basic “Hello World” type policy.

/var/tmp $ cat /var/tmp/
     1  body common control
     2  {
     3          bundlesequence  =>      { "hello_prefetch_net_friends" };
     4  }
     5  bundle agent hello_prefetch_net_friends
     6  {
     7  reports:
     8          cfengine_3::
     9                  "Hello World";
    11          sunos_5_10::
    12                  "I am a Solaris 10 host";
    13  }

Great. Lets execute. Use the -f flag to point cf-agent at the Policy we want to execute against. If we don’t supply the -f, then cf-agent assumes the policy file resides in /var/cfengine/inputs. Since we created this example in /var/tmp, we need to direct cf-agent to execute the policy using the absolute path.

/var/tmp $ /var/cfengine/bin/cf-agent -f /var/tmp/   
R: Hello World
R: I am a Solaris 10 host
/var/tmp $ /var/cfengine/bin/cf-agent -f /var/tmp/ 
/var/tmp $
/var/tmp $ /var/cfengine/bin/cf-agent -f /var/tmp/ -K
R: Hello World
R: I am a Solaris 10 host

Excellent! Lets break this down line-by-line on the sample policy above.

* Lines 1-4 define “body common control”. Think of this as the “main” function in Python / C / Java / etc.. This function is the “driver” that calls all other operations to occur, and which order to execute in. Every CFEngine policy needs a “body common control” statement. Here, we define bundesequence. This is a list of “functions” to execute in order.
* LIne 5 defines the “bundle agent hello_prefetch_net_friends” stanza. Bundle agent is the most commonly used type of CFEngine policy stanza. You can define files to be operated upon, commands to be executed, reports generated, etc. Check out the reference manual here for more of what bundle agent can do.
* LIne 7 defines the “type” of promise we are defining here — a “reports:” promise. Reports is a type of promise that will print some output to stdout or can send an email to an administrator with actions executed.
* Line 8 defines the “class” in the promise that will be affected. Here, we state that we only want to execute the actions if the host is in the “cfengine_3” class. If we belong to the “cfengine_3” class, then we print hello world.
* Line 11 defines a block of actions to execute upon if we match the “sunos_5_10” class. If we do, lets print that we are a Solaris 10 host.

So, this example forms the backbone of how CFengine policies are interpreted and executed. We defined a single promise in our bundlesequence, and in that bundlesequence we defined that we wanted the “type” to be a reports action — based upon the class of machine that cf-agent discovered. Nice! But wait.. When we executed the same command again immediately after, nothing was printed to stdout. Why?

By default, cf-agent will only execute a policy at minimum once a minute. When we specify the -K flag on the command line at the 3rd execution, the -K instructs cf-agent to ignore this “one minute rule.”

$ /var/cfengine/bin/cf-agent --help | grep '\-K'
--no-lock     , -K       - Ignore locking constraints during execution (ifelapsed/expireafter) if "too soon" to run

Nice! I want to see all the gory details of what cf-agent actually does. Show me the money! Throw in the -v flag for cf-agent to execute verbosely.

/var/cfengine/bin/cf-agent -f /var/tmp/ -v
     1  cf3 Cfengine - autonomous configuration engine - commence self-diagnostic prelude
     2  cf3 ------------------------------------------------------------------------
     3  cf3 Work directory is /var/cfengine
     4  cf3 Making sure that locks are private...
     5  cf3 Checking integrity of the state database
     6  cf3 Checking integrity of the module directory
     7  cf3 Checking integrity of the PKI directory
     8  cf3 Looking for a source of entropy in /var/cfengine/randseed
     9  cf3 Loaded /var/cfengine/ppkeys/localhost.priv
    10  cf3 Loaded /var/cfengine/ppkeys/
    11  cf3 No registered cfengine service, using default
    12  cf3  !!! System error for getservbyname: "Error 0"
    13  cf3 Setting cfengine default port to 5308 = 5308
    14  cf3 Reference time set to Fri Jul  2 11:15:21 2010
    15  cf3 Cfengine - 3.0.4 (C) Cfengine AS 2008-
    16  cf3 ------------------------------------------------------------------------
    17  cf3 Host name is: sinatra
    18  cf3 Operating System Type is sunos
    19  cf3 Operating System Release is 5.10
    20  cf3 Architecture = i86pc
    21  cf3 Using internal soft-class solarisx86 for host sinatra
    22  cf3 The time is now Fri Jul  2 11:15:21 2010
    23  cf3 ------------------------------------------------------------------------
    24  cf3 # Extended system discovery is only available in version Nova and above
    25  cf3 Additional hard class defined as: 32_bit
    26  cf3 Additional hard class defined as: sunos_5_10
    27  cf3 Additional hard class defined as: sunos_i86pc
    28  cf3 Additional hard class defined as: sunos_i86pc_5_10
    29  cf3 Additional hard class defined as: i386
    30  cf3 Additional hard class defined as: i86pc
    31  cf3 GNU autoconf class from compile time: compiled_on_solaris2_10
    32  cf3 Address given by nameserver:
    33  cf3 Adding alias loghost..
    34  cf3 Trying to locate my IPv6 address
    35  cf3 Looking for environment from cf-monitor...
    36  cf3 Loading environment...
    37  cf3 Environment data loaded
    38  cf3 ***********************************************************
    39  cf3  Loading persistent classes
    40  cf3 ***********************************************************
    41  cf3 ***********************************************************
    42  cf3  Loaded persistent memory
    43  cf3 ***********************************************************
    44  cf3  > Verifying the syntax of the inputs...
    45  cf3   > Parsing file /var/tmp/
    46  cf3 Initiate variable convergence...
    47  cf3 Initiate control variable convergence...
    48  cf3 Initiate variable convergence...
    49  cf3 # Knowledge map reporting feature is only available in version Nova and above
    50  cf3  -> Defined classes = { 172_18_33_58 32_bit Day2 Friday GMT_Hr18 Hr11 Hr11_Q2 July Lcycle_0 Min15 Min15_20 Morning Q2 Yr2010 agent any cfengine_3 cfengine_3_0 cfengine_3_0_4 community_edition compiled_on_solaris2_10 corp diskfree_low_dev1 entropy_cfengine_in_low entropy_dns_in_low entropy_dns_out_low entropy_ftp_in_low entropy_ftp_out_low entropy_icmp_in_low entropy_icmp_out_low entropy_irc_in_low entropy_irc_out_low entropy_misc_in_low entropy_misc_out_low entropy_netbiosdgm_in_low entropy_netbiosdgm_out_low entropy_netbiosns_in_low entropy_netbiosns_out_low entropy_netbiosssn_in_low entropy_netbiosssn_out_low entropy_nfsd_in_low entropy_nfsd_out_low entropy_smtp_in_low entropy_smtp_out_low entropy_ssh_out_low entropy_tcpack_in_low entropy_tcpack_out_low entropy_tcpfin_in_low entropy_tcpfin_out_low entropy_tcpsyn_in_low entropy_tcpsyn_out_low entropy_udp_in_low entropy_udp_out_low entropy_www_in_low entropy_www_out_low entropy_wwws_in_low entropy_wwws_out_low sinatra sinatra i386 i86pc ipv4_172 ipv4_172_18 ipv4_172_18_33 ipv4_172_18_33_58 loghost net_iface_e1000g833000_2 net_iface_lo0_2 rootprocs_high_normal solarisx86 sunos_5_10 sunos_i86pc sunos_i86pc_5_10 sunos_i86pc_5_10_Generic_127128_11 verbose_mode }
    51  cf3  -> Negated Classes = { }
    52  cf3 Initiate variable convergence...
    53  cf3 Initiate control variable convergence...
    54  cf3  -> Immunizing against parental death
    55  cf3 -> Bundlesequence =>  {'hello_prefetch_net_friends'}
    56  cf3 
    57  cf3 *****************************************************************
    58  cf3 BUNDLE hello_prefetch_net_friends
    59  cf3 *****************************************************************
    60  cf3 
    61  cf3 
    62  cf3      +  Private classes augmented:
    63  cf3 
    64  cf3      -  Private classes diminished:
    65  cf3 
    66  cf3 
    67  cf3 
    68  cf3    =========================================================
    69  cf3    reports in bundle hello_prefetch_net_friends (1)
    70  cf3    =========================================================
    71  cf3 
    72  cf3 Verifying SQL table promises is only available with Cfengine Nova or above
    73  cf3  XX Nothing promised here [lock.hello_prefetch_net_friend] (0/1 minutes elapsed)
    74  cf3  XX Nothing promised here [lock.hello_prefetch_net_friend] (0/1 minutes elapsed)
    75  cf3 
    76  cf3      +  Private classes augmented:
    77  cf3 
    78  cf3      -  Private classes diminished:
    79  cf3 
    80  cf3 
    81  cf3 
    82  cf3    =========================================================
    83  cf3    reports in bundle hello_prefetch_net_friends (2)
    84  cf3    =========================================================
    85  cf3 
    86  cf3 Verifying SQL table promises is only available with Cfengine Nova or above
    87  cf3 
    88  cf3      +  Private classes augmented:
    89  cf3 
    90  cf3      -  Private classes diminished:
    91  cf3 
    92  cf3 
    93  cf3 
    94  cf3    =========================================================
    95  cf3    reports in bundle hello_prefetch_net_friends (3)
    96  cf3    =========================================================
    97  cf3 
    98  cf3 Verifying SQL table promises is only available with Cfengine Nova or above
    99  cf3 Outcome of version (not specified) (agent-0): Promises observed to be kept 100%, Promises repaired 0%, Promises not repaired 0%
   100  cf3 Estimated system complexity as touched objects = 0, for 2 promises

Lets break this down line-by-line on what cf-agent is telling us that happened.

* Lines 1-15 are some basic bootstrap operations for cf-agent to come online. Lines 8-10 contain encryption stuff. More on that later.
* Lines 17-23 are some basic facts that cf-agent discovered about the host it was executed on.
* Line 50 is the real meat of what we want to analyse here. It defines all of the automatically discovered classes that cf-agent found. From these classes, we can use these to build complex policies that will only execute on specific hosts. In this list we see:
** Time of day (CFengine can execute in a cron-like fashion where things will only execute at specific times of day / etc..)
** Subnet / VLAN the machine resides on
** Type of O/S the machine is running
** Hardware platform (x86 or sparc?)
** The kernel revision of Solaris 10 we’re currently on
** Version of CFengine the agent is..
* Lines 51-98 is the execution of our bundle hello_prefetch_net_friend.
* Lines 99 and 100 tell us how many promises were able to be executed, how many could not be executed, how complex the operation was, etc.

By default, if we execute cf-agent, it will only report back to us the actions it failed to take / problems that it encountered. This may not be the best of examples, but the -I flag (–inform) will instruct cf-agent to report back to us the “good” things it did. It will “inform” us of changes made, commands executed, config files modified, etc. So a common invocation of cf-agent from the CLI when testing newly written policies is:

$ /var/cfengine/bin/cf-agent -f [absolute path to policy file] -I -K

Again, -I will inform us of “successfully executed” actions and -K will allow the policy to execute even if its been less than a minute from the previous run.

2 thoughts on “CFengine 3 Tutorial — Part 3 — Hello World”

Leave a Reply

Your email address will not be published. Required fields are marked *