base

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Jun 7, 2026 License: AGPL-3.0 Imports: 12 Imported by: 0

Documentation

Overview

Package base provides the shared lifecycle harness every flate controller wraps around its per-resource reconcile body.

Each concrete controller (source, kustomization, helmrelease) embeds *base.Controller and contributes only the controller-specific dependencies (Fetchers, Helm client, Staging cache, ...) plus the reconcile function itself. Lifecycle wiring — the started gate, the unsubscriber slice, the per-id coalescer, the change filter, the Suspend/Filter pre-gate — lives here exactly once.

The package also owns the panic-recovery + status-transition harness (Recover, RunWithStatus) that surrounds individual reconcile bodies.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Recover

func Recover(s *store.Store, id manifest.NamedResource, logKind string)

Recover catches a panic from the current goroutine and marks id StatusFailed with a "panic: <r>" message so the orchestrator surfaces it. Intended for use as `defer base.Recover(store, id, "kind")` in controllers that don't go through RunWithStatus (e.g. source fetchers that own their own status writes).

After recording status, re-raises the panic so the enclosing task.Service.Go increments its failures counter — a panicked reconcile MUST count against the orchestrator's failure gate, not silently masquerade as success when Service.Failures() is consulted for the final exit-code decision.

func RunWithStatus

func RunWithStatus[T manifest.BaseManifest](
	ctx context.Context,
	s *store.Store,
	id manifest.NamedResource,
	logKind string,
	fn func(context.Context, T) error,
)

RunWithStatus is the standard reconcile body for controllers that (a) coalesce concurrent submits per-id and (b) want the recover → re-read → run → mark-Ready/Failed pattern. The re-read lets a coalesced re-run pick up patches a parent KS installed mid-flight rather than the stale payload from the original event. A missing object (deleted between coalescer enqueue and run) is treated as a no-op rather than a failure.

On success the terminal status write is conditional: if the current status already carries an informative Ready message (a soft-skip from --allow-missing-secrets, an MsgUnchanged from the change filter, an MsgSuspended from PreGate), the empty-message overwrite is suppressed so the informative message survives a short-circuited coalesced re-run that returns nil. Plain Ready (no message) and any non-Ready status get the standard "" Ready write so the controller's terminal contract is preserved.

Types

type Controller

type Controller struct {
	Store *store.Store
	Tasks *task.Service
	// contains filtered or unexported fields
}

Controller is the embeddable lifecycle harness. Construct via New, install reconcile-shaping config via SetFilter (panics if called after Start), then Start to register listeners.

All three concrete controllers carry the same lifecycle shape:

  • a started gate so Configure-after-Start is a hard error
  • a coalescer so duplicate AddObject events don't double-reconcile
  • a filter for changed-only mode
  • an unsubscriber list cleared by Close

Encoding it once means new pre-reconcile concerns (rate-limit, retries, debug-mode toggle) drop into one place and propagate.

The KS and HR controllers additionally share depwait configuration (Existence, Renders) and a pre-reconcile preflight check (Preflight, ParentOf). Configure these via SetDepwait / SetPreflight / SetParentOf before Start so reconcile bodies can call NewWaiter, PreflightError, and LookupParent without each controller duplicating the nil-check boilerplate.

func New

func New(s *store.Store, t *task.Service) *Controller

New constructs a base controller. Concrete controllers call this from their own constructor and embed the result.

func (*Controller) AddListener

func (c *Controller) AddListener(event store.EventKind, l store.Listener)

AddListener registers a store listener and records it so Close can unsubscribe. snapshot=true matches every concrete controller's needs (deliver the current store state on subscribe). Safe to call concurrently with Close — once Close has flipped the closed gate, any in-flight or later AddListener is refused and the registration is rolled back so the underlying store does not retain a listener the controller will never unsubscribe.

func (*Controller) Await

func (c *Controller) Await(
	ctx context.Context,
	id manifest.NamedResource,
	w *depwait.Waiter,
	deps []manifest.DependencyRef,
	pendingMsg string,
	onFail func(depwait.Summary) error,
) error

Await blocks until each dep in deps reaches Ready, yielding the caller's worker-pool slot during the wait so children depended on can themselves acquire a slot and make progress. Centralizes the "set pending → yield → WaitAll → check failed" dance the three concrete controllers each implemented inline; the per-call-site difference (which error sentinel wraps a failed summary) is expressed via onFail.

pendingMsg is the StatusPending message written before the wait — surfaces in `flate test` reporting and the orchestrator's failure rollup. Pass an empty string to skip the status write (e.g. when the caller already set its own).

onFail receives the depwait Summary on any AnyFailed and returns the error the caller propagates. Use it to pick between manifest.DependencyFailedError, ErrObjectNotFound, etc. — the concrete controllers each have their own conventions.

func (*Controller) Close

func (c *Controller) Close()

Close removes every registered listener and refuses any later AddListener so a late call from a shutdown-racing goroutine cannot leak a registration past us. Idempotent: a second Close is a no-op because the closed flag is set via Swap.

func (*Controller) Filter

func (c *Controller) Filter() *change.Filter

Filter returns the configured filter (may be nil-but-non-active).

