reconciler

package
v0.7.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 3, 2026 License: Apache-2.0 Imports: 46 Imported by: 0

README

pkg/reconciler

The reconciler package is the execution engine of every Orkestra operator. It takes a CR event from the Kubernetes informer and turns it into a sequence of API calls — creating, updating, or deleting child resources according to the Katalog's declarative configuration.

What lives here

File Role
generic.go GenericReconciler[PTR] — the single reconciler used by all CRDs; PTR must be a pointer to the concrete CR type (e.g. *Database)
ptr_hooks.go Design note: explains the PTR naming convention, the ObjectHooks adapter, and why the two-type-parameter form was not used
generic_autoscale.go AutoscaleTarget, AutoscalerRunner, ResyncLoopStarter, QueueInjector, QueueDepthReporter implementations; SetSpawnWorker, SetRollbackNotifiers, GetAutoMetrics, WorkerInfo wiring helpers
rollback.go Rollback subsystem — spec snapshotting, trigger evaluation, onRollback template execution, annotation lifecycle
run_template_reconcile.go Declarative pipeline: normalize → resolver → onCreate → onReconcile → providers; calls runners in pkg/runners
normalize.go applyNormalize — in-memory spec normalization before mutation/validation
run_delete_ordered.go Sequential staged deletion with completion gates (onDelete.ordered: true)
run_customresource.go Resolves, conditions-checks, and applies Custom Resource declarations; skips gracefully when target CRD is missing
conditions.go EvaluateWhen wrappers and helpers
run_namespace_guard.go CheckNamespace — allowed/restricted namespace enforcement
gvr.go GVR aliases used by run_delete_ordered.go; authoritative GVR definitions live in pkg/children

Per-resource-type runners (deployments, services, secrets, roles, …) live in pkg/runners.

Child resource reading, forEach expansion, enrichment, and the built-in kind registry live in pkg/children.

Developer documentation

Full step-by-step documentation is in docs/.

I want to… Go to
Understand the full reconcile pipeline (including rollback gate) 01 — Architecture
Understand the runner contract and canonical shape pkg/runners — 01 Runner Contract
Quick runner reference (reconciler-scoped) 02 — Runner Function Contract
Understand the registry layer 03 — Registry Layer
Debug condition / activeNames issues 04 — Conditions
Understand forEach: expansion 05 — forEach
Add normalize: to a Katalog 06 — normalize
Add a new resource type end-to-end 07 — Adding a Resource

For the autoscaler design and the operatorBox.autoscale: declaration, see docs/runtime-manual/concepts/operator-autoscaler.md.

Documentation

Overview

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 for example, 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 runtime_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 runtime_konstructor.go — which infers PTR = domain.Object — would fail to compile. Updating runtime_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 ──────────────────────────────────────

  1. User writes: func DatabaseHooks() domain.AnyReconcileHooks { return domain.ReconcileHooks[*Database]{OnReconcile: onReconcile} }

  2. Generated registry registers DatabaseHooks in HookRegistry.

  3. runtime_konstructor.go calls HookFactory() → gets ReconcileHooks[*Database].

  4. NewGenericReconciler receives it as domain.AnyReconcileHooks. PTR is inferred as domain.Object (from func() domain.Object newObj).

  5. anyHooks.(domain.HookBinder) succeeds because ReconcileHooks[*Database] has BindToObjectHooks() via the generic method.

  6. BindToObjectHooks() builds ObjectHooks with closures: obj.(*Database).

  7. At reconcile time, reconcileCore does raw.(*Database or domain.Object) — whichever PTR resolves to — producing a domain.Object value whose underlying type is *Database.

  8. 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:

  1. snapshotSpec — writes the current spec to the previous-spec annotation before any spec change is applied. Called when generation changes.

  2. shouldRollback — reads the operatorbox health state and evaluates the rollback trigger. Returns true when rollback should activate.

  3. 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_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_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:

  1. resolver = NewResolver(ctx, obj)
  2. resolver = resolver.WithCross(ReadCross(...)) ← cross-CRD first
  3. resolver, err = runGit(ctx, ...) ← Git second
  4. resolver, err = runExternal(ctx, ...) ← HTTP third
  5. resolver, err = runDocker(ctx, ...) ← Docker fourth
  6. 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:

  1. resolver = NewResolver(ctx, obj)
  2. resolver = resolver.WithCross(ReadCross(...)) ← cross-CRD first
  3. resolver, err = runExternal(ctx, resolver, ...) ← HTTP calls second
  4. 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_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:

  1. resolver = NewResolver(ctx, obj)
  2. resolver = resolver.WithCross(ReadCross(...)) ← cross-CRD first
  3. resolver, err = runGit(ctx, gvk, resolver, ...) ← Git second
  4. resolver, err = runExternal(ctx, ...) ← HTTP third
  5. resolver, err = runDocker(ctx, ...) ← Docker fourth
  6. 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_mutation.go

