Files
mesh-controller/internal/catalogue/secret_restart_test.go
T
jschoubben d6b867a87a
mesh/merge-gate pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without …
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestTheSecondControllerWaitsAndTakesAHigherEpochOnHandover (4.67s)
mesh/delivery superseded: a newer head of the same pull request
Restart what reads a secret family member when the member is given again (issue 405)
secretsReadBy looked only at secrets declared by name, so a container
mounting a member kept the value it started with.
2026-10-11 02:51:11 +02:00

91 lines
4.6 KiB
Go

package catalogue
import (
"reflect"
"strings"
"testing"
)
// What reads one of a module's own secrets is restarted when the secret changes (novox/hq issue 203,
// issue 206): the build machine kept an hour-old credential open because its manifest restarted it
// on its environment file alone. Composed, so a manifest need not say it; a scheduled process is
// left alone, because the host refuses a restart-on for one and it reads the file afresh each run.
func TestAContainerReadingAnOwnSecretIsRestartedWhenItChanges(t *testing.T) {
m := Manifest{Module: "agent", Version: "1",
OwnSecrets: OwnSecrets{"broker": {Path: "/var/lib/mesh/agent/broker"}},
Resources: []map[string]any{
{"id": "mesh-state", "type": "directory", "path": "/var/lib/mesh/agent", "mode": "0700"},
{"id": "settings", "type": "file", "path": "/var/lib/mesh/agent/agent.env", "mode": "0600", "content": "A=1\n"},
{"id": "server", "type": "container", "name": "agent", "network": "host",
"image": "registry.example/agent@sha256:" + strings.Repeat("a", 64),
"volumes": []any{"/var/lib/mesh/agent:/run/mesh:ro", "/var/lib/mesh/agent/broker:/run/mesh/broker:ro"},
"env-file": []any{"/var/lib/mesh/agent/agent.env"},
"restart-on": []any{"settings"}},
{"id": "nightly", "type": "container", "name": "agent-nightly", "schedule": "0 3 * * *",
"image": "registry.example/agent@sha256:" + strings.Repeat("a", 64),
"volumes": []any{"/var/lib/mesh/agent/broker:/run/mesh/broker:ro"}},
{"id": "other", "type": "container", "name": "agent-other",
"image": "registry.example/agent@sha256:" + strings.Repeat("a", 64)},
}}
got, err := Resolve(shelf(m), []string{m.Module},
Node{Name: "anchor", At: "10.0.0.1", Capabilities: map[string]bool{"container-runtime": true}}, World{})
if err != nil {
t.Fatal(err)
}
out, err := got.Declaration(Rendering{Needed: map[string]map[string]string{"agent": {"broker": "SEALED"}}})
if err != nil {
t.Fatal(err)
}
by := map[string]map[string]any{}
for _, r := range out {
by[r["id"].(string)] = r
}
if want := []any{"agent.settings", "agent.needs-broker"}; !reflect.DeepEqual(by["agent.server"]["restart-on"], want) {
t.Fatalf("the server reads the credential and is not restarted on it: %v", by["agent.server"]["restart-on"])
}
if _, has := by["agent.nightly"]["restart-on"]; has {
t.Fatalf("a scheduled container was given a restart-on, which the host refuses: %v", by["agent.nightly"]["restart-on"])
}
if _, has := by["agent.other"]["restart-on"]; has {
t.Fatalf("a container that reads no secret was given one to restart on: %v", by["agent.other"]["restart-on"])
}
}
// A member of a secret family is an own secret too (novox/hq issue 405, ADR 0283 decision 6): a container that
// reads a member given is restarted when it changes, as for a secret declared by name. A member not given is no
// file, so nothing is restarted on it.
func TestAContainerReadingAFamilyMemberIsRestartedWhenItChanges(t *testing.T) {
m := Manifest{Module: "mounts", Version: "1",
OwnSecrets: OwnSecrets{"smb-password-*": {Path: "/var/lib/mesh/mounts/smb-password-*.secret", IssuedBy: IssuedOutside}},
Resources: []map[string]any{
{"id": "games", "type": "container", "name": "mounts-games", "network": "host",
"image": "registry.example/mounts@sha256:" + strings.Repeat("a", 64),
"volumes": []any{"/var/lib/mesh/mounts/smb-password-games.secret:/run/password:ro"}},
{"id": "library", "type": "container", "name": "mounts-library", "network": "host",
"image": "registry.example/mounts@sha256:" + strings.Repeat("a", 64),
"volumes": []any{"/var/lib/mesh/mounts/smb-password-library.secret:/run/password:ro"}},
}}
got, err := Resolve(shelf(m), []string{m.Module},
Node{Name: "anchor", At: "10.0.0.1", Capabilities: map[string]bool{"container-runtime": true}}, World{})
if err != nil {
t.Fatal(err)
}
out, err := got.Declaration(Rendering{Needed: map[string]map[string]string{"mounts": {"smb-password-games": "SEALED"}}})
if err != nil {
t.Fatal(err)
}
by := map[string]map[string]any{}
for _, r := range out {
by[r["id"].(string)] = r
}
if want := []any{"mounts.needs-smb-password-games"}; !reflect.DeepEqual(by["mounts.games"]["restart-on"], want) {
t.Fatalf("a container reading a member given is not restarted on it: %v", by["mounts.games"]["restart-on"])
}
if _, placed := by["mounts.needs-smb-password-games"]; !placed {
t.Fatalf("the member given is not placed, so a restart-on names nothing: %v", out)
}
if _, has := by["mounts.library"]["restart-on"]; has {
t.Fatalf("a container reading a member not given was given a restart-on naming nothing: %v", by["mounts.library"]["restart-on"])
}
}