Restart what reads a secret family member when the member is given again (issue 405)
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
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.
This commit is contained in:
@@ -1076,7 +1076,7 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
|
||||
// credential the mesh had replaced, because its manifest restarted it on its
|
||||
// environment file and nobody had thought to name the credential too. Composed here so
|
||||
// no manifest has to say it, for a container or a daemon that names the secret's path.
|
||||
if reads := secretsReadBy(copied, m); len(reads) > 0 {
|
||||
if reads := secretsReadBy(copied, m, with.Needed[m.Module]); len(reads) > 0 {
|
||||
copied["restart-on"] = withRestartOn(copied["restart-on"], reads)
|
||||
}
|
||||
// **A version prepares its state before it runs** (novox/hq ADR 0135). Derived from the
|
||||
@@ -2392,7 +2392,12 @@ func portOfEndpoint(values map[string]any, ports map[string]int) {
|
||||
// — named in its volumes, its environment or its env-files by the secret's placed path — as
|
||||
// restart-on ids. Nothing for other shapes, and nothing for a scheduled or run-once process, which
|
||||
// the host refuses a restart-on for (it runs again anyway, and reads the file afresh).
|
||||
func secretsReadBy(resource map[string]any, m Manifest) []string {
|
||||
//
|
||||
// **A member of a secret family given here is read the same way** (novox/hq issue 405, ADR 0283 decision 6):
|
||||
// it is an own secret placed at its own path, and a container that mounted it would keep the value it
|
||||
// started with when the operator gives it again. given is the module's own secrets sealed to this
|
||||
// machine; a member not given is no file, so nothing restarts on it.
|
||||
func secretsReadBy(resource map[string]any, m Manifest, given map[string]string) []string {
|
||||
kind := fmt.Sprint(resource["type"])
|
||||
if kind != "container" && kind != "process" {
|
||||
return nil
|
||||
@@ -2404,9 +2409,18 @@ func secretsReadBy(resource map[string]any, m Manifest) []string {
|
||||
for _, key := range []string{"volumes", "env", "env-file"} {
|
||||
mentioned = append(mentioned, stringsIn(resource[key])...)
|
||||
}
|
||||
paths := map[string]string{}
|
||||
for name, own := range m.OwnSecrets.Plain() {
|
||||
paths[name] = own.Path
|
||||
}
|
||||
for name, sealed := range given {
|
||||
if own, family, ok := m.OwnSecrets.Lookup(name); ok && family != "" && sealed != "" {
|
||||
paths[name] = own.Path
|
||||
}
|
||||
}
|
||||
var out []string
|
||||
for _, name := range sortedKeys(m.OwnSecrets.Plain()) {
|
||||
path := m.OwnSecrets[name].Path
|
||||
for _, name := range sortedKeys(paths) {
|
||||
path := paths[name]
|
||||
if path == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -50,3 +50,41 @@ func TestAContainerReadingAnOwnSecretIsRestartedWhenItChanges(t *testing.T) {
|
||||
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"])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user