condition-file-exists and condition-file-missing gate a service on
the state of a file: the first runs the process only if the path exists,
the second only if it doesn't. Setting both means both must hold.
condition-env-equals is different in kind: it decides whether the
service exists in this run of the config at all. One image serving several
roles (ROLE=api, ROLE=worker, ...) lists every service once and
tags each with the role it belongs to; in a container of another role the
service is dropped at load, as if the entry were not written. To switch a
service on or off by a variable's mere presence, and keep manual start
working, use the startup template in
service-gating instead.
The classic file-condition use is a preparation oneshot that must run only
when its work is not done yet — no sh -c 'if [ ! -e ... ]' wrapper
needed.
condition-file-exists: /etc/app/enable # run only if present
condition-file-missing: /etc/app/done # run only if absent
condition-env-equals: # every key must match (AND)
ROLE: api # exists only when ROLE is "api"
TIER: "1" # ... and TIER is "1" (values are strings)- Paths must be absolute; a relative path is rejected at load.
- The condition is re-evaluated at every start attempt: initial
startup,
on-failure/on-successrestarts, each scheduled cron tick, and manualstartvia the control socket. A restart loop therefore stops on its own once the watched file state changes. - Symlinks are followed (
os.Stat), so a k8s configmap/secret key resolved through..data/gives the right answer; a dangling symlink counts as missing. Astaterror other than not-exist (e.g. permission denied) leaves the condition unmet with the error in the skip reason, so it never silently masquerades as a missing file. - The check is advisory, not a lock: the file state can change between the probe and the exec.
- Resolved once per config load against the same environment snapshot
the
{{.VAR}}templates use, so the two can never disagree. A hot reload re-resolves it: a service can join or leave the config that way, and leaving stops it like a deleted service. - Read from gopherd's own environment, independent of
pass-env,environment:anddotenv:. - Exact string comparison, no trimming, no case folding. All keys must
match (AND). An unset variable compares as
"". - An unmet condition excludes the service: it is not listed by
status,status <name>andstart <name>report an unknown service, and everyafter:/before:/requires:edge pointing at it is dropped so the survivors order as if the entry were absent. The daemon logs<name> excluded (condition-env-equals: KEY does not match "value")once at load. - The excluded entry is still validated, so a typo in a rarely deployed role fails the load everywhere.
- A check referenced only by excluded services (via
ready-checkoron-check-failure) is dropped with them and logged ascheck <name> excluded (only used by excluded service <svc>). A check some surviving service still references, or that no service references at all, keeps running. - Keys must be valid POSIX variable names and the value must be a mapping; anything else is rejected at load rather than opening the gate.
- The log line names the key and the expected value only, never the observed value, which may be a secret.
- An unmet condition skips the start: it is logged with the reason,
shown as
skippedin thestatusoverview and asskipped (...)bystatus <name>(JSON carries"state":"skipped"plus"reason"), and counts as success — services withafter:/requires:on it start normally, and noon-success/on-failureaction fires (nothing ran). The state clears once the service starts.
processes:
- name: aux-cfg
condition-file-missing: /etc/haproxy/haproxy-aux.cfg
command: /bin/sh
args:
- -c
- |
touch /etc/haproxy/haproxy-aux.cfg
chmod g+w /etc/haproxy/haproxy-aux.cfg
startup: oneshot
- name: app
command: /usr/sbin/haproxy
requires: [aux-cfg]- First boot: the file is missing,
aux-cfgruns and creates it,appstarts after it completes. - Later boots: the file exists,
aux-cfgis skipped (aux-cfg skipped (condition-file-missing: ... exists)), andappstill starts — skip satisfiesrequires:. status aux-cfgreportsskipped (condition-file-missing: ... exists); a manualstart aux-cfgreports the same instead of running it.
Run level. Boots the daemon twice against the same directory and asserts the run/skip split above.
TestServiceConditionsEnv boots the same config twice. With
ROLE=worker the gated app is excluded: status does not list it,
status app and start app report an unknown service, the daemon log
carries the exclusion line, and a second service whose after: [app] edge
was dropped runs anyway. With ROLE=api the app starts and reaches
running.
SIGTERM yields a clean exit 0.
go test ./documentation/service-conditions/ -v