Class expressions are powerful. They let you restrict the context for multiple promises in a single statement. What if you want to further control the context of a specific promise?
Let’s take a look at a contrived example:
bundle agent __main__
{
reports:
"I am running $(sys.os_release[PRETTY_NAME])";
linux::
"I love Linux!";
linux.ubuntu::
"Especially Ubuntu.";
linux.redhat::
"Especially RedHat.";
linux.!(ubuntu|redhat)::
"But not RedHat or Ubuntu.";
}
cf-agent -Kf /tmp/feature-friday-28-0.cf
R: I am running Ubuntu 22.04.4 LTS
R: I love Linux!
R: Especially Ubuntu.
Here, we have a report showing the distribution we’re running through class expressions protecting the individual promises. We would see I love Linux!
on Linux hosts. Depending on the specific distribution running we would see Especially Ubuntu.
or Especially RedHat.
. If we are running something other than those two distributions But not RedHat or Ubuntu.
would be reported.
We can make this policy a bit less repetitive by leveraging if
and unless
to restrict the individual promises that are all available when linux
is defined:
bundle agent __main__
{
reports:
"I am running $(sys.os_release[PRETTY_NAME])";
linux::
"I love Linux!";
"Especially Ubuntu." if => "ubuntu";
"Especially RedHat." if => "redhat";
"But not RedHat or Ubuntu." unless => or( "ubuntu",
"redhat" );
}
By executing this policy, we see that it returns the exact same output:
cf-agent -Kf /tmp/feature-friday-28-1.cf
R: I am running Ubuntu 22.04.4 LTS
R: I love Linux!
R: Especially Ubuntu.
The if
and unless
attributes also let us use functions which can be useful for a cases where you only use the restriction once, without defining a class separately.
bundle agent __main__
{
reports:
"I am running $(sys.os_release[PRETTY_NAME])";
linux::
"I love Linux!";
"Especially $(with)."
with => "$(sys.os_release[NAME])",
# Case insensitive `(?i)` match against classes contining ubuntu or redhat
if => regcmp( "(?i).*(ubuntu|redhat).*",
"$(sys.os_release[PRETTY_NAME])" );
"But not RedHat or Ubuntu." unless => or( "ubuntu",
"redhat" );
}
Again, we can see executing this policy results in the same output.
cf-agent -Kf /tmp/feature-friday-28-2.cf
R: I am running Ubuntu 22.04.4 LTS
R: I love Linux!
R: Especially Ubuntu.
To a large extent, if
and unless
are opposites, and if => not()
should be equivalent to unless
.
Ignoring the not()
function for a second, if
and unless
are exact opposites, in any case where if
would skip a promise, unless
would evaluate it, and vice versa. This holds true even for the more weird situations (edge cases), such as when the right-hand side is unresolved.
That’s not the case for the not()
function. There are cases (unresolved variables and function calls) where if => x
and if => not(x)
behave the same when the right-hand side is unresolved. In both of these cases, the promise would be skipped.
bundle agent main
{
classes:
"a" if => "$(no_such_var)"; # Will be skipped
"b" if => not("$(no_such_var)"); # Will be skipped
}
In other words, unresolved variables in function calls cause skipping. Similarly, if
defaults to skipping, while unless
defaults to evaluating. Strictly speaking, if
and unless
are true opposites, while if
and if => not()
are not.
So, if you are interested in what should be the default behavior when variables/function calls are not resolved. Then use if => not()
when you want to default to skipping, and unless
when you want to default to evaluating.
For more info, see the docs for if
and unless
.
Happy Friday! 🎉