CFEngine: A bunch of new slist facilities
  2013-05-03

Recently (still in master branch, and certainly in CFEngine-3.5), great new functions have been added to make powerful manipulations on slists. You will find below for each function its documentation (extracted from master branch) and a downloadable example:

filter

Extracts a sublist of elements matching arg1 as a regular expression (if arg3 is true) or as an exact string (if arg3 is false) from a list variable specified in arg2. In regular expression mode, the regex is anchored. If arg4 is true, the matches are inverted (you get elements that don’t match arg1). arg5 specifies a maximum number of elements to return.

filter.cf

body common control {
  bundlesequence =>  { "my_filter" };
}

bundle agent my_filter {

  vars:
    "countries"     slist   =>  {
                                  "barbados",
                                  "belgium",
                                  "belize",
                                  "bulgaria",
                                  "canada",
                                  "cambodia",
                                  "fiji"
                                };

    "countries_str" string  => join(" ", "countries");

    # 1. Apply filter on "countries" slist
    # 2. Format result as strings for reports

    "sl1"     slist   =>  filter("^b[ea].*", "countries", true, false, 10);
    "sl1_str" string  =>  join(" ", "sl1");

    "sl2"     slist   =>  filter("belgium", "countries", false, false, 10);
    "sl2_str" string  =>  join(" ", "sl2");

    "sl3"     slist   =>  filter("^b[eu].*", "countries", true, true, 10);
    "sl3_str" string  =>  join(" ", "sl3");

    "sl4"     slist   =>  filter("^ca.*", "countries", true, false, 1);
    "sl4_str" string  =>  join(" ", "sl4");

  reports:
    "   All countries:                                        $(countries_str)";
    "1. Starts with 'be' or 'bu', use of regex:               $(sl1_str)";
    "2. Is 'belgium', exact match:                            $(sl2_str)";
    "3. Doesn't start with 'be' or 'bu', inverted regex:      $(sl3_str)";
    "4. The first starting with 'ca', use of regex:           $(sl4_str)";
}

Output:

$ cf-agent -KIf filter.cf
R:    All countries:                                        barbados belgium belize bulgaria canada cambodia fiji
R: 1. Starts with 'be' or 'bu', use of regex:               barbados belgium belize
R: 2. Is 'belgium', exact match:                            belgium
R: 3. Doesn't start with 'be' or 'bu', inverted regex:      barbados canada cambodia fiji
R: 4. The first starting with 'ca', use of regex:           canada

every

arg1 is a regular expression tested against every element of the list arg2. If all match, this function returns true.

every.cf

body common control {
  bundlesequence => { "my_every" };
}

bundle agent my_every {

  vars:
    "countries"     slist   =>  {
                                  "australia",
                                  "bulgaria",
                                  "canada",
                                  "cambodia",
                                  "malaysia"
                                };

  classes:
    "ending_with_ia" expression =>  every(".*ia$", "countries");
    "ending_with_a"  expression =>  every(".*a$", "countries");


  reports:
    ending_with_ia::
      "All countries don't end with 'ia'";

    ending_with_a::
      "All countries don't with 'a'";
}

Output:

$ cf-agent -KIf every.cf
Running full policy integrity checks
R: All countries don't end with 'a'

none

arg1 is a regular expression tested against every element of the list arg2. If none match, this function returns true.

none.cf

body common control {
  bundlesequence => { "my_none" };
}

bundle agent my_none {

  vars:
    "countries"     slist   =>  {
                                  "australia",
                                  "bulgaria",
                                  "canada",
                                  "cambodia",
                                  "malaysia"
                                };

  classes:
    "not_ending_with_a" expression => none("^.*a$", "countries");
    "not_ending_with_e" expression => none("^.*e$", "countries");

  reports:
    not_ending_with_a::
      "No country ends with 'a'";

    not_ending_with_e::
      "No country ends with 'e'";
}

Output:

$ cf-agent -KIf none.cf
Running full policy integrity checks
R: No country ends with 'e'

nth

Extracts a single element from a list variable specified in arg1 at the zero-based position arg2. Thus, if arg1 has N elements, arg2 would be useful between 0 and N-1. Above N-1, the call does not return a valid value.

nth.cf

body common control {
  bundlesequence => { "my_nth" };
}

bundle agent my_nth {

  vars:
    "countries"     slist   =>  {
                                  "australia",
                                  "bulgaria",
                                  "canada",
                                  "cambodia",
                                  "malaysia"
                                };

    "first"   string  =>  nth("countries", "0");
    "second"  string  =>  nth("countries", "1");
    "fourth"  string  =>  nth("countries", "3");

  reports:
    "First:  $(first)";
    "Second: $(second)";
    "Fourth: $(fourth)";
}

Output:

$ cf-agent -KIf nth.cf
Running full policy integrity checks
R: First:  australia
R: Second: bulgaria
R: Fourth: cambodia

some

arg1 is a regular expression tested against every element of the list arg2. If at least one matches, this function returns true.

some.cf

body common control {
  bundlesequence => { "my_some" };
}

bundle agent my_some {

  vars:
    "countries"     slist   =>  {
                                  "australia",
                                  "bulgaria",
                                  "canada",
                                  "cambodia",
                                  "malaysia"
                                };

  classes:
    "starting_with_b"   expression  =>  some("^b.*", "countries");
    "starting_with_d"   expression  =>  some("^d.*", "countries");

  reports:
    starting_with_b::
      "At least one country starts with 'b'";

    starting_with_d::
      "At least one country starts with 'd'";
}

