CFengine 3 Tutorial Part 5 Client promises.cf and regular cf-agent operation

Finally, the moment we’ve been waiting for. Lets take a crack at promises.cf and what we have defined.


 $ cat -n /var/cfengine/inputs/promises.cf 
     1  # promises.cf -- clients only
     2  
     3  body common control
     4  {
     5          bundlesequence  => { 
     6                          "update",
     7                          "garbage_collection",
     8                          "smf_update",
     9                          "verify_root",
    10                          "general_global_configs",
    11                          };
    12  
    13          inputs          => {
    14                          "update.cf",
    15                          "cfengine_stdlib.cf",
    16                          "cf-execd.cf",
    17                          "smf_update.cf",
    18                          "verify_root.cf",
    19                          "general_global_configs.cf",
    20                          };
    21  }
    22  
    23  #######################################################
    24  
    25  bundle common g
    26  {
    27  # Define some global variables
    28  vars:
    29          "masterfiles" string => "/var/cfengine/masterfiles";
    30          "inputs" string => "/var/cfengine/inputs";
    31          "workdir" string => "/var/cfengine";
    32          "phost" string => "192.168.1.10";
    33  
    34          # Define global or local zones
    35          "solaris_zone_type"     expression      =>      usemodule("init_zone-type","");
    36  }
    37  
    38  #######################################################
    39  
    40  body agent control
    41  {
    42  # if default runtime is 5 mins we need this for long jobs
    43  ifelapsed => "15";
    44  
    45  # Allow us to use DNS
    46  skipidentify => "false";
    47  }
    48  
    49  #######################################################
    50  
    51  body monitor control
    52  {
    53  forgetrate => "0.7";
    54  histograms => "true";
    55  monitorfacility => "LOG_DAEMON";
    56  }
    57  
    58  #######################################################
    59  
    60  body reporter control
    61  
    62  {
    63  reports => { "all" };
    64  build_directory => "$(sys.workdir)/reports";
    65  report_output => "text";
    66  time_stamps => "true";
    67  }
    68  
    69  #######################################################

* Line 3 defines “body common control” — our main() function which drives all other executions of the policy.
* Lines 13-20 pull in additional CFEngine Policy files for execution.
* Lines 5-11 define the bundlesequence — the order of which functions are executed. These functions are “included” into this policy by pulling in the CFengine policy files in lines 13-20.

Note, the first action cf-agent will execute in “normal operation” is to update itself from the master policy server.

* Line 15 imports the Standard CFengine library. Where ever you installed CFEngine during the compilation process, you can find this file. (if you didn’t use the –prefix of /var/cfengine, its probably in /usr/local). This library contains some pre-written functions to do a TON of different operations. If you want to add more functions, this might be a good place to place them. Once this library is imported, you can call any of their functions from policies defined later in promises.cf


$ find /var/cfengine -name cfengine_stdlib.cf
/var/cfengine/share/doc/cfengine/cfengine_stdlib.cf

And some examples of what these functions can do...

$ egrep 'bundle|body' /var/cfengine/inputs/cfengine_stdlib.cf 
bundle edit_line comment_lines_matching(regex,comment)
bundle edit_line uncomment_lines_matching(regex,comment)
bundle edit_line delete_lines_matching(regex)
bundle edit_line append_if_no_line(str)
bundle edit_line append_if_no_lines(list)
bundle edit_line resolvconf(search,list)
bundle edit_line set_variable_values(v)
bundle edit_line append_users_starting(v)
bundle edit_line append_groups_starting(v)
bundle edit_line set_user_field(user,field,val)
bundle edit_line append_user_field(group,field,allusers)
bundle edit_line expand_template(templatefile)
body edit_field quoted_var(newval,method)
body edit_field col(split,col,newval,method)
body replace_with value(x)
body select_region INI_section(x)
body edit_defaults std_defs
body edit_defaults empty
body location start
body replace_with comment(c)
body replace_with uncomment
body action if_elapsed(x)
body action measure_performance(x)
body action warn_only
body action bg(elapsed,expire)
body contain silent
body contain in_dir(s)
body contain silent_in_dir(s)
body contain in_shell
body contain setuid(x)
body contain setuid_sh(x)
body contain jail(owner,root,dir)
body classes if_desired(a)
body classes if_repaired(x)
body classes if_else(yes,no)
body classes if_notkept(x)
body classes if_ok(x)
body copy_from secure_cp(from,server)
body copy_from remote_cp(from,server)
body copy_from local_cp(from)
body copy_from no_backup_cp(from)
body copy_from no_backup_rcp(from,server)
body link_from ln_s(x)
body link_from linkchildren(tofile)
body perms m(mode)
body perms mo(mode,user)
body perms mog(mode,user,group)
body perms og(u,g) 
body perms owner(user)
body depth_search recurse(d)
body depth_search recurse_ignore(d,list)
body delete tidy
body rename disable
body rename rotate(level)
body rename to(file)
body file_select name_age(name,age)
body file_select days_old(days)
body file_select size_range(from,to)
body file_select exclude(name)
body file_select plain
body file_select dirs
body file_select ex_list(names)
body changes detect_all_change
body changes detect_content
body package_method zypper
body package_method apt
body package_method yum
body package_method solaris (pkgname, spoolfile, adminfile)
body package_method freebsd
body volume min_free_space(free)
body mount nfs(server,source)
body mount nfs_p(server,source,perm)
body mount unmount
body select_process exclude_procs(x)
body process_count check_range(name,lower,upper)

