Feature Friday #13: classesmatching()

Posted by Nick Anderson
June 7, 2024

Did you know you can find classes by name and tag?

classesmatching() dynamically sources information from the current state. For example, let’s say you have classes representing a system’s role. Furthermore, let’s say that we want a host to only have a single role class defined. Finally, if we have more than one role class defined, then we don’t want to proceed.

To achieve this without classesmatching(), we might have a policy file that looks like this (/tmp/feature-friday-13/tags-on-classes-0.cf)

/tmp/feature-friday-13/tags-on-classes-0.cf
bundle agent __main__
{
  classes:
    "have_role"
      expression => "role_1|role_2|role_3";
    "too_many_roles"
      expression => "(role_1.(role_2|role3))|(role_2.(role_1|role3))|role_3.(role_2|role_3)";
    "no_roles"
      expression => "!(role_1|role_2|role_3)";

  reports:
    have_role::
      "I have a role";
    role_1::
      "I have role_1";
    role_2::
      "I have role_2";
    role_3::
      "I have role_3";
    too_many_roles::
      "Uh oh! I have more than 1 role class defined.";
    no_roles::
      "Uh oh! I don't have any role classes defined.";
}

We can run the policy defining different variations, checking that the various cases are covered.

With no role classes:

command
cf-agent -Kf /tmp/feature-friday-13/tags-on-classes-0.cf
output
R: Uh oh! I don't have any role classes defined.

With all possible role classes:

command
cf-agent -Kf /tmp/feature-friday-13/tags-on-classes-0.cf --define role_1,role_2,role_3
output
R: I have a role
R: I have role_1
R: I have role_2
R: I have role_3
R: Uh oh! I have more than 1 role class defined.

With multiple role classes:

command
cf-agent -Kf /tmp/feature-friday-13/tags-on-classes-0.cf --define role_1,role_3
output
R: I have a role
R: I have role_1
R: I have role_3
R: Uh oh! I have more than 1 role class defined.

With a single role class defined:

command
cf-agent -Kf /tmp/feature-friday-13/tags-on-classes-0.cf --define role_1
output
R: I have a role
R: I have role_1

This can be much more clear and maintainable if implemented using classesmatching():

/tmp/feature-friday-13/tags-on-classes-1.cf
bundle agent __main__
{
  vars:
    "role_classes"
      slist => classesmatching("role_.*");

  classes:
    "have_role"
      expression => isgreaterthan(length(role_classes), 0);
    "too_many_roles"
      expression => isgreaterthan(length(role_classes), 1);
    "no_roles"
      expression => strcmp(length(role_classes), 0);

  reports:
    have_role::
      "I have a role";
      "I have $(role_classes)"
        if => "$(role_classes)";
    too_many_roles::
      "Uh oh! I have more than 1 role class defined.";
    no_roles::
      "Uh oh! I don't have any role classes defined.";
}

We can run it through the same checks we did previously and see that the result is identical. However, we no longer have to maintain expressions for each combination. All we need to do is to follow our role class naming convention.

First with no role classes defined:

command
cf-agent -Kf /tmp/feature-friday-13/tags-on-classes-1.cf
output
R: Uh oh! I don't have any role classes defined.

With all possible role classes:

command
cf-agent -Kf /tmp/feature-friday-13/tags-on-classes-1.cf --define role_1,role_2,role_3
output
R: I have a role
R: I have role_1
R: I have role_3
R: I have role_2
R: Uh oh! I have more than 1 role class defined.

With multiple role classes:

command
cf-agent -Kf /tmp/feature-friday-13/tags-on-classes-1.cf --define role_1,role_3
output
R: I have a role
R: I have role_1
R: I have role_3
R: Uh oh! I have more than 1 role class defined.

With a single role class defined:

command
cf-agent -Kf /tmp/feature-friday-13/tags-on-classes-1.cf --define role_1
output
R: I have a role
R: I have role_1

Sometimes, naming conventions aren’t enough. Sometimes we want a bit more. Well, classesmatching() can also be searched by tag. Let’s refactor the above example to not care about the class name. Instead, let’s care about having more than one class defined with the same tag.

/tmp/feature-friday-13/tags-on-classes-3.cf
bundle agent __main__
{
  vars:
    "role_classes"
      slist => classesmatching(".*", "role_class");

  classes:
    "have_role"
      expression => isgreaterthan(length(role_classes), 0);
    "too_many_roles"
      expression => isgreaterthan(length(role_classes), 1);
    "no_roles"
      expression => strcmp(length(role_classes), 0);

  reports:
    have_role::
      "I have a role";
      "I have $(role_classes)"
        if => "$(role_classes)";
    too_many_roles::
      "Uh oh! I have more than 1 role class defined.";
    no_roles::
      "Uh oh! I don't have any role classes defined.";
}

Unfortunately, we can’t specify tags for a class when using the --define option as we did previously, so we will modify the policy to define the classes used.

/tmp/feature-friday-13/tags-on-classes-3.cf
bundle common classification
{
  classes:
    "Rick"
      expression => "any",
      meta => { "role_class" };
    "Astley"
      expression => "any",
      meta => { "role_class" };
    "Never_Gonna_Give_You_Up"
      expression => "any",
      meta => { "role_class" };
}

bundle agent __main__
{
  vars:
    "role_classes"
      slist => classesmatching(".*", "role_class");

  classes:
    "have_role"
      expression => isgreaterthan(length(role_classes), 0);
    "too_many_roles"
      expression => isgreaterthan(length(role_classes), 1);
    "no_roles"
      expression => strcmp(length(role_classes), 0);

  reports:
    have_role::
      "I have a role";
      "I have $(role_classes)"
        if => "$(role_classes)";
    too_many_roles::
      "Uh oh! I have more than 1 role class defined.";
    no_roles::
      "Uh oh! I don't have any role classes defined.";
}
command
cf-agent -Kf /tmp/feature-friday-13/tags-on-classes-3.cf
output
R: I have a role
R: I have Astley
R: I have Never_Gonna_Give_You_Up
R: I have Rick
R: Uh oh! I have more than 1 role class defined.

Take it for a spin, and see how this methodology can be applied in your own environment.

Happy Friday! 🎉