gitops-reverser

module
v0.40.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: Apache-2.0

README

CI OpenSSF Scorecard OpenSSF Best Practices Release License Go Platforms codecov Container Artifact Hub Open Issues

GitOps Reverser

GitOps Reverser is a Kubernetes operator that turns Kubernetes API resources into clean YAML in Git. It is configurable, and can be used as:

  • a live audit trail, or
  • a "reverse" GitOps-reconcilable repo that keeps API-first workflows intact.

The broader pattern is described at reversegitops.dev.

Want proof? See this example commit in ConfigButler/example-audit.

What it does

  • Reconciles existing Kubernetes API state into Git: the repo reflects the exact current state.
  • Captures live changes through watches.
  • Includes real actors for every change if you configure kube-apiserver audit webhooks.

Overview diagram showing how API events flow through the operator into Git

What it can't

It edits the intent layer: the documents a human authored. The expansion layer a controller derives from them stays out of scope. So it cannot reverse Helm-rendered resources into a clean values.yaml, and it will not invent structure for templating it does not model. Simple Kustomize layouts are supported (see below); the full verdict table is in support-contract.md.

When it fits

Good fit if you want API-first and Git at the same time.

Good fit Poor fit
Clusters where you can grant watch/RBAC and write to Git (optionally run Valkey/Redis for the full feature set) Production HA requirements today
Teams that want API-to-Git capture first, then named author attribution later Shared paths with two always-on writers fighting over the same resources
API-first or hybrid teams that still want Git history; brownfield discovery, hotfix capture, migration toward GitOps Teams who want Git to stay the write path, with humans editing manifests first

How it works

  1. Watches the Kubernetes API for the resource types each GitTarget claims. Watch is the single source of object state.
  2. Sanitizes each change (status, managedFields, and runtime noise removed) and diffs it against the current Git content.
  3. Writes stable YAML to Git with useful commit metadata, authored according to the mode below.

It can also:

  • Encrypt Secret values before commit with SOPS + age (Secret-shaped custom resources can opt in).
  • SSH-sign every commit via GitProvider.spec.commit.signing.
  • Group changes within a time window into a single commit.
  • Take your own commit message and "why" from a CommitRequest.
Who authors the commits

Every commit carries a Git author and a Git committer. By default both are one configured identity (configured-author). Turn on attribution and the author becomes the real Kubernetes actor (user, service account, or CI identity), while the committer never moves. That needs kube-apiserver audit delivery (managed control planes like EKS/GKE/AKS generally do not expose it) plus Valkey/Redis: see the attribution setup guide.

If attribution is enabled but a live change has no usable audit fact, the commit says so: its author is unknown (attribution unresolved), and the configured committer identity is never substituted in its place. For a change that should have a named actor, treat that identity as a signal to verify the audit policy, webhook route, source identity, and Redis connectivity.

Valkey/Redis is optional but advised. Without it the default mode works fine; adding it unlocks warm-restart cursors, CommitRequest author capture, and attribution.

Delivery guarantees

While the watch is connected the operator sees each individual update and commits it, so Git tracks changes as they happen. Across a gap (pod restart, disconnect, 410 Gone) it reconciles to current state instead of replaying versions it never saw, so edits made during the gap collapse into one commit. Nothing is lost or left stale; deletes are reconciled on reconnect. See docs/architecture.md for replay and 410 Gone details.

Simple Kustomize support

The write path runs kustomize itself (sigs.k8s.io/kustomize/api) in memory, with no plugins, no exec, no network, and no remote bases. That render decides where a change belongs and checks the result before committing. What it can do:

  • Edit resources: (and bases:), namespace:, images:, and replicas: as real declarations.
  • Write a change where the value lives in source (the document itself, or the governing images:/replicas: entry) instead of mirroring rendered output over your source files.
  • Add a new file to the right resources: list in the same commit, and remove the entry when that file's last document is deleted, so the repo never stops building.
  • Support base/ + overlays/{env}/: the base is read-only context, never written through an overlay.
  • Create a missing images:/replicas: entry in an overlay, so one environment changes without touching a shared base, and author a $patch: delete for an object an overlay inherits.
  • Read patches: (local strategic-merge files), commonLabels, labels, and commonAnnotations as read-only build context.
  • Verify every commit by re-rendering before and after, refusing it unless your change lands exactly and nothing else moves.

