Skip to content

feat: controller-runtime compatibility, watch:, and queue.retryBackoff: - #264

Merged
iAlexeze merged 1 commit into
mainfrom
feat/ctrlruntime-compat
Aug 20, 2026
Merged

feat: controller-runtime compatibility, watch:, and queue.retryBackoff:#264
iAlexeze merged 1 commit into
mainfrom
feat/ctrlruntime-compat

Conversation

@iAlexeze

@iAlexeze iAlexeze commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Phase 1

Every controller-runtime operator carries the same infrastructure: a Manager, scheme registration, SetupWithManager, leader election wiring. Orkestra already absorbs all of that. This PR closes the gap between the two worlds.

kubeclient.Interface is the single injection point. kubeclient.ToClient hands back a client.Client. domain.ReconcilerFrom adapts the reconcile.Reconciler signature. The result is a two-line constructor — the reconciler body is untouched:

func NewWebAppReconciler(kube kubeclient.Interface) domain.Reconciler {
    return domain.ReconcilerFrom(&WebAppReconciler{
        client: kubeclient.ToClient(kube),
    })
}

That is the entire migration. No Manager. No scheme. No SetupWithManager. ork migrate --mode toclient generates it.


The watch: block gives an operatorBox a secondary watch. When a Deployment or ConfigMap changes, the owning CR is re-enqueued — without writing an informer. Sentinels (generationChanged, labelsChanged, annotationsChanged, deletionStarted, finalizersChanged) are computed at UpdateFunc time and carried through the queue so enqueueGate can filter before the item is ever dequeued.

watch:
  - apiVersion: apps/v1
    kind: Deployment
    namespace: default
    on: [update, delete]
    enqueueGate:
      sentinels: [generationChanged]

queue.retryBackoff: puts the runtime in control of retry timing after a failed reconcile. external[].retryBackoff: does the same for each HTTP call. Both take a shorthand duration or a full initial / max / multiplier / maxAttempts block. Both are validated by ork validate.

reconciler:
  queue:
    retryBackoff:
      initial: 500ms
      max: 5m
      multiplier: 2.0
      maxAttempts: 5

Future Work (Phase 2)

Client Caching
All client.Get() calls currently bypass the informer cache and make direct API server requests. While Orkestra maintains informers for all registered and watched resources, the kubeclient.ToClient adapter does not yet leverage the cached client. The next PR will switch to an informer-backed client to align with controller-runtime's caching behavior.

Requeue Semantics
The requeue.after field is not yet supported. Orkestra's Reconcile method signature (ctx, key) does not align with controller-runtime's (ctx, request) (result, error) pattern, which is required for proper requeue handling. The next PR will adopt the controller-runtime reconciliation signature, enabling:

  • requeue.after support for all operators
  • when and or condition-based requeue timing for declarative operators

Planned declarative syntax:

reconciler:
  requeue:
    after: "{{ spec.requeueSeconds | default 60s }}"
    when:     # AND semantics; use `or` block for OR
      - field: spec.myField
        equals: "do-it"
      - field: spec.status.phase
        notEquals: "complete"

- kubeclient.Interface + ToClient + domain.ReconcilerFrom: two-line constructor, zero changes to Reconcile
- watch: block: secondary resource watches that re-enqueue the owning CR
- queue.retryBackoff: per-CRD backoff; external[].retryBackoff: per-call retry config
- sentinel package: generationChanged, labelsChanged, annotationsChanged, deletionStarted, finalizersChanged
- docs: README, CHANGELOG, landing page, architecture, sentinel + watch + retryBackoff schema pages
@iAlexeze
iAlexeze merged commit ef5e97d into main Aug 20, 2026
8 checks passed
@iAlexeze
iAlexeze deleted the feat/ctrlruntime-compat branch August 20, 2026 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant