Skip to content

feat: a Kubernetes operator - #202

Merged
kennywillbe merged 1 commit into
mainfrom
feat/operator
Aug 31, 2026
Merged

feat: a Kubernetes operator#202
kennywillbe merged 1 commit into
mainfrom
feat/operator

Conversation

@kennywillbe

Copy link
Copy Markdown
Owner

What and why

The Helm chart is one release per source database, edited by hand. This adds a
Pg2osync custom resource (pg2osync.io/v1alpha1) and an operator that
reconciles 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.configs is the chart's configs: map, so a release migrates by copying
the tree. A credential written into a spec is refused in status.message
instead 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 no
dependency; the image is a second target of the same Dockerfile.

docs/operator.md and a docs/decisions.md entry record
what it owns and what it deliberately leaves manual — the replication slot
above all.

How it was verified

  • ./dev/ci-local.sh ended with a green RESULT line
RESULT: green — 22 passed, 0 skipped
  • Regression test added that fails without this change

Unit tests cover config rendering against checked-in fixtures, every validation
refusal and the generated CRD against the checked-in manifest. compat-operator
is a new nightly cell that runs the whole thing on kind.

Checklist

  • Comments explain why, not what
  • No architecture boundary crossed (see CONTRIBUTING.md); if a recorded
    decision changed, docs/decisions.md is updated here
  • Docs updated for behaviour or configuration changes
  • No secrets in code, tests, logs or fixtures

Closes #147

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
kennywillbe enabled auto-merge (squash) August 31, 2026 17:07
@kennywillbe
kennywillbe merged commit bb7c06d into main Aug 31, 2026
24 checks passed
@kennywillbe
kennywillbe deleted the feat/operator branch August 31, 2026 17:18
@kennywillbe kennywillbe mentioned this pull request Aug 31, 2026
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A Kubernetes operator

1 participant