CFEngine is changing how the persistence timer of a class defined by a classes
promise is handled when that class is rediscovered on a later run. We consider
this a bug fix: classes promises should always have been able to reset the timer,
just like classes bodies can. A new timer_policy attribute on the classes
promise type lets you choose whether the timer is reset on each evaluation
(reset) or left to count down from its original expiry (absolute). In
CFEngine 3.28.0 the default becomes reset, aligning classes promises with
classes bodies. The attribute is also being backported to the 3.27.x and 3.24.x
LTS streams (3.27.2 and 3.24.5), where the default stays absolute so that
existing behavior is unchanged and the new behavior is strictly opt-in.
Persistent classes and their timers
A persistent class survives between agent runs for a set number of minutes,
recorded in a local database (cf_state.lmdb). It is pre-defined before policy
evaluation begins, so it can influence behavior very early in a run.
There are two ways to define a persistent class, and historically they did not behave the same way when the class was rediscovered on a later run:
- The
classespromise type, using thepersistenceattribute (minutes). - A
classesbody attached to another promise (for exampleif_repaired), usingpersist_timetogether withtimer_policy.
The classes body has carried a timer_policy attribute for a long time, defaulting
to reset: each time the owning promise repairs, the timer starts over. The
classes promise type had no such control – it only had persistence, and the
timer always behaved as absolute. That inconsistency is the bug this change
addresses (tracked as CFE-4681).
The previous behavior
Previously, a classes promise whose class was already defined (loaded from the
persistent store) was simply skipped. The timer counted down from the original
expiry and the class disappeared after the configured minutes, regardless of how
many agent runs happened in between. There was no way to extend the window
directly (only as the result of some other promise using a classes body).
The new behavior: timer_policy
The classes promise type now accepts a timer_policy attribute:
absolute– preserve the original expiry (the old behavior).reset– refresh the timer on each evaluation, even when the class is already defined.
With reset, the effect is a sliding window: the class stays defined as long as
the expression keeps evaluating true, expiring after the configured minutes from
the last run rather than the first.
Try it – run this policy twice:
body common control
{
bundlesequence => { "demo" };
}
bundle agent demo
{
classes:
"my_class"
expression => "any",
persistence => "120",
timer_policy => "reset";
reports:
"my_class is defined";
}# Requires a cf-agent build that includes CFE-4681.
# Clear any prior state so the first run is a clean "Creating".
rm -f /var/cfengine/state/cf_state.lmdb*
echo "=== Run 1 ==="
cf-agent -Kvf /tmp/timer_policy_demo.cf 2>&1 | grep -i "persistent class"
echo "=== Run 2 ==="
cf-agent -Kvf /tmp/timer_policy_demo.cf 2>&1 | grep -i "persistent class"Run 1 logs Creating persistent class ... policy reset and run 2 logs Resetting persistent class ... timer to 120 minutes.
Defaults and availability
The timer_policy attribute for classes promises arrives in three releases, but
the default differs:
| Release | timer_policy available |
Default for classes promises |
|---|---|---|
| 3.28.0 | yes | reset |
| 3.27.2 | yes (backport) | absolute |
| 3.24.5 | yes (backport) | absolute |
In 3.28.0 the default flips to reset to align classes promises with classes
bodies and match what most users expect: a persistent class that keeps being
discovered should keep persisting. The backports ship the attribute on the LTS
streams while keeping the absolute default so nothing changes unless you ask
for it.
If you depend on a specific behavior, set timer_policy explicitly rather than
relying on the default. That makes the policy behave the same on 3.24.5, 3.27.2,
and 3.28.0 alike.
If you have questions or need help, reach out on the mailing list or GitHub discussions. If you have a support contract, feel free to open a ticket in our support system.