Documentation
¶
Overview ¶
pkg/reconciler/children.go
pkg/reconciler/children_gvr.go
pkg/reconciler/conditions.go
pkg/reconciler/cross_util.go
pkg/reconciler/generic.go
pkg/reconciler/generic_autoscale.go
AutoscaleTarget implementation for GenericReconciler.
GenericReconciler implements the three AutoscaleTarget methods so it can be passed directly to NewAutoscaler. The autoscaler calls these methods; user code should not call them directly.
ResizeWorkers — fully wired via ResizableSemaphore. SetQueueDepthLimit — enforced atomically in Workqueue.Enqueue. SetResyncInterval — drives an independent resync goroutine that re-enqueues
all cached objects at the declared interval. 0 = idle (informer handles baseline resync). The goroutine is started once by startCRDWorkers via the ResyncLoopStarter interface; SetResyncInterval adjusts its rate at runtime.
pkg/reconciler/generic_namespace.go
Namespace guard wiring for GenericReconciler.
RestrictedNamespaces and AllowedNamespaces live on orktypes.CRDEntry directly — not on OperatorBoxConfig. GenericReconciler stores the full CRDEntry as r.crd, so the guard reads r.crd.RestrictedNamespaces and r.crd.AllowedNamespaces directly.
namespaceGuardFunc is called once per runResourceGroup invocation and returns a closure. run_*.go files call it after resolving the target namespace and before condition evaluation.
Fast path: returns nil when CRD has no restrictions — all run_*.go guard with "if guard != nil" so nil means zero allocation, zero check.
pkg/reconciler/generic_registry.go
KatalogRegistry — the interface GenericReconciler needs from the Kordinator registry to perform zero-API-call cross-CRD observation.
Why a local interface instead of importing pkg/kordinator directly:
pkg/reconciler → pkg/kordinator would create an import cycle. pkg/kordinator already imports pkg/reconciler (for domain.Reconciler). A local interface breaks the cycle — Go's implicit interface satisfaction means *kordinator.ResourceKatalog satisfies this automatically.
pkg/reconciler/kube_reader_adapter.go
KubeReader adapter — bridges kubeclient.Kubeclient to the orktypes.KubeReader interface used by provider libraries.
Providers receive KubeReader not *kubeclient.Kubeclient because:
- The interface is narrow: GetSecret and GetConfigMap only — no write access
- Providers must not write Kubernetes resources (Orkestra owns cluster state)
- A narrow interface is easier to mock in provider tests
- The kubeclient package is internal; providers should not depend on it
pkg/reconciler/normalize.go
applyNormalize — canonical spec transformation.
Called as the first step of reconcileImpl, before mutation and validation. Returns a deep copy of the CR with normalize.spec fields replaced by their rendered values. The informer cache object is never modified.
Pipeline position:
informer cache → DeepCopy → applyNormalize → mutation → validation → runTemplateReconcile
After applyNormalize, every downstream step sees the normalized spec. The raw CR in etcd is unchanged — normalize is purely an in-memory operation.
Example — CronJob accepting both string and map schedule:
normalize:
spec:
schedule: >
{{ if eq (typeOf .spec.schedule) "map" }}
{{ cronExpr .spec.schedule.minute .spec.schedule.hour .spec.schedule.dayOfMonth .spec.schedule.month .spec.schedule.dayOfWeek }}
{{ else }}
{{ .spec.schedule }}
{{ end }}
After normalize, .spec.schedule is always a cron string. onReconcile templates use {{ .spec.schedule }} directly — no branching needed.
pkg/reconciler/ptr_hooks.go
── Design note: pointer type parameter and the ObjectHooks adapter ───────────
WHY PTR, NOT T ────────────── GenericReconciler uses the type parameter name PTR rather than the conventional T to make the pointer expectation visible at every call site. Kubernetes informers store and return pointer values: when you create an informer for Database objects, every item retrieved from its cache is a *Database. The reconciler type-asserts the raw interface{} value from the cache with raw.(PTR), which succeeds only when PTR is a pointer type.
Users therefore write:
domain.ReconcileHooks[*Database]{OnReconcile: myFunc}
not:
domain.ReconcileHooks[Database]{OnReconcile: myFunc} // wrong
This matches the pattern used throughout the Kubernetes ecosystem: controller-runtime, kubebuilder, and all typed client-go code work with pointer receivers and pointer type arguments for the same reason.
THE MISMATCH PROBLEM ──────────────────── Go generics are invariant: ReconcileHooks[*Database] and ReconcileHooks[domain.Object] are unrelated types even though *Database implements domain.Object. A direct type assertion:
anyHooks.(domain.ReconcileHooks[PTR])
fails at runtime whenever the caller provides ReconcileHooks[*Database] but PTR is inferred as domain.Object — which is exactly what happens in the runtime registry path inside konstructor.go, where the concrete type is not known at compile time.
WHY NOT TWO TYPE PARAMETERS ─────────────────────────── The constraint PTR interface{ *S; domain.Object } (with a second parameter S any) enforces at compile time that PTR is a pointer to a concrete struct. However, it cannot be satisfied by domain.Object (an interface), so the runtime registry path in konstructor.go — which infers PTR = domain.Object — would fail to compile. Updating konstructor.go to supply concrete types would require the registry to carry a separate typed factory per CRD, a much larger change with no benefit for the dynamic template path where no hooks exist.
THE OBJECTHOOKS ADAPTER ─────────────────────── Instead of storing domain.ReconcileHooks[PTR] on the reconciler, we store domain.ObjectHooks — a plain struct whose function fields are parameterised on domain.Object. The adapter is built once in NewGenericReconciler via the domain.HookBinder interface:
binder, ok := anyHooks.(domain.HookBinder) hooks = binder.BindToObjectHooks()
Every domain.ReconcileHooks[T] value satisfies HookBinder automatically through its BindToObjectHooks() method. That method wraps each hook in a closure that performs obj.(T) before delegating to the typed function:
oh.OnReconcile = func(ctx context.Context, obj domain.Object) error {
return fn(ctx, obj.(T)) // T = *Database at runtime
}
The assertion is safe because:
- The informer is always constructed for a single concrete type.
- Every item returned from its cache IS that type, even when retrieved as domain.Object (or interface{}).
- GenericReconciler already asserts raw.(PTR) earlier in reconcileCore, so obj arriving at the hook is guaranteed to be of the right type.
END-TO-END CALL PATH FOR TYPED HOOKS ──────────────────────────────────────
User writes: func DatabaseHooks() domain.AnyReconcileHooks { return domain.ReconcileHooks[*Database]{OnReconcile: onReconcile} }
Generated registry registers DatabaseHooks in HookRegistry.
konstructor.go calls HookFactory() → gets ReconcileHooks[*Database].
NewGenericReconciler receives it as domain.AnyReconcileHooks. PTR is inferred as domain.Object (from func() domain.Object newObj).
anyHooks.(domain.HookBinder) succeeds because ReconcileHooks[*Database] has BindToObjectHooks() via the generic method.
BindToObjectHooks() builds ObjectHooks with closures: obj.(*Database).
At reconcile time, reconcileCore does raw.(*Database or domain.Object) — whichever PTR resolves to — producing a domain.Object value whose underlying type is *Database.
r.hooks.OnReconcile(ctx, obj) invokes the closure from step 6. obj.(*Database) succeeds. The user's typed function receives *Database. ✓
CUSTOM HOOK WRAPPERS ──────────────────── If you wrap ReconcileHooks[T] in your own struct (e.g. to add middleware), implement domain.HookBinder by forwarding to the inner hooks:
type LoggingHooks[T domain.Object] struct {
Inner domain.ReconcileHooks[T]
}
func (h LoggingHooks[T]) isHooks() {}
func (h LoggingHooks[T]) BindToObjectHooks() domain.ObjectHooks {
oh := h.Inner.BindToObjectHooks()
inner := oh.OnReconcile
oh.OnReconcile = func(ctx context.Context, obj domain.Object) error {
log.Info("reconciling", "name", obj.GetName())
return inner(ctx, obj)
}
return oh
}
pkg/reconciler/rollback.go
Rollback implementation for GenericReconciler.
Three responsibilities:
snapshotSpec — writes the current spec to the previous-spec annotation before any spec change is applied. Called when generation changes.
shouldRollback — reads the operatorbox health state and evaluates the rollback trigger. Returns true when rollback should activate.
runRollback — applies the onRollback resource group using the previous spec hydrated into the resolver as .previous.*.
The rollback phase state lives in status.phase. The reconciler checks it at the top of reconcileImpl and blocks normal reconciliation while active. The only exit from rollback is a spec generation change.
pkg/reconciler/run_configmaps.go
pkg/reconciler/run_cronjobs.go
pkg/reconciler/run_cross.go
Cross-CRD HTTP fallback — fetches a CR detail from another Orkestra instance.
The primary path (informer cache, zero API calls) lives in run_template_reconcile.go in ReadCrossFromDeclarations. That function calls fetchCrossViaHTTP when the informer is unavailable (cross-binary, cross-cluster).
The full informer-cache path requires threading the Kordinator registry into GenericReconciler. ReadCrossFromInformer is called instead of fetchCrossViaHTTP for same-binary CRDs.
pkg/reconciler/run_delete_ordered.go
Sequential deletion with completion gates.
When onDelete.ordered: true the reconciler deletes resources in stages rather than relying on garbage collection. Each stage is a HookTemplates block. After submitting all deletes in a stage the reconciler polls the API server until every resource is confirmed gone, then advances to the next stage.
Deletion order within a stage is undefined; order is enforced between stages.
pkg/reconciler/run_deployments.go
pkg/reconciler/run_docker.go
Docker build/push dispatch — runs before resource groups in runTemplateReconcile.
The Docker hook performs a minimal docker build and optional push of the declared image. Results are injected into the resolver context via resolver.WithDocker() so subsequent template expressions and when: conditions can reference them.
Call sequence in runTemplateReconcile:
- resolver = NewResolver(ctx, obj)
- resolver = resolver.WithCross(ReadCross(...)) ← cross-CRD first
- resolver, err = runGit(ctx, ...) ← Git second
- resolver, err = runExternal(ctx, ...) ← HTTP third
- resolver, err = runDocker(ctx, ...) ← Docker fourth
- runDeployments, runServices, ... etc.
Results in template context:
.docker.image → built image reference .docker.buildSucceeded → "true" or "false" .docker.error → error message (if any) .docker.called → "true" if hook executed
When: conditions can gate on these:
when:
- field: docker.buildSucceeded
equals: "true"
pkg/reconciler/run_docker_helper.go
pkg/reconciler/run_external.go
External HTTP call dispatch — runs before resource groups in runTemplateReconcile.
Calls are executed sequentially in declaration order. Results are injected into the resolver context via resolver.WithExternal() so subsequent template expressions and when: conditions can reference them.
Call sequence in runTemplateReconcile:
- resolver = NewResolver(ctx, obj)
- resolver = resolver.WithCross(ReadCross(...)) ← cross-CRD first
- resolver, err = runExternal(ctx, resolver, ...) ← HTTP calls second
- runDeployments, runServices, ... etc. ← resources use results
Results in template context:
.external.<name>.status → HTTP status code ("200", "404", "503")
.external.<name>.body → response body (first 4096 bytes)
.external.<name>.error → error message on failure
.external.<name>.called → "true" if the call was made
When: conditions on deployments can gate on these:
when:
- field: external.health-check.status
equals: "200"
pkg/reconciler/run_foreach.go
forEach expansion — expands template sources with a forEach declaration into N resolved sources, one per element in the list field.
forEach works on every resource type. The expansion happens before the resource-specific run_*.go function is called — each run_*.go function receives an already-expanded slice and is unaware of forEach.
Expansion sequence in runTemplateReconcile:
deployments := expandForEachDeployments(resolver, t.Deployments) runDeployments(ctx, kube, resolver, obj, deployments, update)
YAML:
onReconcile:
deployments:
- name: "{{ .metadata.name }}-{{ .item }}"
image: "{{ .spec.image }}"
forEach:
field: spec.regions
as: item
For CR with spec.regions: ["us-east-1", "eu-west-1"]: Produces two DeploymentTemplateSources:
{Name: "my-app-us-east-1", Image: "nginx:latest", ...}
{Name: "my-app-eu-west-1", Image: "nginx:latest", ...}
The expansion resolves ALL template expressions immediately — the returned slice contains static (non-template) values ready for the registry functions.
when: and anyOf: on forEach sources are evaluated per-item — each expanded source may pass or fail conditions independently.
pkg/reconciler/run_git.go
Git hook dispatch — runs before external calls and resource groups in runTemplateReconcile.
The Git hook performs a minimal clone/fetch of the declared repository, computes the current commit hash, detects whether the commit changed since the previous reconcile, and injects results into the resolver context via resolver.WithGit().
Call sequence in runTemplateReconcile:
- resolver = NewResolver(ctx, obj)
- resolver = resolver.WithCross(ReadCross(...)) ← cross-CRD first
- resolver, err = runGit(ctx, gvk, resolver, ...) ← Git second
- resolver, err = runExternal(ctx, ...) ← HTTP third
- resolver, err = runDocker(ctx, ...) ← Docker fourth
- runDeployments, runServices, ... etc.
Results in template context:
.git.commit → HEAD commit hash .git.changed → "true" if commit changed since last reconcile .git.path → working directory path .git.error → error message if operation failed .git.called → "true" if Git hook executed .git.succeeded → "true" if Git operation succeeded
When: conditions can gate on these:
when:
- field: git.changed
equals: "true"
Change detection is annotation-based — the last-seen commit is stored in:
orkestra.orkspace.io/last-commit
This annotation survives pod restarts, preventing spurious rebuilds.
pkg/reconciler/run_hpas.go
pkg/reconciler/run_ingresses.go
pkg/reconciler/run_jobs.go
pkg/reconciler/run_mutation.go
pkg/reconciler/run_namespace_guard.go
pkg/reconciler/run_namespace.go
pkg/reconciler/run_pdbs.go
pkg/reconciler/run_providers.go
Provider dispatch — the engine that connects Katalog declarations to registered provider libraries.
This file is called from runTemplateReconcile after all Kubernetes resource groups (deployments, services, jobs, etc.) have been reconciled. It:
- Skips immediately if no provider blocks are declared or no providers registered
- Resolves template expressions in every declaration field
- Evaluates when: conditions — drops declarations that do not pass
- Calls provider.Reconcile(ctx, req) for each active block
On CR deletion (finalizer path), runProviderDelete is called instead. It calls provider.Delete for all declarations — no condition filtering.
Integration in runTemplateReconcile (after all Kubernetes resource groups):
if err := runProviders(ctx, obj, resolver, rc.ProviderBlocks, providerRegistry, kubeReader); err != nil {
return fmt.Errorf("providers: %w", err)
}
Integration in the finalizer handler:
if err := runProviderDelete(ctx, obj, resolver, rc.ProviderBlocks, providerRegistry, kubeReader); err != nil {
return fmt.Errorf("provider cleanup: %w", err)
}
pkg/reconciler/run_pvcs.go
pkg/reconciler/run_pvs.go
pkg/reconciler/run_replicasets.go
pkg/reconciler/run_secrets_tls.go
TLS certificate generation and secret rotation for Orkestra secrets.
Extends the once: true pattern with:
- rotateAfter: <duration> — time-based rotation using a generated-at annotation
- tls: {...} — self-signed CA + signed certificate generation
The generated Secret is annotated with:
orkestra.orkspace.io/generated-at: "2026-04-06T08:00:00Z" orkestra.orkspace.io/rotate-after: "90d"
On each reconcile, secretNeedsRotation reads these annotations and returns true when the threshold is crossed. The caller deletes and recreates.
TLS generation produces a Secret of type kubernetes.io/tls with:
tls.crt — PEM-encoded signed certificate tls.key — PEM-encoded private key (for the server) ca.crt — PEM-encoded self-signed CA certificate
The CA is unique per Secret — appropriate for self-signed operator webhooks. For shared CAs across multiple services, store the CA in a separate Secret and reference it (planned: ca.secretRef field).
pkg/reconciler/run_secrets.go
Adds to the previous version:
- orktypes.EvaluateWhen instead of evaluateConditions (fixes anyOf: being ignored)
- rotateAfter: <duration> support — time-based credential rotation
- tls: {...} support — self-signed CA + signed certificate generation
Execution order per secret declaration:
- EvaluateWhen(when:, anyOf:) — skip if conditions fail
- Once/rotation check a. once: true, rotateAfter set — check annotation, delete if expired b. once: true, no rotateAfter — skip if exists c. once: false — standard create/update
- Resolve template expressions — randomAlphanumeric evaluated here
- TLS generation (if tls: is set) — generate CA + cert, skip data resolution
- Create/Update/CopyToNamespaces
- Annotate with generated-at — only when rotateAfter is set
pkg/reconciler/run_secrets_once.go
once: true on secrets — idempotent random secret generation.
The Kubernetes reconcile loop is called on every resync. A secret with random data (password, API key) would change every 30 seconds without the once: guard. once: true makes the create step check for existence first — if the Secret already exists, skip template evaluation entirely.
This is the check-before-generate pattern used by cert-manager and every other tool that generates credentials idempotently.
YAML:
secrets:
- name: "{{ .metadata.name }}-credentials"
once: true
data:
password: "{{ randomAlphanumeric 32 }}"
apiKey: "{{ randomHex 16 }}"
jwtSecret: "{{ randomBase64 32 }}"
Behaviour:
- First reconcile: Secret does not exist → evaluate templates → create
- Every subsequent reconcile: Secret exists → skip completely (no-op)
- CR deleted: Secret deleted via owner reference (garbage collection)
once: true is IGNORED when update=true (onReconcile with reconcile: true). A secret in onReconcile should use static or deterministic template values. Combining once: true with reconcile: true logs a warning and skips the once: guard — the caller opted into continuous reconciliation.
once: false (default): standard create/update behavior, unchanged.
pkg/reconciler/run_serviceaccounts.go
pkg/reconciler/run_services.go
pkg/reconciler/run_statefulsets.go
pkg/reconciler/run_status.go
Status management — three layers:
Layer 1 — Automatic Ready condition.
Written after every reconcile regardless of Katalog declarations. reason: ReconcileSucceeded on success, ReconcileError on failure. No Katalog declaration required. Requires subresources.status in the CRD.
Layer 2 — Declarative status fields.
Written from status.fields declarations in the Katalog. Template expressions resolved against the live CR object map. Optional when: conditions gate individual field writes — this is what makes declarative state machines possible. Written only on successful reconcile.
Layer 3 — Child resource propagation.
Children are read after runTemplateReconcile and injected into the
resolver context via WithChildren. The returned resolver is used for
all subsequent status resolution.
Status field expressions reference children as:
{{ .children.deployment.status.readyReplicas }}
{{ .children.cronjob.status.lastScheduleTime }}
Children are keyed by lowercase Kind — matches kubectl conventions.
pkg/reconciler/run_template_reconcile.go
Execution order (each step can reference all previous steps):
- Recieve NewResolver → .spec.*, .status.*, .metadata.*
- r.readCross → .cross.<crd>.status.* (informer cache, zero API calls)
- runExternal → .external.<n>.status, .body (HTTP calls)
- forEach expand → N sources from N-element list fields
- onCreate groups → deployments, services, secrets, configmaps, ...
- onReconcile groups
- runProviders → aws:, mongodb:, ... (external infra)
pkg/reconciler/run_validation.go
Index ¶
- func AddFinalizer(o domain.Object, finalizer string) (updated bool)
- func ContainsFinalizer(o domain.Object, finalizer string) bool
- func ReadChildren(ctx context.Context, kube *kubeclient.Kubeclient, obj domain.Object, ...) map[string]interface{}
- func ReadCrossFromInformer(indexer cache.Indexer, key string) map[string]interface{}
- func ReadCrossFromInformerByLabel(indexer cache.Indexer, labelKey, labelValue string) map[string]interface{}
- func RemoveFinalizer(o domain.Object, finalizer string) (updated bool)
- type AutoscalerRunner
- type DockerBuildResult
- type DockerPushResult
- type GenericReconciler
- func (r *GenericReconciler[PTR]) GetAutoMetrics() *autoscaler.AutoMetrics
- func (r *GenericReconciler[PTR]) Reconcile(ctx context.Context, key string) error
- func (r *GenericReconciler[PTR]) ReportQueueDepth(depth int64)
- func (r *GenericReconciler[PTR]) ResizeWorkers(n int)
- func (r *GenericReconciler[PTR]) RunAutoscaler(ctx context.Context)
- func (r *GenericReconciler[PTR]) SetQueue(wq *orkqueue.Workqueue)
- func (r *GenericReconciler[PTR]) SetQueueDepthLimit(n int)
- func (r *GenericReconciler[PTR]) SetResyncInterval(d time.Duration)
- func (r *GenericReconciler[PTR]) SetRollbackNotifiers(onTrigger, onClear func())
- func (r *GenericReconciler[PTR]) SetSpawnWorker(fn func())
- func (r *GenericReconciler[PTR]) StartResyncLoop(ctx context.Context)
- func (r *GenericReconciler[PTR]) WorkerInfo(configuredWorkers, configuredQueueDepth int) *autoscaler.WorkerInfo
- type GitOperationResult
- type KatalogRegistry
- type MutationChange
- type MutationResult
- type NamespaceGuardResult
- type QueueDepthReporter
- type QueueInjector
- type ResyncLoopStarter
- type ValidationResult
- func (r *ValidationResult) Blocked() bool
- func (r *ValidationResult) DenialError() error
- func (r *ValidationResult) DenialMessage() string
- func (r *ValidationResult) Denied() bool
- func (r *ValidationResult) Error() error
- func (r *ValidationResult) HasViolations() bool
- func (r *ValidationResult) HasWarnings() bool
- func (r *ValidationResult) Log(crd, name, ns string)
- func (r *ValidationResult) ViolationSummary() string
- func (r *ValidationResult) WarningSummary() string
- type ValidationViolation
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ReadChildren ¶
func ReadChildren( ctx context.Context, kube *kubeclient.Kubeclient, obj domain.Object, resolver *orktmpl.Resolver, operatorBox orktypes.OperatorBoxConfig, ) map[string]interface{}
── Read children ──────────────────────────────────────────────────────── ReadChildren reads all child resources declared in the Katalog's onCreate templates and returns a structured map for use in status field expressions.
The returned map is injected into the template resolver under the "children" key. Status field expressions can then reference child resource state:
# Singular — the first/only resource of this type
{{ .children.deployment.status.readyReplicas }}
{{ .children.service.status.loadBalancer.ingress }}
# Plural — all resources of this type, by name
{{ (index .children.deployments "my-site-api").status.readyReplicas }}
ReadChildren is called after runTemplateReconcile — child resources exist at this point. Missing status fields resolve to "" (missingkey=zero), which is correct eventual consistency behaviour for newly created resources.
API cost: one GET per child resource type × count. For the common case (one Deployment + one Service), this is two GETs.
This function never returns an error that should fail the reconcile. Read failures are logged and the child is omitted from the map. Status patching proceeds with whatever children were successfully read.
func ReadCrossFromInformer ¶
ReadCrossFromInformer reads one CR from an informer cache by namespace/name key. Zero API server calls — pure in-memory map lookup.
key is "namespace/name" for namespaced CRDs or "name" for cluster-scoped.
Returns a consistent map shape regardless of whether the CR was found — callers use .found == "true" to gate their logic.
func ReadCrossFromInformerByLabel ¶ added in v0.1.9
func ReadCrossFromInformerByLabel( indexer cache.Indexer, labelKey, labelValue string, ) map[string]interface{}
ReadCrossFromInformerByLabel reads the first CR from an informer cache whose labels contain labelKey=labelValue.
The original implementation incorrectly called GetByKey with the label key string. This function correctly iterates indexer.List() and filters.
Returns the first match. When multiple CRs share the label, the first returned by List() is used — List() order is not guaranteed. If you need a specific CR, use name-based lookup (ReadCrossFromInformer) instead.
Types ¶
type AutoscalerRunner ¶
AutoscalerRunner is an optional interface implemented by reconcilers that carry an embedded autoscaler. DependencyKordinator checks for this after calling ReconcilerFactory() and launches the goroutine if present.
type DockerBuildResult ¶
type DockerBuildResult struct {
Error string
}
type DockerPushResult ¶
type DockerPushResult struct {
Error string
}
type GenericReconciler ¶
GenericReconciler manages the full lifecycle of one CRD.
This file is a pure dispatcher. It owns:
- Context enrichment
- Cache reads
- Deletion routing
- Finalizer/Annotation/Label management
- Template interpretation
- Reconcile priority (Go hooks → declarative templates → no-op)
- Event firing and logging
Resource-specific logic lives in separate files:
run_deployments.go — Deployment create/update run_services.go — Service create/update run_secrets.go — Secret create/copy/sync run_configmaps.go — ConfigMap create/copy/sync run_serviceaccounts.go — ServiceAccount create run_jobs.go — Job create (onDelete cleanup) run_cronjobs.go — CronJob create/update
Adding a new resource type:
- Add a file run_<resource>.go with a runXxx() function
- Call it from runTemplateReconcile() and/or runTemplateOnDelete()
- Add the field to orktypes.HookTemplates That is all — generic.go does not change.
Type parameter PTR:
PTR must be a pointer to the concrete CR struct (e.g. *Database). This matches Kubernetes informer semantics: the informer stores pointer values so the type assertion raw.(PTR) in reconcileCore succeeds only for pointer types. When used through the dynamic registry path in konstructor.go, PTR is inferred as domain.Object (the interface), which also satisfies the constraint and is safe because the informer cache always holds the correct underlying concrete type. See pkg/reconciler/ptr_hooks.go for the full design rationale.
func NewGenericReconciler ¶
func NewGenericReconciler[PTR domain.Object]( crd orktypes.CRDEntry, informer cache.SharedIndexInformer, ev *event.Event, kube *kubeclient.Kubeclient, anyHooks domain.AnyReconcileHooks, newObj func() PTR, katalogRegistry *kordinator.ResourceKatalog, providerRegistry orktypes.ProviderRegistry, providerStats providerStatsRecorder, ) *GenericReconciler[PTR]
NewGenericReconciler constructs a GenericReconciler for the given CRD.
PTR must be a pointer to the concrete CR type (e.g. *Database). When called from the runtime registry path in konstructor.go, PTR is inferred as domain.Object (the interface) — this is also valid because the constraint domain.Object is satisfied and the informer stores the correct concrete type.
anyHooks, if non-nil, must implement domain.HookBinder. Every domain.ReconcileHooks[T] value satisfies HookBinder automatically via its BindToObjectHooks() method. Passing any other type panics at startup.
func (*GenericReconciler[PTR]) GetAutoMetrics ¶ added in v0.1.9
func (r *GenericReconciler[PTR]) GetAutoMetrics() *autoscaler.AutoMetrics
GetAutoMetrics returns the live AutoMetrics for this reconciler. Called by kordinator to register it in the cross-metrics registry.
func (*GenericReconciler[PTR]) Reconcile ¶
func (r *GenericReconciler[PTR]) Reconcile(ctx context.Context, key string) error
Reconcile dispatches to the correct reconcile implementation. Order:
- Conditional provisioning (when blocks) — handled by runTemplateReconcile
- Go hooks → Declarative templates → No-op (through reconcileImpl())
The semaphore gates concurrent execution — when an autoscaler is active it can reduce effective concurrency below the goroutine count without stopping goroutines.
func (*GenericReconciler[PTR]) ReportQueueDepth ¶
func (r *GenericReconciler[PTR]) ReportQueueDepth(depth int64)
ReportQueueDepth updates the live queue depth metric read by the autoscaler. Called by the worker loop after each item is processed.
func (*GenericReconciler[PTR]) ResizeWorkers ¶
func (r *GenericReconciler[PTR]) ResizeWorkers(n int)
ResizeWorkers adjusts the semaphore capacity and, when scaling up, starts additional goroutines via the injected spawnWorker function so that goroutine count stays equal to the effective concurrency limit.
func (*GenericReconciler[PTR]) RunAutoscaler ¶
func (r *GenericReconciler[PTR]) RunAutoscaler(ctx context.Context)
RunAutoscaler starts the autoscale evaluation loop. Blocks until ctx is cancelled. Called by DependencyKordinator in a dedicated goroutine. No-op when no autoscale spec is declared.
func (*GenericReconciler[PTR]) SetQueue ¶
func (r *GenericReconciler[PTR]) SetQueue(wq *orkqueue.Workqueue)
SetQueue injects the per-CRD workqueue. Called by startCRDWorkers after ReconcilerFactory() so both SetQueueDepthLimit and the resync goroutine have a reference to the right queue.
func (*GenericReconciler[PTR]) SetQueueDepthLimit ¶
func (r *GenericReconciler[PTR]) SetQueueDepthLimit(n int)
SetQueueDepthLimit updates the workqueue's depth limit. New enqueue calls that would push the queue beyond this limit are dropped with a warning. 0 means unlimited (the default). Safe to call at any time.
func (*GenericReconciler[PTR]) SetResyncInterval ¶
func (r *GenericReconciler[PTR]) SetResyncInterval(d time.Duration)
SetResyncInterval sets the resync goroutine's fire rate. 0 idles the goroutine — the informer's built-in resync handles the baseline cadence. Non-zero values trigger an additional re-enqueue of all cached objects at that interval, independently of the informer's resync period.
func (*GenericReconciler[PTR]) SetRollbackNotifiers ¶ added in v0.1.9
func (r *GenericReconciler[PTR]) SetRollbackNotifiers(onTrigger, onClear func())
SetRollbackNotifiers injects CRDHealth callbacks for rollback tracking. Called once by kordinator after constructing the reconciler.
func (*GenericReconciler[PTR]) SetSpawnWorker ¶ added in v0.1.9
func (r *GenericReconciler[PTR]) SetSpawnWorker(fn func())
SetSpawnWorker injects the goroutine-spawn function from kordinator. Called once after construction — before the autoscaler starts.
func (*GenericReconciler[PTR]) StartResyncLoop ¶
func (r *GenericReconciler[PTR]) StartResyncLoop(ctx context.Context)
StartResyncLoop launches the adjustable resync goroutine. Called once by startCRDWorkers when autoscale: is declared on the operatorbox.
func (*GenericReconciler[PTR]) WorkerInfo ¶ added in v0.1.9
func (r *GenericReconciler[PTR]) WorkerInfo(configuredWorkers, configuredQueueDepth int) *autoscaler.WorkerInfo
WorkerInfo returns a live WorkerInfo snapshot for the /katalog/{crd} endpoint. configuredWorkers and configuredQueueDepth come from the CRD entry at startup.
type GitOperationResult ¶
type KatalogRegistry ¶
type KatalogRegistry interface {
// GetInformerByName returns the SharedIndexInformer for a CRD by its
// lowercase name (the map key in spec.crds).
// Returns nil, false when the CRD is not registered.
GetInformerByName(name string) (cache.SharedIndexInformer, bool)
// GetInformerByLabelSelector returns the SharedIndexInformer for a CRD whose
// metadata.labelSelector contain the given key/value pair.
//
// This enables semantic grouping of CRDs for cross-CRD observation
// (e.g. "tier=platform", "domain=payments") without requiring callers
// to know the CRD's short name. Lookup is case-insensitive on both
// key and value. Returns nil, false when no CRD matches.
GetInformerByLabel(key, value string) (cache.SharedIndexInformer, bool)
}
KatalogRegistry is the interface GenericReconciler uses to look up sibling CRD informers for cross-CRD observation. Satisfied by *kordinator.ResourceKatalog without importing that package.
type MutationChange ¶
type MutationChange struct {
Field string
OldValue string
NewValue interface{}
Type string // "default" or "override"
}
MutationChange describes one field mutation that was applied.
type MutationResult ¶
type MutationResult struct {
Applied int
Changes []MutationChange
}
MutationResult holds the outcome of applying mutation rules.
type NamespaceGuardResult ¶
type NamespaceGuardResult struct {
Allowed bool
Namespace string
Pattern string // matched restricted or allowed pattern (for logs)
Reason string // "restricted", "not-allowed", or ""
}
NamespaceGuardResult holds the outcome of a namespace restriction check.
func CheckNamespace ¶
func CheckNamespace( ctx context.Context, obj domain.Object, targetNamespace string, restricted orktypes.RestrictedNamespaces, allowed orktypes.AllowedNamespaces, crdName string, ) *NamespaceGuardResult
CheckNamespace determines whether a target namespace is permitted for child resource creation. It evaluates both RestrictedNamespaces and AllowedNamespaces with the following precedence:
- RestrictedNamespaces — deny-list (always wins)
- AllowedNamespaces — allow-list (optional; empty = allow all)
Called before onCreate, onReconcile, onDelete, and before registry dispatch.
func (*NamespaceGuardResult) EventMessage ¶
func (r *NamespaceGuardResult) EventMessage(resourceKind, resourceName string) string
EventMessage returns the Kubernetes event message for a blocked namespace.
type QueueDepthReporter ¶
type QueueDepthReporter interface {
ReportQueueDepth(depth int64)
}
QueueDepthReporter is an optional interface reconcilers may implement to receive live queue depth updates from the worker loop.
type QueueInjector ¶
QueueInjector is an optional interface for injecting the per-CRD workqueue after the reconciler is constructed. Called by startCRDWorkers.
type ResyncLoopStarter ¶
ResyncLoopStarter is an optional interface for reconcilers that support an adjustable resync goroutine. DependencyKordinator starts it once per CRD.
type ValidationResult ¶
type ValidationResult struct {
// Passed — true when all rules passed
Passed bool
// Deny
Deny bool
// Violations — one entry per failed rule
// Warning
Warnings []ValidationViolation // action: warn violations
Violations []ValidationViolation // action: deny violations
}
ValidationResult holds the outcome of running all validation rules for one reconcile.
func (*ValidationResult) Blocked ¶
func (r *ValidationResult) Blocked() bool
Blocked returns true if reconciliation must stop. Deny takes precedence; warnings alone do NOT block.
func (*ValidationResult) DenialError ¶
func (r *ValidationResult) DenialError() error
DenialError returns a proper error describing the denial reason.
func (*ValidationResult) DenialMessage ¶
func (r *ValidationResult) DenialMessage() string
DenialMessage returns a human‑readable summary of the denial cause.
func (*ValidationResult) Denied ¶
func (r *ValidationResult) Denied() bool
Denied returns true if validation rules explicitly blocked reconciliation.
func (*ValidationResult) Error ¶
func (r *ValidationResult) Error() error
Error returns a combined error message for all violations.
func (*ValidationResult) HasViolations ¶
func (r *ValidationResult) HasViolations() bool
HasViolations returns true if any rules failed.
func (*ValidationResult) HasWarnings ¶
func (r *ValidationResult) HasWarnings() bool
HasWarnings returns true if any advisory warnings were produced.
func (*ValidationResult) Log ¶
func (r *ValidationResult) Log(crd, name, ns string)
Log helper for debugging
func (*ValidationResult) ViolationSummary ¶
func (r *ValidationResult) ViolationSummary() string
ViolationSummary returns a semicolon‑joined list of violations.
func (*ValidationResult) WarningSummary ¶
func (r *ValidationResult) WarningSummary() string
WarningSummary returns a semicolon‑joined list of warnings.
type ValidationViolation ¶
type ValidationViolation struct {
Field string
Rule string
Value string // the actual CR field value that failed
Message string // the user-defined message from the Katalog
Action orktypes.ValidationAction // 'warn' or 'deny' as defined by user
}
ValidationViolation describes one failed validation rule.
Source Files
¶
- children.go
- children_gvr.go
- conditions.go
- cross_util.go
- generic.go
- generic_autoscale.go
- generic_namespace.go
- generic_registry.go
- helper.go
- kube_reader_adapter.go
- normalize.go
- ptr_hooks.go
- rollback.go
- run_admission.go
- run_configmaps.go
- run_cronjobs.go
- run_cross.go
- run_delete_ordered.go
- run_deployments.go
- run_docker.go
- run_docker_helper.go
- run_external.go
- run_foreach.go
- run_git.go
- run_git_helper.go
- run_hpas.go
- run_ingresses.go
- run_jobs.go
- run_mutations.go
- run_namespace_guard.go
- run_namespaces.go
- run_pdbs.go
- run_providers.go
- run_pvcs.go
- run_pvs.go
- run_replicasets.go
- run_secret_tls.go
- run_secrets.go
- run_secrets_once.go
- run_serviceaccounts.go
- run_services.go
- run_statefulsets.go
- run_status.go
- run_template_reconcile.go
- run_validations.go