Targets — One CRD, Multiple Reconcile Strategies

3 min read

In the mixed-operator pattern example pack, a single runtime runs three different CRDs — each with its own operatorBox. One CRD is purely declarative. One uses a typed hooks binary. One uses a constructor. The Komposer composes them and they run together.

Targets are the same idea, applied to a single CRD.

Instead of three CRDs with three different operatorBoxes, you have one CRD with three named surfaces — and each surface has its own operatorBox. Each target defines what gets created, how reconciliation works, and under what conditions it runs. The schema is shared. The gateway surface is shared. What varies is declared per target.

Try the Mixed Operator pattern
ork init --pack advanced/11-mixed-operator-pattern

Follow the steps in the README.


Each target has its own operatorBox

A target’s operatorBox works exactly like a CRD-level operatorBox. It can be declarative — creating resources without any Go code. It can declare hooks. It can declare a constructor. It can import motifs. It can define preReconcile gates that control when reconciliation runs.

serve:
  target:
    standard:
      operatorBox:
        onCreate:
          deployments:
            - image: "{{ .spec.image }}"   # declarative — no hooks needed

    managed:
      operatorBox:
        preReconcile:
          enqueueGate:
            when:
              - field: '{{ inBusinessHours }}'
                equals: "true"
        reconciler:
          hooks:
            location: github.com/myorg/my-operator/hooks
            function: ManagedHooks
            alias: managed
            args:
              tier: managed

    custom:
      operatorBox:
        reconciler:
          default: false
          constructor:
            location: github.com/myorg/my-operator/reconciler
            function: NewCustomReconciler
            alias: custom

A caller picks a surface. The runtime applies that surface’s operatorBox — its gates, its hooks or constructor, its declared resources — for every CR that arrives from that surface.


What a target’s operatorBox can declare

Anything a CRD-level operatorBox can declare is available per target:

DeclarationWhat it does
onCreate resourcesDeclaratively create Deployments, Services, ConfigMaps, etc. when the CR is created
reconciler.hooksA typed hook binary that runs on each reconcile event
reconciler.constructorA custom reconciler that owns the full reconcile loop
preReconcile.enqueueGateA condition that must be true before a CR is enqueued
preReconcile.reconcileGateA condition that must be true before reconciliation proceeds
importsOne or more Motifs, composing shared behaviour into the target
argsConfiguration values passed to hooks or the constructor
status.fieldsStatus fields written after each reconcile cycle

The CRD-level operatorBox is still the base. A target’s operatorBox extends or overrides it for that surface.


Targets in a Katalog

spec:
  crds:
    blockchainapp:
      apiTypes: ...

      operatorBox:          # base — applies to all targets unless overridden
        reconciler:
          workers: 2
          resync: 30s

      serve:
        target:
          v2-enabled:
            primary: true
            operatorBox:
              preReconcile:
                enqueueGate:
                  when:
                    - field: '{{ inBusinessHours }}'
                      equals: "true"
              reconciler:
                hooks:
                  location: github.com/myorg/blockchain/hooks
                  function: BlockchainHooks
                  alias: bchooks
                  args:
                    featureEnabled: "true"

          v2-disabled:
            operatorBox:
              reconciler:
                hooks:
                  location: github.com/myorg/blockchain/hooks
                  function: BlockchainHooks
                  alias: bchooks
                  args:
                    featureEnabled: "false"

          v2-custom:
            operatorBox:
              reconciler:
                default: false
                constructor:
                  location: github.com/myorg/blockchain/reconciler
                  function: NewBlockchainReconciler
                  alias: bcctor

Three surfaces. One CRD. The same schema, the same informer, the same gateway — with different operatorBoxes behind each surface.

When a CR moves from one surface to another, the runtime cleans up what the previous surface created before the new surface takes over.


  • OperatorBox — the full operatorBox schema: onCreate, reconciler, preReconcile, status, and imports
  • Reconciler Model — how a CR moves through the reconcile loop from enqueue to status patch
  • Args — configuration-only variation across targets without changing reconcile logic