mesh/merge-gate head ba2806b985ce merged into trunk 9497dda9291c — pass: the change touches no module of the mesh's graph
mesh/repo-check head ba2806b985ce merged into trunk 9497dda9291c — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript)…
mesh/delivery delivered
The genesis bundle raises the store as the postgres module's server: its pgvector image, its data in a directory made before the store starts and owned as the module declares it, its password from a file. The decision names one measurement before it is relied on: that image, on such a directory, empty, initialises a store that answers with that password. The image and the owner are read from the catalogue's manifest, so the measurement moves with the module's pin.
134 lines
4.7 KiB
Go
134 lines
4.7 KiB
Go
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")
|
|
}
|