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") }