It refuses these by name, before writing anything, and reports Stalled=True: generators, components, namePrefix/nameSuffix, replacements, vars, helmCharts, plugins, inline and JSON6902 patches, remote bases, and any field it does not model. One known gap: a strategic-merge patch that edits a field of a base-owned object is not authored yet. Reasoning in kustomize-support-boundary.md.

Status

Early-stage software; CRDs and behavior may still change.

  • Runs as a single controller pod (replicas=1).
  • Shared-resource bi-directional workflows need explicit coordination.
  • Source recovery covers Kubernetes manifests and simple Kustomize layouts, not Helm authoring models.
  • Tested against Kubernetes 1.36; other versions may work but are not in the matrix.
  • Runtime behavior is deterministic: no AI or heuristic mutation at runtime.

Good fit for pilots, lab clusters, brownfield discovery, and design partners who can tolerate change. Production use should follow an environment-specific review.

On the road to 1.0

Roughly in priority order:

  • High availability: replicaCount > 1 is rejected today. It needs leader/ownership coordination so two replicas never write the same GitTarget, and Redis becomes required rather than advised.
  • A stabilized configuration surface: all six CRDs are v1alpha3 with no conversion path yet.
  • More documentation: day-2 operations, troubleshooting, and worked examples per layout.
  • A durable worker queue: a crash between advancing the watch cursor and landing the write can currently skip work on restart.
  • Write-collision safety across GitProvider objects sharing a repository. Until then, keep one GitProvider per repository.
  • Better queue and worker observability: enough metrics to run it without reading logs.
  • More control over output layout, plus filtering cluster-generated noise out of the Git view.

Backlog in docs/TODO.md; longer-range directions in docs/future/.

Quick start

This brings up the demo: a starter GitProvider, GitTarget, and WatchRule in a gitops-reverser-quickstart-demo namespace, watching ConfigMaps there and writing them to <your-repo>/live-cluster on main. It runs in configured-author mode (no Redis) by default. The chart also renders the cluster-scoped default ClusterProvider the starter target resolves against.

Config basics diagram showing the relationship between GitProvider, GitTarget, and WatchRule

Prerequisites: a Kubernetes cluster with kubectl, Helm 3, and cert-manager for TLS.

1. Install cert-manager

The controller mounts an admission certificate at startup, so cert-manager must be healthy before you install the chart. (--set servers.admission.enabled=false drops the dependency; the chart README covers bring-your-own certificates.)

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.21.0/cert-manager.yaml
kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=cert-manager -n cert-manager --timeout=300s
2. Create a Git repo and a deploy key

Create an (empty) repository the operator will write to, then generate a key. Add the public half as a deploy key with write access; the private half becomes the Secret in the next step:

ssh-keygen -t ed25519 -C "gitops-reverser@cluster" -f /tmp/gitops-reverser-key -N ""
# Add /tmp/gitops-reverser-key.pub to your Git provider as a deploy key (write access)
3. Create the demo namespace and Git credentials

Do this before installing. The starter GitProvider is only re-checked about every 5 minutes, so if the Secret is missing at install time your first commit can be minutes late; having it ready up front lets the starter resources go Ready on the first reconcile:

kubectl create namespace gitops-reverser-quickstart-demo \
  --dry-run=client -o yaml | kubectl apply -f -

kubectl create secret generic git-creds \
  --namespace gitops-reverser-quickstart-demo \
  --from-file=ssh-privatekey=/tmp/gitops-reverser-key \
  --from-literal=known_hosts="$(ssh-keyscan github.com 2>/dev/null)" \
  --dry-run=client -o yaml | kubectl apply -f -

SSH host-key verification fails closed, so the known_hosts line is required. Existing Flux or Argo CD credentials Secrets are accepted as-is (they must have write access). See docs/configuration.md for accepted Secret shapes and docs/github-setup-guide.md for the full GitHub guide and HTTPS/PAT fallback.