pkg/reconciler/run_namespace_guard.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:

  1. Skips immediately if no provider blocks are declared or no providers registered
  2. Resolves template expressions in every declaration field
  3. Evaluates when: conditions — drops declarations that do not pass
  4. 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_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):

  1. Recieve NewResolver → .spec.*, .status.*, .metadata.*
  2. r.readCross → .cross.<crd>.status.* (informer cache, zero API calls)
  3. runExternal → .external.<n>.status, .body (HTTP calls)
  4. forEach expand → N sources from N-element list fields
  5. onCreate groups → deployments, services, secrets, configmaps, ...
  6. onReconcile groups
  7. runProviders → aws:, mongodb:, ... (external infra)

pkg/reconciler/run_validation.go

Index

Constants

This section is empty.

Variables

View Source
var ExternalHTTPTransport http.RoundTripper

ExternalHTTPTransport is the http.RoundTripper used for all external: calls. nil uses http.DefaultTransport (production). Set to a stub in tests or simulation to prevent real network calls. Used by ork simulate

Functions

func AddFinalizer

func AddFinalizer(o domain.Object, finalizer string) (updated bool)

func ContainsFinalizer

func ContainsFinalizer(o domain.Object, finalizer string) bool

func ReadCrossFromInformer

func ReadCrossFromInformer(
	indexer cache.Indexer,
	key string,
	sourceCrossAccess *bool,
) map[string]interface{}

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. sourceCrossAccess is the CrossAccess field of the target CRD entry — nil means allowed (default). When false, returns notFoundCrossResult() without reading.

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.

func RemoveFinalizer

func RemoveFinalizer(o domain.Object, finalizer string) (updated bool)

Types

type AutoscalerRunner

type AutoscalerRunner interface {
	RunAutoscaler(ctx context.Context)
}

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

type GenericReconciler[PTR domain.Object] struct {
	// contains filtered or unexported fields
}

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:

  1. Add a file run_<resource>.go with a runXxx() function
  2. Call it from runTemplateReconcile() and/or runTemplateOnDelete()
  3. 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 runtime_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.Recorder,
	kube kubeclient.KubeClient,
	anyHooks domain.AnyReconcileHooks,
	newObj func() PTR,
	katalogRegistry *kordinator.ResourceKatalog,
	crdHealthRegistry map[string]*kordinator.CRDHealth,
	providerRegistry orktypes.ProviderRegistry,
	providerStats providerStatsRecorder,
	kat *katalog.Katalog,
) *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 runtime_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:

  1. Conditional provisioning (when blocks) — handled by runTemplateReconcile
  2. 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(configuredResync string, 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 GitOperationResult struct {
	Operation string // "clone" | "fetch"
	Commit    string
	Changed   string // "true" | "false"
	Path      string
	Error     string
}

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)

	// GetCrossAccessByName returns the CrossAccess field of the named CRD.
	// nil means the CRD allows cross reads (default). *false means opted out.
	// Returns nil when the CRD is not registered.
	GetCrossAccessByName(name string) *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:

  1. RestrictedNamespaces — deny-list (always wins)
  2. 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

type QueueInjector interface {
	SetQueue(wq *orkqueue.Workqueue)
}

QueueInjector is an optional interface for injecting the per-CRD workqueue after the reconciler is constructed. Called by startCRDWorkers.

type ResyncLoopStarter

type ResyncLoopStarter interface {
	StartResyncLoop(ctx context.Context)
}

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 RunValidation added in v0.3.7

func RunValidation(obj domain.Object, cfg *orktypes.ValidationConfig, crdName string) *ValidationResult

RunValidation exposes runValidation for integration tests.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL