README
¶
10 — Custom Constructor
When you need maximal control — migrate existing operators, run state machines, or own the reconcile loop.
Orkestra is declarative first. Most operators — even complex ones — are fully expressible in a Katalog using when: conditions, dependencies, status fields, and hook templates. Only when you have an existing controller‑runtime operator that you want to bring in as‑is, or when you need complete control over the reconcile loop (e.g., a custom state machine not easily described in YAML), do you reach for a custom constructor.
The three layers of Orkestra
| Layer | What you write | Use case |
|---|---|---|
| Declarative Katalog | YAML (status, when, resources) | 95% of operators |
| Typed Hooks | Go functions (OnReconcile, OnDelete) |
Adding complex external calls, custom logic inside the safe loop |
| Custom Constructor | Full domain.Reconciler implementation |
Migrating existing controllers, state machines that own the loop |
Try declarative first. If you can’t, try hooks. Only then consider a constructor.
What a constructor gives you – and what you lose
| Feature | Declarative / Hooks | Constructor |
|---|---|---|
| Finalizer management | Automatic | You handle |
| Status (Ready condition) | Automatic | You write |
| Declared status fields | Written automatically | Ignored |
| Kubernetes events | Automatic | You emit |
| Drift correction (onReconcile templates) | Yes | You implement |
| Panic recovery | Yes | Yes (still safe) |
| Metrics (reconcile duration, errors) | Yes | Yes |
| Worker pool, queue, informer | Yes | Yes |
Constructor gives you full control over the reconcile method. Use it when you already have an existing reconciler and want to plug it into Orkestra without rewriting.
How to migrate an existing controller‑runtime operator
Suppose you have a standard controller‑runtime reconciler:
// Existing code – controller‑runtime
func (r *PipelineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// your logic here
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}
To move it to Orkestra:
- Change the signature to
Reconcile(ctx context.Context, key string) errorkeyisnamespace/name(same asreq.String())- Return
nilfor done,errorto requeue with exponential backoff
- Remove the manager setup – Orkestra provides the informer, queue, and metrics.
- Register your constructor in the Katalog with
reconciler.default: false - Run
ork generate registryto wire it into Orkestra.
That’s it. Your existing reconcile logic runs inside Orkestra’s runtime infrastructure.
When you might still want a constructor (migration aside)
- State machines with many phases – you could also do this declaratively with
when:conditions, but a constructor lets you centralise the state transition logic. - Complex external API dependencies – if the hook model’s
OnReconcileis too restrictive (e.g., you need to requeue conditionally based on external state), a constructor gives you full control. - Gradual adoption – you have a large codebase that already implements
Reconcile, and you want to run it under Orkestra while you later refactor parts into hooks.
Otherwise, use declarative Katalog or typed hooks.
The example: Pipeline state machine
This example demonstrates a constructor that runs a series of Jobs (build → test → notify). It is intentionally written as a constructor to show the pattern. But the same behaviour can be expressed declaratively in the Katalog. We provide the constructor version for learning and migration reference.
Files in this pack
.
├── api/v1alpha/ ← Pipeline CRD Go types
├── reconciler/ ← custom reconciler implementation
│ └── reconciler.go ← NewPipelineReconciler + Reconcile logic
├── cmd/orkestra/ ← main.go (imports generated registry)
├── pkg/runtime/ ← generated registry (after `make registry`)
├── katalog.yaml
├── Makefile
├── Dockerfile
└── crd.yaml, cr.yaml
Step 1 – Generate the registry
make registry
This creates pkg/runtime/zz_generated_runtime_registry.go, which registers your CRD types and the NewPipelineReconciler constructor.
Step 2 – Enable the registry in main.go
In cmd/orkestra/main.go, uncomment the blank import:
import (
_ "github.com/workspace/orkestra-pipeline-demo/pkg/runtime"
// ...
)
Step 3 – Build your custom binary
First, see the expected error with the standard ork CLI:
ork validate -k katalog.yaml
# error: no reconciler constructor registered for Kind=Pipeline
Now build your own binary:
make clean
make build
cp ~/.orkestra/bin/ork ./ork
Validate with your binary:
./ork validate -k katalog.yaml # passes
Step 4 – Run locally
kind create cluster --name ork-pipeline
kubectl apply -f crd.yaml
./ork run -k katalog.yaml
In another terminal:
kubectl apply -f cr.yaml
kubectl get pipeline -w
kubectl get jobs -w
You’ll see the state machine step through build → test → notify.
Step 5 – Deploy to a cluster
make release IMAGE=yourregistry/pipeline-operator:v1
./ork generate bundle -k katalog.yaml -o bundle.yaml
kubectl apply -f bundle.yaml
helm repo add orkestra https://ialexeze.github.io/orkestra
helm install orkestra orkestra/orkestra \
--set runtime.image.repository=yourregistry/pipeline-operator \
--set runtime.image.tag=v1 \
--namespace orkestra-system --create-namespace
kubectl apply -f crd.yaml
kubectl apply -f cr.yaml
Cleanup
kind delete cluster --name ork-pipeline
helm uninstall orkestra -n orkestra-system
kubectl delete -f bundle.yaml
Next steps
- If you have an existing controller‑runtime operator, try migrating it using this pattern.
- For new operators, start with the declarative Katalog (example 01–08). Only reach for a constructor if you truly need to own the reconcile loop.
Directories
¶
| Path | Synopsis |
|---|---|
|
reconciler/pipeline_reconciler.go
|
reconciler/pipeline_reconciler.go |
|
api/v1alpha1/pipeline_types.go
|
api/v1alpha1/pipeline_types.go |