fc68ee7256
CI / test (push) Successful in 1m33s
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.
132 lines
7.1 KiB
Markdown
132 lines
7.1 KiB
Markdown
# 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.
|