4. Install GitOps Reverser with the demo enabled

Point the starter GitProvider at your repo and install:

helm install gitops-reverser \
  oci://ghcr.io/configbutler/charts/gitops-reverser \
  --namespace gitops-reverser \
  --create-namespace \
  --set quickstart.enabled=true \
  --set-string quickstart.gitProvider.url=git@github.com:OWNER/REPO.git

Replace OWNER/REPO with your repository (angle brackets would be parsed by the shell).

Three things worth knowing about this install:

  • No Redis, so warm-restart cursors and author capture stay inactive. Add one with --set queue.redis.addr=HOST:PORT (plus queue.redis.auth.existingSecret if it needs auth).
  • Cluster-wide read on every watchable type, including Secrets (rbac.watchTypes.mode=any): fine for a demo, probably too wide for a real cluster. docs/rbac.md narrows it.
  • SOPS is on for the starter target, so a sops-age-key Secret is generated in the demo namespace with a backup reminder. Only matters once you mirror Secrets; back it up if you keep the demo.

Wait for the controller. On a fresh install this waits on cert-manager issuing the certificate:

kubectl rollout status deployment/gitops-reverser -n gitops-reverser --timeout=300s

Then check the starter resources:

kubectl get gitprovider,gittarget,watchrule -n gitops-reverser-quickstart-demo

The GitProvider and WatchRule report Ready=True; the GitTarget reports Validated=True. Its aggregate Ready stays Unknown until first source discovery, which is the expected state.

5. Test it
kubectl create configmap test-config --from-literal=key=value -n gitops-reverser-quickstart-demo

A new commit should land in your repository within seconds. If none appears:

kubectl logs -n gitops-reverser deploy/gitops-reverser
kubectl describe gitprovider,gittarget,watchrule -n gitops-reverser-quickstart-demo

Two GitTarget conditions stop the data plane and are worth recognizing: ClusterProviderNotFound (the default ClusterProvider is missing) and NamespaceNotAuthorized (its allowedNamespaces selector does not cover the demo namespace).

To tear the demo down: helm uninstall gitops-reverser -n gitops-reverser and kubectl delete namespace gitops-reverser-quickstart-demo.

Note: the default ClusterProvider is cluster-scoped and chart-owned, so uninstalling takes it with it, holding every other GitTarget in the cluster unready. Mind that if you run the demo alongside a real deployment.

Want named users on your commits?

Turn on attributed-author mode so the real Kubernetes actor becomes the Git author. Needs audit delivery and Valkey/Redis: the attribution setup guide walks it through.

Rather have it managed?

ConfigButler can run a small, secure, public-facing Kubernetes API for you: we operate GitOps Reverser and authorize your end users, with forward-deployed engineers to get you started. You keep a clean, self-owned Git repo where your users express their intent.

Docs

Start with the stable docs surface:

Looking for early users

If this workflow matches a real problem, feedback is very welcome. The most useful reports are install attempts, first-commit experience, audit delivery issues, Git output shape, CRD ergonomics, and security or operational concerns.

Get in touch

Contributing

Issues, docs fixes, and code contributions are welcome. See CONTRIBUTING.md.

License

Apache 2.0

Directories

