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
secretsReadBy looked only at secrets declared by name, so a container mounting a member kept the value it started with.
91 lines
4.6 KiB
Go
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"])
|
|
}
|
|
}
|