Documentation
¶
Overview ¶
Package statusapplier applies template-driven status patches to Kubernetes resources.
The StatusApplier is a stateless consumer: each event carries the patches it needs to apply. There is no side-channel cache. The patches travelling on a deploy event are tautologically the patches for the configuration that deploy carried — no LATEST-vs-deployed race is possible.
Status patches are fully defined by templates — the controller never hardcodes knowledge of specific resource types or condition names. Templates register patches via the statusPatch() template function during rendering, including outcome-keyed variants for each pipeline phase (rendered, deployed, renderFailed, deployFailed).
Event mapping:
- ResourcesAppliedEvent: apply the "rendered" variant directly from event.StatusPatches (forwarded by the ResourceApplier after the same render's resources were applied). Ordering matters: conditions like Accepted=True must not precede the infrastructure resources they describe, so the rendered variant rides the post-apply event rather than TemplateRenderedEvent.
- DeploymentCompletedEvent: apply the "deployed" variant from event.StatusPatches. The Deployer forwards the patches from the DeploymentScheduledEvent that triggered the deploy, so the patches describe exactly the config the deploy shipped. Programmed=True genuinely means "HAProxy is serving this config" because reload verification gates DeploymentCompletedEvent.
- DeploymentSkippedEvent: apply the "deployed" variant from event.StatusPatches. Same data-plane-is-converged semantics as DeploymentCompletedEvent, reached by the scheduler determining the data plane is already at this config. Without this branch, any resource whose addition or update produces no config change (a status-only delta) would stay at the CRD-default condition state indefinitely.
- ReconciliationFailedEvent: apply the failure variant ("renderFailed" / "deployFailed") from event.StatusPatches. The Coordinator forwards the patches from the last successful render — failure paths don't produce fresh patches, so a "last good" snapshot is the only thing the chart's failure variants can be applied against.
Leader transitions: the Reconciler triggers an immediate reconciliation on BecameLeaderEvent (per pkg/controller/reconciler/CLAUDE.md), producing a fresh render whose patches arrive via ResourcesAppliedEvent. The applier therefore has no replay responsibility on leadership change.
Index ¶
Constants ¶
const ( // ComponentName is the unique identifier for this component. ComponentName = "status-applier" // EventBufferSize is the size of the event subscription buffer. // Moderate volume: receives template rendered, reconciliation completed/failed, // and leadership events. // High volume: template.rendered fires on every reconcile. Even with the // coordinator coalescing renders, an occasional slow SSA apply can briefly // back this up, so use a Publishing-tier buffer to avoid dropping the // (coalescible) template.rendered / deployment.completed events — a dropped // deployment.completed leaves Programmed=True unapplied until the next deploy. EventBufferSize = busevents.PublishingSubscriberBuffer )
Variables ¶
This section is empty.
Functions ¶
func IsRetriable ¶
IsRetriable returns true if the error is likely transient and the operation should be retried on the next reconciliation cycle. It is exported so the resourceapplier shares this single retry policy rather than duplicating it.
Types ¶
type Component ¶
Component applies template-driven status patches to Kubernetes resources via Server-Side Apply (SSA).
This is an all-replica component that subscribes in the constructor and applies the appropriate variant based on pipeline lifecycle events. Only the leader applies patches to avoid conflicts.
Event flow (every applied phase reads patches directly from event.StatusPatches):
ResourcesAppliedEvent → apply "rendered" variant (if leader)
DeploymentCompletedEvent → apply "deployed" variant (if leader)
DeploymentSkippedEvent → apply "deployed" variant (if leader); the data
plane is already at the rendered config so Programmed conditions
should reflect the current generation
ReconciliationFailedEvent → apply "renderFailed" or "deployFailed" variant (if leader)
BecameLeaderEvent → clear checksum cache; rely on Reconciler to fire a fresh reconcile
LostLeadershipEvent → flip the leader flag off
func New ¶
New creates a new StatusApplier component.
The component subscribes to events in the constructor (all-replica pattern). It only applies patches when it is the leader.
func (*Component) CoalescesOn ¶
CoalescesOn opts this applier into component.Base's mailbox coalescing. All three declared types are latest-wins FOR THIS COMPONENT: rendered patches ride every ResourcesAppliedEvent, and the deployed variant rides every DeploymentCompleted/SkippedEvent — each event carries the FULL current patch set, so only the newest of an uninterrupted run matters. Collapsing runs keeps the mailbox queue bounded by the deploy cadence instead of the render rate: without it a burst of deployment events (each costing an SSA fan-out to apply) backlogs the queue and status latency grows unboundedly (observed: 512-deep backlog and 90s Programmed lag in gateway-api conformance).
func (*Component) HandleEvent ¶
HandleEvent implements component.EventHandler: it routes events to the appropriate handler, tracking processing time for the health check.
func (*Component) HealthCheck ¶
HealthCheck returns nil if the component is healthy.
type Config ¶
type Config struct {
// EventBus is the event bus for subscribing to events and publishing results.
EventBus *busevents.EventBus
// DynamicClient is the Kubernetes dynamic client for SSA patch operations.
DynamicClient dynamic.Interface
// GVRResolver resolves apiVersion + kind to GroupVersionResource.
GVRResolver GVRResolver
// Logger is the structured logger.
Logger *slog.Logger
}
Config contains configuration for creating a StatusApplier Component.
type GVRResolver ¶
type GVRResolver interface {
Resolve(apiVersion, kind string) (schema.GroupVersionResource, error)
}
GVRResolver resolves apiVersion + kind to a GroupVersionResource. This abstracts the REST mapper for testability.
type RestMapperResolver ¶
type RestMapperResolver struct {
// contains filtered or unexported fields
}
RestMapperResolver implements GVRResolver by consulting a Kubernetes RESTMapper. The kind→resource mapping comes from the cluster's discovery data, so any watched resource — including a CRD with an irregular or fully custom plural — resolves correctly, with no hardcoded or guessed pluralization (RULE #1: the controller stays resource-agnostic).
func NewRestMapperResolver ¶
func NewRestMapperResolver(mapper meta.RESTMapper) *RestMapperResolver
NewRestMapperResolver creates a GVRResolver backed by the given RESTMapper.
func (*RestMapperResolver) Resolve ¶
func (r *RestMapperResolver) Resolve(apiVersion, kind string) (schema.GroupVersionResource, error)
Resolve maps apiVersion + kind to a GroupVersionResource by consulting the RESTMapper. The resource name comes from the cluster's discovery data (and, for CRDs, each CRD's own spec.names.plural), so irregular and fully custom plurals resolve correctly — there are no resource-specific pluralization rules in Go (RULE #1). An unknown kind returns an error rather than a guessed plural.