Files
terdut-operator/ROADMAP.md
T
Niklas Ye fc68ee7256
CI / test (push) Successful in 1m33s
ROADMAP.md: merge Stage 1 + old Stage 5, renumber
Follows DESIGN.md's redesign (previous commit): with no hand-deployed
server to prove the simpler CRDs against, there's no reason left to defer
TerdutServer's Deployment/Service/database management behind a separate
later stage. Stage 1 now covers TerdutServer's full lifecycle --
Deployment, Service, both Postgres paths from §8 at once (bring-your-own
DSN and Zalando, per the user's call, not sequenced), bootstrap,
credentials -- built together, since bootstrap only has something to
bootstrap once the Deployment exists.

Old Stage 5 (TerdutServer absorbs Deployment/Service/bootstrap) is gone,
folded into Stage 1. Old Stage 6 (installer chart + release) renumbers to
Stage 5. Stages 2-4 (TerdutTeam, EscalationRule+DeadmanSwitch,
AlertSource) are unchanged in content, renumbering only where old Stage 5
disappears from ahead of them.

Explicitly supersedes the Stage 1 shipped before this redesign (commit
1be7cf2): that TerdutServerSpec/Status/controller/tests implemented the
now-removed bring-your-own path and get replaced wholesale when Stage 1
is actually implemented next, not extended.
2026-10-01 08:50:24 +02:00

7.1 KiB

terdut-operator build roadmap

This is the staging plan for implementing the operator against DESIGN.md's settled decisions. It exists for the same reason DESIGN.md and SERVICE-ACCOUNTS.md do: so each stage starts from an agreed sequencing instead of re-litigating "what do we build first" mid-PR.

Sequencing call this roadmap makes

The operator creates and owns every TerdutServer it manages — it never adopts one deployed independently, by hand or by charts/terdut-server (DESIGN.md §1). An earlier version of this roadmap staged TerdutServer's Deployment/Service/bootstrap takeover separately (old Stage 5), behind a hand-deployed server the simpler CRDs could be proven against first. That staging existed only because a credential-less operator couldn't /api/bootstrap its way into a server something else had already bootstrapped (DESIGN.md §6's original gap). With no server to adopt at all, that split has nothing left to justify it: TerdutServer now builds its full lifecycle — Deployment, Service, database wiring, bootstrap, credentials — in one stage, Stage 1, since bootstrap only has something to bootstrap once the Deployment exists.

Stage 0 — Scaffolding & CI

  • go.mod (git.ryuvia.com/niklas/terdut-operator) + Kubebuilder v4 scaffold (cmd/main.go, config/, Makefile, PROJECT), matching terdut-server's Go toolchain and house style (§3). Kubebuilder's own scaffolded Makefile already wires manifests/generate (controller-gen) and setup-envtest into test, and golangci-lint into lint, all fetched on demand into bin/ — no separate install step needed beyond what make test/make lint already do.
  • .release.conf deliberately not added yet: it names a HELM_CHART this repo doesn't have until Stage 5. Adding it now would either be a stub that lies about what's releasable or dead config nobody can run — it lands in Stage 5, alongside the chart it describes.
  • Gitea Actions CI calling fmt lint test, mirroring terdut-server's ci.yaml convention (its CLAUDE.md: "a green gate here and a green pipeline are the same code, not two descriptions of it") minus the chart/security jobs, which need a chart (Stage 5) and real controller code (Stage 1+) respectively to have anything to check.
  • Drop kubebuilder's default .github/workflows/* scaffold — this org runs on Gitea, not GitHub; .gitea/workflows/ci.yaml is the only CI this repo has.
  • Housekeeping: drop the stray .DESIGN.md.swp (leftover vim swapfile, shouldn't be committed); correct DESIGN.md §6/§13's "v1-blocking, not v1-shippable" language — the service-account feature it was blocking on has since shipped in terdut-server.

Done when: CI is green on an otherwise-empty scaffold.

Stage 1 — TerdutServer, full lifecycle

Supersedes the Stage 1 shipped before this redesign (commit 1be7cf2) outright — that TerdutServerSpec/Status/controller/tests implemented the now-removed bring-your-own path and get replaced wholesale, not extended. New commits build forward over the old ones; no git history rewrite.

  • Full §4.1 spec: image, replicas, networking, database, sweeper, deadman, notify, oidc, passwordLogin, allowedTeams, all together — no narrowing, since bootstrap needs the Deployment it's narrowed away from in the version this replaces.
  • Controller manages the Deployment + Service, both Postgres paths from §8 at once (bring-your-own DSN and the Zalando postgres-operator postgresClusterRef integration — not sequenced, per the user's call), and bootstrap/credentials per §6's self-registration flow: /api/bootstrap once the Deployment has a ready replica, checkpoint the admin key, mint the instance-scoped service account, generated credentials Secret in the operator's own namespace, status.credentialsSecretRef. Finalizer cleans up that Secret and the server-side rows on delete (now actually needed, unlike the superseded version — this stage creates things server-side).
  • RBAC: read-only watch on postgresql.acid.zalan.do, degrading gracefully if that CRD isn't installed (§8, §9).
  • envtest covering Deployment/Service reconciliation and both database paths — the Zalando path needs that CRD's schema vendored into the test environment (there's no real postgres-operator controller in envtest, only the CRD shape to create fixture objects against) — plus the self-registration flow against an httptest.Server fake of /api/bootstrap and /api/service-accounts (§11), this time exercising the real flow rather than an adopt-only stand-in.
  • A real kind end-to-end pass (bring-your-own DSN is simplest there) to prove a TerdutServer CR actually produces a running, bootstrapped terdut-server pod. The Zalando path can additionally be validated for real against the org's own cluster later, where postgres-operator already runs, rather than only in a disposable kind stand-in.

Stage 2 — TerdutTeam

  • §4.2: serverRef resolution, real update-in-place (POST create / PUT rename / PUT oidc-groups), team-scoped service-account minting once Ready (§6 point 3), finalizer that DELETEs the team server-side and its credential Secret.
  • First place the "every child resolves its own teamRef → TerdutTeam.status, never chains up to TerdutServer" pattern (§5) gets proven end to end.

Stage 3 — TerdutEscalationRule + TerdutDeadmanSwitch

  • Built together: both stay same-namespace-as-their-TerdutTeam (§1), so neither exercises cross-namespace complexity, but together they cover the two different reconciliation shapes §5's table calls out — whole-policy PUT-on-drift for the escalation policy, delete-and-recreate (no PUT available) for the dead man's switch — against the same shared create/finalizer/resync scaffolding Stage 2 already built.

Stage 4 — TerdutAlertSource

  • Last of the children on purpose: it has the subtlest failure mode of the four. Covers webhook Secret generation/ownership (§4.5, §7), the WebhookSecretLost fail-closed condition + Warning event (§5, added 2026-09-30), and the kind-change delete-and-recreate rotation path — all easier to get right with the other three controllers' patterns already in place to build on.

Stage 5 — Installer chart + real release

  • Package CRDs + the operator's own Deployment/RBAC into the installer chart §10 describes; wire .release.conf/release-vars the same way terdut-server does; run it through the release skill for a real first cut.
  • Full kind end-to-end test per §11: create TerdutServer → TerdutTeam → one of each child kind → verify via terdut-server's own API that each object exists with the right shape → delete the CR → verify the server-side object is gone.

Deferred (§13, unchanged by this roadmap)

Cross-namespace allowedTeams exercised against a real second namespace, CloudNativePG support, narrower-than-namespace Secret RBAC for the webhook Secret, gitops-managed team membership, automatic Deployment restart on upstream Postgres credential rotation, admission webhooks/CEL-only validation limits, OLM packaging.