Merge remote-tracking branch 'origin/main' into feat/roster-facts-are-templates

# Conflicts:
#	internal/catalogue/facts.go
#	internal/catalogue/facts_test.go
This commit is contained in:
2026-09-27 01:41:23 +02:00
7 changed files with 168 additions and 58 deletions
-52
View File
@@ -1,52 +0,0 @@
package overlay
import (
"testing"
"github.com/novox/mesh-controller/internal/catalogue"
)
// The network module's `/etc/hosts` is a roster template like any module's (novox/hq: the graph is
// the control plane's, the format is the module's). These pin the format that used to be a Go
// formatter in the control plane, so the file a machine gets does not change with the mechanism:
// the loopback floor, the Debian self-name line, the machine's own line marked and at its mesh
// address, one line per machine, every served name (issue 111).
func hostsFor(t *testing.T, node string, every map[string]string) string {
t.Helper()
m := catalogue.Manifest{Module: "net", Facts: map[string]catalogue.RosterFile{
"node-names": {Path: "/etc/hosts", Template: hostsTemplate},
}}
given, err := catalogue.FactsInto(m, catalogue.Resolution{Node: node}, every, every, "")
if err != nil {
t.Fatal(err)
}
return given[0]["content"].(string)
}
func TestTheHostsFileIsThisExactly(t *testing.T) {
got := hostsFor(t, "homer", map[string]string{"homer.internal": "10.42.0.1", "marge.internal": "10.42.0.2"})
want := "# Generated by the mesh. Do not edit — this file is replaced whenever a machine\n" +
"# joins or leaves, and an edit would survive until then and vanish.\n\n" +
"127.0.0.1\tlocalhost\n" +
"::1\t\tlocalhost ip6-localhost ip6-loopback\n" +
"127.0.1.1\thomer\n\n" +
"10.42.0.1\thomer.internal\thomer\t# this machine\n" +
"10.42.0.2\tmarge.internal\tmarge\n"
if got != want {
t.Fatalf("the hosts file changed with the mechanism:\ngot:\n%q\nwant:\n%q", got, want)
}
}
// With no node named there is no `127.0.1.1` self-line — but the blank line before the machines
// stays, exactly as the old formatter wrote it unconditionally.
func TestTheHostsFileWithoutASelfNameKeepsItsShape(t *testing.T) {
got := hostsFor(t, "", map[string]string{"homer.internal": "10.42.0.1"})
want := "# Generated by the mesh. Do not edit — this file is replaced whenever a machine\n" +
"# joins or leaves, and an edit would survive until then and vanish.\n\n" +
"127.0.0.1\tlocalhost\n" +
"::1\t\tlocalhost ip6-localhost ip6-loopback\n\n" +
"10.42.0.1\thomer.internal\thomer\n"
if got != want {
t.Fatalf("the hosts file without a self-name changed shape:\ngot:\n%q\nwant:\n%q", got, want)
}
}
+4 -3
View File
@@ -24,9 +24,10 @@ const DefaultSuffix = "internal"
//
// This is not the `/etc/hosts` floor the design removes. That floor existed because a node had to
// reach the mesh's database before its own DNS worked — a fallback for a circularity, and the
// circularity is gone. This is the mechanism itself: the complete set of names in this mesh,
// generated whole and owned by the mesh (novox/hq ADR 0011), rather than a patch written
// underneath something else.
// circularity is gone. This is the mechanism itself: the complete set of names in this mesh
// (novox/hq ADR 0011). Written as a marked region *into* the file rather than as the file: the
// rest of it — `localhost`, the machine's own name, other tools' blocks — is the machine's, and
// writing it whole replaced all of that (novox/hq issue 128).
//
// A file rather than a resolver daemon, deliberately, for now: it works on every Linux, needs no
// package, and has no failure mode of its own. A daemon becomes necessary when names are wanted