reconcile

package
v0.1.4 Latest Latest
Warning

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

Go to latest
Published: Apr 7, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

Documentation

Overview

Package reconcile provides shared reconciliation utilities for BubuStack controllers.

This package contains helpers for common reconciliation patterns including timeout handling, requeue logic, deletion processing, status updates, and template validation.

Controller Reconcile

The StartControllerReconcile function sets up reconcile context with timeout and metrics:

ctx, finish := reconcile.StartControllerReconcile(ctx, "StoryRun", timeout)
defer finish(err)

Timeout Handling

The WithTimeout function applies a timeout to the reconcile context:

ctx, cancel := reconcile.WithTimeout(ctx, 5*time.Minute)
defer cancel()

Requeue Logic

Requeue helpers for common patterns:

reconcile.RequeueAfter(30*time.Second)  // Fixed delay
reconcile.RequeueBackoff(attempt)       // Exponential backoff
reconcile.NoRequeue()                   // Terminal state

Deletion Handling

The [HandleDeletion] function provides a standard deletion flow:

if result, done := reconcile.HandleDeletion(ctx, obj, finalizer, cleanupFn); done {
    return result, nil
}

Template Validation

Shared template validation helpers:

result, err := reconcile.ValidateTemplateRef(ctx, templateName, errEmptyRef, loader)
reconcile.ApplyTemplateCondition(cm, &conditions, result)
reconcile.EmitValidationEvent(recorder, logger, obj, result, prevCondition)

Status Updates

Status update utilities:

reconcile.PatchStatus(ctx, client, obj)
reconcile.MarkDirty(ctx)  // Flag for deferred status update

This package is designed to reduce boilerplate in controller reconcile loops and ensure consistent behavior across all BubuStack controllers.

File: pkg/controllerx/status/status.go

Package reconcile provides shared helpers for controller reconciliation.

Index

Constants

View Source
const StoryRunTriggerTokensAnnotationKey = "bubustack.io/storyrun-trigger-tokens"

IMPORTANT NOTE (wire-up): - This package needs to know *where* tokens are stored (annotation key) and how they are encoded. - If your project already has a canonical annotation key (e.g., in contracts), use it here.

Adjust this constant to match your existing system.

Variables

This section is empty.

Functions

func ApplyTemplateCondition

func ApplyTemplateCondition(
	conds []metav1.Condition,
	generation int64,
	result *TemplateRefResult,
) []metav1.Condition

ApplyTemplateCondition updates the TemplateResolved condition on a conditions slice using the TemplateRefResult.

Behavior:

  • Clones the conditions slice to avoid mutating the original.
  • Sets the TemplateResolved condition with the result's fields.
  • Returns the updated conditions slice.

Arguments:

  • conds []metav1.Condition: the existing conditions.
  • generation int64: the resource generation for ObservedGeneration.
  • result *TemplateRefResult: the validation result.

Returns:

  • []metav1.Condition: the updated conditions slice.

func ContainsToken

func ContainsToken(tokens []string, token string) bool

ContainsToken checks if token is in slice.

func Emit

func Emit(recorder events.EventRecorder, obj runtime.Object, eventType, reason, message string)

Emit emits a Kubernetes event if recorder is non-nil. It is a small shared wrapper to avoid repeating nil checks.

func EmitValidationEvent

func EmitValidationEvent(
	recorder events.EventRecorder,
	obj client.Object,
	result *TemplateRefResult,
	prevCondition *metav1.Condition,
)

EmitValidationEvent emits a Kubernetes warning event for a validation failure when the condition transition warrants it (to prevent event spam).

Behavior:

  • Does nothing when recorder is nil or result is valid.
  • Checks ShouldEmitEvent against the previous condition.
  • Emits a Warning event with the condition reason and message.

Arguments:

  • recorder events.EventRecorder: the event recorder (may be nil in tests).
  • obj client.Object: the object to emit the event for.
  • result *TemplateRefResult: the validation result.
  • prevCondition *metav1.Condition: the previous condition for transition detection.

func EnsureStoryRunTriggerTokens

func EnsureStoryRunTriggerTokens(
	ctx context.Context,
	c client.Client,
	run *runsv1alpha1.StoryRun,
	token string,
) ([]string, error)

EnsureStoryRunTriggerTokens ensures token exists on the StoryRun in a conflict-safe way. Returns the list of tokens that were newly added (empty if already present).

func ImpulseUsageRelevantPredicate

func ImpulseUsageRelevantPredicate() predicate.Predicate

ImpulseUsageRelevantPredicate triggers Story usage recount when an Impulse is created/deleted, or when the Impulse's StoryRef changes (moves between stories/namespaces).

