Monedula
monedula-gitops docs menu

Safety gates: dry runs, approvals and pruning

How monedula-gitops gates risky changes: dry runs, --allow-destructive, --allow-delete and --prune approvals, and operator deletion policies.

Concepts 4 of 6

4 min read

Updated 9 October 2026

Markdown

On this page

Every change can be previewed: validate checks manifests without a running Kafka, and diff and apply --dry-run list the planned operations without changing anything. KafkaUser passwords are resolved only when an operation executes, never at plan time, so they never appear in a rendered plan.

Operations that need approval

Three kinds of operation run only with explicit approval. The CLI approves with flags on apply, the operator with annotations and fields on the resource:

Gate Covers Without approval CLI Operator
Destructive a partition increase, a replication factor change, a schema compatibility lowering (including a first-time level below the registry’s global default), a quota limit removal reported as Blocked and not run; the rest of the plan runs --allow-destructive annotation gitops.monedula.dev/allow-destructive: "true"
Delete Kafka cleanup when a KafkaTopic or KafkaAccessPolicy under deletionPolicy: Delete is deleted finalizer kept, DeleteNotApproved Warning event --allow-delete exists but has nothing to approve: the CLI never plans a deletion annotation gitops.monedula.dev/allow-delete: "true"
Prune in-scope ACLs and role bindings that the manifests no longer declare reported as PruneDisabled and kept --prune spec.prune: true

Each gate is separate: --allow-destructive does not enable pruning, and the prune opt-in approves nothing else.

In the CLI, when Blocked operations are the only problem, apply exits 3: the plan is sound and waits for approval. In the operator, a blocked operation leaves the resource at Ready=False, and the operator emits an event.

Two changes never run, approved or not. A partition decrease is always rejected, because Kafka cannot shrink a topic. A replication factor change passes the gate but is then reported as Unsupported, because monedula-gitops cannot perform the partition reassignment it needs.

Pruning is opt-in

Pruning deletes in-scope ACLs and Confluent role bindings that are no longer desired, for example after you remove an operation from an access entry that still names the principal. Removing the whole entry takes that principal and topic pair out of the managed scope, so its ACLs are neither pruned nor reported. Pruning is off by default so that an accidentally truncated manifest cannot cut production access:

  • Without the opt-in, a prune candidate is reported as PruneDisabled by diff, verify and apply. verify counts it as drift, but it is never deleted and it does not fail apply.
  • In the operator, a candidate is deleted only when every resource whose scope covers it has spec.prune: true; otherwise it stays as PruneDisabled drift.

Pruning never touches ACLs outside the managed scope.

Delete resources safely with --prune walks through a prune and lists what it leaves in place.

Deletion policies in the operator

When a custom resource is deleted, its finalizer, gitops.monedula.dev/finalizer, follows spec.deletionPolicy: Orphan leaves the Kafka objects in place, and Delete removes them.

Kind Default Delete removes Needs allow-delete
KafkaTopic Orphan the topic’s ACLs, then the topic, then the schema subjects whose content the topic manages yes
KafkaAccessPolicy Delete the policy’s managed ACLs yes
KafkaRoleBinding Delete its MDS role bindings no
KafkaUser Delete its SCRAM credential no

Delete keeps every ACL and role binding that another live resource still declares. The operator then emits a SharedACLsRetained or SharedRoleBindingsRetained Normal event that names up to three of those resources as Kind/namespace/name and counts the rest as “and N more”.

Role bindings and users need no annotation because their external state is their whole reason to exist: deleting the resource is itself the explicit action. A KafkaQuota has no deletion policy, and deleting it removes its quota.

If the operator cannot build a client for the cluster, because the KafkaCluster is gone or its credentials cannot be read, it keeps the finalizer and emits a DeletionBlocked Warning event. The annotation gitops.monedula.dev/force-finalizer-removal: "true" then removes the finalizer without cluster-side cleanup. When the KafkaCluster exists but Kafka is down or rejects the cleanup, that annotation is not consulted: a KafkaTopic or KafkaAccessPolicy retries with a DeleteFailed Warning event, and a KafkaRoleBinding with DeletionFailed, until the cleanup succeeds or you switch its deletionPolicy to Orphan. A KafkaQuota or KafkaUser emits a QuotaDeletionFailed or CredentialDeletionFailed Warning event and finishes the deletion anyway.

Execution never rolls back

apply is best-effort: independent operations continue past a failure, ACLs for a topic that failed to be created are skipped, and the next apply converges the rest. The CLI overview lists every operation outcome and exit code.