mesh-names, mesh-resolver and the names half of the overlay generators are gone.
They ran no software and could not be swapped for anything, which is the test of
whether something is a module at all — they existed because computed output
needed somewhere to live, and the control plane's only shape for output was a
module.
Now a module says where it wants what the mesh knows:
facts: { node-zones: /etc/mesh-resolver/nodes.conf }
and is given a file, under its own name, applied and removed like anything else
it declares. Two facts exist: node-names (a hosts file — exact names) and
node-zones (every machine as a wildcard, *.homer.internal is homer). Asking for
a fact the mesh does not compute is refused naming what would have worked,
because a daemon that starts and reads a file nobody wrote is a worse way to
find out.
The names ride with the network now: wireguard's manifest asks for node-names
into /etc/hosts, because being on the private network is what gives a machine a
name. networking no longer requires name-resolution — names are not a provision,
and the module that answered it ran nothing.
One behaviour inverted, deliberately: choosing another VPN used to drag
WireGuard in anyway, because only WireGuard provided the addressing the names
module required — the node-scope claim existed to at least make that loud. With
names as a fact there is nothing to drag in: tailscale assigned means tailscale,
alone. The claim still catches two VPNs assigned explicitly.
And a machine the mesh cannot place is left out of both files rather than named
at nothing: a name resolving to nothing hangs a connection, where an unknown
name fails at once and says so. In practice that is only ever a token issued and
not yet used — a machine that has announced itself has an address.
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
98 lines
3.6 KiB
Go
98 lines
3.6 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
var threeMachines = map[string]string{"homer": "10.42.0.1", "marge": "10.42.0.2", "bart": ""}
|
|
|
|
// **`*.homer.internal` is homer. That is the whole rule.**
|
|
func TestEveryMachineIsAWildcardUnderItsOwnName(t *testing.T) {
|
|
out := nodeZones(Resolution{Node: "homer"}, threeMachines)
|
|
for _, want := range []string{
|
|
"address=/homer.internal/10.42.0.1",
|
|
"address=/marge.internal/10.42.0.2",
|
|
} {
|
|
if !strings.Contains(out, want) {
|
|
t.Fatalf("missing %q:\n%s", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A machine the mesh has a record for and cannot place is left out of both.
|
|
//
|
|
// **Not an oversight — the alternative is worse.** A name written with no address resolves to
|
|
// nothing, and a connection to that hangs. Leaving it out fails at once and says the name is
|
|
// unknown, which is a thing somebody can act on.
|
|
func TestAMachineWithNoAddressIsNotNamed(t *testing.T) {
|
|
for _, out := range []string{
|
|
nodeNames(Resolution{Node: "homer"}, threeMachines),
|
|
nodeZones(Resolution{Node: "homer"}, threeMachines),
|
|
} {
|
|
if strings.Contains(out, "bart") {
|
|
t.Fatalf("a machine with no address was named, so its name resolves to nothing:\n%s", out)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A machine's own mesh name points at its address on the private network, not at loopback — or a
|
|
// service binding the name it was given is unreachable from everywhere else.
|
|
func TestAMachinesOwnNameIsItsMeshAddress(t *testing.T) {
|
|
out := nodeNames(Resolution{Node: "homer"}, threeMachines)
|
|
var line string
|
|
for _, l := range strings.Split(out, "\n") {
|
|
if strings.Contains(l, "homer.internal") {
|
|
line = l
|
|
}
|
|
}
|
|
if !strings.HasPrefix(line, "10.42.0.1") {
|
|
t.Fatalf("a machine's own mesh name is not its mesh address: %q", line)
|
|
}
|
|
// And the loopback floor is still there, or things with nothing to do with the mesh break.
|
|
if !strings.Contains(out, "127.0.0.1\tlocalhost") {
|
|
t.Fatalf("the loopback floor was removed:\n%s", out)
|
|
}
|
|
}
|
|
|
|
// A module says where it wants a fact, and is given a file.
|
|
func TestAModuleIsGivenTheFactsItAskedFor(t *testing.T) {
|
|
m := Manifest{Module: "dnsmasq", Facts: map[string]string{FactNodeZones: "/etc/mesh/zones.conf"}}
|
|
given, err := FactsInto(m, Resolution{Node: "homer"}, threeMachines)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(given) != 1 {
|
|
t.Fatalf("expected one file, got %d", len(given))
|
|
}
|
|
if given[0]["path"] != "/etc/mesh/zones.conf" || given[0]["type"] != "file" {
|
|
t.Fatalf("not written where it was asked for: %v", given[0])
|
|
}
|
|
if !strings.Contains(given[0]["content"].(string), "homer.internal") {
|
|
t.Fatalf("the file does not hold the fact: %v", given[0]["content"])
|
|
}
|
|
}
|
|
|
|
// **Asking for a fact the mesh does not have is refused here, not on a machine.** A daemon that
|
|
// starts, reads a file nobody wrote, and answers no queries is a much worse way to find out.
|
|
func TestAskingForAFactTheMeshDoesNotHaveIsRefused(t *testing.T) {
|
|
m := Manifest{Module: "dnsmasq", Facts: map[string]string{"the-weather": "/etc/weather"}}
|
|
_, err := FactsInto(m, Resolution{}, nil)
|
|
if err == nil {
|
|
t.Fatal("a module asked for something nobody computes and was given nothing, silently")
|
|
}
|
|
for _, known := range []string{FactNodeNames, FactNodeZones} {
|
|
if !strings.Contains(err.Error(), known) {
|
|
t.Fatalf("the refusal does not say what would have worked: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// And a relative path is refused, or a module decides where the mesh writes on a machine.
|
|
func TestAFactMustBeAskedForAtAnAbsolutePath(t *testing.T) {
|
|
m := Manifest{Module: "dnsmasq", Facts: map[string]string{FactNodeNames: "etc/hosts"}}
|
|
if _, err := FactsInto(m, Resolution{}, nil); err == nil {
|
|
t.Fatal("a relative path was accepted")
|
|
}
|
|
}
|