Write a check plugin
Extend pgdba with third-party checks — no fork, same safety contract.
pgdba can run third-party checks alongside its 68 core checks. Plugins are
opt-in (check --plugins on, default off): installed plugin code never
runs uninvited, and the catalog always shows what is installed and whether
it is enabled.
The contract
A plugin is an installed Python package exposing one function:
# mycompany_pgdba_checks.py
from pgdba.models import Finding
def register_pgdba_checks(api) -> None:
def stale_replica() -> list[Finding]:
return [
Finding(
id="acme.stale_replica",
severity="warning",
evidence="replica lag > 1h",
remediation="Review the replication slot; see runbook.",
)
]
api.check(
"acme.stale_replica",
stale_replica,
duty="availability",
description="Flags replicas behind by more than an hour.",
data_requirements=["pg_stat_replication"],
severity_model=["info", "warning"],
)
Rules:
- Id prefix: every plugin check id shares one prefix (
acme.above) that must not collide with a core prefix or another plugin’s. Collisions fail at load naming both ids. - Metadata:
duty,description,severity_model, and (optionally)data_requirementsandpg_majorsare validated at registration; incomplete metadata refuses to load with the missing keys named. - Finding contract: return
pgdba.models.Findingobjects exactly like core checks — they flow through the report, agent JSON, drift baselines, and the audit journal unchanged, and run under the same read-only safety contract and statement capture.
Installing
Two ways to declare a plugin; both can be active:
-
Entry point in your package metadata:
# pyproject.toml (your plugin package) [project.entry-points."pgdba.checks"] acme = "mycompany_pgdba_checks" -
Declaration file next to where you run pgdba:
# pgdba-checks.toml [[plugins]] name = "acme" module = "mycompany_pgdba_checks"
Running and inspecting
pgdba checks list # catalog shows: acme.* (installed, disabled; --plugins on)
pgdba check --plugins on --dsn "$URL" # run core + plugin checks
Plugin checks run last, after every core check, and a broken plugin (bad metadata, missing register function, prefix collision) fails the run with exit 3 rather than silently disappearing.