Help us shape the future of Go! Take the 2026 Go Developer Survey

controllerutils

package
v0.0.0-...-dbf75d7 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package controllerutils holds controller helpers shared across services (backend, kube-applier, fleet, ...) that all build Cosmos-backed informer-driven controllers and want consistent cadence/gating behavior.

The cooldown gate exposed here was originally backend/pkg/utils/controllerutils' TimeBasedCooldownChecker; it lives in internal/ so other services can use the same implementation without re-importing the backend module.

Index

Constants

View Source
const (
	// Fingerprint for direct comparison with HyperShift's SecretEncryption History fingerprints
	// and to avoid leaking sensitive information such as the KMS key version in logs or metrics.
	HcpClusterKmsKeyFingerprintAnnotation = "azure.microsoft.com/hcp-cluster-kms-key-fingerprint"
	HcpClusterAzureResourceIdAnnotation   = "azure.microsoft.com/hcp-cluster-azure-resource-id"
)

Variables

This section is empty.

Functions

func BuildReadDesire

func BuildReadDesire(resourceIDString string, managementCluster *azcorearm.ResourceID, target kubeapplierapi.ResourceReference) *kubeapplierapi.ReadDesire

BuildReadDesire produces the desired-state ReadDesire for a resource. The status section is intentionally zero — the kube-applier owns status.

func NeedsUpdate

func NeedsUpdate(existing, desired any) bool

NeedsUpdate reports whether `desired` differs from `existing` in any way that should cause us to write `desired` back to Cosmos. It is a strict-but-server-managed-fields-aware semantic equality check: all the fields that actually persist must match, but cosmos-managed values like the document etag are ignored, as are Go-level representation differences (RawExtension Raw vs Object, parent pointer chains in ResourceID, etc.).

func ReadDesireNeedsWork

func ReadDesireNeedsWork(existing, desired *kubeapplierapi.ReadDesire) bool

ReadDesireNeedsWork reports whether existing matches desired in the fields the controller writes (Spec.ManagementCluster, Spec.TargetItem). A nil existing means "doesn't exist yet" — work is required.

func ResourceIDsEqual

func ResourceIDsEqual(a, b *azcorearm.ResourceID) bool

ResourceIDsEqual compares two *azcorearm.ResourceID for equality by their canonical string form. Both may be nil; non-nil values are compared by String(), so independently-parsed instances with different parent pointer chains still compare equal when they represent the same ARM ID.

The comparison is case-insensitive: ARM resource IDs are case-insensitive for their provider namespaces and resource types (for example Azure may return "Microsoft.RedHatOpenshift" where our internal types use "Microsoft.RedHatOpenShift"), so two IDs that differ only by casing represent the same resource and must compare equal.

Types

type AfterEnqueuer

type AfterEnqueuer interface {
	EnqueueAfter(keyObj any, duration time.Duration)
}

AfterEnqueuer allows scheduling a workqueue item for processing after an explicit delay. Validation controllers use this to implement EarliestRetryAfter semantics.

type CacheSyncWaiter

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

func (*CacheSyncWaiter) AddCacheSyncs

func (waiter *CacheSyncWaiter) AddCacheSyncs(synced ...cache.InformerSynced)

func (*CacheSyncWaiter) WaitForCacheSync

func (waiter *CacheSyncWaiter) WaitForCacheSync(ctx context.Context) bool

type CooldownChecker

type CooldownChecker interface {
	CanSync(ctx context.Context, key any) bool
}

CooldownChecker decides whether a key may be (re-)queued.

Implementations must be safe to call concurrently — informer event handlers, periodic resyncs, and worker goroutines may all consult the same checker.

CanSync takes a context so implementations may emit logs or use context-bound services; the time-based variant ignores it. A true return is taken as "the caller WILL sync this key" and the implementation is free to record state (e.g. stamp a next-allowed time) on that basis.

type Enqueuer

type Enqueuer interface {
	Enqueue(keyObj any)
}

Enqueuer schedules work immediately after an input change. Unlike delayed requeues, these events should not increment the workqueue retry metric.

type GenericSyncer

type GenericSyncer[T comparable] interface {
	SyncOnce(ctx context.Context, keyObj T) error
	CooldownChecker() CooldownChecker
	MakeKey(resourceID *azcorearm.ResourceID) T
}

type GenericWatchingController

type GenericWatchingController[T comparable] struct {
	CacheSyncWaiter
	// contains filtered or unexported fields
}

func NewGenericWatchingController

func NewGenericWatchingController[T comparable](name string, resourceType azcorearm.ResourceType, syncer GenericSyncer[T], reconcileTotal *prometheus.CounterVec) *GenericWatchingController[T]

NewGenericWatchingController creates a controller that watches Cosmos-backed informers and delegates reconciliation to syncer.

func (*GenericWatchingController[T]) AddInterestingResource

func (c *GenericWatchingController[T]) AddInterestingResource(resourceType azcorearm.ResourceType, resourceName string)

AddInterestingResource accepts events for a resource type and name, ignoring case. The name "*" accepts every resource of that type. With no registrations all events are accepted. Register interests before informers start delivering resource events; registrations are not safe to mutate concurrently with events.

func (*GenericWatchingController[T]) Enqueue

func (c *GenericWatchingController[T]) Enqueue(keyObj any)

func (*GenericWatchingController[T]) EnqueueAfter

func (c *GenericWatchingController[T]) EnqueueAfter(keyObj any, duration time.Duration)

func (*GenericWatchingController[T]) EnqueueResourceIDAdd

func (c *GenericWatchingController[T]) EnqueueResourceIDAdd(resourceID *azcorearm.ResourceID, changed bool)