By importing this library, a lot of the complexity of lower-level policy writing is taken care of for you. You can also view a web-based version of what these policies contain here.

Looking back at promises.cf, on lines 34-35 we define a custom class using a module. Remember from before, when cf-agent executes in verbose mode, we can see which classes that were discovered. Using basic shell scripts, we can extend this to define whatever classes we want to. Lets take a look at the module we’ve defined.


$ cat /var/cfengine/modules/init_zone-type 
#!/bin/bash

PATH=/usr/bin:/usr/sbin

TYPE=`zoneadm list -cp | cut -d: -f2`

if [ "$TYPE" == 'global' ]
then
        echo '+global_zone'
else
        echo '+local_zone'
fi

So this is super easy. We can execute whatever shell commands we want and use basic logic to determine if we’ve matched that class. If so, echo “+[class_name]” and cf-agent will pick it up. So, if you wanted to create a class for machines with Python installed, specific running processes, etc… The possibilities here are endless. Use modules to define classes of systems. Don’t modify system state — just collect data and make decisions about what type of classes a machine should belong to. Use CFEngine policies to execute changes.

Finally, the last interesting part about promises.cf here is on line 16. We define a separate file for cf-execd. Using Neil Watson’s example tutorial, he split up configuration settings for the separate daemons into their own file. It makes sense, so I followed his example here. Remember, cf-execd actually drives the execution of cf-agent, so lets take a look at this file.


$ cat -n /var/cfengine/inputs/cf-execd.cf 
     1  body executor control 
     2  {
     3          # Splaytime is a critical varible that determines the "back off" time in minutes for cf-agent
     4          # to check in with cf-serverd.  Setting this to a higher value like "10" will allow thousands
     5          # of clients to pull updates from a single policy server without hammering the network too bad all
     6          # at once.
     7          splaytime               =>      "10";
     8          mailto                  =>      "fatkitty@sinatra.com";
     9          mailfrom                =>      "root@$(sys.host)";
    10          smtpserver              =>      "localhost";
    11          schedule                =>       { "Min00_Min10",  "Min15", "Min20", "Min25", "Hr07.Min30", "Min35", "Min40", "Min45", "Min50", "Min55" };
    12          executorfacility        =>      "LOG_DAEMON";
    13  
    14          # This is the command that actually drives cf-execd to execute cf-agent on the schedule above.
    15          exec_command            =>      "${sys.workdir}/bin/cf-agent -f failsafe.cf && ${sys.workdir}/bin/cf-agent";
    16  }
    17  ##########################################
    18  bundle agent garbage_collection
    19  {
    20  files:
    21  
    22    "$(sys.workdir)/outputs" 
    23  
    24      delete => tidy,
    25      file_select => days_old("3"),
    26      depth_search => recurse("inf");
    27  }

* Line 7 is critical. This is the tunable of how often / how hard cf-agent is going to pound cf-serverd. The clients take the value of splaytime and using a hash algorithm with their hostname, will check in randomly over this given time frame.
* Line 8 is where we want to mail the output of reports: type promises to.
* Line 9 uses an internal variable $(sys.host) to substitute the host name of the box.
* Line 11 is critical. By default, cf-execd will fire cf-agent off every 5 minutes. It doesn’t have to be this way. If you only want cf-agent to execute once an hour, then define the time classes here. Use periods to “combine” classes together. For example, Hr07.Min30 will only execute at 07:30. The example Min00_Min10 will execute throughout that entire span between the two values. You get the idea.
* Line 15 is critical. This is the command cf-execd will execute based upon its schedule. It executes cf-agent against failsafe.cf so configs are updated from the master policy servers — then it executes in “normal mode” against promises.cf.
** With no policy defined as in the 2nd operation on Line 15, promises.cf is assumed to be the default action.

That’s it in a nutshell! We’ve traced through normal client operations and how to construct general CFengine policies. To continue to add functionality into what cf-agent will execute upon, just include more *.cf files on the import and bundlesequence statemetns!

CFengine is an extremely powerful tool for controlling the configuration of hundreds / thousands / tens of thousands of machines. Its used at enterprises like Facebook to drive system management. ALWAYS test changes in a dev/test environment BEFORE pushing new policies into production. With a powerful tool, you can damage machines easily!

1 thought on “CFengine 3 Tutorial Part 5 Client promises.cf and regular cf-agent operation”

  1. Nice. That little trick on lines 34-35 (wherein you add a class) is pretty handy and elegant. IMHO, this series isn’t “complete.” I’m glad that it’s not. I was able to glean some information from it without spending much time muddling through the background info (e.g., the official documentation/tutorial/etc). Cheers.

Leave a Reply

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