Manifests for a provider and a consumer, so the contract can be read rather than only exercised through a lab fixture that stages the grants by hand. Checked as a pair rather than separately, because two manifests that only ever parse alone are two manifests nobody has held against each other. The test asserts the names match, that each side says where it wants to be told, and that the consumer contributes the key the provisioner actually reads. That last one is the trap worth having a test for: a consumer contributing "name" — which is exactly what a database consumer contributes — resolves cleanly, deploys, and then fails on the machine with "asked for a bucket and did not name it". Nothing in that message points back at the manifest that caused it. Both mistakes were made while writing these two files.
76 lines
4.1 KiB
Markdown
76 lines
4.1 KiB
Markdown
# Example modules
|
|
|
|
Manifests, not programs. They are here because the contract is easier to read as something that
|
|
works than as a description of something that would.
|
|
|
|
**Third-party software runs *on* the mesh, not *of* it** (novox/hq ADR 0001). dnsmasq is not the
|
|
mesh's, and neither is systemd-resolved — what is the mesh's is the fact only it can know, which
|
|
is which machines exist and where they are. So the mesh writes that to a file and these read it.
|
|
|
|
## Resolving a service named under a machine
|
|
|
|
`postgres.novox.internal`, `plex.ace.internal`. The first label is the service and the rest is the
|
|
node, so **anything under a node's name must resolve to that node** and a proxy there routes by
|
|
the name it was asked for. That routing is a separate concern and stays separate.
|
|
|
|
Two roles, and they are genuinely different things:
|
|
|
|
| | claims | |
|
|
|---|---|---|
|
|
| **serving** | `the-dns-port` | answers the wildcards — `dnsmasq.json` |
|
|
| **asking** | `the-resolver-configuration` | decides what the machine asks — `resolved-split-dns.json`, `resolv-conf.json` |
|
|
|
|
**systemd-resolved cannot serve a wildcard**, so it is only ever an *asking* module: it routes the
|
|
mesh's suffix to something that can. Treating the two roles as one would produce a module that
|
|
cannot work, which is the mistake worth naming.
|
|
|
|
Assign one of each. Two of either is refused by the mesh rather than fought over on the machine:
|
|
|
|
> `resolved-split-dns and resolv-conf both claim "the-resolver-configuration", and only one thing
|
|
> may hold it per node`
|
|
|
|
ADR 0009's table names that resource `/etc/resolv.conf`, which is what it *is*, the way it writes
|
|
*the seat*. A claim is a name in the catalogue's own form, so it is written as one.
|
|
|
|
## Why neither needs to know the machine's address
|
|
|
|
Both would ordinarily need it — a resolver must bind somewhere, and a stub must be pointed
|
|
somewhere — and a static manifest cannot know it.
|
|
|
|
Neither does, because both name things **the mesh itself named**: the private network's interface
|
|
is `mesh0` on every machine, and the address a resolver listens on for the machine's own use is
|
|
`127.0.0.54` on every machine. A name the mesh chose is a name a manifest can use.
|
|
|
|
## Asking for a bucket
|
|
|
|
`object-store.json` provides one, `photos.json` asks for one. Together they are the whole of an
|
|
edge, and they are here as a **pair** because that is the only way to see the halves line up:
|
|
|
|
| the provider says | the consumer says |
|
|
|---|---|
|
|
| `provides: s3-bucket` | `requires: s3-bucket` |
|
|
| `receives` — where to be told who asked | `contributes: {bucket: photos}` — what it wants |
|
|
| `grants` — where their credentials land | `secrets` — where to be given its key |
|
|
| `serves` — port, scheme, region | `binds` — where to be told all that |
|
|
|
|
**The mesh adds the half neither can know**: which machine the provider is on, and where it is on
|
|
the private network. Neither manifest names an address, and that is what lets the same pair work
|
|
on any mesh.
|
|
|
|
**`s3-bucket` names the protocol, not the product** ([ADR 0027](../../../hq/02-DECISIONS/)). A
|
|
consumer's code is written against the S3 API, and swapping one store for another does not break
|
|
it — so the coupling is to S3. A database is the other case: an application is written against
|
|
PostgreSQL or against SQL Server, so those provisions name the engine.
|
|
|
|
**What the pair is checked for.** That the names match, that each side says where it wants to be
|
|
told, and that the consumer contributes the key the provisioner actually reads — `bucket`, not
|
|
`name`. Contributing `name` (which is what a database consumer contributes) resolves perfectly and
|
|
then fails on the machine with *asked for a bucket and did not name it*, which is a long way from
|
|
the manifest that caused it.
|
|
|
|
The provisioner that makes the credential true lives in
|
|
[`../objectstore-provisioner`](../objectstore-provisioner), and is proven against a real store in
|
|
the lab — including the assertion a database does not need, that **a consumer cannot reach another
|
|
consumer's bucket**. One store holds every bucket behind one endpoint, so that isolation is a
|
|
policy somebody wrote rather than a boundary the product has.
|