From 28b7fb81ba903277fb33da29d9bd98d0e9b1bac1 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 22 Sep 2026 17:51:38 +0200 Subject: [PATCH] Write the registry's trust into the runtime's file and reload the runtime instead of restarting it; prefix reload-on like restart-on (hq ADR 0102) --- internal/catalogue/computed_test.go | 45 +++++++++++++++++++++++++++++ internal/catalogue/declaration.go | 5 ++++ internal/overlay/generator.go | 18 +++++++----- internal/overlay/generator_test.go | 14 +++++++-- 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/internal/catalogue/computed_test.go b/internal/catalogue/computed_test.go index 55167db..3e74716 100644 --- a/internal/catalogue/computed_test.go +++ b/internal/catalogue/computed_test.go @@ -1,6 +1,7 @@ package catalogue import ( + "fmt" "strings" "testing" ) @@ -157,3 +158,47 @@ func TestTwoWaysToBeOnAPrivateNetworkRefuseAndNameBoth(t *testing.T) { } } } + +// reloading answers the runtime's trust as the networking module does (novox/hq ADR 0102): a file +// written into, and the runtime reloaded on it. +type reloading struct{} + +func (reloading) Resources(node string) ([]map[string]any, bool, error) { + return []map[string]any{ + {"id": "registry-trust", "type": "file", "path": "/etc/docker/daemon.json", + "merge": MergeJSON, "into": "json", "content": `{"insecure-registries":["r:5000"]}`}, + {"id": "registry-trust-reload", "type": "service", "unit": "docker.service", + "state": "running", "reload-on": []string{"registry-trust"}}, + }, true, nil +} + +func TestWhatAServiceIsReloadedOnIsNamedAsTheHostWillSeeIt(t *testing.T) { + // Ids are prefixed with their module on the way out. An unprefixed reload-on would name a + // resource the host never sees, and the runtime would never be reloaded for its trust. + got, err := Resolve(computedShelf(), []string{"mesh-network"}, workstation(), World{}) + if err != nil { + t.Fatal(err) + } + out, err := got.Declaration(Rendering{Generators: map[string]Generator{"mesh-network": reloading{}}}) + if err != nil { + t.Fatal(err) + } + var file, service map[string]any + for _, r := range out { + switch r["type"] { + case "file": + file = r + case "service": + service = r + } + } + if file == nil || service == nil { + t.Fatalf("got %v", out) + } + if file["into"] != "json" { + t.Errorf("into did not reach the host: %v", file) + } + if want := "[" + file["id"].(string) + "]"; fmt.Sprint(service["reload-on"]) != want { + t.Errorf("reload-on names %v, the file is %v", service["reload-on"], file["id"]) + } +} diff --git a/internal/catalogue/declaration.go b/internal/catalogue/declaration.go index 2e07ddf..59281f1 100644 --- a/internal/catalogue/declaration.go +++ b/internal/catalogue/declaration.go @@ -559,6 +559,11 @@ func (r Resolution) compose(with Rendering, owner map[string]string) ([]map[stri if renamed := reflectsRenamed(m.Module, resource["restart-on"]); renamed != nil { copied["restart-on"] = renamed } + // And what it is reloaded on, by the same rule (novox/hq ADR 0102): an id left + // unprefixed matches nothing, and the service is never reloaded. + if renamed := reflectsRenamed(m.Module, resource["reload-on"]); renamed != nil { + copied["reload-on"] = renamed + } owner[fmt.Sprint(copied["id"])] = m.Module out = append(out, copied) } diff --git a/internal/overlay/generator.go b/internal/overlay/generator.go index 54995ed..c30eb67 100644 --- a/internal/overlay/generator.go +++ b/internal/overlay/generator.go @@ -140,18 +140,20 @@ func (g *Generator) Resources(node string) ([]map[string]any, bool, error) { } resources = append(resources, map[string]any{ - // Merged, not owned: the runtime's daemon file is the machine's, and this states - // one fact into it. The registry speaks plain HTTP because every path to it is - // already inside the overlay's encryption (ADR 0082) — this line is the runtime - // being told what the mesh already means. + // Written into, not over (novox/hq ADR 0102): the runtime's daemon file is the + // machine's — its data directory, its logging, whatever a predecessor set — and + // this states one fact in it. The host sets this key and keeps every other. + // ("merge" is the operator's settings merged into this content; "into" is the + // content written into the machine's file.) The registry speaks plain HTTP + // because every path to it is already inside the overlay's encryption (ADR 0082). "id": "registry-trust", "type": "file", "path": "/etc/docker/daemon.json", - "content": string(trust) + "\n", "mode": "0644", "merge": "json", + "content": string(trust) + "\n", "mode": "0644", "merge": "json", "into": "json", }, map[string]any{ - // The runtime reloads nothing for this setting, so it is restarted when the fact - // changes — once, at joining, before the machine runs anything that would mind. + // Reloaded, not restarted: the runtime re-reads its trusted registries on a reload, + // and a restart stops every container on the machine (measured; ADR 0102). "id": "registry-trust-reload", "type": "service", "unit": "docker.service", - "state": "running", "restart-on": []string{"registry-trust"}, + "state": "running", "reload-on": []string{"registry-trust"}, }) } return resources, true, nil diff --git a/internal/overlay/generator_test.go b/internal/overlay/generator_test.go index 0cc935a..2a54332 100644 --- a/internal/overlay/generator_test.go +++ b/internal/overlay/generator_test.go @@ -1,6 +1,7 @@ package overlay import ( + "fmt" "strings" "testing" ) @@ -44,13 +45,20 @@ func TestTheNetworkCarriesRegistryTrust(t *testing.T) { if file == nil || service == nil { t.Fatalf("the trust file or its reload is missing: %v", trusted) } - if file["path"] != "/etc/docker/daemon.json" || file["merge"] != "json" { - t.Fatalf("the trust is not a merged daemon.json: %v", file) + if file["path"] != "/etc/docker/daemon.json" || file["merge"] != "json" || file["into"] != "json" { + t.Fatalf("the trust is not written into daemon.json (ADR 0102): %v", file) } if content, _ := file["content"].(string); !strings.Contains(content, `"anchor.internal:5000"`) { t.Fatalf("the trust does not name the store: %v", file["content"]) } if service["unit"] != "docker.service" { - t.Fatalf("the reload does not restart the runtime: %v", service) + t.Fatalf("the reload is not the runtime's: %v", service) + } + // Reloaded, never restarted: a restart stops every container on the machine (ADR 0102). + if _, restarts := service["restart-on"]; restarts { + t.Fatalf("the runtime is restarted for its trust: %v", service) + } + if fmt.Sprint(service["reload-on"]) != "[registry-trust]" { + t.Fatalf("the runtime is not reloaded for its trust: %v", service) } }