lib/bundles.cf

Table of Contents

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::
      "crontab" string => "/var/spool/cron/tabs";
    redhat|fedora::
      "crontab" string => "/var/spool/cron";
    freebsd::
      "crontab" string => "/var/cron/tabs";
    !(suse|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)");
}

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)'";
}