diff --git a/ROADMAP.md b/ROADMAP.md index 1f0fdfb..d575cbd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7,20 +7,18 @@ 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. +**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 @@ -32,13 +30,13 @@ land in Stage 5, not before. 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 + 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 6, alongside the chart it describes. + 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 6) and real controller + `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 @@ -50,28 +48,40 @@ land in Stage 5, not before. **Done when:** CI is green on an otherwise-empty scaffold. -## Stage 1 — `TerdutServer`, bootstrap/credentials only +## Stage 1 — `TerdutServer`, full lifecycle -- 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 `TerdutServer` CR exists. -- `TerdutServer` CRD narrowed to `spec.endpoint` + `spec.credentialsSecretRef` - + `spec.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.credentialsSecretRef` set and the Secret exists → adopt it, - `status.credentialsSecretRef` mirrors 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/bootstrap` with 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 `envtest` suite (controller-runtime's fake API server) plus an - `httptest.Server` fake of terdut-server's bootstrap/service-account - endpoints, per §11. +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` @@ -101,19 +111,7 @@ land in Stage 5, not before. 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 +## 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