Notifications¶
A notification runs an action on one resource because another resource
changed: reload nginx after its configuration file changed, run
systemctl daemon-reload after a unit file changed, restart a service when
its settings changed. Nothing is triggered when the watched resource was
already as the recipe wants it, so a service is only restarted when there is
a reason to. By default a notified action runs once, at the end of apply,
however many resources asked for it; marked :immediately, it runs right
after the change that triggered it.
Minimal example¶
# Reload nginx when its configuration changes. The reload runs once, at the
# end of apply, and only if the file changed.
package "nginx"
file "/etc/nginx/conf.d/status.conf",
content: "server {\n listen 127.0.0.1:8080;\n location = /status { stub_status; }\n}\n",
mode: "0644",
notifies: [:reload, "service[nginx]"]
service "nginx", enable: true, state: :running
Options¶
Both options are available on every resource.
| Option | Form | Meaning |
|---|---|---|
notifies |
[:action, "type[name]"] |
When this resource changes, run action on the named resource. |
subscribes |
[:action, "type[name]"] |
When the named resource changes, run action on this resource. |
| either | [:action, "type[name]", :immediately] |
Run the action right after the change instead of at the end. |
| either | [:action, "type[name]", :delayed] |
The default, written out. |
| either | [[...], [...]] |
Several notifications from one resource. |
subscribes is notifies declared from the other side; use whichever
reads better. Actions each resource type accepts:
| Resource | Actions |
|---|---|
service |
:start, :stop, :restart, :reload, :enable, :disable |
exec |
:run |
package, file, directory, user |
none |
exec also takes action: :nothing: the command then never runs on its
own, only when notified. That is the usual way to write "run this after
that changed".
How notifications run¶
- Only a resource that changed during
applytriggers its notifications. Unchanged, failed and skipped resources trigger nothing. - Delayed notifications (the default) run after the last resource, in the order they were first triggered. Each action on each resource runs once: if three configuration files ask to restart nginx, it restarts once.
- Immediate notifications run right after the resource that triggered them, every time they are triggered, before the next resource is looked at.
- If a resource fails, the run stops: later resources are skipped and delayed notifications do not run. A failed immediate notification stops the run in the same way.
plananddriftnever run notifications. They list, under each resource that would change, what it would trigger.- Every notification must name a declared resource and an action it supports. Mistakes are reported when the recipe loads, before anything runs.
Scenarios¶
The example below manages a small systemd service together with its unit file and settings:
# A small systemd service whose unit file and settings are managed here.
# Notifications keep the running process in step with those files.
# Runs only when notified.
exec "systemctl daemon-reload", action: :nothing
# systemd must re-read a changed unit file before the service below is
# started or restarted, so daemon-reload runs immediately. The restart can
# wait until the end of the run.
file "/etc/systemd/system/ticker.service",
template: "ticker.service.erb",
mode: "0644",
notifies: [
[:run, "exec[systemctl daemon-reload]", :immediately],
[:restart, "service[ticker]"]
]
# Settings the service reads when it starts.
file "/etc/default/ticker", content: "INTERVAL=#{vars[:interval]}\n", mode: "0644"
# The service asks to be restarted when its settings change; this is the
# same as notifies: on the file above, declared from the other side.
service "ticker",
enable: true,
state: :running,
subscribes: [:restart, "file[/etc/default/ticker]"]
# Managed by sadame. Local changes will be overwritten.
[Unit]
Description=<%= vars[:description] %>
[Service]
EnvironmentFile=/etc/default/ticker
ExecStart=/bin/sh -c 'while true; do date >> /var/log/ticker.log; sleep ${INTERVAL}; done'
[Install]
WantedBy=multi-user.target
{
interval: 60,
description: "Writes the date to /var/log/ticker.log"
}
First apply¶
The unit file is new, so systemctl daemon-reload runs immediately after
it, before the service is enabled and started. At the end, the delayed
restart runs once, although both files asked for it:
$ sudo sadame apply examples/notifications
sadame 0.5.1 apply on local (Debian GNU/Linux 12 (bookworm))
unchanged exec[systemctl daemon-reload]
changed file[/etc/systemd/system/ticker.service]
state: absent -> present
mode: 0644
--- /dev/null
+++ /etc/systemd/system/ticker.service (desired)
@@ -0,0 +1,10 @@
+# Managed by sadame. Local changes will be overwritten.
+[Unit]
+Description=Writes the date to /var/log/ticker.log
+
+[Service]
+EnvironmentFile=/etc/default/ticker
+ExecStart=/bin/sh -c 'while true; do date >> /var/log/ticker.log; sleep ${INTERVAL}; done'
+
+[Install]
+WantedBy=multi-user.target
notified exec[systemctl daemon-reload]: run (immediately, from file[/etc/systemd/system/ticker.service])
changed file[/etc/default/ticker]
state: absent -> present
mode: 0644
--- /dev/null
+++ /etc/default/ticker (desired)
@@ -0,0 +1 @@
+INTERVAL=60
changed service[ticker]
enabled: false -> true
state: stopped -> running
notified service[ticker]: restart (delayed, from file[/etc/systemd/system/ticker.service])
Summary
changed 3 file[/etc/systemd/system/ticker.service]
file[/etc/default/ticker]
service[ticker]
unchanged 1
failed 0
skipped 0
notified 2 exec[systemctl daemon-reload]: run
service[ticker]: restart
Finished in 0.06s.
Changing a setting¶
plan shows which notifications a change would trigger:
$ sudo sadame plan examples/notifications --var interval=30
sadame 0.5.1 plan on local (Debian GNU/Linux 12 (bookworm))
~ file[/etc/default/ticker]
--- /etc/default/ticker
+++ /etc/default/ticker (desired)
@@ -1 +1 @@
-INTERVAL=60
+INTERVAL=30
notifies: restart service[ticker] (delayed)
Plan: 0 to create, 1 to update, 0 to remove, 0 to run, 3 unchanged.
apply restarts the service once the settings file is written:
$ sudo sadame apply examples/notifications --var interval=30
sadame 0.5.1 apply on local (Debian GNU/Linux 12 (bookworm))
unchanged exec[systemctl daemon-reload]
unchanged file[/etc/systemd/system/ticker.service]
changed file[/etc/default/ticker]
--- /etc/default/ticker
+++ /etc/default/ticker (desired)
@@ -1 +1 @@
-INTERVAL=60
+INTERVAL=30
unchanged service[ticker]
notified service[ticker]: restart (delayed, from file[/etc/default/ticker])
Summary
changed 1 file[/etc/default/ticker]
unchanged 3
failed 0
skipped 0
notified 1 service[ticker]: restart
Finished in 0.01s.
Changing the unit file¶
A changed unit file triggers both: daemon-reload right away, the restart
at the end.
$ sudo sadame apply examples/notifications --var interval=30 --var description='Ticker demo'
sadame 0.5.1 apply on local (Debian GNU/Linux 12 (bookworm))
unchanged exec[systemctl daemon-reload]
changed file[/etc/systemd/system/ticker.service]
--- /etc/systemd/system/ticker.service
+++ /etc/systemd/system/ticker.service (desired)
@@ -1,6 +1,6 @@
# Managed by sadame. Local changes will be overwritten.
[Unit]
-Description=Writes the date to /var/log/ticker.log
+Description=Ticker demo
[Service]
EnvironmentFile=/etc/default/ticker
notified exec[systemctl daemon-reload]: run (immediately, from file[/etc/systemd/system/ticker.service])
unchanged file[/etc/default/ticker]
unchanged service[ticker]
notified service[ticker]: restart (delayed, from file[/etc/systemd/system/ticker.service])
Summary
changed 1 file[/etc/systemd/system/ticker.service]
unchanged 3
failed 0
skipped 0
notified 2 exec[systemctl daemon-reload]: run
service[ticker]: restart
Finished in 0.03s.
Common mistakes¶
- Delayed daemon-reload.
systemctl daemon-reloadafter a unit file change must happen before the service is started or restarted. Mark it:immediately; a delayed reload runs after the restart it was meant to prepare. - An
execwithoutaction: :nothing. A notifiedexecthat also has no guard runs on every apply anyway, anddriftalways reports it. - Reloading a unit that cannot reload.
systemctl reloadfails for units withoutExecReload=; notify:restartinstead. - Expecting a notification after a failed run. Delayed notifications are skipped when a resource fails. Fix the failure and apply again; the resource that changed earlier is now unchanged and will not notify again, so restart by hand if needed.
- Typos in the target.
"service[ngnix]"is reported when the recipe loads:file[/etc/nginx/nginx.conf] notifies service[ngnix], which is not declared.
See also¶
- service for the service actions.
- exec for
action: :nothing. - Plan, apply, drift for what each command does with notifications.