Common Bodies and Bundles
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 evaluationsexpire
: 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 messagesmessage
: 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 repairedno
: 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 repairedno
: 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 repaircl
: 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 definedx
: 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 withmXC_
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 runuser
: The owner of crontabhours
: The hours at which the job should runmins
: 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 namedepth
: 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 rotationmax_size
: minimum size in bytes that the file will grow to before being rotatedrotate_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 filesmax_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 namemethod
: the HTTP method (HEAD or GET)port
: the port number, e.g. 80uri
: 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 createvarlist
: 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)'";
}