Measure that the postgres module's image initialises where genesis raises the store (issue 609, hq ADR 0338 §5) #99

Merged
mesh-admin merged 1 commits from fix/609-the-store-genesis-raises-initialises into main 2026-10-13 08:55:43 +00:00
2 changed files with 135 additions and 1 deletions
+2 -1
View File
@@ -109,6 +109,7 @@ func (d *Docker) RemoveNetwork(ctx context.Context, id string) {
type Run struct {
Image string
Cmd []string
Env []string
Binds []string
Network string
DNS []string
@@ -138,7 +139,7 @@ func (d *Docker) Start(ctx context.Context, r Run) (string, string, error) {
path += "?name=" + url.QueryEscape(r.Name)
}
if err := d.call(ctx, http.MethodPost, path, map[string]any{"Image": r.Image, "Cmd": r.Cmd,
"Labels": labels, "HostConfig": host}, &made); err != nil {
"Env": r.Env, "Labels": labels, "HostConfig": host}, &made); err != nil {
return "", "", err
}
if err := d.call(ctx, http.MethodPost, "/containers/"+made.ID+"/start", nil, nil); err != nil {
+133
View File
@@ -0,0 +1,133 @@
package replays
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// THE STORE GENESIS RAISES INITIALISES WHERE THE POSTGRES MODULE KEEPS IT (novox/hq ADR 0338 §5, issue 609).
//
// Genesis raises the store as the catalogue's postgres module declares its server: the module's image, its
// data in a directory made before the store starts and owned 999:70 as the module declares it, and its
// superuser password read from a file. The measurement the decision names: the module's image, started on
// such a directory, empty, initialises a store that answers with that password. The image is read from the
// catalogue's manifest, so the measurement moves with the module's pin.
//
// Needs a container runtime (its socket) and the catalogue (MESH_REPLAY_CATALOGUE, or the checkout beside
// the lab); skipped aloud without either.
func TestTheStoreGenesisRaisesInitialisesOnTheDirectoryGenesisMakes(t *testing.T) {
catalogue := os.Getenv("MESH_REPLAY_CATALOGUE")
if catalogue == "" {
catalogue = filepath.Join("..", "..", "mesh-catalog")
}
raw, err := os.ReadFile(filepath.Join(catalogue, "modules", "postgres", "module.json"))
if err != nil {
t.Skipf("no catalogue to read the store's image from (%v); MESH_REPLAY_CATALOGUE names one", err)
}
docker, ok := DockerFromEnv()
if !ok {
t.Skip("no container runtime: the store runs in a container")
}
var m struct {
Resources []struct {
ID, Type, Path, Owner, Mode, Image string
Env map[string]string
} `json:"resources"`
}
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
var image, pgdata, owner, mode string
for _, r := range m.Resources {
switch {
case r.Type == "container" && r.ID == "server":
image, pgdata = r.Image, r.Env["PGDATA"]
case r.Type == "directory" && r.ID == "store-data":
owner, mode = r.Owner, r.Mode
}
}
if image == "" || pgdata == "" || owner == "" {
t.Fatalf("the postgres module names no server image (%q), PGDATA (%q) or data directory owner (%q)", image, pgdata, owner)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
// Under the working directory: a check's workspace is mounted into its containers at the same path,
// so the runtime binds what this test made.
here, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
dir, err := os.MkdirTemp(here, "store-measure-")
if err != nil {
t.Fatal(err)
}
for _, img := range []string{"alpine:3.20", image} {
if err := docker.Pull(ctx, img); err != nil {
t.Fatal(err)
}
}
// The directory as genesis makes it, and the password file as genesis writes it: as root, a helper
// container doing what the node-engine does on the machine.
prep, _, err := docker.Start(ctx, Run{Image: "alpine:3.20", Binds: []string{dir + ":/w"}, Cmd: []string{"sh", "-c",
"mkdir -p /w/store /w/state && chown " + owner + " /w/store && chmod " + orMode(mode) + " /w/store && " +
"printf measured-pw > /w/state/superuser.secret && chmod 0600 /w/state/superuser.secret"}})
if err != nil {
t.Fatal(err)
}
if code, said, err := docker.Wait(ctx, prep); err != nil || code != 0 {
t.Fatalf("the directory could not be made as genesis makes it: %v %d %s", err, code, said)
}
docker.Remove(ctx, prep)
t.Cleanup(func() {
clean, _, err := docker.Start(context.Background(), Run{Image: "alpine:3.20", Binds: []string{dir + ":/w"},
Cmd: []string{"sh", "-c", "rm -rf /w/store /w/state"}})
if err == nil {
_, _, _ = docker.Wait(context.Background(), clean)
docker.Remove(context.Background(), clean)
}
_ = os.RemoveAll(dir)
})
store, _, err := docker.Start(ctx, Run{Image: image,
Env: []string{"POSTGRES_PASSWORD_FILE=/run/secrets/superuser", "PGDATA=" + pgdata},
Binds: []string{dir + "/store:/var/lib/postgresql/data", dir + "/state/superuser.secret:/run/secrets/superuser:ro"}})
if err != nil {
t.Fatal(err)
}
defer docker.Remove(context.Background(), store)
answered := false
for i := 0; i < 90 && !answered; i++ {
// Over TCP, and with the password: the temporary server initdb runs listens on the socket only.
code, err := docker.Exec(ctx, store, "sh", "-c",
"PGPASSWORD=measured-pw psql -h 127.0.0.1 -U postgres -tAc 'select 1' | grep -qx 1")
answered = err == nil && code == 0
if !answered {
time.Sleep(2 * time.Second)
}
}
if !answered {
t.Fatalf("%s did not initialise a store that answers on a directory made %s %s, as genesis makes it:\n%s",
image, owner, orMode(mode), lastLinesOf(docker.Logs(ctx, store), 15))
}
}
func orMode(m string) string {
if m == "" {
return "0700"
}
return m
}
func lastLinesOf(s string, n int) string {
lines := strings.Split(strings.TrimRight(s, "\n"), "\n")
if len(lines) > n {
lines = lines[len(lines)-n:]
}
return strings.Join(lines, "\n")
}