Common Bodies and Bundles

Table of Contents

See the common promise attributes documentation for a comprehensive reference on the body types and attributes used here.

To use these bodies, add the following to your policy:

body file control
{
    inputs => { "common.cf", "bundles.cf" }
}

action bodies

if_elapsed

Prototype: if_elapsed(x)

Description: Evaluate the promise every x minutes

Arguments:

  • x: The time in minutes between promise evaluations

Implementation:

body action if_elapsed(x)
{
      ifelapsed => "$(x)";
      expireafter => "$(x)";
}

if_elapsed_day

Prototype: if_elapsed_day

Description: Evalute the promise once every 24 hours

Implementation:

body action if_elapsed_day
{
      ifelapsed => "1440";    # 60 x 24
      expireafter => "1400";
}

measure_performance

Prototype: measure_performance(x)

Description: Measure repairs of the promiser every x minutes

Repair-attempts are cancelled after x minutes.

Arguments:

  • x: The time in minutes between promise evaluations.

Implementation:

body action measure_performance(x)
{
      measurement_class => "Detect changes in $(this.promiser)";
      ifelapsed => "$(x)";
      expireafter => "$(x)";
}

measure_promise_time

Prototype: measure_promise_time(identifier)

Description: Performance will be measured and recorded under identifier

Arguments:

  • identifier: Measurement name.

Implementation:

body action measure_promise_time(identifier)
{
      measurement_class => "$(identifier)";
}

warn_only

Prototype: warn_only

Description: Warn once an hour if the promise needs to be repaired

The promise does not get repaired.

Implementation:

body action warn_only
{
      action_policy => "warn";
      ifelapsed => "60";
}

bg

Prototype: bg(elapsed, expire)

Description: Evaluate the promise in the background every elapsed minutes, for at most expire minutes

Arguments:

  • elapsed: The time in minutes between promise evaluations
  • expire: The time in minutes after which a repair-attempt gets cancelled

Implementation:

body action bg(elapsed,expire)
{
      ifelapsed   => "$(elapsed)";
      expireafter => "$(expire)";
      background  => "true";
}

ifwin_bg

Prototype: ifwin_bg

Description: Evaluate the promise in the background when running on Windows

Implementation:

body action ifwin_bg
{
    windows::
      background => "true";
}

immediate

Prototype: immediate

Description: Evaluate the promise at every cf-agent execution.

Implementation:

body action immediate
{
      ifelapsed => "0";
}

policy

Prototype: policy(p)

Description: Set the action_policy to p

Arguments:

  • p: The action policy

Implementation:

body action policy(p)
{
      action_policy => "$(p)";
}

log_repaired

Prototype: log_repaired(log, message)

Description: Log message to a file log=[/file|stdout]

Arguments:

  • log: The log file for repaired messages
  • message: The log message

Implementation:

body action log_repaired(log,message)
{
      log_string => "$(sys.date), $(message)";
      log_repaired => "$(log)";
}

log_verbose

Prototype: log_verbose

Description: Sets the log_level attribute to "verbose"

Implementation:

body action log_verbose
{
      log_level => "verbose";
}

sample_rate

Prototype: sample_rate(x)

Description: Evaluate the promise every x minutes, A repair-attempt is cancelled after 10 minutes

Arguments:

  • x: The time in minutes between promise evaluation

Implementation:

body action sample_rate(x)
{
      ifelapsed => "$(x)";
      expireafter => "10";
}

classes bodies

if_repaired

Prototype: if_repaired(x)

Description: Define class x if the promise has been repaired

Arguments:

  • x: The name of the class

Implementation:

body classes if_repaired(x)
{
      promise_repaired => { "$(x)" };
}

if_else

Prototype: if_else(yes, no)

Description: Define the classes yes or no depending on promise outcome

Arguments:

  • yes: The name of the class that should be defined if the promise is kept or repaired
  • no: The name of the class that should be defined if the promise could not be repaired

Implementation:

body classes if_else(yes,no)
{
      promise_kept     => { "$(yes)" };
      promise_repaired => { "$(yes)" };
      repair_failed    => { "$(no)" };
      repair_denied    => { "$(no)" };
      repair_timeout   => { "$(no)" };
}

cf2_if_else

Prototype: cf2_if_else(yes, no)

Description: Define the classes yes or no, depending on promise outcome

A version of if_else that matches CFEngine2 semantics. Neither class is set if the promise does not require any repair.

Arguments:

  • yes: The name of the class that should be defined if the promise is repaired
  • no: The name of the class that should be defind if teh promise could not be repaired

Implementation:

body classes cf2_if_else(yes,no)
{
      promise_repaired => { "$(yes)" };
      repair_failed    => { "$(no)" };
      repair_denied    => { "$(no)" };
      repair_timeout   => { "$(no)" };
}

if_notkept

Prototype: if_notkept(x)

Description: Define the class x if the promise is not kept and cannot be repaired.

