Reconcile with hq 128: hosts template is the region form, node-names is shared
The merge commit took only the staged index; these reconciliation edits sat unstaged in the working tree. Integrate the template mechanism with #79's region write (hq 128): RosterFile gains Shared, FactsInto sets into:block for a shared fact, /etc/hosts becomes the region form (no floor) and node-names is marked shared. Without this the merge would have regressed /etc/hosts back to a whole-file write, replacing the operator's own lines.
This commit is contained in:
@@ -39,6 +39,13 @@ type RosterFile struct {
|
||||
// Template is the module's format, a Go text/template over the rosterView. It is the module's,
|
||||
// not the mesh's: the mesh renders it and does not read it.
|
||||
Template string `json:"template"`
|
||||
// Shared is whether the file the fact goes to belongs to the machine rather than the mesh. When
|
||||
// it does, the mesh owns only a marked region of it and keeps the rest byte for byte (novox/hq
|
||||
// issue 128) — a hosts file is shared, since the distribution's `localhost`, the operator's own
|
||||
// lines and other tools' blocks live there too; a resolver's zones file is not, the mesh owns it
|
||||
// whole. A property of the fact, not of the path: the format determines whether the file is
|
||||
// wholly the mesh's, not where a module happened to ask for it.
|
||||
Shared bool `json:"shared,omitempty"`
|
||||
}
|
||||
|
||||
// rosterView is what a RosterFile's template sees. A closed shape — a template referencing a field
|
||||
@@ -91,10 +98,19 @@ func FactsInto(m Manifest, r Resolution, every, machines map[string]string, suff
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s cannot render %q: %w", m.Module, name, err)
|
||||
}
|
||||
out = append(out, map[string]any{
|
||||
file := map[string]any{
|
||||
"id": "fact-" + name, "type": "file", "path": fact.Path, "mode": "0644",
|
||||
"content": content,
|
||||
})
|
||||
}
|
||||
if fact.Shared {
|
||||
// The host owns only the lines between `# BEGIN mesh <id>` and `# END mesh <id>` and
|
||||
// keeps the rest of the file byte for byte; undeclared, the region goes and nothing else
|
||||
// does (novox/hq issue 128). Every node on the private network receives this, so every
|
||||
// node's host — the controller's own machine included — must be block-aware before a
|
||||
// controller emitting it is rolled out: the order ADR 0102 set for `into: json`.
|
||||
file["into"] = "block"
|
||||
}
|
||||
out = append(out, file)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -33,6 +33,32 @@ func TestAModuleIsGivenTheFileItAskedFor(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// **A shared fact is written into a region of the machine's file, not over it** (novox/hq issue
|
||||
// 128). A hosts file is the machine's — its localhost, the operator's lines, other tools' blocks —
|
||||
// so the mesh owns only a marked region (`into: block`); a resolver's zones file is the mesh's
|
||||
// whole, and carries no `into`.
|
||||
func TestASharedFactIsWrittenIntoARegion(t *testing.T) {
|
||||
roster := map[string]string{"homer.internal": "10.42.0.1"}
|
||||
m := Manifest{Module: "net", Facts: map[string]RosterFile{
|
||||
"node-names": {Path: "/etc/hosts", Template: "{{range .Names}}{{.FQDN}}\n{{end}}", Shared: true},
|
||||
"node-zones": {Path: "/etc/zones", Template: "{{range .Machines}}{{.FQDN}}\n{{end}}"},
|
||||
}}
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, roster, roster, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
by := map[string]map[string]any{}
|
||||
for _, f := range given {
|
||||
by[f["path"].(string)] = f
|
||||
}
|
||||
if by["/etc/hosts"]["into"] != "block" {
|
||||
t.Fatalf("a shared fact is not written into a region, so the mesh writes the file whole: %v", by["/etc/hosts"])
|
||||
}
|
||||
if _, has := by["/etc/zones"]["into"]; has {
|
||||
t.Fatalf("an unshared fact was written into a region, so the mesh does not own its own file whole: %v", by["/etc/zones"])
|
||||
}
|
||||
}
|
||||
|
||||
// **The format is the module's — the mesh renders whatever template it gives.** The same roster
|
||||
// through two templates is two entirely different files, and the control plane reads neither.
|
||||
func TestTheFormatIsTheModulesOwn(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user