lib/bundles.cf
file bodies
control
Prototype: control
Description: Include policy files used by this policy file as part of inputs
Implementation:
body file control
{
      inputs => { @(bundles_common.inputs) };
}
common bodies
bundles_common
Prototype: bundles_common
Description: Enumerate policy files used by this policy file for inclusion to inputs
Implementation:
bundle common bundles_common
{
  vars:
      "inputs" slist => { "$(this.promise_dirname)/paths.cf",
                          "$(this.promise_dirname)/files.cf",
                          "$(this.promise_dirname)/commands.cf" };
}
agent bundles
cronjob
Prototype: cronjob(commands, user, hours, mins)
Description: Defines a cron job for user
Adds a line to crontab, if necessary.
Arguments:
- commands: The commands that should be run
- user: The owner of crontab
- hours: The hours at which the job should run
- mins: The minutes at which the job should run
Example:
methods:
 "cron" usebundle => cronjob("/bin/ls","mark","*","5,10");
Implementation:
bundle agent cronjob(commands,user,hours,mins)
{
  vars:
    suse|sles::
      "crontab" string => "/var/spool/cron/tabs";
    redhat|fedora::
      "crontab" string => "/var/spool/cron";
    freebsd::
      "crontab" string => "/var/cron/tabs";
    !(suse|sles|redhat|fedora|freebsd)::
      "crontab" string => "/var/spool/cron/crontabs";
    any::
      # We escape the user supplied values so that we can search to see if the
      # entry already exists with slightly different spacing.
      "e_mins" string => escape("$(mins)");
      "e_hours" string => escape("$(hours)");
      "e_commands" string => escape("$(commands)");
  classes:
    !windows::
      # We tolerate existing entries that differ only in whitespace and avoid
      # entering duplicate entries.
      "present_with_potentially_different_spacing"
        expression => regline( "^$(e_mins)\s+$(e_hours)\s+\*\s+\*\s+\*\s+$(e_commands)", "$(crontab)/$(user)");
  files:
    !windows.!present_with_potentially_different_spacing::
      "$(crontab)/$(user)"
      comment => "A user's regular batch jobs are added to this file",
      create => "true",
      edit_line => append_if_no_line("$(mins) $(hours) * * * $(commands)"),
      perms => mo("600","$(user)"),
      classes => if_repaired("changed_crontab");
  processes:
    changed_crontab::
      "cron"
      comment => "Most crons need to be huped after file changes",
      signals => { "hup" };
}
rm_rf
Prototype: rm_rf(name)
Description: recursively remove name to any depth, including base
Arguments:
- name: the file or directory name
This bundle will remove name to any depth, including name itself.
Example:
methods:
    "bye" usebundle => rm_rf("/var/tmp/oldstuff");
Implementation:
bundle agent rm_rf(name)
{
  methods:
      "rm" usebundle => rm_rf_depth($(name),"inf");
}
rm_rf_depth
Prototype: rm_rf_depth(name, depth)
Description: recursively remove name to depth depth, including base
Arguments:
- name: the file or directory name
- depth: how far to descend
This bundle will remove name to depth depth, including name itself.
Example:
methods:
    "bye" usebundle => rm_rf_depth("/var/tmp/oldstuff", "100");
