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)_reached" };
repair_denied => { "repair_denied_$(x)", "$(x)_denied", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" };
repair_timeout => { "repair_timeout_$(x)", "$(x)_timeout", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" };
promise_kept => { "promise_kept_$(x)", "$(x)_kept", "$(x)_ok", "$(x)_reached" };
}
results
Prototype: results(scope, class_prefix)
Description: Define classes prefixed with class_prefix
and suffixed with
appropriate outcomes: _kept, _repaired, _not_kept, _error, _failed,
_denied, _timeout, _reached
Arguments:
scope
: The scope in which the class should be defined (bundle
ornamespace
)class_prefix
: The prefix for the classes defined
This body can be applied to any promise and sets global
(namespace
) or local (bundle
) classes based on its outcome. For
instance, with class_prefix
set to abc
:
if the promise is to change a file's owner to
nick
and the file was already owned bynick
, the classesabc_reached
andabc_kept
will be set.if the promise is to change a file's owner to
nick
and the file was owned byadam
and the change succeeded, the classesabc_reached
andabc_repaired
will be set.
This body is a simpler, more consistent version of the body
scoped_classes_generic
, which see. The key difference is that
fewer classes are defined, and only for outcomes that we can know.
For example this body does not define "OK/not OK" outcome classes,
since a promise can be both kept and failed at the same time.
It's important to understand that promises may do multiple things, so a promise is not simply "OK" or "not OK." The best way to understand what will happen when your specific promises get this body is to test it in all the possible combinations.
Suffix Notes:
_reached
indicates the promise was tried. Any outcome will result in a class with this suffix being defined._kept
indicates some aspect of the promise was kept_repaired
indicates some aspect of the promise was repaired_not_kept
indicates some aspect of the promise was not kept. error, failed, denied and timeout outcomes will result in a class with this suffix being defined_error
indicates the promise repair encountered an error_failed
indicates the promise failed_denied
indicates the promise repair was denied_timeout
indicates the promise timed out
Example:
bundle agent example
{
commands:
"/bin/true"
classes => results("bundle", "my_class_prefix");
reports:
my_class_prefix_kept::
"My promise was kept";
my_class_prefix_repaired::
"My promise was repaired";
}
See also: scope
, scoped_classes_generic
, classes_generic
Implementation:
body classes results(scope, class_prefix)
{
scope => "$(scope)";
promise_kept => { "$(class_prefix)_reached",
"$(class_prefix)_kept" };
promise_repaired => { "$(class_prefix)_reached",
"$(class_prefix)_repaired" };
repair_failed => { "$(class_prefix)_reached",
"$(class_prefix)_error",
"$(class_prefix)_not_kept",
"$(class_prefix)_failed" };
repair_denied => { "$(class_prefix)_reached",
"$(class_prefix)_error",
"$(class_prefix)_not_kept",
"$(class_prefix)_denied" };
repair_timeout => { "$(class_prefix)_reached",
"$(class_prefix)_error",
"$(class_prefix)_not_kept",
"$(class_prefix)_timeout" };
}
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)_reached" };
repair_denied => { "repair_denied_$(x)", "$(x)_denied", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" };
repair_timeout => { "repair_timeout_$(x)", "$(x)_timeout", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" };
promise_kept => { "promise_kept_$(x)", "$(x)_kept", "$(x)_ok", "$(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";
}
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 insidecollect_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)'";
}