Stage 1's own setup (chart bootstraps before the CR exists) never exercises the self-registration fallback, and the narrowed spec has nowhere to put the username/email /api/bootstrap needs anyway. Matches DESIGN.md's §6 rewrite (bring-your-own is now the primary path, not an equal alternative).
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
TerdutServer does not take over Deployment/Service/bootstrap (§4.1,
§10) until Stage 5. Every earlier stage targets a terdut-server deployed by
hand, via the existing charts/terdut-server chart, into a namespace
dedicated to this work — disposable, no risk to anything else, and
explicitly not something this roadmap plans to migrate in place. This is a
green-field build: there's no existing install this operator is on the hook
to preserve, so the hand-deployed instance from Stage 1 is meant to be
retired once Stage 5's operator-managed instance is proven, not migrated.
TerdutTeam.spec.serverRef still needs a real TerdutServer object to
resolve against from Stage 1 onward, though (§4.2, §6) — so TerdutServer
shows up early, just with a deliberately narrow first spec covering only
what §6's bootstrap/credential flow needs. image/replicas/database
land in Stage 5, not before.
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 scaffoldedMakefilealready wiresmanifests/generate(controller-gen) andsetup-envtestintotest, andgolangci-lintintolint, all fetched on demand intobin/— no separate install step needed beyond whatmake test/make lintalready do..release.confdeliberately not added yet: it names aHELM_CHARTthis repo doesn't have until Stage 6. Adding it now would either be a stub that lies about what's releasable or dead config nobody can run — it lands in Stage 6, alongside the chart it describes.- Gitea Actions CI calling
fmt lint test, mirroring terdut-server'sci.yamlconvention (itsCLAUDE.md: "a green gate here and a green pipeline are the same code, not two descriptions of it") minus thechart/securityjobs, which need a chart (Stage 6) 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.yamlis the only CI this repo has. - Housekeeping: drop the stray
.DESIGN.md.swp(leftover vim swapfile, shouldn't be committed); correctDESIGN.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, bootstrap/credentials only
- Deploy terdut-server via its existing chart into a fresh, disposable
namespace — manual, out-of-band, nothing operator-managed yet. This chart
run bootstraps the server itself, before the
TerdutServerCR exists. TerdutServerCRD narrowed tospec.endpoint+spec.credentialsSecretRefspec.allowedTeams; the rest of §4.1's spec (image,replicas,networking,database) waits for Stage 5.
- Controller implements §6's bring-your-own path only:
spec.credentialsSecretRefset and the Secret exists → adopt it,status.credentialsSecretRefmirrors it,Bootstrapped/Ready: True. Unset (or not found yet) →Ready: False, requeue, no API call made. Self-registration (the/api/bootstrap-race fallback in §6 point 1) is explicitly deferred to Stage 5, not implemented here: Stage 1's own setup never exercises it (the chart always bootstraps first, per above), and this narrowed spec has no username/email fields for it to call/api/bootstrapwith in the first place. Adding it later is additive, not a breaking change to this stage's shape. - No Deployment/Service reconciliation at all in this stage.
- First
envtestsuite (controller-runtime's fake API server) plus anhttptest.Serverfake of terdut-server's bootstrap/service-account endpoints, per §11.
Stage 2 — TerdutTeam
- §4.2:
serverRefresolution, real update-in-place (POST create / PUT rename / PUT oidc-groups), team-scoped service-account minting onceReady(§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 toTerdutServer" 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
WebhookSecretLostfail-closed condition +Warningevent (§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 — TerdutServer absorbs Deployment/Service/bootstrap (§10, executed)
- Extend
TerdutServerto the full §4.1 spec and stand up a second, operator-managed terdut-server in the same dev namespace — not an in-place takeover of Stage 1's hand-deployed instance. Once this is solid, that hand-deployed instance is simply retired. - Postgres integration (§8): bring-your-own DSN path first (no external CRD
dependency); Zalando
postgres-operatorpath as a follow-up — split into its own stage if it turns out bigger than expected once started. - This is where §10's "the chart becomes an installer chart" recommendation actually gets executed, rather than just recommended.
Stage 6 — 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 thereleaseskill for a real first cut. - Full
kindend-to-end test per §11: createTerdutServer→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.