Conditional Reconciliation
Conditional reconciliation lets you gate an entire reconcile cycle on conditions evaluated before the reconciler is called. When a CR does not satisfy the conditions, the reconciler is skipped entirely — no resources are created or deleted, and the CRD’s health state stays idle (not degraded).
This is distinct from resource-level conditions (when: on individual resources inside onCreate/onReconcile). Those conditions are evaluated inside the reconciler. operatorBox.preReconcile is evaluated earlier — either at the informer level (enqueueGate:) before the item enters the queue, or at the kordinator level (reconcileGate:) before the reconciler is called.
Declaring a pre-reconcile gate
spec:
crds:
app:
apiTypes:
group: apps.demo.io
kind: App
version: v1alpha1
operatorBox:
preReconcile:
reconcileGate:
when:
- field: "{{ .spec.enabled }}"
equals: "true"
onReconcile:
deployments:
- name: "{{ .metadata.name }}"
image: "{{ .spec.image }}"
reconcile: true
With this configuration:
- A CR with
spec.enabled: true→ reconciler runs, Deployment is created/updated. - A CR with
spec.enabled: false→ reconciler is skipped, Deployment is not created. No error recorded. CRD health state isgated.
Gate semantics
| Property | Behavior |
|---|---|
| Scope | Per-CR object — each CR’s conditions are evaluated independently |
| Phase | Before the reconciler is called — after dequeue, before safeReconcile |
| On gate | Item is dropped from the queue without re-queuing. No error. No status write. |
| Health state | gated — healthy (not degraded), idle, with the gate reason reported |
| On next change | If the CR is updated (e.g. spec.enabled flipped to true), the object is re-enqueued and the gate is re-evaluated |
Condition types
All condition operators available in resource-level when: blocks are available here — equals, contains, exists, gt/lt, in, regex, and more. See the full operator reference.
anyOf: (OR semantics) is also supported alongside when: (AND semantics):
preReconcile:
reconcileGate:
when:
- field: "{{ .spec.enabled }}"
equals: "true"
anyOf:
- field: "{{ .spec.environment }}"
equals: "production"
- field: "{{ .spec.environment }}"
equals: "staging"
Both blocks must pass for reconciliation to proceed.
Gated state in Control Center
When a CRD is gated, the Control Center displays a purple badge and the gate reason:
● gated spec.enabled is false
The state is separate from healthy and degraded — it is idle, not an error. It clears automatically the next time a reconcile succeeds (e.g. the CR is updated with a passing value).
Enqueue-level gate (preReconcile.enqueueGate)
preReconcile.enqueueGate fires even earlier — inside the informer’s handleEvent, before the item enters the work queue. The object is silently dropped without ever reaching the kordinator.
operatorBox:
preReconcile:
enqueueGate:
when:
- field: "{{ .spec.active }}"
equals: "true"
Use enqueueGate when you want zero queue pressure for objects that should be completely ignored. Use reconcileGate when you want the kordinator to track the gated state and surface it as health (gated).
preReconcile.enqueueGate | preReconcile.reconcileGate | |
|---|---|---|
| Evaluated by | Informer (handleEvent) | Kordinator (after dequeue) |
| Phase | Before queue entry | After dequeue |
| Health on gate | No effect | gated (idle) |
| Caveat | Object stays out until next watch event | Clears on next successful reconcile |
Difference from resource-level conditions
preReconcile.enqueueGate or reconcileGate | onCreate / onReconcile resource when: | |
|---|---|---|
| Evaluated by | Informer or kordinator | Reconciler (inside reconcile loop) |
| Scope | Entire reconcile cycle | Individual resource |
| Effect | Object never queued or reconciler never called | Resource is skipped; other resources still created |
| Health on gate | enqueueGate: no effect; reconcileGate: gated | No effect on health |
| Error on gate | None | None |
| Re-queue | No — waits for next CR change event | Normal re-queue |
Use pre-reconcile gates when the entire operator should stay dormant until a condition is met. Use resource-level conditions when most resources should be created but some are optional.
Testing gates
Simulate (with --envtest)
Use absent: to assert a resource was never created when the gate fires:
# simulate-gated.yaml
crFiles:
- cr-app-disabled.yaml # spec.enabled: false
expect:
crds:
app:
absent:
- resource: deployments
Run with:
ork simulate -f simulate.yaml --envtest
E2E
Assert the /katalog runtime endpoint reports gated: true after patching the CR:
steps:
- kubectl:
patch:
resource: apps
name: my-app
patch: '{"spec":{"enabled":false}}'
- kubectl:
port-forward:
pod-selector: "app=orkestra-leader"
port: 8080
assert:
path: /katalog
jq: '.crds.app.gated'
equals: "true"
Try it
ork init --pack intermediate
cd 05-when-conditions/conditional-reconciliation
The pack includes an App CRD (gated by spec.enabled), a Route CRD (unconditional), a Simulate suite that tests both pass and gate scenarios, and a minimal E2E.