From f3187835398982d7537a3b88d0300fc6fc44ad06 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Fri, 16 Oct 2026 13:33:56 +0200 Subject: [PATCH] Updated cf-reactor documentation Signed-off-by: Victor Moene --- content/overview/_index.markdown | 11 + content/overview/directory-structure.markdown | 2 + .../reference/components/cf-reactor.markdown | 200 +++++++++++++++- .../language-concepts/bundles.markdown | 2 + .../language-concepts/classes.markdown | 1 + .../reference/promise-types/_index.markdown | 44 ++-- .../reference/promise-types/events.markdown | 222 ++++++++++++++++++ 7 files changed, 451 insertions(+), 31 deletions(-) create mode 100644 content/reference/promise-types/events.markdown diff --git a/content/overview/_index.markdown b/content/overview/_index.markdown index 38341195c..743e08aeb 100644 --- a/content/overview/_index.markdown +++ b/content/overview/_index.markdown @@ -58,6 +58,9 @@ All machines, whether they are policy servers or hosts, will have these three im - [/var/cfengine/bin/cf-serverd][Overview#cf-serverd] - [/var/cfengine/bin/cf-monitord][Overview#cf-monitord] +On hosts using systemd, [`cf-reactor`][Overview#cf-reactor] runs as well, to +react to events described in policy. + #### cf-execd `cf-execd` is the scheduling daemon for `cf-agent`, similar to cron. It executes and collects the output of `cf-agent` and @@ -95,6 +98,14 @@ See also: [cf-serverd][cf-serverd] reference documentation. See also: [cf-monitord][cf-monitord] reference documentation. +#### cf-reactor + +`cf-reactor` is the event reaction daemon for CFEngine. It watches for events described in policy using `events` type promises, such as the deletion of a file, and runs bundles of promises as soon as they happen, without waiting for the next `cf-agent` run. + +`cf-reactor` keeps the promises made in `common` and `reactor` bundles, and is affected by the `common` control body. + +See also: [cf-reactor][cf-reactor] reference documentation. + ### Other component applications - [/var/cfengine/bin/cf-agent][Overview#cf-agent] diff --git a/content/overview/directory-structure.markdown b/content/overview/directory-structure.markdown index cdf8cda98..9c8a8d1ec 100644 --- a/content/overview/directory-structure.markdown +++ b/content/overview/directory-structure.markdown @@ -21,6 +21,7 @@ The CFEngine application is fully contained within the /var/cfengine directory t - `cf-execd`: Starts the cf-agent process at a specified time interval. - `cf-monitord`: Collects system statistics +- `cf-reactor`: Reacts to events described in policy - `cf-serverd`: Provides network services; used to distribute policy and data files - `runalerts.sh`: Updates Mission Portal status and activates alert actions (Enterprise only) - `cf-hub`: Responsible for collecting reports from remote agents. (CFEngine Enterprise only) @@ -181,6 +182,7 @@ _pid files_ in the work directory. - `cf-execd.pid` - `cf-hub.pid` - `cf-monitord.pid` +- `cf-reactor.pid` - `cf-serverd.pid` ## Sockets in /var/cfengine diff --git a/content/reference/components/cf-reactor.markdown b/content/reference/components/cf-reactor.markdown index 213bb8530..aadd4e386 100644 --- a/content/reference/components/cf-reactor.markdown +++ b/content/reference/components/cf-reactor.markdown @@ -1,29 +1,207 @@ --- layout: default title: cf-reactor -keywords: [reactor] +keywords: [reactor, events] aliases: - "/reference-components-cf-reactor.html" --- -`cf-reactor` is the CFEngine event reaction daemon. -We are currently working on making `cf-reactor` respond to events specified in policy language, using `when` bodies and `reactor` bundles. -This feature is coming in 3.29.0 and will be further documented here in the future. +`cf-reactor` is the CFEngine event reaction daemon. It watches for the events +described by [`events`][events] promises in `reactor` bundles, and as soon as +an event happens, it runs the bundles promised for it, without waiting for the +next scheduled `cf-agent` run. -**Notes:** +`cf-reactor` keeps the promises made in `common` and `reactor` bundles, and is +affected by the `common` control body. + +```cf3 +bundle agent main +{ + methods: + "motd" usebundle => motd; +} + +bundle agent motd +{ + files: + "/etc/motd" content => "This host is managed by CFEngine$(const.n)"; +} + +bundle reactor motd_watch +{ + events: + "motd_deleted" + when => deleted("/etc/motd"), + then => motd; +} + +body when deleted(path) +{ + file_deleted => "$(path)"; +} +``` -- In CFEngine Enterprise `cf-reactor` has some extra responsibilities, where it listens to `NOTIFY` events in the PostgreSQL database and performs actions when those events occur. - One example of such events is refreshing the `host_specific.json` files whenever there is an event in the `cmdb_refresh` channel. +In this example, `cf-agent` creates `/etc/motd` on its regular runs, and +whenever the file is deleted, `cf-reactor` runs the `motd` bundle right away to +restore it: -- In the future, the daemon should also take care of inventory refresh for hosts - (now part of `cf-hub`) and many DB maintenance tasks that are now promises in - the Masterfiles Policy Framework policy under `/cfe_internal/enterprise`. +```output + info: Registering a file_deleted watcher for events promise 'motd_deleted', on file '/etc/motd' + notice: Reactor watcher 'motd_deleted' fired + info: Created file '/etc/motd', mode 0600 +``` + +**Notes:** + +- `cf-reactor` always considers the class `reactor` to be defined. +- This daemon reloads its policy when the SIGHUP signal is received. +- `SIGUSR1` sets the log level to debug. +- `SIGUSR2` sets the log level to notice. +- On CFEngine Enterprise hubs, `cf-reactor` has extra responsibilities, see + [Enterprise hub tasks][cf-reactor#Enterprise hub tasks]. **History:** - In 3.18.2 / 3.20.0 `cf-reactor` was introduced as an Enterprise-only hub component. -- In 3.29.0 `cf-reactor` was moved to CFEngine community to be used as the daemon for event driven CFEngine policy. +- In 3.29.0 `cf-reactor` was moved to CFEngine community, and `reactor` bundles + and [`events`][events] promises were introduced for event driven CFEngine + policy. ## Command reference {{< CFEngine_include_snippet(cf-reactor.help, [\s]*--[a-z], ^$) >}} + +## Reactor bundles + +Bundles of type `reactor` describe the events `cf-reactor` watches for, with +[`events`][events] promises. `cf-reactor` evaluates all the `reactor` bundles of +the policy, so there is no need to list them in a `bundlesequence`. Reactor +bundles taking parameters are not evaluated: `cf-reactor` logs a warning about +them instead. + +In a reactor bundle, `cf-reactor` keeps the `meta`, `vars`, `classes` and +`events` promises, in that order. `defaults` and `reports` promises are allowed, +but ignored. Other components, like `cf-agent`, ignore reactor bundles. + +Like the other components, `cf-reactor` also evaluates the `vars` and `classes` +promises of `common` bundles, so their variables and classes can be used in +reactor bundles. + +Classes defined in reactor bundles are bundle scoped by default, as in agent +bundles. To use a class of a reactor bundle in the bundles run on events, or to +guard `events` promises, define it with [`scope`][classes#scope] `namespace`: + +```cf3 +bundle reactor watch_nginx_config +{ + classes: + "nginx_installed" + expression => fileexists("/usr/sbin/nginx"), + scope => "namespace"; + + events: + nginx_installed:: + "nginx_config_deleted" + when => deleted("/etc/nginx/nginx.conf"), + then => nginx_config_deleted; +} + +bundle agent nginx_config_deleted +{ + reports: + "/etc/nginx/nginx.conf was deleted"; +} + +body when deleted(path) +{ + file_deleted => "$(path)"; +} +``` + +**Note:** Currently, the classes guarding `events` promises, with class +expressions or with [`if`][Promise types#if] and +[`unless`][Promise types#unless], must be namespace scoped (or hard classes). +When an `events` promise is guarded by a bundle scoped class, its event is +detected, but the [`then`][events#then] bundles are not run. + +## Running bundles on events + +`cf-reactor` checks for the events of the `events` promises every second. When +an event happens, it runs the bundles of the [`then`][events#then] attribute of +the `events` promise, in order, like a `bundlesequence`. Events are handled one +at a time: the events happening while bundles run are handled next. + +Each event gets a fresh run of the bundles: + +- Promises act every time: the default [`ifelapsed`][Promise types#ifelapsed] of + the bundles run on events is `0`, so the promise locks of previous runs don't + apply. Promises setting `ifelapsed` in their `action` body are still locked + for that long. +- Functions are evaluated again: their results are not cached between events. +- Classes and variables defined by the run are undone once the event is handled, + except persistent classes (see [`persistence`][classes#persistence]), which + stay defined until they expire, as in `cf-agent`. +- Time classes are updated for each event. The other hard classes, and the + variables like [`sys.date`][sys#sys.date], are discovered when `cf-reactor` + loads its policy, and are not updated for each event. Use functions like + [`now()`][now] to get the time of the event. + +The bundles run by `cf-reactor` are not part of a `cf-agent` run: + +- Only the bundles of the `then` attribute are run, not the `bundlesequence`. +- The class `reactor` is defined, the class `agent` is not. +- `body agent control` does not apply. +- The output of the bundles, like their reports, is in the output of + `cf-reactor`. When it runs as a systemd service, see `journalctl -u cf-reactor`. + +## Policy updates + +Like the other daemons, `cf-reactor` reloads its policy when new policy is +validated, which it checks for every 5 minutes. It also reloads its policy when +it receives the SIGHUP signal. + +The new policy is validated with `cf-promises` first: if it has errors, +`cf-reactor` logs them and keeps its previous policy, still watching for the +same events. If the policy has errors when `cf-reactor` starts, it falls back to +the failsafe policy, like the other components. The failsafe policy has no +`events` promises, so `cf-reactor` doesn't watch for any event until the policy +is fixed. + +When the policy is reloaded, `cf-reactor` stops watching for the events of the +`events` promises that were removed, and starts watching for those of the new +ones. The `events` promises that didn't change keep watching for their events, +without missing those happening while the policy is reloaded. + +## Running cf-reactor + +Since 3.29.0, `cf-reactor` is installed by all CFEngine packages, Community and +Enterprise, on hubs and clients. + +On hosts using systemd, `cf-reactor` runs as the `cf-reactor` service, which is +part of the `cfengine3` service. The Masterfiles Policy Framework ensures +`cf-reactor` is running on Enterprise hubs. + +To try out policy with `cf-reactor`, stop the `cf-reactor` service, as only one +instance of `cf-reactor` can run at a time, and run `cf-reactor` in the +foreground with the policy file to test: + +```command +systemctl stop cf-reactor +cf-reactor --no-fork --inform --file ./reactor_test.cf +``` + +**Note:** On Enterprise hubs, the [Enterprise hub tasks][cf-reactor#Enterprise hub tasks] +are not performed while the `cf-reactor` service is stopped. + +## Enterprise hub tasks + +On CFEngine Enterprise hubs, `cf-reactor` also listens to `NOTIFY` events in the +PostgreSQL database and performs actions when those events occur, and runs +periodic hub tasks: + +- Refreshing the `host_specific.json` files whenever there is an event in the + `cmdb_refresh` channel. +- Refreshing the inventory of hosts. +- Handling the requests of the Build application (cfbs requests and responses). +- Running alerts, scheduled reports, the cleanup of hosts, and the update of + health failures. diff --git a/content/reference/language-concepts/bundles.markdown b/content/reference/language-concepts/bundles.markdown index db282f52a..5ea41ab76 100644 --- a/content/reference/language-concepts/bundles.markdown +++ b/content/reference/language-concepts/bundles.markdown @@ -53,6 +53,8 @@ Currently, these are the possible _bundle types_: - `agent`: Bundle of promises specific to the `cf-agent` binary. - `server`: Bundle of promises specific to the `cf-serverd` binary. - `monitor`: Bundle of promises specific to the `cf-monitord` binary. +- `reactor`: Bundle of promises specific to the `cf-reactor` binary, see + [Reactor bundles][cf-reactor#Reactor bundles]. - `common`: Bundle of promises shared among the different binaries / components. - `edit_line`: Bunde of promises used in a `files` promise with the `edit_line` attribute. diff --git a/content/reference/language-concepts/classes.markdown b/content/reference/language-concepts/classes.markdown index d7685c87b..075c41da8 100644 --- a/content/reference/language-concepts/classes.markdown +++ b/content/reference/language-concepts/classes.markdown @@ -140,6 +140,7 @@ reserved hard class 'cfengine' in post-condition classes`. - `cf-runagent` :: `runagent` - `cf-key` :: `keygenerator` - `cf-hub` :: `hub` + - `cf-reactor` :: `reactor` - `cf-promises` :: `common` - Operating System Classes (note that the presence of these classes doesn't imply platform support) - Operating System Architecture - `arista`, `big_ip`, `debian`, `eos`, `fedora`, `Mandrake`, `Mandriva`, `oracle`, `redhat`, `slackware`, `smartmachine`, `smartos`, `solarisx86`, `sun4`, `SuSE`, `ubuntu`, `ultrix`, the always-favorite `unknown_ostype`, etc. diff --git a/content/reference/promise-types/_index.markdown b/content/reference/promise-types/_index.markdown index 031d690ab..4f41c1b22 100644 --- a/content/reference/promise-types/_index.markdown +++ b/content/reference/promise-types/_index.markdown @@ -10,30 +10,34 @@ Within a bundle, the promise types are executed in a round-robin fashion in the following [normal order][Policy evaluation]. Which promise types are available depends on the [bundle][bundles] type: -| Promise type | common | agent | server | monitor | -| --------------------------------------------------------------------- | :----: | :---: | :----: | :------ | -| [defaults][defaults] - a default value for bundle parameters | x | x | x | x | -| [classes][classes] - a class, representing a state of the system | x | x | x | x | -| [meta][meta] - information about promise bundles | x | x | x | x | -| [reports][reports] - report a message | x | x | x | x | -| [vars][vars] - a variable, representing a value | x | x | x | x | -| [commands][commands] - execute a command | | x | | | -| [databases][databases] - configure a database | | x | | | -| [files][files] - configure a file | | x | | | -| [packages][packages] - install a package | | x | | | -| [guest_environments][guest_environments] | | x | | | -| [methods][methods] - take on a whole bundle of other promises | | x | | | -| [processes][processes] - start or terminate processes | | x | | | -| [services][services] - manage services or define new abstractions | | x | | | -| [storage][storage] - verify attached storage | | x | | | -| [users][users] - add or remove users | | x | | | -| [access][access] - grant or deny access to file objects | | | x | | -| [roles][roles] - allow certain users to activate certain classes | | | x | | -| [measurements][measurements] - measure or sample data from the system | | | | x | +| Promise type | common | agent | server | monitor | reactor | +| --------------------------------------------------------------------- | :----: | :---: | :----: | :-----: | :------ | +| [defaults][defaults] - a default value for bundle parameters | x | x | x | x | | +| [classes][classes] - a class, representing a state of the system | x | x | x | x | x | +| [meta][meta] - information about promise bundles | x | x | x | x | x | +| [reports][reports] - report a message | x | x | x | x | | +| [vars][vars] - a variable, representing a value | x | x | x | x | x | +| [commands][commands] - execute a command | | x | | | | +| [databases][databases] - configure a database | | x | | | | +| [files][files] - configure a file | | x | | | | +| [packages][packages] - install a package | | x | | | | +| [guest_environments][guest_environments] | | x | | | | +| [methods][methods] - take on a whole bundle of other promises | | x | | | | +| [processes][processes] - start or terminate processes | | x | | | | +| [services][services] - manage services or define new abstractions | | x | | | | +| [storage][storage] - verify attached storage | | x | | | | +| [users][users] - add or remove users | | x | | | | +| [access][access] - grant or deny access to file objects | | | x | | | +| [roles][roles] - allow certain users to activate certain classes | | | x | | | +| [measurements][measurements] - measure or sample data from the system | | | | x | | +| [events][events] - react to an event | | | | | x | See each promise type's reference documentation for detailed lists of available attributes. +**Note:** `defaults` and `reports` promises are allowed in `reactor` bundles, +but `cf-reactor` ignores them, see [Reactor bundles][cf-reactor#Reactor bundles]. + ## Common promise attributes The following attributes are available to all promise types. diff --git a/content/reference/promise-types/events.markdown b/content/reference/promise-types/events.markdown new file mode 100644 index 000000000..dbbc65dd9 --- /dev/null +++ b/content/reference/promise-types/events.markdown @@ -0,0 +1,222 @@ +--- +layout: default +title: events +keywords: [reactor, events] +--- + +`events` promises describe events for [`cf-reactor`][cf-reactor] to react to: +the event to watch for, with the [`when`](#when) body, and the bundles to run +when it happens, with the [`then`](#then) attribute. `events` promises can only +be made in `reactor` bundles, see [Reactor bundles][cf-reactor#Reactor bundles]. + +```cf3 +bundle reactor motd_watch +{ + events: + "motd_deleted" + when => deleted("/etc/motd"), + then => motd; +} + +body when deleted(path) +{ + file_deleted => "$(path)"; +} + +bundle agent motd +{ + files: + "/etc/motd" content => "This host is managed by CFEngine$(const.n)"; +} +``` + +The promiser is the name of the event, which `cf-reactor` uses in its logs: + +```output + info: Registering a file_deleted watcher for events promise 'motd_deleted', on file '/etc/motd' + notice: Reactor watcher 'motd_deleted' fired +``` + +Like other promises, `events` promises iterate over the lists used in their +promiser and attributes, and `cf-reactor` watches for the event of each +iteration separately: + +```cf3 +bundle reactor watch_config_files +{ + vars: + "files" slist => { "/etc/hosts", "/etc/resolv.conf" }; + + events: + "$(files) deleted" + when => deleted("$(files)"), + then => log_deletion("$(files)"); +} + +body when deleted(path) +{ + file_deleted => "$(path)"; +} + +bundle agent log_deletion(path) +{ + reports: + "$(path) was deleted"; +} +``` + +`cf-reactor` watches for the event of identical `events` promises only once, so +their bundles are run once per event. `events` promises that differ, for example +by their promiser or their `then` bundles, are watched for separately, even when +they watch for the same event. + +See [Running bundles on events][cf-reactor#Running bundles on events] for how +the bundles run on events differ from a `cf-agent` run. + +**History:** Introduced in 3.29.0. + +## Promise outcome + +The outcome of an `events` promise is decided when `cf-reactor` evaluates its +policy: + +- **kept** when `cf-reactor` watches for its event. +- **not kept** (`repair_failed`) when the promise is invalid, and its event is + not watched for. For example, when the `when` body or the `then` attribute is + missing or empty, or when one of the `then` bundles can't be found, or is + neither an `agent` nor a `common` bundle. `cf-reactor` logs why the promise + is invalid. + +Use a [`classes`][Promise types#classes] body to define classes based on the +outcome: + +```cf3 +bundle reactor watch_hosts_file +{ + events: + "hosts_deleted" + when => deleted("/etc/hosts"), + then => log_deletion("/etc/hosts"), + classes => watching("hosts"); +} + +body classes watching(name) +{ + promise_kept => { "watching_$(name)" }; + repair_failed => { "not_watching_$(name)" }; +} + +body when deleted(path) +{ + file_deleted => "$(path)"; +} + +bundle agent log_deletion(path) +{ + reports: + "$(path) was deleted"; +} +``` + +## Attributes + +{{< CFEngine_include_markdown(common-attributes.include.markdown) >}} + +### then + +**Description:** The bundle, or the list of bundles, to run in order when the +event happens. + +Bundles are referred to like in a `bundlesequence`: by name, quoted or not, with +their arguments, if any, in parentheses. Arguments can use the variables of the +reactor bundle, and lists are passed with `@(list)`. Only `agent` and `common` +bundles can be run. + +If one of the bundles can't be found, the `events` promise is not kept, and none +of its bundles are run. + +**Type:** `bundle agent` or `bundle common`, or a list of them + +**Example:** + +```cf3 +bundle reactor watch_motd +{ + vars: + "admins" slist => { "alice", "bob" }; + + events: + "motd_deleted" + when => deleted("/etc/motd"), + then => { log_deletion("/etc/motd"), notify(@(admins)), motd }; +} + +body when deleted(path) +{ + file_deleted => "$(path)"; +} + +bundle agent log_deletion(path) +{ + reports: + "$(path) was deleted"; +} + +bundle agent notify(users) +{ + reports: + "Notifying $(users)"; +} + +bundle agent motd +{ + files: + "/etc/motd" content => "This host is managed by CFEngine$(const.n)"; +} +``` + +**History:** Introduced in 3.29.0. + +### when + +**Type:** `body when` + +**See also:** [Common body attributes][Promise types#Common body attributes] + +#### file_deleted + +**Description:** The file whose deletion is the event. + +The event happens when the file, which existed when `cf-reactor` last checked, +doesn't exist anymore. It happens on every deletion of the file: if the file is +created again and deleted again, it happens again. + +**Type:** `string` + +**Allowed input range:** (arbitrary string) + +**Example:** + +```cf3 +body when deleted(path) +{ + file_deleted => "$(path)"; +} +``` + +**Notes:** + +- `cf-reactor` checks whether the file exists every second, so a file deleted + and created again in the meantime may not be seen as deleted. +- If the file doesn't exist when `cf-reactor` starts watching for its deletion, + the event only happens once the file has been created, and then deleted. +- Moving or renaming the file is a deletion, as nothing exists at its path + anymore. +- Symbolic links are not followed: deleting a symbolic link is an event, + deleting the file it points to is not. +- If `cf-reactor` can't tell whether the file exists, for example because of + missing permissions, it logs an error, at most once a minute. +- `file_deleted` takes a single file. To watch for the deletion of several + files, iterate over a list, as shown above. + +**History:** Introduced in 3.29.0.