A roster fact used to be a name from a closed list, each formatted in Go here — node-names as a hosts file, node-zones as a resolver's zones. Every new consumer (ssh's known_hosts, an authorized_keys) meant another formatter in the control plane, in the consumer's own configuration language. Now a fact is a path and a Go template over the roster view (this node, the suffix, and every served name vs the machines). The mesh owns the data; the module owns the format. /etc/hosts is a template on the network module; dnsmasq's zones move to dnsmasq. The controller renders and reads neither. WireGuard stays a computed generator: the overlay is the substrate delivery rides on, and its config is topology, not a roster projection. Output is byte-for-byte unchanged, pinned by the hosts golden tests and the resolver tests that compose the real dnsmasq manifest.
53 lines
2.3 KiB
Go
53 lines
2.3 KiB
Go
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)
|
|
}
|
|
}
|