Arguments:

  • x: The name of the class that should be defined

Implementation:

body classes if_notkept(x)
{
      repair_failed   => { "$(x)" };
      repair_denied   => { "$(x)" };
      repair_timeout  => { "$(x)" };
}

if_ok

Prototype: if_ok(x)

Description: Define the class x if the promise is kept or could be repaired

Arguments:

  • x: The name of the class that should be defined

Implementation:

body classes if_ok(x)
{
      promise_repaired => { "$(x)" };
      promise_kept => { "$(x)" };
}

if_ok_cancel

Prototype: if_ok_cancel(x)

Description: Cancel the class x if the promise ks kept or repaired

Arguments:

  • x: The name of the class that should be cancelled

Implementation:

body classes if_ok_cancel(x)
{
      cancel_repaired => { "$(x)" };
      cancel_kept => { "$(x)" };
}

cmd_repair

Prototype: cmd_repair(code, cl)

Description: Define the class cl if an external command in a commands, file or packages promise is executed with return code code

Arguments:

  • code: The return codes that indicate a successful repair
  • cl: The name of the class that should be defined

See also: repaired_returncodes

Implementation:

body classes cmd_repair(code,cl)
{
      repaired_returncodes => { "$(code)" };
      promise_repaired => { "$(cl)" };
}

classes_generic

Prototype: classes_generic(x)

Description: Define x prefixed/suffixed with promise outcome

Arguments:

  • x: The unique part of the classes to be defined

Implementation:

body classes classes_generic(x)
{
      promise_repaired => { "promise_repaired_$(x)", "$(x)_repaired", "$(x)_ok", "$(x)_reached" };
      repair_failed => { "repair_failed_$(x)", "$(x)_failed", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" };
      repair_denied => { "repair_denied_$(x)", "$(x)_denied", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" };
      repair_timeout => { "repair_timeout_$(x)", "$(x)_timeout", "$(x)_not_ok", "$(x)_error","$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" };
      promise_kept => { "promise_kept_$(x)", "$(x)_kept", "$(x)_ok", "$(x)_not_repaired", "$(x)_reached" };
}

scoped_classes_generic

Prototype: scoped_classes_generic(scope, x)

Description: Define x prefixed/suffixed with promise outcome

See also: scope

Arguments:

  • scope: The scope in which the class should be defined
  • x: The unique part of the classes to be defined

Implementation:

body classes scoped_classes_generic(scope, x)
{
      scope => "$(scope)";
      promise_repaired => { "promise_repaired_$(x)", "$(x)_repaired", "$(x)_ok", "$(x)_reached" };
      repair_failed => { "repair_failed_$(x)", "$(x)_failed", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" };
      repair_denied => { "repair_denied_$(x)", "$(x)_denied", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" };
      repair_timeout => { "repair_timeout_$(x)", "$(x)_timeout", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" };
      promise_kept => { "promise_kept_$(x)", "$(x)_kept", "$(x)_ok", "$(x)_not_repaired", "$(x)_reached" };
}

state_repaired

Prototype: state_repaired(x)

Description: Define x for 10 minutes if the promise was repaired

Arguments:

  • x: The name of the class that should be defined

Implementation:

body classes state_repaired(x)
{
      promise_repaired => { "$(x)" };
      persist_time => "10";
}

enumerate

Prototype: enumerate(x)

Description: Define x for 15 minutes if the promise is either kept or repaired This is used by commercial editions to count instances of jobs in a cluster

Arguments:

  • x: The unqiue part of the class that should be defind The class defined is prefixed with mXC_

Implementation:

body classes enumerate(x)
{
      promise_repaired => { "mXC_$(x)" };
      promise_kept => { "mXC_$(x)" };
      persist_time => "15";
}

always

Prototype: always(x)

Description: Define class x no matter what the outcome of the promise is

Arguments:

  • x: The name of the class to be defined

Implementation:

body classes always(x)
{
      promise_repaired => { "$(x)" };
      promise_kept => { "$(x)" };
      repair_failed => { "$(x)" };
      repair_denied => { "$(x)" };
      repair_timeout => { "$(x)" };
}

kept_successful_command

Prototype: kept_successful_command

Description: Set command to "kept" instead of "repaired" if it returns 0

Implementation:

body classes kept_successful_command
{
      kept_returncodes => { "0" };
}

Re-usable agent bundles

These agent bundles can be used via usebundle in methods promises.

methods:
    usebundle => library_bundle(parameters)

To use these bundles, add the following to your policy:

body file control
{
    inputs => { "bundles.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::
      "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";

  files:

    !windows::
      "$(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");
}

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

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);

  classes:
      "runidentified" expression => strcmp(1, $(count));

  methods:
      "any"
      usebundle  => $(bundlesfound),
      ifvarclass => "runidentified";

  reports:
    verbose_mode::
      "$(this.bundle): found matching bundles $(bundlesfound) for namespace '$(namespace)' and bundle '$(mybundle)'";
}