EnqueueResourceIDAdd is equivalent to calling EnqueueResourceIDAddWithMaxDepth with a maxDepth of -1. See EnqueueResourceIDAddWithMaxDepth for more details. It is exposed so that individual controllers can add other items to requeue based on easily.

func (*GenericWatchingController[T]) EnqueueResourceIDAddWithMaxDepth

func (c *GenericWatchingController[T]) EnqueueResourceIDAddWithMaxDepth(resourceID *azcorearm.ResourceID, changed bool, maxDepth int)

EnqueueResourceIDAddWithMaxDepth traverses resourceID and its parents according to maxDepth until it finds a resourceID that is of the resource type of c.resourceType and adds it if found. Each walk to Parent consumes one level. maxDepth is the maximum number of parent hops to traverse when searching for a resourceID of type c.resourceType. maxDepth 0 means only the resourceID itself is considered. maxDepth -1 (or any negative value) means no limit. The parent walk continues until a match or nil parent is reached. It is exposed so that individual controllers can add other items to requeue based on easily. When there's a match of resourceType: when changed is true, the resourceID is added to the queue immediately. Otherwise, the resourceID is added to the queue only if the cooldown checker allows it. Interesting-resource filters apply to the original event, not the ancestors visited while resolving the controller's key.

func (*GenericWatchingController[T]) QueueForChildInformers

func (c *GenericWatchingController[T]) QueueForChildInformers(resyncDuration time.Duration, notifiers ...Notifier) error

QueueForChildInformers subscribes to direct children of the controller's key resource. Like all generic watches, it observes deletions: removing a child can unblock teardown or change a status aggregate. Resource interests still filter the original child before the one-hop owner mapping.

func (*GenericWatchingController[T]) QueueForDescendantInformers

func (c *GenericWatchingController[T]) QueueForDescendantInformers(resyncDuration time.Duration, maxDepth int, notifiers ...Notifier) error

QueueForDescendantInformers extends child mapping to an explicit depth, for controllers consuming both direct children and node-pool-nested documents. It uses resource IDs already carried by the event; no lookup is required.

func (*GenericWatchingController[T]) QueueForInformers

func (c *GenericWatchingController[T]) QueueForInformers(resyncDuration time.Duration, notifiers ...Notifier) error

QueueForInformers is equivalent to calling QueueForInformersWithMaxDepth with maxDepth of -1. See QueueForInformersWithMaxDepth for more details.

func (*GenericWatchingController[T]) QueueForInformersWithMaxDepth

func (c *GenericWatchingController[T]) QueueForInformersWithMaxDepth(resyncDuration time.Duration, maxDepth int, notifiers ...Notifier) error

QueueForInformersWithMaxDepth adds event handlers to the notifiers for the controller with a given max depth. maxDepth is the maximum number of parent hops to traverse when searching for a resourceID whose type is c.resourceType. Each walk to Parent consumes one level. maxDepth 0 means only the resourceID itself is considered. maxDepth -1 (or any negative value) means no limit. The parent walk continues until a match or nil parent is reached. It is exposed so that individual controllers can add other items to requeue based on easily.

func (*GenericWatchingController[T]) Run

func (c *GenericWatchingController[T]) Run(ctx context.Context, threadiness int)

func (*GenericWatchingController[T]) SyncOnce

func (c *GenericWatchingController[T]) SyncOnce(ctx context.Context, keyObj any) error

type Notifier

type Notifier interface {
	AddEventHandlerWithOptions(handler cache.ResourceEventHandler, options cache.HandlerOptions) (cache.ResourceEventHandlerRegistration, error)
}

type SettableCooldownChecker

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

SettableCooldownChecker is a cooldown gate where the per-key cooldown duration is set explicitly by the caller via SetCooldown, rather than being fixed at construction time. A key with no cooldown set is always allowed.

func NewSettableCooldownChecker

func NewSettableCooldownChecker() *SettableCooldownChecker

func (*SettableCooldownChecker) CanSync

func (c *SettableCooldownChecker) CanSync(_ context.Context, key any) bool

func (*SettableCooldownChecker) SetClock

func (c *SettableCooldownChecker) SetClock(clock utilsclock.PassiveClock)

func (*SettableCooldownChecker) SetCooldown

func (c *SettableCooldownChecker) SetCooldown(key any, duration time.Duration)

SetCooldown records that the given key should not be re-synced until now+duration has elapsed.

func (*SettableCooldownChecker) TimeUntilReady

func (c *SettableCooldownChecker) TimeUntilReady(key any) time.Duration

TimeUntilReady returns the duration until the key's cooldown expires. Returns 0 if the key has no cooldown set or the cooldown has already expired.

type TimeBasedCooldownChecker

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

TimeBasedCooldownChecker is a fixed-interval cooldown gate: after CanSync returns true for a key, subsequent calls for the same key return false until cooldownDuration has elapsed since the allowed call.

The next-exec map is an LRU rather than an unbounded map so that a long-running process whose keys come and go does not leak memory. 1M entries is far above any realistic management cluster's resource count.

func NewTimeBasedCooldownChecker

func NewTimeBasedCooldownChecker(cooldownDuration time.Duration) *TimeBasedCooldownChecker

NewTimeBasedCooldownChecker constructs a checker bound to the real wall-clock and a 1M-entry LRU. Tests should call SetClock to inject a fake clock.

func (*TimeBasedCooldownChecker) CanSync

func (c *TimeBasedCooldownChecker) CanSync(_ context.Context, key any) bool

CanSync stamps now+cooldownDuration on a true return so subsequent calls within the cooldown window return false. A key with no record is always allowed. ctx is part of the CooldownChecker interface but unused here.

func (*TimeBasedCooldownChecker) SetClock

SetClock substitutes the time source used to evaluate the cooldown. Intended for tests; production code should use the real clock from the constructor.

Jump to

Keyboard shortcuts

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