func JitteredRequeueDelay

func JitteredRequeueDelay(baseDelay, maxDelay, fallbackBase, fallbackMax time.Duration) time.Duration

JitteredRequeueDelay returns a jittered delay bounded by maxDelay.

The function treats non-positive values as "unset" and falls back to the provided fallback durations. When maxDelay is smaller than baseDelay, maxDelay is clamped up to baseDelay so callers never receive a delay larger than maxDelay.

func PatchStatusIfChanged

func PatchStatusIfChanged[T client.Object](
	ctx context.Context,
	c client.Client,
	obj T,
	mutate func(T),
) (bool, error)

PatchStatusIfChanged applies mutate() and updates status only if it changed. Returns (changed, err).

This prevents self-reconcile storms caused by unconditional status writes.

func RecordEvent

func RecordEvent(
	recorder events.EventRecorder,
	logger logr.Logger,
	obj client.Object,
	eventType,
	reason,
	message string,
)

RecordEvent emits a Kubernetes event through the provided recorder while falling back to structured logging when the recorder is unavailable.

Arguments:

  • recorder events.EventRecorder: events API recorder; may be nil in tests.
  • logger logr.Logger: optional logger used when the recorder is nil; zero-value loggers are ignored.
  • obj client.Object: the Kubernetes object associated with the event.
  • eventType string: event type such as corev1.EventTypeWarning.
  • reason string: short machine-readable reason describing the event.
  • message string: human-readable message that will be trimmed before emission.

Returns:

  • None. The helper is best-effort and silently drops empty messages.

func Run

func Run[T client.Object](
	ctx context.Context,
	req ctrl.Request,
	opts RunOptions,
	hooks RunHooks[T],
) (res ctrl.Result, err error)

Run wraps common reconcile scaffolding (timeout, metrics, fetch, deletion handling, and finalizer enforcement) while delegating controller-specific logic to the provided hooks.

func RunPipeline

func RunPipeline[T client.Object, Prepared any](
	ctx context.Context,
	obj T,
	hooks PipelineHooks[T, Prepared],
) (ctrl.Result, error)

RunPipeline executes the shared prepare → ensure → finalize pipeline.

func ShortCircuitDeletion

func ShortCircuitDeletion(
	ctx context.Context,
	obj client.Object,
	deleteFn func(context.Context) (ctrl.Result, error),
) (bool, ctrl.Result, error)

ShortCircuitDeletion checks the object's DeletionTimestamp and, when set, executes deleteFn to perform controller-specific cleanup before bubbling the returned ctrl.Result/error back to the caller. It reports whether deletion handling ran so reconcilers can skip the rest of their pipelines.

func SnapshotCondition

func SnapshotCondition(conds []metav1.Condition, conditionType string) *metav1.Condition

SnapshotCondition captures the current state of a condition for later comparison.

Behavior:

  • Returns nil when the condition is not found.
  • Returns a snapshot of Status, Reason, Message for transition detection.

Arguments:

  • conds []metav1.Condition: the conditions slice to search.
  • conditionType string: the condition type to find.

Returns:

  • *metav1.Condition: the found condition or nil.

func StartControllerReconcile

func StartControllerReconcile(
	ctx context.Context,
	controller string,
	timeout time.Duration,
) (context.Context, func(error))

StartControllerReconcile applies an optional timeout to ctx and returns a closure that records controller reconcile metrics and cancels the context. Callers should `defer finish(err)` so metrics see the final error value.

func StepRunEngramTriggerRelevantUpdates

func StepRunEngramTriggerRelevantUpdates() predicate.Predicate

StepRunEngramTriggerRelevantUpdates enqueues only when a StepRun is created or its spec changes (generation changes). It ignores all status transitions.

func StepRunMeaningfulUpdates

func StepRunMeaningfulUpdates() predicate.Predicate

StepRunMeaningfulUpdates returns a predicate that:

  • allows Create events (new runs matter)
  • ignores annotation-only churn (e.g., token marking)
  • allows updates when generation changes (spec change) OR when status changes in meaningful ways
  • ignores Delete events (optional; change if you want deletions to trigger recounts)

func StoryRunMeaningfulUpdates

func StoryRunMeaningfulUpdates() predicate.Predicate

StoryRunMeaningfulUpdates returns a predicate that:

  • allows Create events
  • allows updates on meaningful lifecycle transitions (phase, started/finished timestamps)
  • ignores annotation-only churn (token marking)
  • ignores Delete events by default

func StoryRunTriggerRelevantPredicate

