Documentation
¶
Overview ¶
Package depwait blocks a controller until a set of NamedResource dependencies become Ready (or, for kinds without a status pipeline, merely exist) — with per-dep timeouts and fail-fast for missing refs.
Index ¶
Constants ¶
const DefaultTimeout = 30 * time.Second
DefaultTimeout is the per-dep timeout when not specified. The upstream Flux controllers default to several minutes since they wait for in-cluster reconciliation; flate is purely offline, so waits past a few seconds almost always indicate a misconfigured reference. Keep this short so typos in dependsOn / sourceRef surface immediately instead of stalling a render.
const MissingGrace = 2 * time.Second
MissingGrace is the brief window we tolerate a missing dep before failing — it covers the legitimate case where a KS render produces the dep slightly later in the same run.
Variables ¶
var RenderProducingTimeout = 10 * time.Second
RenderProducingTimeout caps how long step-2 will wait for a render-only dep (no file-existence record) to appear via a parent render's emitRenderedChildren chain. Bounded so a typo'd dependsOn — which also returns IsFileIndexed(id)=false and falls into step-2 — fails in a reasonable time instead of burning the full per-dep Timeout. Chosen to comfortably cover a multi-level kustomize component+replacement chain (~few seconds in practice) while keeping a typo's wall time well under DefaultTimeout.
Exposed as a var so tests can shrink it.
Functions ¶
func TimeoutFromSpec ¶
TimeoutFromSpec resolves a Flux spec.timeout (`*metav1.Duration` — the shape used by both Kustomization and HelmRelease) into the effective per-dep wait. Honors a user-supplied value when set; falls back to flate's offline-tuned DefaultTimeout otherwise. Matches the principle that a real Flux reconcile would respect spec.timeout.
Types ¶
type DepStatus ¶
type DepStatus int
DepStatus enumerates the per-dependency resolution result. Every Watch event carries one of the terminal statuses below — there is no "pending" wire value because Watch only fires when the dep resolves (Ready) or hits a wait-ending condition (Failed / Timeout / Cancelled).
type Event ¶
type Event struct {
Dep manifest.NamedResource
Status DepStatus
Reason string
}
Event is yielded for each dependency as it resolves.
type ExistenceLookup ¶
type ExistenceLookup interface {
// IsFileIndexed reports whether id has a file-existence record.
IsFileIndexed(id manifest.NamedResource) bool
// Promote attempts to materialize id into the Store from the
// file-existence index. Returns true when promotion succeeded
// and id is now reachable via store.GetObject.
Promote(id manifest.NamedResource) bool
}
ExistenceLookup is the seam depwait uses to resolve missing dependencies. Bundled into one interface so the orchestrator (and any future embedder) wires a single object through the controller Options pipe instead of two parallel closures.
The orchestrator's implementation reads from the loader's ExistenceIndex — file-indexed objects (CMs/Secrets/HRs the DiscoveryOnly loader kept out of the Store) get lazy-promoted the moment a depwait edge needs them, while render-only ids (no file record) signal depwait to keep waiting for an emitRenderedChildren chain instead of failing fast.
When the grace window expires with id still missing:
- Promote(id) == true: dep is now in the Store (lazy-promoted from a file YAML). Wait continues against built-in status / exists semantics.
- Promote(id) == false, IsFileIndexed(id) == true: file existed but promote failed (parse error, file mutated since record). Fail fast as "dependency not found".
- Promote(id) == false, IsFileIndexed(id) == false: no file record. Dep can only arrive via a parent render's emitRenderedChildren chain. Keep watching for up to RenderProducingTimeout — a deeply-chained kustomize component+replacement pattern can take longer than the 2s grace, and failing fast surfaces a spurious "dependency not found" for a dep that would have arrived a few seconds later.
type RenderInflight ¶
type RenderInflight interface {
// QuiescenceCh returns a channel closed when the pool drains to
// "no other work in flight". Each call returns a fresh channel;
// callers receive once. Implementations that can't deliver an
// event-driven signal may return a nil channel, in which case
// waitRenderEmission falls back to the timeout cap.
QuiescenceCh() <-chan struct{}
}
RenderInflight is the positive quiescence signal depwait's step-2 uses to fail fast on truly-missing render-only deps.
Semantically the inverse of the Existence-based step-2 wait: that path keeps waiting on the absence of a finite signal (RenderProducingTimeout); RenderInflight short-circuits the wait once a positive signal ("no more work could produce this") fires. Both together pin the wait between a fast-fail floor (no other work running) and a hard ceiling (the timeout cap).
When Renders is nil, step-2 still works via the timeout cap alone — embedders that don't drive a task pool get the legacy behavior.
type Summary ¶
type Summary struct {
Ready []manifest.NamedResource
Failed []manifest.NamedResource
Messages map[manifest.NamedResource]string
}
Summary tallies the final state of a Waiter run. Every dep ends up in Ready or Failed (Watch always emits a terminal status); a Pending slot would be dead code.
type Waiter ¶
type Waiter struct {
Store *store.Store
Parent manifest.NamedResource
Timeout time.Duration
// AdditiveReadyExpr toggles Flux's AdditiveCELDependencyCheck
// feature gate. When false (the default, matching Flux), a
// dep's ReadyExpr REPLACES the built-in Ready check — flate
// re-evaluates the expression on every status update for the
// dep and treats the dep as Ready when the expression returns
// true. When true, the dep must satisfy both the built-in
// Ready check AND the ReadyExpr (additive mode).
AdditiveReadyExpr bool
// Existence, when non-nil, drives the post-grace branching for
// missing deps. The orchestrator wires this against the loader's
// ExistenceIndex (file-loaded YAML records); tests supply stubs.
// When Existence is nil, depwait preserves the historical fast-
// fail-after-grace behavior — callers that don't wire an
// existence index can't distinguish render-only from typo'd.
//
// See ExistenceLookup for the decision matrix at the grace
// boundary.
Existence ExistenceLookup
// Renders, when non-nil, drives the step-2 fast-fail signal:
// when the orchestrator's task pool drains (no other reconcile
// is doing work), no future render can produce the missing dep.
// The wait short-circuits to "dependency not found" instead of
// burning the full RenderProducingTimeout cap. When Renders is
// nil, step-2 falls back to the fixed cap alone (preserves the
// legacy timing).
Renders RenderInflight
}
Waiter holds the parameters for one dependency-wait operation.
func (*Waiter) ReadyNow ¶
func (w *Waiter) ReadyNow(deps []manifest.DependencyRef) bool
ReadyNow reports whether every dependency is already satisfied without waiting. Controllers use this to avoid marking a reconcile quiescent while it is only confirming dependencies that are ready and can continue producing render output immediately.
func (*Waiter) Watch ¶
Watch concurrently watches each dep and returns a channel of Events. The channel closes when every dep has reached a terminal state or ctx expires. Callers should drain the channel.
Watch picks WatchReady vs WatchExists per-dep based on store.SupportsStatus. When dep.ReadyExpr is non-empty the built-in Ready check is *replaced* by the CEL evaluation (Flux's default). Set Waiter.AdditiveReadyExpr=true to require BOTH (Flux's AdditiveCELDependencyCheck=true mode).