The hourly release of ADR 0229's brake still ended in the mesh acting alone on a mistake. A consumer now stays active until the same unasked set holds for five passes, waits for a person past three or half of those held, is disabled and marked rather than withdrawn, comes back as it was when asked again, and is deleted only through the provider's delete tool. The backend keeps the mark, so a restart forgets nothing and finds what was withdrawn before.
101 lines
3.4 KiB
Go
101 lines
3.4 KiB
Go
// postgres-provider: postgres's code, one binary the node's runtime launches and speaks MCP to over
|
|
// stdio through the Go SDK (novox/hq ADR 0188, 0193). It serves postgres's tools and the mesh-store
|
|
// seat's verbs and, beside them, runs long: the provisioner that makes postgres the provider of the
|
|
// mesh `postgres-database` interface, and an audit line for each database it provisions or withdraws.
|
|
//
|
|
// stdout is the MCP channel; everything this module says, it says on stderr.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
func say(format string, args ...any) {
|
|
fmt.Fprintf(os.Stderr, "[postgres] "+format+"\n", args...)
|
|
}
|
|
|
|
func main() {
|
|
pg, err := ClientFromEnv(os.Getenv)
|
|
if err != nil {
|
|
// Without the server there is nothing to serve and nothing to provision; said, not fatal,
|
|
// so the runtime does not restart a process that cannot do better.
|
|
say("%v; serving no tools and provisioning nothing", err)
|
|
if err := stdio.Serve("", nil); err != nil {
|
|
say("%v", err)
|
|
os.Exit(1)
|
|
}
|
|
return
|
|
}
|
|
var h *Harness
|
|
if receives := os.Getenv("MESH_RECEIVES"); receives == "" {
|
|
say("MESH_RECEIVES is not set — the provisioner cannot run without it")
|
|
} else {
|
|
h = &Harness{
|
|
Resource: "postgres-database",
|
|
Receives: receives,
|
|
Adapter: provisioner{pg: pg, announce: announce},
|
|
Log: func(format string, args ...any) { fmt.Fprintf(os.Stderr, format+"\n", args...) },
|
|
// A consumer failed for minutes is said on the bus, where the controller hears it and
|
|
// `status` names it (novox/hq ADR 0224).
|
|
Announce: func(event string, body map[string]any) {
|
|
if err := stdio.Emit(event, body); err != nil {
|
|
say("emit %s failed: %v", event, err)
|
|
}
|
|
},
|
|
Node: os.Getenv("MESH_NODE"),
|
|
}
|
|
go h.Run(context.Background())
|
|
}
|
|
go audit()
|
|
// The retirement tools beside postgres's own: what is retired here, approving or rejecting what waits
|
|
// for a person, and deleting a retired consumer (novox/hq ADR 0230).
|
|
if err := stdio.Serve("", append(Tools(pg), RetirementTools(h)...)); err != nil {
|
|
say("%v", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// announce emits a lifecycle event without letting a broker hiccup fail the provisioning itself.
|
|
func announce(event string, body map[string]string) {
|
|
if err := stdio.Emit(event, body); err != nil {
|
|
say("emit %s failed: %v", event, err)
|
|
}
|
|
}
|
|
|
|
// audit keeps a line for each database granted and withdrawn — observability the provider is best
|
|
// placed to log. The events are its own, read back from its consumer (`consumes`).
|
|
func audit() {
|
|
subscribe := func() error {
|
|
return stdio.Subscribe("postgres.database.*", func(e stdio.Envelope) error {
|
|
var body struct {
|
|
Consumer string `json:"consumer"`
|
|
Database string `json:"database"`
|
|
}
|
|
_ = json.Unmarshal(e.Body, &body)
|
|
switch e.Key {
|
|
case "postgres.database.provisioned":
|
|
say("database provisioned for %s (db %s)", body.Consumer, body.Database)
|
|
case "postgres.database.deprovisioned":
|
|
say("database retired, kept (db %s)", body.Database)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
// Asked again until the runtime takes it: the first ask may come before Serve is running.
|
|
for wait := time.Second; ; wait = min(wait*2, time.Minute) {
|
|
if err := subscribe(); err == nil {
|
|
say("auditing database lifecycle events")
|
|
return
|
|
} else if wait >= 8*time.Second {
|
|
say("subscribing to the lifecycle events failed, will retry: %v", err)
|
|
}
|
|
time.Sleep(wait)
|
|
}
|
|
}
|