func StoryRunTriggerRelevantPredicate() predicate.Predicate

StoryRunTriggerRelevantPredicate enqueues only on transitions that should affect Story triggers. This must ignore annotation-only writes (e.g., token marking) and status fields that change frequently (Duration, StepsComplete, Message) to prevent reconcile storms.

Only trigger on:

  • Phase transitions (the primary signal for trigger counting)
  • StartedAt/FinishedAt being set (lifecycle milestones)

This prevents O(n²) reconcile loops where running StoryRuns update Duration every reconcile, which would otherwise trigger the Story controller repeatedly.

func UpdateStepRunSpec

func UpdateStepRunSpec(existing, desired *runsv1alpha1.StepRun) bool

UpdateStepRunSpec mutates existing to match the desired StepRun spec and returns true when any field changed.

func WithTimeout

func WithTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc)

WithTimeout wraps ctx with a timeout when timeout > 0. Returns the original ctx with a no-op cancel when timeout <= 0.

This is shared across controllers to consistently bound reconcile duration.

Types

type PipelineHooks

type PipelineHooks[T client.Object, Prepared any] struct {
	// Prepare performs controller-specific preparation work and may
	// short-circuit reconciliation by returning a ctrl.Result.
	// When Prepare returns a non-nil ctrl.Result, the pipeline exits immediately.
	Prepare func(ctx context.Context, obj T) (Prepared, *ctrl.Result, error)
	// Ensure performs the primary reconciliation using the prepared context.
	Ensure func(ctx context.Context, obj T, prepared Prepared) (ctrl.Result, error)
	// Finalize is invoked regardless of Ensure success. When nil, the pipeline
	// simply returns Ensure's result/error.
	Finalize func(
		ctx context.Context,
		obj T,
		prepared Prepared,
		ensureResult ctrl.Result,
		ensureErr error,
	) (ctrl.Result, error)
}

PipelineHooks defines the callbacks required to run the standard prepare → ensure → finalize reconcile pipeline.

type ReferenceLoadResult

type ReferenceLoadResult[T any] struct {
	// Object is the loaded reference (nil on error).
	Object T

	// Stop indicates reconciliation should stop (NotFound case).
	Stop bool

	// Error is the transient error that should trigger backoff (nil when Stop=true for NotFound).
	Error error

	// BlockMessage is the message to use when setting phase to Blocked.
	BlockMessage string

	// EventReason is the reason for the warning event.
	EventReason string
}

ReferenceLoadResult holds the outcome of loading a reference (template, story, etc.) with phase-blocking semantics used by Impulse-style reconcilers.

Behavior:

  • Encapsulates whether to stop reconciliation (NotFound → block).
  • Provides the loaded object when successful.
  • Used by Impulse controller for ImpulseTemplate and Story reference loading.

func LoadClusterTemplate

func LoadClusterTemplate[T client.Object](
	ctx context.Context,
	c client.Client,
	templateName string,
	templateType string,
	newTemplate func() T,
) *ReferenceLoadResult[T]

LoadClusterTemplate fetches a cluster-scoped template and returns a result with phase-blocking semantics.

Behavior:

  • Returns the template on success.
  • Returns Stop=true with BlockMessage on NotFound.
  • Returns Error on transient failures.

Arguments:

  • ctx context.Context: for API calls.
  • c client.Client: the Kubernetes client.
  • templateName string: the name of the template.
  • templateType string: for messages (e.g., "ImpulseTemplate").
  • newTemplate func() T: factory for the template type.

Returns:

  • *ReferenceLoadResult[T]: the load result.

type RunHooks

type RunHooks[T client.Object] struct {
	// Get must fetch the latest object for the reconcile request.
	Get func(ctx context.Context, req ctrl.Request) (T, error)
	// HandleDeletion may short-circuit reconciliation when the object is deleting.
	// When handled is true, Run returns the provided result/error immediately.
	HandleDeletion func(ctx context.Context, obj T) (handled bool, result ctrl.Result, err error)
	// EnsureFinalizer adds controller-specific finalizers when needed.
	EnsureFinalizer func(ctx context.Context, obj T) error
	// HandleNormal performs the controller-specific reconciliation once the
	// object is fetched, deletion is handled, and finalizers are ensured.
	HandleNormal func(ctx context.Context, obj T) (ctrl.Result, error)
}

RunHooks defines controller-specific callbacks executed by Run.

type RunOptions

type RunOptions struct {
	// Controller is the name recorded in reconcile metrics.
	Controller string
	// Timeout bounds each reconcile loop; zero disables the timeout.
	Timeout time.Duration
}

RunOptions configures the shared reconcile runner.

