Files
mesh-catalog/modules/mesh-build-go/cmd/mesh-build-go/store.go
T

101 lines
4.4 KiB
Go

package main
// The integration level's throwaway store (novox/hq ADR 0470 §1; issue 1486): one postgres server of the installed
// package for each run, `initdb` into the run's own directory, listening on a unix socket in that directory and on no
// TCP address, as the module's account, with durability off as today's check sets it.
import (
"context"
"fmt"
"net/url"
"os"
"path/filepath"
"regexp"
"strconv"
"time"
"git.novox.be/novox/mesh-sdk/go/buildstep"
)
// storeStopBound is how long stopping the store is given: it runs after the level, whatever the level did.
const storeStopBound = 30 * time.Second
var postgresVersion = regexp.MustCompile(`PostgreSQL\)\s+(\d+)`)
// postgresMajor is the major version of the installed postgres: `postgres --version` ("postgres (PostgreSQL) 18.6").
func (m *Module) postgresMajor(ctx context.Context) (int, error) {
r := m.Run(ctx, nil, "postgres", "--version")
if r.Status != 0 || r.Err != "" {
return 0, fmt.Errorf("`postgres --version`: %s", firstLine(r.Err+" "+r.Stderr))
}
f := postgresVersion.FindStringSubmatch(r.Stdout)
if f == nil {
return 0, fmt.Errorf("`postgres --version` answered %q", firstLine(r.Stdout))
}
return strconv.Atoi(f[1])
}
// checkPostgres refuses the integration level when the installed postgres major is not the recipe's, naming both
// (ADR 0470 §4); the unit level is not refused. It answers the major it found.
func (m *Module) checkPostgres(ctx context.Context) (int, error) {
major, err := m.postgresMajor(ctx)
if err != nil {
return 0, &buildstep.Refusal{Text: fmt.Sprintf("the installed postgres version could not be read: %v", err), Want: strconv.Itoa(m.Recipe.PostgresMajor)}
}
if major != m.Recipe.PostgresMajor {
return major, &buildstep.Refusal{Text: fmt.Sprintf("postgres %d installed, the integration level is built for postgres %d", major, m.Recipe.PostgresMajor),
Want: strconv.Itoa(m.Recipe.PostgresMajor), Have: strconv.Itoa(major)}
}
return major, nil
}
// socketLimit is the longest path of a socket postgres accepts, with the file's name: it refuses 108 bytes and over.
const socketLimit = 100
// socketName is the file postgres makes in the socket directory.
const socketName = "/.s.PGSQL.5432"
// socketDir makes the directory of the store's socket: under the run when the socket's path stays under socketLimit,
// else a short directory of its own under /tmp (answering short, so the caller removes it). A unix socket's path is
// bounded far below a file's, and a work directory deep in a data root is over it.
func socketDir(run string) (dir string, short bool, err error) {
dir = filepath.Join(run, "pg", "socket")
if len(dir)+len(socketName) < socketLimit {
return dir, false, os.MkdirAll(dir, 0o700)
}
dir, err = os.MkdirTemp("/tmp", "pg")
return dir, true, err
}
// storeSettings are the server's own settings: no TCP address, a socket directory, and no durability.
func storeOptions(socket string) string {
return "-k " + socket + " -c listen_addresses='' -c fsync=off -c synchronous_commit=off -c full_page_writes=off"
}
// startStore makes and starts the run's postgres in run/pg and answers the address tests are given as
// MESH_TEST_POSTGRES, and what stops it. The address names the socket directory percent-encoded, as the controller's
// tests cut the URL at its last slash to name their own databases.
func (m *Module) startStore(ctx context.Context, run string) (string, func(), error) {
data := filepath.Join(run, "pg", "data")
socket, short, err := socketDir(run)
if err != nil {
return "", nil, err
}
stop := func() {
if short {
defer os.RemoveAll(socket)
}
sctx, cancel := context.WithTimeout(context.Background(), storeStopBound)
defer cancel()
m.Run(sctx, nil, "pg_ctl", "-D", data, "-m", "immediate", "-w", "stop")
}
if r := m.Run(ctx, nil, "initdb", "-D", data, "-U", "postgres", "--auth=trust", "-E", "UTF8", "--no-locale"); r.Status != 0 || r.Err != "" {
return "", nil, fmt.Errorf("initdb: %s", firstLine(r.Err+" "+r.Stderr+" "+r.Stdout))
}
if r := m.Run(ctx, nil, "pg_ctl", "-D", data, "-l", filepath.Join(run, "pg", "log"), "-w", "-t", "30", "-o", storeOptions(socket), "start"); r.Status != 0 || r.Err != "" {
stop() // a server that was half started is stopped
return "", nil, fmt.Errorf("pg_ctl start: %s", firstLine(r.Err+" "+r.Stderr+" "+r.Stdout))
}
return "postgres://postgres@/postgres?host=" + url.QueryEscape(socket) + "&sslmode=disable", stop, nil
}