Implementation:
bundle agent rm_rf_depth(name,depth)
{
  classes:
      "isdir" expression => isdir($(name));
  files:
    isdir::
      "$(name)"
      file_select => all,
      depth_search => recurse_with_base($(depth)),
      delete => tidy;
      "$(name)/."
        delete => tidy;
    !isdir::
      "$(name)" delete => tidy;
}
fileinfo
Prototype: fileinfo(f)
Description: provide access to file stat fields from the bundle caller and report file stat info for file "f" if "verbose_mode" class is defined
Arguments:
- f: file or files to stat
Example:
bundle agent example
{
  vars:
    "files" slist => { "/tmp/example1", "/tmp/example2" };
  files:
    "$(files)"
      create => "true",
      classes => if_ok("verbose_mode"),
      comment => "verbose_mode is defined because the fileinfo bundle restricts the report of the file info to verbose mode";
    "/tmp/example3"
      create => "true",
      classes => if_ok("verbose_mode"),
      comment => "verbose_mode is defined because the fileinfo bundle restricts the report of the file info to verbose mode";
  methods:
    "fileinfo" usebundle => fileinfo( @(files) );
    "fileinfo" usebundle => fileinfo( "/tmp/example3" );
  reports:
    "$(this.bundle): $(files): $(fileinfo.fields) = '$(fileinfo.stat[$(files)][$(fileinfo.fields)])'";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][size])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][gid])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][uid])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][ino])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][nlink])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][ctime])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][atime])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][mtime])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][mode])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][modeoct])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][permstr])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][permoct])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][type])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][devno])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][dev_minor])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][dev_major])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][basename])";
    "$(this.bundle): $(fileinfo.stat[/tmp/example3][dirname])";
}
Implementation:
bundle agent fileinfo(f)
{
  vars:
      "fields" slist => splitstring("size,gid,uid,ino,nlink,ctime,atime,mtime,mode,modeoct,permstr,permoct,type,devno,dev_minor,dev_major,basename,dirname,linktarget,linktarget_shallow", ",", 999);
      "stat[$(f)][$(fields)]" string => filestat($(f), $(fields));
  reports:
    verbose_mode::
      "$(this.bundle): file $(f) has $(fields) = $(stat[$(f)][$(fields)])";
}
logrotate
Prototype: logrotate(log_files, max_size, rotate_levels)
Description: rotate specified "log_files" larger than "max_size". Keep "rotate_levels" versions of the files before overwriting the oldest one
Arguments:
- log_files: single file or list of files to evaluate for rotation
- max_size: minimum size in bytes that the file will grow to before being rotated
- rotate_levels: number of rotations to keep before overwriting the oldest one
Example:
bundle agent example
{
  vars:
    "logdirs" slist => { "/var/log/syslog", "/var/log/maillog"};
  methods:
    "logrotate" usebundle => logrotate( @(logdirs), "1M", "2" );
    "logrotate" usebundle => logrotate( "/var/log/mylog, "1", "5" );
    "logrotate" usebundle => logrotate( "/var/log/alog, "500k", "7" );
}
Implementation:
bundle agent logrotate(log_files, max_size, rotate_levels)
{
  files:
      "$(log_files)"
      comment     => "Rotate file if above specified size",
      rename      => rotate("$(rotate_levels)"),
      file_select => bigger_than("$(max_size)"),
      if => fileexists( $(log_files) );
}
prunedir
Prototype: prunedir(dir, max_days)
Description: delete plain files inside "dir" older than "max_days" (not recursively).
Arguments:
- dir: directory to examine for files
- max_days: maximum number of days old a files mtime is allowed to before deletion
Example:
bundle agent example
{
  vars:
    "dirs" slist => { "/tmp/logs", "/tmp/logs2" };
  methods:
    "prunedir" usebundle => prunedir( @(dirs), "1" );
}
Implementation:
bundle agent prunedir(dir, max_days)
{
  files:
      "$(dir)"
      comment       => "Delete plain files inside directory older than max_days",
      delete        => tidy,
      file_select   => filetype_older_than("plain", "$(max_days)"),
      depth_search  => recurse("1");
}
prunetree
Prototype: prunetree(dir, depth, max_days)
Description: Delete files and directories inside "dir" up to "depth" older than "max_days".
Arguments:
- dir: directory to examine for files
- depth: How many levels to descend
- max_days: maximum number of days old a files mtime is allowed to before deletion
Example:
bundle agent example
{
  vars:
    "dirs" slist => { "/tmp/logs", "/tmp/logs2" };
  methods:
    "prunetree" usebundle => prunetree( @(dirs), inf, "1" );
}
Implementation:
bundle agent prunetree(dir, depth, max_days)
{
  files:
      "$(dir)"
      comment => "Delete files and directories under $(dir) up to $(depth)
                    depth older than $(max_days)",
      delete        => tidy,
      file_select   => days_old( $(max_days) ),
      depth_search  => recurse_with_base( $(depth) );
}
url_ping
Prototype: url_ping(host, method, port, uri)
Description: ping HOST:PORT/URI using METHOD
Arguments:
- host: the host name
- method: the HTTP method (HEAD or GET)
- port: the port number, e.g. 80
- uri: the URI, e.g. /path/to/resource
This bundle will send a simple HTTP request and read 20 bytes back,
then compare them to 200 OK.* (ignoring leading spaces).
If the data matches, the global class "url_ok_HOST" will be set, where
HOST is the canonified host name, i.e. canonify($(host))
Example:
methods:
    "check" usebundle => url_ping("cfengine.com", "HEAD", "80", "/bill/was/here");
reports:
  url_ok_cfengine_com::
    "CFEngine's web site is up";
  url_not_ok_cfengine_com::
    "CFEngine's web site *may* be down.  Or you're offline.";
Implementation:
bundle agent url_ping(host, method, port, uri)
{
  vars:
      "url_check" string => readtcp($(host),
                                    $(port),
                                    "$(method) $(uri) HTTP/1.1$(const.r)$(const.n)Host:$(host)$(const.r)$(const.n)$(const.r)$(const.n)",
                                    20);
      "chost" string => canonify($(host));
  classes:
      "url_ok_$(chost)"
      scope => "namespace",
      expression => regcmp("[^\n]*200 OK.*\n.*",
                           $(url_check));
      "url_not_ok_$(chost)"
      scope => "namespace",
      not => regcmp("[^\n]*200 OK.*\n.*",
                           $(url_check));
  reports:
    verbose_mode::
      "$(this.bundle): $(method) $(host):$(port)/$(uri) got 200 OK"
      ifvarclass => "url_ok_$(chost)";
      "$(this.bundle): $(method) $(host):$(port)/$(uri) did *not* get 200 OK"
      ifvarclass => "url_not_ok_$(chost)";
}
cmerge
Prototype: cmerge(name, varlist)
Description: bundle to merge many data containers into one
Arguments:
- name: the variable name to create
- varlist: a list of variable names (**MUST** be a list)
The result will be in cmerge.$(name).  You can also use
cmerge.$(name)_str for a string version of the merged containers.
The name is variable so you can call this bundle for more than one merge.
If you merge a key-value map into an array or vice versa, the map
always wins.  So this example will result in a key-value map even
though cmerge.$(name) starts as an array.
Example:
bundle agent run
{
  vars:
      # the "mymerge" tag is user-defined
      "a"       data  => parsejson('{ "mark": "b" }'), meta => { "mymerge" };
      "b"       data  => parsejson('{ "volker": "h" }'), meta => { "mymerge" };
      # you can list them explicitly: "default:run.a" through "default:run.d"
      "todo"    slist => variablesmatching(".*", "mymerge");
      # you can use cmerge.all_str instead of accessing the merged data directly
      "merged_str" string => format("%S", "cmerge.all");
  methods:
      "go" usebundle => cmerge("all", @(todo)); # merge a, b into container cmerge.all
  reports:
      "merged = $(cmerge.all_str)"; # will print a map with keys "mark" and "volker"
}
Implementation:
bundle agent cmerge(name, varlist)
{
  vars:
      "$(name)" data => parsejson('[]'),            policy => "free";
      "$(name)" data => mergedata($(name), $(varlist)), policy => "free"; # iterates!
      "$(name)_str" string => format("%S", $(name)),    policy => "free";
}
collect_vars
Prototype: collect_vars(name, tag, flatten)
Description: bundle to collect tagged variables into a data container
Arguments:
- name: the variable name to create inside- collect_vars
- tag: the tag regex string to match e.g. "beta,gamma=.*"
- flatten: to flatten variable values, set to "any" or "true" or "1"
The result will be a map in collect.$(name). You can also use
cmerge.$(name)_str for a string version of the merged containers
(if it fits in a CFEngine string).
The name is variable so you can call this bundle for more than one collection.
Every found variable will be a key in the map, unless you specify
flatten, in which case they'll be flattened into a top-level array
of data.
The flatten parameter can be "any" or "true" or "1" to be true.
Example:
body common control
{
      inputs => { "$(sys.libdir)/stdlib.cf" };
}
bundle agent main
{
  vars:
      # the "mymerge" tag is user-defined
      "a" data  => parsejson('{ "mark": "burgess" }'), meta => { "mymerge" };
      "b" data  => parsejson('{ "volker": "hilsheimer" }'), meta => { "mymerge" };
  methods:
      # merge a, b into container collect_vars.all
      "go" usebundle => collect_vars("all", "mymerge", "");
      # flatten a, b into container collect_vars.flattened
      "go_flatten" usebundle => collect_vars("flattened", "mymerge", "true");
  reports:
      # merged = {"default:main.a":{"mark":"burgess"},"default:main.b":{"volker":"hilsheimer"}}
      "merged = $(collect_vars.all_str)";
      # flattened = {"mark":"burgess","volker":"hilsheimer"}
      "flattened = $(collect_vars.flattened_str)";
}
Implementation:
bundle agent collect_vars(name, tag, flatten)
{
  classes:
      "flatten" expression => strcmp($(flatten), "any");
      "flatten" expression => strcmp($(flatten), "1");
      "flatten" expression => strcmp($(flatten), "true");
  vars:
      "todo_$(name)"    slist => variablesmatching(".*", $(tag));
    !flatten::
      "$(name)"
        data => parsejson('{}'),
        policy => "free";
      # this iterates!
      "$(name)"
        data => mergedata($(name), '{ "$(todo_$(name))": $(todo_$(name)) }'),
        policy => "free";
    flatten::
      "$(name)"
        data => parsejson('[]'),
        policy => "free";
      # this iterates!
      "$(name)"
        data => mergedata($(name), "$(todo_$(name))"),
        policy => "free";
    any::
      "$(name)_str"
        string => format("%S", $(name)),
        policy => "free";
}
run_ifdefined
Prototype: run_ifdefined(namespace, mybundle)
Description: bundle to maybe run another bundle dynamically
Arguments:
- namespace: the namespace, usually- $(this.namespace)
- mybundle: the bundle to maybe run
This bundle simply is a way to run another bundle only if it's defined.
Example:
bundle agent run
{
  methods:
      # does nothing if bundle "runthis" is not defined
      "go" usebundle => run_ifdefined($(this.namespace), runthis);
}
Implementation:
bundle agent run_ifdefined(namespace, mybundle)
{
  vars:
      "bundlesfound" slist => bundlesmatching("^$(namespace):$(mybundle)$");
      "count" int => length(bundlesfound);
  methods:
      "any"
      usebundle  => $(bundlesfound),
      ifvarclass => strcmp(1, $(count));
  reports:
    verbose_mode::
      "$(this.bundle): found matching bundles $(bundlesfound) for namespace '$(namespace)' and bundle '$(mybundle)'";
}