type Set

type Set struct {
	// contains filtered or unexported fields
}

Set is a tiny "dirty flag" set.

Key rule: controllers must NOT "consume" (delete) a dirty flag until the corresponding recount/status update has succeeded.

func (*Set) Clear

func (s *Set) Clear(key string) bool

Clear deletes the dirty flag and returns true if it existed.

func (*Set) IsDirty

func (s *Set) IsDirty(key string) bool

func (*Set) Mark

func (s *Set) Mark(key string)

type TemplateRefResult

type TemplateRefResult struct {
	// ValidationStatus is the overall validation status (Valid, Invalid, Unknown).
	ValidationStatus enums.ValidationStatus

	// ValidationErrors contains human-readable error messages for invalid references.
	ValidationErrors []string

	// ConditionStatus is the status to set on the TemplateResolved condition.
	ConditionStatus metav1.ConditionStatus

	// ConditionReason is the reason code for the condition (e.g., TemplateResolved, TemplateNotFound).
	ConditionReason string

	// ConditionMessage is the human-readable message for the condition.
	ConditionMessage string

	// IsTransient indicates the error is transient (network, timeout) and should trigger backoff.
	IsTransient bool

	// OriginalError is the underlying error that caused the validation failure.
	OriginalError error
}

TemplateRefResult holds the outcome of a template reference validation so reconcilers can update conditions, emit events, and set validation status uniformly without duplicating the classification logic.

Behavior:

  • Encapsulates all condition/status fields needed for template validation.
  • Provides ShouldEmitEvent to determine if condition transition warrants an event.
  • Used by Engram, Impulse, and Story reconcilers for template/reference validation.

func ClassifyTemplateError

func ClassifyTemplateError(
	err error,
	templateType string,
	templateName string,
	emptyRefError error,
	emptyRefReason string,
) *TemplateRefResult

ClassifyTemplateError converts a template lookup error into a TemplateRefResult with appropriate condition status, reason, and message.

Behavior:

  • Returns Valid result when err is nil.
  • Returns Invalid with TemplateNotFound when err is NotFound.
  • Returns Invalid with custom reason when err is a sentinel (e.g., empty name).
  • Returns Invalid with TransientError for other errors (network, timeout).

Arguments:

  • err error: the error from template lookup (may be nil).
  • templateType string: the template type for messages (e.g., "EngramTemplate").
  • templateName string: the template name for messages.
  • emptyRefError error: sentinel error for empty reference name (compared via errors.Is).
  • emptyRefReason string: condition reason for empty reference (e.g., "TemplateRefInvalid").

Returns:

  • *TemplateRefResult: the classified result with all fields populated.

func ValidateTemplateRef

func ValidateTemplateRef(
	ctx context.Context,
	c client.Client,
	templateName string,
	templateType string,
	newTemplate func() client.Object,
	key client.ObjectKey,
	emptyRefError error,
	emptyRefReason string,
) (client.Object, *TemplateRefResult)

ValidateTemplateRef performs a template reference validation by fetching the template and classifying any errors into a TemplateRefResult.

Behavior:

  • Validates the template name is non-empty (using emptyRefError sentinel).
  • Fetches the template using the provided client.
  • Classifies the result using ClassifyTemplateError.

Arguments:

  • ctx context.Context: for API calls.
  • c client.Client: the Kubernetes client.
  • templateName string: the name of the template to fetch (trimmed).
  • templateType string: the type for messages (e.g., "EngramTemplate").
  • newTemplate func() client.Object: factory for the template type.
  • key client.ObjectKey: the key to fetch (cluster-scoped uses just Name).
  • emptyRefError error: sentinel error for empty reference.
  • emptyRefReason string: condition reason for empty reference.

Returns:

  • client.Object: the fetched template (nil on error).
  • *TemplateRefResult: the validation result.

func (*TemplateRefResult) IsValid

func (r *TemplateRefResult) IsValid() bool

IsValid returns true when the template reference is valid.

func (*TemplateRefResult) ShouldEmitEvent

func (r *TemplateRefResult) ShouldEmitEvent(prev *metav1.Condition) bool

ShouldEmitEvent determines if a condition transition warrants emitting a Kubernetes event.

Behavior:

  • Returns true when the condition status changed.
  • Returns true when the reason changed (different failure type).
  • Returns true when the message changed (different failure detail).
  • Used to prevent event spam on repeated reconciles with the same state.

Arguments:

  • prev *metav1.Condition: the previous condition state; nil means first observation.

Returns:

  • bool: true when an event should be emitted.

Jump to

Keyboard shortcuts

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