Files
mesh-catalog/modules/postgres/cmd/postgres-provider/provisioner.go
T

83 lines
3.5 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"
"git.novox.be/novox/mesh-sdk/go/provider/harness"
"time"
)
// 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 harness.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
}
// The active mark: a retired consumer asked for again loses its mark to delete here, its LOGIN
// already set again by the role statement above (novox/hq ADR 0230).
if err := a.pg.MarkActive(ctx, p.As, p.Consumer); err != nil {
return err
}
a.announce("database.provisioned", map[string]string{"consumer": p.Consumer, "database": database, "user": p.As})
return nil
}
// Retire locks the login, never drops (novox/hq issue 241, ADR 0230): the database is kept under its
// own name, the role marked retired with when and why (retire_pg.go). On 2026-10-04 a misread
// contributions file withdrew every consumer at once, and dropping made that a loss of seven databases.
// Deleting is `cleanup delete`, a person's act; taking a database out of service by hand is
// postgres_retire_database, which renames rather than drops.
func (a provisioner) Retire(ctx context.Context, as string, _ map[string]any, why string, at time.Time) error {
if err := a.pg.RetireRole(ctx, as, why, at); err != nil {
return err
}
a.announce("database.deprovisioned", map[string]string{"database": as, "kept": "true", "retired": "true"})
return nil
}
// Inventory is what this server holds that the mesh made (retire_pg.go).
func (a provisioner) Inventory(ctx context.Context) (harness.Inventory, error) {
return a.pg.Inventory(ctx)
}
// Delete drops a retired consumer's database and role, or a database set aside — a person's act.
func (a provisioner) Delete(ctx context.Context, r harness.Retired) (int64, error) {
return a.pg.DeleteRetired(ctx, r)
}
// 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 harness.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)
}