func (*Controller) IsFileIndexed

func (c *Controller) IsFileIndexed(id manifest.NamedResource) bool

IsFileIndexed reports whether id is tracked by the file-existence index wired at Configure time. Returns false when no index is configured (offline / unit-test paths), which degrades safely by treating the resource as not-file-indexed.

func (*Controller) KeepEmitted

func (c *Controller) KeepEmitted(parent manifest.NamedResource, child manifest.BaseManifest)

KeepEmitted extends the change filter's keep set so render-emitted children pass the changed-only-mode PreGate check. Without this, a parent whose render emits a child that wasn't on disk at filter-build time (kustomize component+replacement KSes, charts that render source CRs) would silently drop that child from the diff comparison. Routed through Filter.AddEmitted so an ancestor-only parent doesn't cascade unrelated file-loaded children into keep (#204/#260/#308).

MUST be called BEFORE Store.AddObject so the listener that fires synchronously during AddObject sees the extended keep set.

func (*Controller) LookupParent

LookupParent reports the structural parent KS of id via the configured resolver, or (zero, false) when no parent exists or no resolver was configured.

func (*Controller) NewWaiter

func (c *Controller) NewWaiter(id manifest.NamedResource, timeout *metav1.Duration) *depwait.Waiter

NewWaiter constructs a depwait.Waiter pre-wired with the controller's Store, Existence lookup, and Renders quiescence signal, parented to id and budgeted from timeout. HR and KS controllers call this rather than constructing their own Waiter literals so the Existence/Renders wiring is set once in Configure and flows through automatically.

func (*Controller) PreGate

func (c *Controller) PreGate(id manifest.NamedResource, suspended bool) bool

PreGate is the canonical Suspend/Filter pre-reconcile check. Returns true when the resource is gated out — caller MUST bail.

  • suspended → marks Ready "suspended", returns true
  • filter excludes the id → marks Ready "unchanged", returns true
  • otherwise → returns false, caller proceeds to Submit/reconcile

func (*Controller) PreflightError

func (c *Controller) PreflightError(id manifest.NamedResource) error

PreflightError returns an error wrapping the preflight failure message for id, or nil when no failure is recorded. Used at each yield point inside reconcile so a cycle detection or topology error published mid-flight aborts the current pass without waiting.

func (*Controller) PreflightFailure

func (c *Controller) PreflightFailure(id manifest.NamedResource) (string, bool)

PreflightFailure reports the pre-reconcile failure for id if the orchestrator detected a dependency-graph error. Returns ("", false) when no preflight check is configured or no failure was recorded.

func (*Controller) ReportRendered

func (c *Controller) ReportRendered(parent manifest.NamedResource, children []manifest.NamedResource)

ReportRendered reports parent→child render emissions to the configured RenderTracker; no-op when none is wired or there are no children. The emit loop accumulates every child it emits and flushes through this helper exactly once, holding the tracker's lock for one acquisition instead of N.

func (*Controller) SetDepwait

func (c *Controller) SetDepwait(existence depwait.ExistenceLookup, renders depwait.RenderInflight)

SetDepwait installs the depwait resolution wires. Panics after Start.

func (*Controller) SetFilter

func (c *Controller) SetFilter(f *change.Filter)

SetFilter installs the change filter that gates reconciliation in changed-only mode. Panics if called after Start — the invariant is that reconcile-shaping config is frozen once dispatch begins.

func (*Controller) SetParentOf

func (c *Controller) SetParentOf(f func(manifest.NamedResource) (manifest.NamedResource, bool))

SetParentOf installs the structural parent resolver. Panics after Start.

func (*Controller) SetPreflight

func (c *Controller) SetPreflight(f func(manifest.NamedResource) (string, bool))

SetPreflight installs the pre-reconcile failure reporter. Panics after Start.

func (*Controller) SetRenderTracker

func (c *Controller) SetRenderTracker(rt RenderTracker)

SetRenderTracker installs the render-emission tracker. Panics after Start — reconcile-shaping config is frozen once dispatch begins.

func (*Controller) StartLifecycle

func (c *Controller) StartLifecycle(kindLabel string)

StartLifecycle flips the started gate and allocates the coalescer. Concrete controllers call this from their Start(ctx) before installing listeners via AddListener.

func (*Controller) Submit

func (c *Controller) Submit(ctx context.Context, id manifest.NamedResource, fn func(context.Context))

Submit enqueues a reconcile body keyed by id. Duplicate submits with the same id coalesce so a re-emit by a parent KS doesn't double the work. Caller-supplied fn runs with panic-recover already installed.

type RenderTracker

type RenderTracker interface {
	MarkRenderedBatch(parent manifest.NamedResource, children []manifest.NamedResource)
}

RenderTracker is the seam a controller uses to report "this child id was emitted by THIS parent's render" to the orchestrator. Nil is OK — the controller no-ops.

The parent linkage feeds detectOrphans, the structural-parent resolver, and ResourceSet extension attribution for render-emitted resources. Both the KS and HR controllers report through it identically; MarkRenderedBatch records multiple children under a single lock acquisition so a render emitting N children pays one tracker round-trip rather than N.

Jump to

Keyboard shortcuts

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