depwait

package
v0.3.3 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: 14 Imported by: 0

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

View Source
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.

View Source
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

View Source
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

func TimeoutFromSpec(d *metav1.Duration) time.Duration

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).

const (
	DepReady DepStatus = iota + 1
	DepFailed
	DepTimeout
	DepCancelled
)

Possible DepStatus values.

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.

func WaitAll

func WaitAll(ch <-chan Event) Summary

WaitAll consumes the channel returned by Watch and produces a Summary. First-failure cancellation is not performed automatically — callers that want it should drive Watch directly.

func (Summary) AnyFailed

func (s Summary) AnyFailed() bool

AnyFailed reports whether at least one dependency ended in failure.

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

func (w *Waiter) Watch(ctx context.Context, deps []manifest.DependencyRef) <-chan Event

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).

Jump to

Keyboard shortcuts

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