mesh/merge-gate head 52600ec7bd61 merged into trunk cdcba9ca7375 — pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes wi…
mesh/repo-check head 52600ec7bd61 merged into trunk cdcba9ca7375 — pass: its merge-check.sh passed
mesh/delivery delivered
164 lines
6.5 KiB
Go
164 lines
6.5 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/declaration"
|
|
)
|
|
|
|
// Defends novox/hq ADR 0338 (issue 609): genesis raises its store as the postgres module's server, so
|
|
// the store step adopts it as it runs. Until then the bundle raised `mesh-store`, on another image, in a
|
|
// docker volume, and genesis stopped at step 17 of 24.
|
|
|
|
// theStoreGenesisRaises is the store container of the bundle as the installer applies it: rewritten for
|
|
// this machine's credentials, so its password is the file the module reads too.
|
|
func theStoreGenesisRaises(t *testing.T) *declaration.Container {
|
|
t.Helper()
|
|
r := producedBundle(t)
|
|
store, err := storeIn(r.Declaration)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return store
|
|
}
|
|
|
|
// The bundle's store is exactly the catalogue's postgres module's server, read from the module's own
|
|
// manifest (ADR 0338 §1): its name, its image (so a drift of either pin fails here), where its data is,
|
|
// and the file its superuser password is read from, with the environment that says so.
|
|
func TestTheBundleRaisesTheStoreAsTheCataloguesPostgresModuleDeclaresIt(t *testing.T) {
|
|
manifest := theCataloguesModule(t, "postgres")
|
|
var m struct {
|
|
Resources []struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
Image string `json:"image"`
|
|
Env map[string]string `json:"env"`
|
|
Ports []string `json:"ports"`
|
|
Volumes []string `json:"volumes"`
|
|
} `json:"resources"`
|
|
}
|
|
if err := json.Unmarshal(manifest, &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ours := theStoreGenesisRaises(t)
|
|
found := false
|
|
for _, r := range m.Resources {
|
|
if r.Type != "container" || r.ID != "server" {
|
|
continue
|
|
}
|
|
found = true
|
|
if ours.Name != r.Name {
|
|
t.Errorf("the bundle raises the store as %q; the module's server is %q", ours.Name, r.Name)
|
|
}
|
|
if ours.Image != r.Image {
|
|
t.Errorf("the bundle raises the store on %s; the module pins %s — the two pins move together", ours.Image, r.Image)
|
|
}
|
|
for _, key := range []string{"POSTGRES_PASSWORD_FILE", "PGDATA"} {
|
|
if ours.Env[key] != r.Env[key] {
|
|
t.Errorf("the bundle's store has %s=%q; the module's %q", key, ours.Env[key], r.Env[key])
|
|
}
|
|
}
|
|
if _, has := ours.Env["POSTGRES_PASSWORD"]; has {
|
|
t.Error("the bundle's store takes its password from its environment; the module reads a file")
|
|
}
|
|
// The module's own directory for its state is <data root>/<module>, where genesis keeps the
|
|
// superuser's file (StoreSuperuserFile).
|
|
var theirs []string
|
|
for _, v := range r.Volumes {
|
|
theirs = append(theirs, strings.ReplaceAll(v, "${dir:state}", "/var/lib/postgres"))
|
|
}
|
|
if strings.Join(ours.Volumes, " ") != strings.Join(theirs, " ") {
|
|
t.Errorf("the bundle's store mounts %v; the module's %v", ours.Volumes, theirs)
|
|
}
|
|
if strings.Join(ours.Ports, " ") != strings.Join(r.Ports, " ") {
|
|
t.Errorf("the bundle publishes the store on %v; the module on %v", ours.Ports, r.Ports)
|
|
}
|
|
// And the store step's own check agrees: by name and image, before anything is registered.
|
|
if err := serverMatchesFoundation(manifest, ours, "postgres"); err != nil {
|
|
t.Errorf("the store step would refuse the store the bundle raises: %v", err)
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("the postgres module declares no server container")
|
|
}
|
|
}
|
|
|
|
// The store's data directory is made by the bundle before the store starts, owned by the image's
|
|
// postgres user, as the module declares it (ADR 0338 §1, and the measurement under its §5).
|
|
func TestTheBundleMakesTheStoresDataDirectoryAsTheModuleDoes(t *testing.T) {
|
|
r := producedBundle(t)
|
|
at, store := -1, -1
|
|
for i, res := range r.Declaration.Resources {
|
|
if d, ok := res.(*declaration.Directory); ok && d.Path == StoreDataDir {
|
|
at = i
|
|
if d.Mode != "0700" || d.Owner != "999:70" {
|
|
t.Errorf("the store's data directory is made %s %s; the module makes it 0700 999:70", d.Mode, d.Owner)
|
|
}
|
|
}
|
|
if res.Identity() == StoreID {
|
|
store = i
|
|
}
|
|
}
|
|
if at < 0 || store < 0 || at > store {
|
|
t.Fatalf("the store's data directory %s is not made before the store (directory at %d, store at %d)", StoreDataDir, at, store)
|
|
}
|
|
}
|
|
|
|
// No step of the bundle and no step of the installer names `mesh-store`, which is the seat's name only
|
|
// (ADR 0338 §2): every step that reaches the store reaches the container the bundle raised.
|
|
func TestNoStepReachesTheStoreByTheSeatsName(t *testing.T) {
|
|
text := string(theRealBundle(t))
|
|
for _, by := range []string{`"in": "mesh-store"`, `container:mesh-store`, `exec mesh-store`, `"name": "mesh-store"`, `mesh-store-data`} {
|
|
if strings.Contains(text, by) {
|
|
t.Errorf("the bundle still reaches the store as %s", by)
|
|
}
|
|
}
|
|
store := theStoreGenesisRaises(t)
|
|
if foundationStore != store.Name {
|
|
t.Errorf("the packages step reaches the store as %q; the bundle raises it as %q", foundationStore, store.Name)
|
|
}
|
|
r := producedBundle(t)
|
|
for _, res := range r.Declaration.Resources {
|
|
if a, ok := res.(*declaration.Action); ok && a.In != "" && a.In != store.Name && strings.Contains(a.ID, "database") {
|
|
t.Errorf("the step %s reaches the store in %q", a.ID, a.In)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The installer's refusal to raise a store over data it cannot open looks where the store keeps its data
|
|
// (ADR 0338 §2): a cluster there and no credential is refused; the old docker volume alone is not.
|
|
func TestTheRefusalOverExistingDataLooksInTheStoresDirectory(t *testing.T) {
|
|
made := RootCredentials{Store: "s", Bus: "b", StoreMade: true}
|
|
machine := func(cluster, volume bool) Runner {
|
|
return func(_ context.Context, name string, args ...string) (string, error) {
|
|
joined := name + " " + strings.Join(args, " ")
|
|
switch {
|
|
case joined == "test -e "+StoreDataDir+"/pgdata/PG_VERSION":
|
|
if cluster {
|
|
return "", nil
|
|
}
|
|
case strings.HasPrefix(joined, "docker volume inspect"):
|
|
if volume {
|
|
return "[]", nil
|
|
}
|
|
}
|
|
return "", errors.New("exit status 1")
|
|
}
|
|
}
|
|
err := RefuseExistingServers(context.Background(), machine(true, false), made)
|
|
if err == nil || !strings.Contains(err.Error(), StoreDataDir) {
|
|
t.Fatalf("a cluster in %s and no credential was not refused naming it: %v", StoreDataDir, err)
|
|
}
|
|
if err := RefuseExistingServers(context.Background(), machine(false, true), made); err != nil {
|
|
t.Fatalf("a machine holding only the old volume was refused: %v", err)
|
|
}
|
|
if err := RefuseExistingServers(context.Background(), machine(true, false), RootCredentials{Store: "s"}); err != nil {
|
|
t.Fatalf("a kept credential was refused: %v", err)
|
|
}
|
|
}
|