Output:

$ cf-agent -KIf some.cf
Running full policy integrity checks
R: At least one country starts with 'b'

sublist

Extracts a sublist of elements from a list variable specified in arg1. arg2 can be “head” to specify extraction from the head of the list or “tail” to extract from the tail. arg3 specifies how many items to extract.

No inner sublist yet, too invasive for the next CFEngine release.

sublist.cf

body common control {
  bundlesequence => { "my_sublist" };
}

bundle agent my_sublist {

  vars:
    "countries"     slist   =>  {
                                  "australia",
                                  "bulgaria",
                                  "canada",
                                  "cambodia",
                                  "malaysia"
                                };

    "head3"  slist  =>  sublist("countries", "head", 3);
    "tail2"  slist  =>  sublist("countries", "tail", 2);

    # Slist to string for reports
    "head3_str"  string  =>  join(" ", "head3");
    "tail2_str"  string  =>  join(" ", "tail2");

  reports:
    "The first 3 countries are: $(head3_str)";
    "The last 2 countries are:  $(tail2_str)";
}

Output:

$ cf-agent -KIf sublist.cf
Running full policy integrity checks
R: The first 3 countries are: australia bulgaria canada
R: The last 2 countries are:  cambodia malaysia

uniq

Extracts a sublist of unique elements (determined by a string comparison) from a list variable specified in arg1.

uniq.cf

body common control {
  bundlesequence => { "my_uniq" };
}

bundle agent my_uniq {

  vars:
    "countries"     slist   =>  {
                                  "australia",
                                  "australia",
                                  "bulgaria",
                                  "canada",
                                  "canada",
                                  "malaysia"
                                };

    "uniq_countries"  slist  =>  uniq("countries");

    # Slist to string for reports
    "countries_str"       string  =>  join(" ", "countries");
    "uniq_countries_str"  string  =>  join(" ", "uniq_countries");

  reports:
    "All countries are:                $(countries_str)";
    "Countries without duplicates are: $(uniq_countries_str)";
}

Output:

$ cf-agent -KIf uniq.cf
Running full policy integrity checks
R: All countries are:                australia australia bulgaria canada canada malaysia
R: Countries without duplicates are: australia bulgaria canada malaysia

difference

Returns the unique elements in the list variable specified in arg1 that are not in the list variable specified in arg2.

difference.cf

body common control {
  bundlesequence => { "my_difference" };
}

bundle agent my_difference {

  vars:
    "countries_list1" slist =>  {
                                  "australia",
                                  "bulgaria",
                                  "canada",
                                };

    "countries_list2" slist =>  {
                                  "australia",
                                  "bulgaria",
                                };

    "countries_difference"  slist =>  difference("countries_list1", "countries_list2");

    # Slist to string for reports
    "countries_list1_str"       string  =>  join(" ", "countries_list1");
    "countries_list2_str"       string  =>  join(" ", "countries_list2");
    "countries_difference_str"  string  =>  join(" ", "countries_difference");

  reports:
    "List 1 of countries is:              $(countries_list1_str)";
    "List 2 of countries is:              $(countries_list2_str)";
    "Countries in list1 but not in list2: $(countries_difference_str)";
}

Output:

$ cf-agent -KIf difference.cf
Running full policy integrity checks
R: List 1 of countries is:              australia bulgaria canada
R: List 2 of countries is:              australia bulgaria
R: Countries in list1 but not in list2: canada

intersection

Returns the unique elements in the list variable specified in arg1 that are also in the list variable specified in arg2.

intersection.cf

body common control {
  bundlesequence => { "my_intersection" };
}

bundle agent my_intersection {

  vars:
    "countries_list1" slist =>  {
                                  "australia",
                                  "canada",
                                  "fiji",
                                };

    "countries_list2" slist =>  {
                                  "australia",
                                  "bulgaria",
                                  "malaysia",
                                  "fiji",
                                };

    "countries_intersection"  slist =>  intersection("countries_list1", "countries_list2");

    # Slist to string for reports
    "countries_list1_str"         string  =>  join(" ", "countries_list1");
    "countries_list2_str"         string  =>  join(" ", "countries_list2");
    "countries_intersection_str"  string  =>  join(" ", "countries_intersection");

  reports:
    "List 1 of countries is:              $(countries_list1_str)";
    "List 2 of countries is:              $(countries_list2_str)";
    "Countries present in both lists are: $(countries_intersection_str)";
}

Output:

$ cf-agent -KIf intersection.cf
Running full policy integrity checks
R: List 1 of countries is:              australia canada fiji
R: List 2 of countries is:              australia bulgaria malaysia fiji
R: Countries present in both lists are: australia fiji

length

And last but not least :)

Returns the number of elements in the list variable specified in arg1.

length.cf

body common control {
  bundlesequence => { "my_length" };
}

bundle agent my_length {

  vars:
    "countries_list1" slist =>  {
                                  "australia",
                                  "canada",
                                };

    "countries_list2" slist =>  {
                                  "australia",
                                  "bulgaria",
                                  "malaysia",
                                  "fiji",
                                };

    "countries_list1_len"  int  =>  length("countries_list1");
    "countries_list2_len"  int  =>  length("countries_list2");

  reports:
    "List 1 has $(countries_list1_len) countries";
    "List 2 has $(countries_list2_len) countries";
}

Output:

$ cf-agent -KIf length.cf
Running full policy integrity checks
R: List 1 has 2 countries
R: List 2 has 4 countries