Dependencies¶
Resources declare what has to be settled before them. Ordering is never implied by where a document sits in a file or by the order files appear on disk.
datum: v1alpha1
type: Service
name: nginx
requires:
- Package[nginx]
- File[nginx-config]
restartOn:
- File[nginx-config]
desired:
state: running
enabled: true
requires sits alongside desired and not inside it, because ordering is
behaviour common to every resource type instead of something a type defines.
What requires means¶
It means three things, and they need stating precisely because the third is the one people expect and do not get.
The referenced resources are processed before this one, so Package[nginx] is
applied, or determined to need nothing, before Service[nginx] is considered.
If a referenced resource fails to apply, this resource is not attempted and its
action becomes skip. Failure propagates along dependency edges, so a resource
depending on something that was skipped is also skipped.
The reference must resolve to a resource in the same effective manifest. A dependency on something not present is an error raised by the graph builder before the host is read, rather than a dependency silently treated as satisfied.
What it does not mean is a requirement that the target exists on the host.
requires: [Package[nginx]] combined with Package[nginx] declaring
state: absent is a coherent, if unusual, manifest, and Datum orders the two
without objecting.
Ordering is not enough on its own¶
Ordering says a file is written before a service is considered. It does not say the service should restart because the file changed.
On a converged host where only the file content changed in the repository, ordering
alone produces an update on the file and none on the service, and nginx carries
on serving the old configuration until something restarts it. That is why change
reaction is a separate mechanism.
restartOn¶
Service.restartOn lists resources whose change should cause the service to
restart.
When a listed resource has a non-none action in the same plan, the service gets an
update whose reason records which resource triggered it.
Declaring the reaction on the service instead of on the file is deliberate.
Reading Service[nginx] shows everything that can restart it, whereas
notification declared on files means reconstructing the list by searching for
anything that mentions the service.
restartOn also orders. A resource listed there is processed before the service in the
same way as one listed in requires, so the reference does not have to appear in both
fields.
| Field | Orders | Triggers an update |
|---|---|---|
requires |
Yes | No |
restartOn |
Yes | Yes, as a restart |
reloadOn |
Yes | Yes, as a reload |
Reaction implies order because a service that restarts when its configuration changes always wants the configuration written first. A rule requiring both fields to name the same resource would have no effect other than allowing somebody to write one and forget the other.
Open question
Reacting to change is specific to Service, which has both restartOn and
reloadOn. Whether a general mechanism is
needed for a type whose reaction is neither is undecided. Adding one prematurely risks a
generic trigger system that ends up being used to sequence arbitrary work, which is the
direction this design is trying to avoid.
Where dependencies come from¶
Most dependencies are obvious from the resources themselves. A file in a directory depends on the directory, a file owned by a user depends on the user, a service depends on the package providing its unit.
Datum does not infer any of them. A File at /etc/nginx/conf.d/tls.conf is not
automatically ordered after a Directory at /etc/nginx/conf.d, even though the
relationship is plain from the paths.
Inference was rejected because it is either incomplete or surprising. Path nesting would cover directories and not users, adding ownership inference would cover users and not packages, and each rule added makes the plan order depend on knowledge that is not in the repository. An engineer reading a manifest would then have to know Datum's inference rules to predict the order, which is a cost the design declines to pay.
The consequence is that dependencies are the most common thing to forget, and the failures are the confusing kind where a resource works on a converged host and fails on a fresh one.
Open question
Whether Datum should detect probable missing dependencies and warn, without
acting on them, is still open. A warning that a File under a managed
Directory has no dependency on it would catch the common mistake while
keeping the plan order fully determined by what the repository says.
Cycles¶
A cycle is rejected. The graph builder detects it and the pass ends before the host is read.
There is no attempt to break a cycle by dropping an edge, because the choice of which edge to drop would be arbitrary and the resulting order would be one nobody asked for.