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)_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 or namespace)
  • 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 by nick, the classes abc_reached and abc_kept will be set.

  • if the promise is to change a file's owner to nick and the file was owned by adam and the change succeeded, the classes abc_reached and abc_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 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)_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 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";
}

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