Files
mesh-catalog/modules/postgres/cmd/postgres-provider/provisioner.go
T
jochen d8b4d20886 Port postgres to Go and install the extensions a consumer asks for
letta crash-loops on 'type "vector" does not exist': pgvector is not a
trusted extension, so only the provider's superuser can create it, and
the provisioner never did. A contribution may now name extensions; the
provider creates each (IF NOT EXISTS, available ones only) in the
consumer's database on every pass. Go per the standing rule for a
TypeScript module that changes. letta asks for vector.
2026-10-05 23:29:55 +02:00

65 lines
2.6 KiB
Go

package main
// postgres's provisioner — the adapter that makes postgres a provider of the mesh
// `postgres-database` interface (novox/hq ADR 0039/0040/0048). A consumer connects to a database it
// alone owns, as `as` with the password the mesh minted.
//
// **The role name and password are the mesh's, not the provisioner's (ADR 0048).** postgres creates
// a role and a same-named database under exactly that login — a name the consumer cannot learn is a
// database it cannot reach.
//
// **Extensions are the provider's to install.** A contribution may name extensions
// (`"extensions": ["vector"]`); most are not trusted, so only the superuser this module holds can
// create them, in the consumer's database, on every pass, and never drops one.
import (
"context"
)
// provisioner is the adapter over the client; announce emits a lifecycle event.
type provisioner struct {
pg *Client
announce func(event string, body map[string]string)
}
func (a provisioner) Create(ctx context.Context, p Provision) error {
// Read before anything runs: a malformed list is refused without touching the server.
extensions, err := Extensions(p.Values)
if err != nil {
return err
}
// Database and owning role share the consumer's login, so the consumer owns exactly its own.
database := p.As
if err := a.pg.CreateDatabaseAndRole(ctx, database, p.As, p.Password); err != nil {
return err
}
if err := a.pg.EnsureExtensions(ctx, database, extensions); err != nil {
return err
}
a.announce("database.provisioned", map[string]string{"consumer": p.Consumer, "database": database, "user": p.As})
return nil
}
// Remove withdraws, never drops (novox/hq issue 241). The login is locked and the database kept under
// its own name: on 2026-10-04 a misread contributions file withdrew every consumer at once, and
// dropping made that a loss of seven databases. Taking a database out of service is a person's act —
// postgres_retire_database — and even that renames rather than drops.
func (a provisioner) Remove(ctx context.Context, as string, _ map[string]any) error {
if err := a.pg.LockRole(ctx, as); err != nil {
return err
}
a.announce("database.deprovisioned", map[string]string{"database": as, "kept": "true"})
return nil
}
// Holds is asked every minute: whether the consumer can still log in as the mesh gave it, and finds
// the extensions it asked for, so a login or extension lost behind the provisioner's back is made
// again (novox/hq issue 120).
func (a provisioner) Holds(ctx context.Context, p Provision) (bool, error) {
extensions, err := Extensions(p.Values)
if err != nil {
return false, err
}
return a.pg.Holds(ctx, p.As, p.As, p.Password, extensions)
}