Path Synopsis
api
v1alpha3
Package v1alpha3 contains API Schema definitions for the configbutler.ai v1alpha3 API group.
Package v1alpha3 contains API Schema definitions for the configbutler.ai v1alpha3 API group.
cmd
gitea-signing-debug command
gitea-signing-debug drives the full SSH-signed-commit flow against a live Gitea and reports Gitea's commit verification verdict at every stage.
gitea-signing-debug drives the full SSH-signed-commit flow against a live Gitea and reports Gitea's commit verification verdict at every stage.
manifest-analyzer command
manifest-analyzer is a standalone, read-only CLI that analyzes a folder of Kubernetes manifests.
manifest-analyzer is a standalone, read-only CLI that analyzes a folder of Kubernetes manifests.
mutation-capture-lab command
Command mutation-capture-lab is the mutation-capture lab binary: a minimal set of recorders (native watch, audit webhook, validating admission webhook) whose output is a versioned corpus of the exact structures Kubernetes emits.
Command mutation-capture-lab is the mutation-capture lab binary: a minimal set of recorders (native watch, audit webhook, validating admission webhook) whose output is a versioned corpus of the exact structures Kubernetes emits.
hack
doccheck command
Command doccheck verifies that every documentation reference in the repository resolves to a file that exists.
Command doccheck verifies that every documentation reference in the repository resolves to a file that exists.
internal
audit/outcome
Package outcome defines the single, bounded vocabulary for what the audit ingestion pipeline did with one event — its "outcome" — and records it on the gitopsreverser_audit_events_total counter.
Package outcome defines the single, bounded vocabulary for what the audit ingestion pipeline did with one event — its "outcome" — and records it on the gitopsreverser_audit_events_total counter.
auditutil
Package auditutil contains shared Kubernetes audit-event parsing helpers.
Package auditutil contains shared Kubernetes audit-event parsing helpers.
authz
Package authz holds the ClusterProvider namespace-admission decision.
Package authz holds the ClusterProvider namespace-admission decision.
controller
Package controller contains shared constants for all controllers.
Package controller contains shared constants for all controllers.
git
Package git provides Git repository operations and abstractions for the GitOps Reverser controller.
Package git provides Git repository operations and abstractions for the GitOps Reverser controller.
git/manifestedit
Package manifestedit is an isolated proof of concept for the manifest-inventory "file-agnostic placement" feature.
Package manifestedit is an isolated proof of concept for the manifest-inventory "file-agnostic placement" feature.
giteaclient
Package giteaclient is a small, focused Gitea REST client used by e2e tests and debug tools.
Package giteaclient is a small, focused Gitea REST client used by e2e tests and debug tools.
kubeconfig
Package kubeconfig resolves and safety-checks a source-cluster kubeconfig held in a Secret, shared by the watch data plane's SourceClusterResolver and the GitTarget controller's Validated gate so both apply exactly one contract: Flux's value→value.yaml key order, and a REJECT — not Flux's silent strip — of exec auth providers and insecure-skip-tls-verify.
Package kubeconfig resolves and safety-checks a source-cluster kubeconfig held in a Secret, shared by the watch data plane's SourceClusterResolver and the GitTarget controller's Validated gate so both apply exactly one contract: Flux's value→value.yaml key order, and a REJECT — not Flux's silent strip — of exec auth providers and insecure-skip-tls-verify.
manifestanalyzer
Package manifestanalyzer is a runtime-independent analyzer for a folder of Kubernetes manifests.
Package manifestanalyzer is a runtime-independent analyzer for a folder of Kubernetes manifests.
manifestreport
Package manifestreport is the integration layer that drives the cluster-free manifestedit library against a real repository and cluster state.
Package manifestreport is the integration layer that drives the cluster-free manifestedit library against a real repository and cluster state.
mutationlab
Package mutationlab holds the mutation-capture lab: a small, separate application that records the exact structures Kubernetes exposes through native watches, audit webhooks, and validating admission webhooks, and commits those structures as a versioned corpus.
Package mutationlab holds the mutation-capture lab: a small, separate application that records the exact structures Kubernetes exposes through native watches, audit webhooks, and validating admission webhooks, and commits those structures as a versioned corpus.
mutationlab/corpus
Package corpus turns captured records into the browsable golden tree: one directory per scenario, one file per emitted moment, named so an ordered fan-out is self-describing (watch.deleted.cm-a.yaml, ...).
Package corpus turns captured records into the browsable golden tree: one directory per scenario, one file per emitted moment, named so an ordered fan-out is self-describing (watch.deleted.cm-a.yaml, ...).
mutationlab/labserver
Package labserver assembles the lab's HTTP surface: the read/clear records API and the health endpoint.
Package labserver assembles the lab's HTTP surface: the read/clear records API and the health endpoint.
mutationlab/normalize
Package normalize rewrites the volatile fields of captured Kubernetes payloads to stable, relational placeholders so the lab corpus changes only when behavior changes — never when a run merely produces fresh UIDs, resource versions, or timestamps.
Package normalize rewrites the volatile fields of captured Kubernetes payloads to stable, relational placeholders so the lab corpus changes only when behavior changes — never when a run merely produces fresh UIDs, resource versions, or timestamps.
mutationlab/recorder
Package recorder turns live Kubernetes observations — audit webhook posts, admission reviews, and native watch events — into mutationlab.Record values in the store.
Package recorder turns live Kubernetes observations — audit webhook posts, admission reviews, and native watch events — into mutationlab.Record values in the store.
mutationlab/store
Package store is the lab's in-memory record store.
Package store is the lab's in-memory record store.
rulestore
Package rulestore manages the in-memory cache of compiled WatchRule configurations.
Package rulestore manages the in-memory cache of compiled WatchRule configurations.
sanitize
Package sanitize removes server-generated fields from Kubernetes objects.
Package sanitize removes server-generated fields from Kubernetes objects.
ssh
Package ssh provides SSH authentication utilities for Git operations.
Package ssh provides SSH authentication utilities for Git operations.
telemetry
Package telemetry provides the OpenTelemetry-based metrics exporter for GitOps Reverser.
Package telemetry provides the OpenTelemetry-based metrics exporter for GitOps Reverser.
types
Package types provides common type definitions used across the GitOps Reverser.
Package types provides common type definitions used across the GitOps Reverser.
typeset
Package typeset is GitOps Reverser's single decision surface for "is this resource type followable, and if not, what is the one reason it is not?".
Package typeset is GitOps Reverser's single decision surface for "is this resource type followable, and if not, what is the one reason it is not?".
watch
Package watch drives the api-source-of-truth reconcile: it keeps the followability registry and the demand-driven materialization axis fresh, fills per-type checkpoints, and reconciles each watched type into Git by SPLICING the per-type Redis materialization (checkpoint + audit log) into a desired set — no long-lived object watch is held (R3).
Package watch drives the api-source-of-truth reconcile: it keeps the followability registry and the demand-driven materialization axis fresh, fills per-type checkpoints, and reconciles each watched type into Git by SPLICING the per-type Redis materialization (checkpoint + audit log) into a desired set — no long-lived object watch is held (R3).
pkg
manifestanalyzer
Package manifestanalyzer is the public answer to two questions a tool built around GitOps Reverser needs to ask about a Git repository, without a cluster and without writing anything:
Package manifestanalyzer is the public answer to two questions a tool built around GitOps Reverser needs to ask about a Git repository, without a cluster and without writing anything:
test
e2e
Package e2e provides helper functions for end-to-end testing of the GitOps Reverser controller.
Package e2e provides helper functions for end-to-end testing of the GitOps Reverser controller.
e2e/tools/ginkgo-allure command
ginkgo-allure converts Ginkgo JSON reports into Allure result files.
ginkgo-allure converts Ginkgo JSON reports into Allure result files.
e2e/tools/spec-timings command
spec-timings reads a Ginkgo JSON report and prints a duration-sorted table of specs with each spec's share of the total wallclock.
spec-timings reads a Ginkgo JSON report and prints a duration-sorted table of specs with each spec's share of the total wallclock.
e2e/tools/ts command
ts prefixes each stdin line with the elapsed seconds since startup and the current wallclock, then writes it to stdout.
ts prefixes each stdin line with the elapsed seconds since startup and the current wallclock, then writes it to stdout.
loadtest command
SPDX-License-Identifier: Apache-2.0
SPDX-License-Identifier: Apache-2.0
mutationlab/e2e
Package e2e drives the mutation-capture lab against a live cluster: it runs each scenario, drains the lab's records API, and compares (or, with MUTATIONLAB_UPDATE=1, rewrites) the golden corpus.
Package e2e drives the mutation-capture lab against a live cluster: it runs each scenario, drains the lab's records API, and compares (or, with MUTATIONLAB_UPDATE=1, rewrites) the golden corpus.
utils
Package utils provides utility functions for e2e testing.
Package utils provides utility functions for e2e testing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL