Question

I have two different versions of linux/unix each running cfengine3. Is it possible to have one promises.cf file I can put on both machines that will copy different files based on what os is on the clients? I have been searching around the internet for a few hours now and have not found anything useful yet.

Was it helpful?

Solution

There are several ways of doing this. At the simplest, you can simply have different files: promises depending on the operating system, for example:

files:
  ubuntu_10::
    "/etc/hosts"
      copy_from => mycopy("$(repository)/etc.hosts.ubuntu_10");
  suse_9::
    "/etc/hosts"
      copy_from => mycopy("$(repository)/etc.hosts.suse_9");
  redhat_5::
    "/etc/hosts"
      copy_from => mycopy("$(repository)/etc.hosts.redhat_5");
  windows_7::
    "/etc/hosts"
      copy_from => mycopy("$(repository)/etc.hosts.windows_7");

This example can be easily simplified by realizing that the built-in CFEngine variable $(sys.flavor) contains the type and version of the operating system, so we could rewrite this example as follows:

"/etc/hosts"
    copy_from => mycopy("$(repository)/etc.$(sys.flavor)");

A more flexible way to achieve this task is known in CFEngine terminology as "hierarchical copy." In this pattern, you specify an arbitrary list of variables by which you want files to be differentiated, and the order in which they should be considered, from most specific to most general. When the copy promise is executed, the most-specific file found will be copied.

This pattern is very simple to implement:

# Use single copy for all files
body agent control
{
   files_single_copy => { ".*" };
}

bundle agent test
{
vars:
  "suffixes"   slist => { ".$(sys.fqhost)", ".$(sys.uqhost)", ".$(sys.domain)",
                          ".$(sys.flavor)", ".$(sys.ostype)", "" };
files:
  "/etc/hosts"
    copy_from => local_dcp("$(repository)/etc/hosts$(suffixes)");
}

As you can see, we are defining a list variable called $(suffixes) that contains the criteria by which we want to differentiate the files. All the variables contained in this list are automatically defined by CFEngine, although you could use any arbitrary CFEngine variables. Then we simply include that variable, as a scalar, in our copy_from parameter. Because CFEngine does automatic list expansion, it will try each variable in turn, executing the copy promise multiple times (one for each value in the list) and copy the first file that exists. For example, for a Linux SuSE 11 machine called superman.justiceleague.com, the @(suffixes) variable will contain the following values:

{ ".superman.justiceleague.com", ".superman", ".justiceleague.com", ".suse_11",
  ".linux", "" }

When the file-copy promise is executed, implicit looping will cause these strings to be appended in sequence to "$(repository)/etc/hosts", so the following filenames will be attempted in sequence: hosts.superman.justiceleague.com, hosts.justiceleague.com, hosts.suse_11, hosts.linux and hosts. The first one to exist will be copied over /etc/hosts in the client, and the rest will be skipped.

For this technique to work, we have to enable "single copy" on all the files you want to process. This is a configuration parameter that tells CFEngine to copy each file at most once, ignoring successive copy operations for the same destination file. The files_single_copy parameter in the agent control body specifies a list of regular expressions to match filenames to which single-copy should apply. By setting it to ".*" we match all filenames.

For hosts that don't match any of the existing files, the last item on the list (an empty string) will cause the generic hosts file to be copied. Note that the dot for each of the filenames is included in $(suffixes), except for the last element.

I hope this helps.

(p.s. and shameless plug: this is taken from my upcoming book, "Learning CFEngine 3", published by O'Reilly)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top