Files
terdut-operator/ROADMAP.md
T
Niklas Ye ba253b7bf7 Add CI, repo CLAUDE.md, and finish Stage 0
- .gitea/workflows/ci.yaml: fmt/lint/test, same no-actions/checkout-and-manual-clone
  shape as terdut-server's ci.yaml, and the same reasoning for why (Node/ES2022
  incompatibility on the runner image). No chart/security jobs yet -- nothing for
  either to check until Stage 6 / real controller code exists.
- CLAUDE.md: Checks + Release sections, matching the sibling repos' convention from
  the workspace-level CLAUDE.md ("each repo has its own CLAUDE.md... read it before
  working in that repo"). Release is explicitly marked not-wired-yet rather than
  copying terdut-server's, since there's no chart to release against until Stage 6.
- ROADMAP.md: moved the .release.conf bullet out of Stage 0 (it names a HELM_CHART
  this repo doesn't have yet) -- it was already duplicated into Stage 6, which is
  where it actually belongs.

Stage 0 done: `make fmt lint test` verified green locally. Real open question the CI
workflow's comments flag rather than assume past: whether storage.googleapis.com
(envtest's binary source) is reachable from this Gitea runner's container network the
way proxy.golang.org is -- terdut-server's own ci.yaml notes get.helm.sh/github.com are
not. Only running the workflow for real will confirm; the comment names the fallback
(move the job out of `container:`, like terdut-server's chart job) if it isn't.
2026-09-30 19:22:19 +02:00

6.6 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 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 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'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 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.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, bootstrap/credentials only

  • Deploy terdut-server via its existing chart into a fresh, disposable namespace — manual, out-of-band, nothing operator-managed yet.
  • TerdutServer CRD narrowed to spec.endpoint + spec.allowedTeams; the rest of §4.1's spec (image, replicas, networking, database) waits for Stage 5.
  • Controller implements §6 exactly: call /api/bootstrap only on a genuinely empty install; otherwise GET /api/service-accounts?name=terdut-operator and POST one if it doesn't exist. Writes the instance-scoped key to a generated Secret in the operator's own namespace; sets status.credentialsSecretRef and the Bootstrapped/Ready conditions.
  • No Deployment/Service reconciliation at all in this stage.
  • First envtest suite (controller-runtime's fake API server) plus an httptest.Server fake of terdut-server's bootstrap/service-account endpoints, per §11.

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 — TerdutServer absorbs Deployment/Service/bootstrap (§10, executed)

  • Extend TerdutServer to 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-operator path 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 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.