feat: a Kubernetes operator - #202
Merged
Merged
Conversation
The Helm chart is one release per source database, edited by hand; with many of them that is a values file per tenant and an upgrade per change. `crates/operator` reconciles one `Pg2osync` object into the ConfigMap, headless Service, Deployment and — where prometheus-operator is installed — ServiceMonitor the chart assembles. The spec is the chart's `configs:` map one level down, so a release moves onto it by copying the tree: the sections are opaque objects in the CRD and the pipeline validates them, which keeps the operator from becoming the second place an option has to be added and the first place a new one is dropped. A credential written into a spec is refused with the `*_env` form named in `status.message`, because a spec is readable by everyone with `get` on the resource; the operator has no verb on secrets at all, since the kubelet is what resolves `envFrom`. kube-rs and k8s-openapi live only in the new crate, so `pg2osync` gains no dependency and the image is a second target of the same Dockerfile, sharing its builder stage. Status reports the Deployment's readiness and the number of sources rendered rather than a per-source summary: `/healthz/<name>` already answers that from the process that knows. Deletion is owner references, and the replication slot stays the documented manual step it is for every other deployment shape, so there is no finalizer. `dev/e2e-operator.sh` runs the whole thing on kind — rendered file, rows end to end, a second source added to a live resource, a refused spec, garbage collection — as a nightly compatibility cell that also runs when the operator, its manifests or the Dockerfile change.
kennywillbe
enabled auto-merge (squash)
August 31, 2026 17:07
Merged
kennywillbe
added a commit
that referenced
this pull request
Aug 31, 2026
🤖 I have created a release *beep* *boop* --- ## [1.5.0](v1.4.0...v1.5.0) (2026-08-31) ### Features * a --source for every subcommand, and the operations story ([#188](#188)) ([b69a0b4](b69a0b4)) * a ceiling on total retry time ([#162](#162)) ([547df29](547df29)) * a keyed pseudonym transform ([#164](#164)) ([d118b38](d118b38)), closes [#143](#143) * a Kubernetes operator ([#202](#202)) ([bb7c06d](bb7c06d)), closes [#147](#147) * a lookup transform ([#165](#165)) ([60ab49e](60ab49e)) * a pgvector sink ([#193](#193)) ([8781038](8781038)), closes [#185](#185) [#186](#186) * a Qdrant sink ([#194](#194)) ([1d22926](1d22926)), closes [#187](#187) * a rate limit for the initial load ([#155](#155)) ([5ff180d](5ff180d)), closes [#144](#144) * add and remove a table on reload ([#199](#199)) ([293a7ac](293a7ac)), closes [#170](#170) * aggregate children keep a count live ([#189](#189)) ([ba5ab79](ba5ab79)), closes [#179](#179) * fan out a delimited column, and let the element be the join parent ([#190](#190)) ([ee0c0ae](ee0c0ae)), closes [#180](#180) * flatten a one-to-one child into the parent document ([#191](#191)) ([ad72c29](ad72c29)) * Helm readiness probe and PodDisruptionBudget ([#160](#160)) ([8172a82](8172a82)) * load a directory of configs and validate them together ([#168](#168)) ([ccd8427](ccd8427)) * many-to-many children through a junction table ([#156](#156)) ([2c5b2c5](2c5b2c5)) * opt-in OpenTelemetry traces ([#167](#167)) ([afdd8ff](afdd8ff)), closes [#152](#152) * reload the configuration without a restart ([#169](#169)) ([ab2ad96](ab2ad96)), closes [#145](#145) * require_alias refuses writes past the alias ([#158](#158)) ([e6fe2a4](e6fe2a4)) * retry a source that is unreachable at startup ([#197](#197)) ([510b91f](510b91f)), closes [#172](#172) * run every configured source in one process ([#174](#174)) ([99570b3](99570b3)) ### Bug Fixes * a MySQL table removed on reload stops being decoded ([#200](#200)) ([8c037e7](8c037e7)) * writes through the alias satisfy require_alias on Elasticsearch ([#196](#196)) ([8dabf40](8dabf40)), closes [#178](#178) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
The Helm chart is one release per source database, edited by hand. This adds a
Pg2osynccustom resource (pg2osync.io/v1alpha1) and an operator thatreconciles it into the ConfigMap, headless Service, Deployment and — where
prometheus-operator is installed — ServiceMonitor the chart assembles, so
adding a tenant is one object instead of a release.
spec.configsis the chart'sconfigs:map, so a release migrates by copyingthe tree. A credential written into a spec is refused in
status.messageinstead of deployed, and the operator has no verb on secrets at all. kube-rs
lives only in the new
crates/operator, so the pipeline binary gains nodependency; the image is a second target of the same Dockerfile.
docs/operator.mdand adocs/decisions.mdentry recordwhat it owns and what it deliberately leaves manual — the replication slot
above all.
How it was verified
./dev/ci-local.shended with a greenRESULTlineUnit tests cover config rendering against checked-in fixtures, every validation
refusal and the generated CRD against the checked-in manifest.
compat-operatoris a new nightly cell that runs the whole thing on kind.
Checklist
decision changed,
docs/decisions.mdis updated hereCloses #147