Conditional Reconciliation

4 min read

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 is gated.

Gate semantics

PropertyBehavior
ScopePer-CR object — each CR’s conditions are evaluated independently
PhaseBefore the reconciler is called — after dequeue, before safeReconcile
On gateItem is dropped from the queue without re-queuing. No error. No status write.
Health stategated — healthy (not degraded), idle, with the gate reason reported
On next changeIf 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.enqueueGatepreReconcile.reconcileGate
Evaluated byInformer (handleEvent)Kordinator (after dequeue)
PhaseBefore queue entryAfter dequeue
Health on gateNo effectgated (idle)
CaveatObject stays out until next watch eventClears on next successful reconcile

Difference from resource-level conditions

preReconcile.enqueueGate or reconcileGateonCreate / onReconcile resource when:
Evaluated byInformer or kordinatorReconciler (inside reconcile loop)
ScopeEntire reconcile cycleIndividual resource
EffectObject never queued or reconciler never calledResource is skipped; other resources still created
Health on gateenqueueGate: no effect; reconcileGate: gatedNo effect on health
Error on gateNoneNone
Re-queueNo — waits for next CR change eventNormal 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.