gitea: fill repositories.json through the provisioner client, so an approved value lands at once (issue 1128)
mesh/merge-gate head e75e793a4b3b merged into trunk cfb90d7b2f96 — pass: builds gitea → novox; no bus step; every machine composes with the change as it di…
mesh/repo-check head e75e793a4b3b merged into trunk cfb90d7b2f96 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered

The file was merged by the controller (merge, takes-config), which writes a
config change only on the node's next send (ADR 0348 §10): the operator's
Approve of the mesh-delivery repository set the value and nothing made it.
A plain template saying ${config:repositories} is filled by the provisioner
client on config-changed, and the node-engine reruns the run-once program
that names the file under restart-on.
This commit is contained in:
2026-10-18 17:40:59 +02:00
parent cfb90d7b2f
commit e75e793a4b
3 changed files with 27 additions and 11 deletions
@@ -3,6 +3,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"os" "os"
"path/filepath"
"strings" "strings"
"testing" "testing"
) )
@@ -35,14 +36,33 @@ func TestManifestDeclaresTheValueAndItsTrigger(t *testing.T) {
} }
var file, process map[string]any var file, process map[string]any
for _, r := range m.Resources { for _, r := range m.Resources {
for _, k := range strs(r["takes-config"]) { if r["id"] == "repositories-file" {
if k == ConfigKey { file = r
file = r
}
} }
} }
if file == nil || file["type"] != "file" { if file == nil || file["type"] != "file" {
t.Fatalf("no file takes the value %s", ConfigKey) t.Fatalf("no file repositories-file")
}
// 1128/AC1: a plain template the provisioner client fills, so an approved value lands at once: a file the
// controller merges (merge, takes-config) takes a config change only on the node's next send (ADR 0348 §10).
if file["merge"] != nil || file["takes-config"] != nil {
t.Errorf("repositories-file is merged by the controller (merge %v, takes-config %v), want a plain template",
file["merge"], file["takes-config"])
}
text, err := os.ReadFile("../../" + file["template"].(string))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(text), "${config:"+ConfigKey+"}") {
t.Fatalf("the template %s does not say ${config:%s}: %s", file["template"], ConfigKey, text)
}
filled := filepath.Join(t.TempDir(), "repositories.json")
value := `{"mesh-delivery":{"private":false,"description":"d"}}`
if err := os.WriteFile(filled, []byte(strings.ReplaceAll(string(text), "${config:"+ConfigKey+"}", value)), 0o600); err != nil {
t.Fatal(err)
}
if v, err := ReadValue(filled); err != nil || len(v) != 1 || v["mesh-delivery"].Description != "d" {
t.Errorf("the filled template reads as %v, %v; want mesh-delivery", v, err)
} }
for _, r := range m.Resources { for _, r := range m.Resources {
if r["type"] == "process" && has(strs(r["restart-on"]), file["id"].(string)) { if r["type"] == "process" && has(strs(r["restart-on"]), file["id"].(string)) {
+1 -1
View File
@@ -1 +1 @@
{} {"repositories": ${config:repositories}}
+1 -5
View File
@@ -322,11 +322,7 @@
"type": "file", "type": "file",
"path": "${dir:mesh-state}/repositories.json", "path": "${dir:mesh-state}/repositories.json",
"mode": "0600", "mode": "0600",
"template": "files/repositories.json", "template": "files/repositories.json"
"merge": "json",
"takes-config": [
"repositories"
]
}, },
{ {
"id": "consumers", "id": "consumers",