watch

package
v0.52.0 Latest Latest
Warning

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

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

Documentation

Overview

Package watch drives the api-source-of-truth reconcile: it keeps the followability registry and the per-GitTarget watched-type tables fresh, and mirrors each watched type into Git from a long-lived Kubernetes watch per claimed (GitTarget, GVR, scope). The live watch is the only source of ongoing object state, and its events are the writes. The INITIAL desired set the mark-and-sweep folds over the Git folder comes from the same watch's sendInitialEvents replay, or, on an apiserver that does not serve it, from a LIST with the watch's events buffered behind it (targetWatchListAndStream). See docs/architecture.md.

Index

Constants

View Source
const (
	// TriggerReasonDeclare is the GitTarget controller submitting a declaration.
	TriggerReasonDeclare = "declare"
	// TriggerReasonRuleChange is a WatchRule or ClusterWatchRule edit, refusal, or deletion.
	TriggerReasonRuleChange = "rule_change"
	// TriggerReasonSharedRefresh is a shared snapshot moving under a target: a CRD installed or
	// removed, an APIService appearing, a source-namespace grant or revocation. It is derived
	// rather than posted — the refresh compares each target's rendered plan across the
	// re-projection and marks only the ones it actually changed.
	TriggerReasonSharedRefresh = "shared_refresh"
	// TriggerReasonPeriodic is the 30s floor, the one path that still marks everything.
	TriggerReasonPeriodic = "periodic"
	// TriggerReasonWorkerReplaced is a branch worker replaced because its GitProvider now names a
	// different repository. The new worker has no clone and no queue, so every GitTarget on that
	// branch has to re-establish its folder there.
	TriggerReasonWorkerReplaced = "worker_replaced"
)

Trigger reasons, as they appear on the triggers-by-reason metric and in logs. There are three because there are three ways intent reaches the owner, and telling them apart is what shows which source is noisy.

View Source
const (
	StreamReasonInitialReplay          = "InitialReplay"
	StreamReasonResumeReplay           = "ResumeReplay"
	StreamReasonExpiredResourceVersion = "ExpiredResourceVersion"
	StreamReasonWatchError             = "WatchError"
	StreamReasonWatchNotPermitted      = "WatchNotPermitted"
	StreamReasonAllStreamsReady        = "AllStreamsReady"
	StreamReasonReplaying              = "Replaying"
	StreamReasonNoResolvedTypes        = "NoResolvedTypes"
	// StreamReasonBranchIntakePaused is a stream waiting for its branch worker to reopen intake.
	StreamReasonBranchIntakePaused = "BranchIntakePaused"
)
View Source
const (
	// ClusterWatchRuleReasonGitTargetNamespaceNotAuthorized is the terminal reason when the
	// referenced GitTarget's namespace is not admitted by that target's ClusterProvider — either
	// because spec.accessFrom excludes it or because the provider does not exist at all.
	//
	// One rule-side reason covers both provider-side causes on purpose: from the ClusterWatchRule's
	// point of view the single fact that matters is that this rule may not compile against this
	// target. The Message carries which of the two it was.
	ClusterWatchRuleReasonGitTargetNamespaceNotAuthorized = "GitTargetNamespaceNotAuthorized"
)

ClusterWatchRule compile refusal reasons. They live here rather than in the controller because bootstrap refuses on the same grounds without a controller in sight, and one vocabulary is what keeps the two from drifting.

View Source
const DefaultAttributionGraceWindow = 3 * time.Second

DefaultAttributionGraceWindow is the bounded wait a watch event spends for a matching audit fact to arrive in the index before it ships as committer. It is the "slack" that makes "a late audit arrival must not rewrite a shipped commit" enforceable: we wait briefly BEFORE shipping rather than rewrite afterwards.

View Source
const ReasonCollectionOverlap = "CollectionOverlap"

ReasonCollectionOverlap is the ResourcesResolved reason for a rule refused because one of its collections overlaps a different collection the same GitTarget already selects.

View Source
const ReasonInvalidObjectSelector = "InvalidObjectSelector"

ReasonInvalidObjectSelector is the terminal ResourcesResolved reason for a rule whose spec.rules[].objectSelector does not parse. The rule is refused as a whole, for the reason a denied source namespace refuses one: mirroring the items that did parse is worse than a loud failure.

Variables

This section is empty.

Functions

func CompileWatchRule added in v0.39.0

CompileWatchRule is THE ONLY PATH from a WatchRule to a compiled rule. It resolves the whole per-item source-namespace scope first and compiles only on an admitted verdict.

It is one function, called by both the WatchRule reconciler and the watch manager's startup bootstrap, because two call sites that each remember to check is an arrangement this codebase has already got wrong once. Bootstrap lists every WatchRule and seeds the store BEFORE the first reconcile, then marks the store ready — so a gate the reconciler alone enforced would be bypassed for the whole startup window, on EVERY restart, which is exactly when nobody is watching. Routing compilation through here closes that by construction rather than by discipline: there is no second place that can call AddOrUpdateWatchRule for a WatchRule.

Its two outcomes map onto the two things the caller must do:

  • ADMITTED — the rule is compiled with every item resolved to its source-namespace set. The caller publishes SourceNamespaceAuthorized=True.
  • DENIED — any previously compiled rule is REMOVED here, before the caller publishes anything. A gate that only writes a condition is not a gate; the caller must still replan the watch manager and then publish the Failed trio, in that order.

There is no third "cannot say yet" outcome, and there used to be. It existed because the gate's selector half read Namespace labels in ANOTHER cluster, which could be syncing, unreachable or Forbidden, and a rule with an already-resolved scope had to RETAIN it through such a gap rather than narrow to the empty set (a narrowed set is the input to a sweep, so failing closed there would have deleted a tenant's Git content over an outage). Every input is now a control-plane object this reconcile already read, so there is nothing left to retain a scope through, and the per-rule grant memory that existed only to serve that retention is gone with it.

Bootstrap cannot publish status (it runs before controllers start), so a rule denied there is simply not compiled and the first reconcile writes the terminal condition. That ordering — fail closed first, explain second — is correct, not a limitation.

Types

type APIResourceCatalog

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

APIResourceCatalog is the per-scan normalizer between Kubernetes discovery and the typeset registry: it turns one ServerGroupsAndResources() result into a policy-annotated typeset.Scan. It holds NO judgement and no time-sensitive state — retain-on-error and the removal grace for omissions both live in typeset.Registry.UpdateFromScan (see docs/spec/typeset-owns-discovery-grace.md). The only state kept is mechanical bookkeeping: the last normalized scan (the change fingerprint, and the registry's re-derive source for refreshes without a discovery round-trip), the scan generation, and readiness.

func NewAPIResourceCatalog

func NewAPIResourceCatalog() *APIResourceCatalog

NewAPIResourceCatalog constructs an empty API resource catalog.

func (*APIResourceCatalog) DegradedGroupVersions

func (c *APIResourceCatalog) DegradedGroupVersions() []schema.GroupVersion

DegradedGroupVersions returns the group/versions the latest scan reported as failed, sorted for stable logging.

func (*APIResourceCatalog) Generation

func (c *APIResourceCatalog) Generation() uint64

Generation reports the current scan generation. It bumps only when the normalized scan facts change, so downstream consumers gated on it do not churn on steady rescans.

func (*APIResourceCatalog) Ready

func (c *APIResourceCatalog) Ready() bool

Ready reports whether the catalog has accepted any trusted discovery data.

func (*APIResourceCatalog) Refresh

func (c *APIResourceCatalog) Refresh(disco apiResourceDiscovery) (bool, error)

Refresh normalizes one discovery scan and stores it as the latest. It returns whether the normalized facts changed from the previous scan. It makes no retention decision of any kind: a group/version this scan failed on or no longer lists is simply reported as such in the scan; the registry judges what that means.

func (*APIResourceCatalog) Scan

Scan returns the last normalized scan, policy-annotated and stamped with the current generation — the registry's one input (UpdateFromScan). ok is false before the first trusted scan. sensitive is the operator-configured SensitiveResourcePolicy, applied at projection exactly like the allow/deny resource policy is applied at normalization: a startup-known fact typeset never infers.

func (*APIResourceCatalog) ServesWatchable

func (c *APIResourceCatalog) ServesWatchable(gvr schema.GroupVersionResource) bool

ServesWatchable reports whether the latest scan saw this exact group/version/resource served with both list and watch verbs. It is the precondition for opening an informer on a resource the operator does not require: an API server that does not aggregate does not serve apiregistration.k8s.io at all, and a blind informer on it retries forever. It deliberately ignores the allow/deny watch policy, which governs what the operator mirrors, not what it may inspect. Reports false before the first trusted scan.

func (*APIResourceCatalog) Stats

func (c *APIResourceCatalog) Stats() CatalogStats

Stats returns a point-in-time summary of the latest scan for metrics.

type AttributionLookup

type AttributionLookup interface {
	// Await resolves the strongest author fact for a watch event, waiting up to grace for one that
	// has not been delivered yet. It returns an AttributionAbsent resolution when nothing matched in
	// time; it never blocks longer than grace and never returns an error path.
	Await(ctx context.Context, query queue.FactQuery, grace time.Duration) queue.AuthorResolution
}

AttributionLookup is the read side of the attribution fact index. The in-process queue.FactIndex satisfies it; nil means configured-author.

It waits rather than polls. The index registers the waiter BEFORE it reads itself, so a fact delivered in the gap between the two wakes a waiter that is already listening — the race the old 150ms poll loop papered over by looking again. There is no Redis call anywhere on this path: the fast case is a map read and the waiting case is a channel receive.

type AuthorQuery added in v0.41.0

type AuthorQuery struct {
	// AuditRoute partitions the facts. It is the route the API server posts audit under, NOT the
	// ClusterProvider's name: several providers may name one cluster and all read its facts.
	AuditRoute string
	// GVR is the watched type. The version serves the metric labels only; the join is keyed on the
	// group/resource, which is what a fact carries.
	GVR             schema.GroupVersionResource
	UID             k8stypes.UID
	ResourceVersion string
	// Namespace and Labels serve the collection tier only: they are how a removal finds the
	// deletecollection whose scope covered it.
	Namespace string
	Labels    map[string]string
	// Name serves the name tier only, the floor for a fact carrying neither a uid nor a
	// resourceVersion — an aggregated-API write, whose audit objectRef holds the name and nothing
	// else. The watch event always carries it, so supplying it costs nothing when no fact needs it.
	Name string
	// ExactCapable is true for ADDED and MODIFIED, whose resourceVersion is the one the write
	// produced. A removal's is not, so it consults the weaker tiers the exact-capable events skip.
	ExactCapable bool
	// FilteredRemoval and Terminating select the exact-evidence policy for a DELETED frame from a
	// selected collection; see queue.FactQuery.FilteredRemoval.
	FilteredRemoval bool
	Terminating     bool
}

AuthorQuery is one watch event's identity, as the author resolver reads it.

It is a struct rather than the parameter list this used to be because the collection tier needs the object's namespace and labels: a body-less deletecollection is joined by scope — type, namespace, selector, window — and neither the namespace nor the labels could be expressed in the old six arguments, so that tier would have been unreachable from the resolver.

type AuthorResolver

type AuthorResolver interface {
	// ResolveAuthor returns the author UserInfo for a watch event together with the
	// attribution OUTCOME. It may wait up to the grace window for a matching fact; it never
	// blocks indefinitely and never returns an error path. exactCapable distinguishes
	// ADDED/MODIFIED events (true) from known RV-mismatch removals (false).
	//
	// The outcome is returned explicitly rather than as an ok bool because the two possible
	// "no author" cases are NOT the same and callers must be able to tell them apart:
	// AttributionNotAttempted (configured-author mode — the committer legitimately is the
	// author) versus AttributionUnresolved (attribution ran and found nothing — a gap worth
	// surfacing). An empty UserInfo cannot distinguish them, which is exactly how the loss
	// stayed invisible. A resolved outcome always carries a non-empty UserInfo.
	//
	// In production this method only ever returns the latter two: configured-author mode is
	// expressed by leaving Manager.AuthorResolver nil (attachAuthor returns early, leaving the
	// event's zero AttributionNotAttempted), never by constructing a resolver over a nil
	// lookup. cmd/main.go only builds one with a non-nil index.
	ResolveAuthor(ctx context.Context, query AuthorQuery) (git.UserInfo, git.AttributionOutcome)
}

AuthorResolver names the commit author for a live watch event from audit facts.

func NewAuthorResolver

func NewAuthorResolver(
	lookup AttributionLookup,
	grace time.Duration,
	log logr.Logger,
	health *attribution.RouteHealth,
) AuthorResolver

NewAuthorResolver builds the conservative author resolver over the attribution index. grace bounds the per-event wait for a late fact; a zero grace disables waiting (single lookup). A matched actor — human or service account — is always named by its own username.

health is the process-wide route registry the audit ingress publishes into; nil is accepted (every method is nil-safe) and only costs the never-resolves warning.

type CatalogStats

type CatalogStats struct {
	// AllowedResources is the count of served top-level resources the default
	// watch policy permits.
	AllowedResources int
	// ExcludedResources is the count of served top-level resources the default
	// watch policy excludes (pods, events, leases, …).
	ExcludedResources int
	// TrustedGroupVersions is the count of group/versions the latest scan served cleanly.
	TrustedGroupVersions int
	// DegradedGroupVersions is the count of group/versions the latest scan reported as failed.
	DegradedGroupVersions int
	// Generation is the current scan generation.
	Generation uint64
}

CatalogStats is a point-in-time summary of the latest scan used to set the api_catalog_* gauges. All counts exclude subresources.

type ClusterWatchRuleDecision added in v0.39.0

type ClusterWatchRuleDecision struct {
	// Admitted reports whether the rule compiled.
	Admitted bool
	// Reason is the terminal condition reason when it did not.
	Reason string
	// Message explains the refusal to an operator.
	Message string
}

ClusterWatchRuleDecision is the outcome of the shared ClusterWatchRule compile path.

func CompileClusterWatchRule added in v0.39.0

CompileClusterWatchRule is THE ONLY PATH from a ClusterWatchRule to a compiled cluster rule, and it is the compile-time half of the cluster-scope-only narrowing.

Two refusals, both terminal, in this order:

  1. the referenced GitTarget's namespace must be admitted by that target's ClusterProvider — a ClusterWatchRule's gitTargetRef carries a namespace, so it can name a target in ANY namespace and widen that target's mirror scope cluster-wide;
  2. the rule must not carry a stored scope other than "Cluster". Admission rejects the value on write, but a pre-release object keeps it in etcd, and resolving it as if it had asked for cluster scope would silently change what a running rule mirrors.

Like CompileWatchRule it is shared by the reconciler and the startup bootstrap, so a restart cannot open an unauthorized or namespaced watch before the first reconcile can publish status.

type CursorStore

type CursorStore interface {
	LookupWatchCursor(ctx context.Context, gitTargetUID string, collection types.CollectionKey) (string, bool)
	RecordWatchCursor(ctx context.Context, gitTargetUID string, collection types.CollectionKey, rv string) error
}

CursorStore persists the last processed resourceVersion for each (GitTarget UID, collection) watch shard, bounded by a TTL. The GitTarget is identified by its UID alone — globally unique, so namespace/name would be redundant. The collection carries the label selector, so a stream never resumes under a cursor another selection recorded. Cursors are refreshed on write and never deleted: a live watch keeps its cursor fresh, a dead one's cursor expires. Nil means every new watch session rebuilds from a fresh replay.

type DeclareStatus added in v0.42.0

type DeclareStatus struct {
	// Declared reports whether the GitTarget has ever reached DeclareForGitTarget. It is the
	// observable form of the capture-on-Declare contract: a GitTarget the controller's Validated
	// gate refused never reaches Declare, so it never appears here, which makes "an unauthorized
	// namespace starts no watch" assertable from outside this package.
	Declared bool
	// ClusterID is the source cluster the declaration named — a ClusterProvider name, empty for
	// the cluster the operator runs in. Meaningful only when Declared.
	ClusterID string
	// Pending reports whether a plan pass is owed — the target is dirty, or has never
	// successfully applied a plan.
	Pending bool
	// Failures counts consecutive failed passes; LastError carries the most recent message.
	Failures  int
	LastError string
}

DeclareStatus is the observable state of a GitTarget's declaration: whether the owner still owes it a plan pass, and how the last one ended. It is what makes a target whose passes keep timing out read as failing rather than as idle.

func (DeclareStatus) Settled added in v0.42.0

func (s DeclareStatus) Settled() bool

Settled reports whether the target's declaration has landed: it declared, nothing is owed, and the last pass succeeded.

type EventRouter

type EventRouter struct {
	WorkerManager *git.WorkerManager
	WatchManager  *Manager
	Client        client.Client
	Log           logr.Logger
	// contains filtered or unexported fields
}

EventRouter orchestrates control flow between components. It dispatches live events to BranchWorkers, routes them through per-GitTarget event streams for buffering and deduplication, and drives the synchronous streaming-snapshot resync (M8).

func NewEventRouter

func NewEventRouter(
	workerManager *git.WorkerManager,
	watchManager *Manager,
	client client.Client,
	log logr.Logger,
) *EventRouter

NewEventRouter creates a new event router.

func (*EventRouter) GetGitTargetEventStream

func (r *EventRouter) GetGitTargetEventStream(gitDest types.ResourceReference) *reconcile.GitTargetEventStream

GetGitTargetEventStream returns the registered GitTargetEventStream for a GitTarget.

func (*EventRouter) RegisterGitTargetEventStream

func (r *EventRouter) RegisterGitTargetEventStream(
	gitDest types.ResourceReference,
	stream *reconcile.GitTargetEventStream,
)

RegisterGitTargetEventStream registers a GitTargetEventStream with the router. This allows routing events to specific GitTargetEventStreams for buffering and deduplication.

func (*EventRouter) RouteToGitTargetEventStream

func (r *EventRouter) RouteToGitTargetEventStream(
	event git.Event,
	gitDest types.ResourceReference,
) error

RouteToGitTargetEventStream routes an event to a specific GitTargetEventStream. This replaces direct routing to BranchWorkers, enabling event buffering and deduplication.

func (*EventRouter) ServiceCommitRequest

func (r *EventRouter) ServiceCommitRequest(
	ctx context.Context,
	attach git.AttachCommitRequest,
) (git.FinalizeResult, bool, error)

ServiceCommitRequest is the controller's attach-then-poll seam (§6.4.3): it resolves the worker that holds the request, or else the GitTarget's branch worker, registers the CommitRequest attach idempotently on that worker's FIFO event queue (attach it to the author's window with its message and timers), and returns the request's current outcome. resolved=false means the worker has not finished — the controller requeues and polls again.

attach.GitTargetName/GitTargetNamespace name the GitTarget; the worker is keyed by its provider+branch. A worker that does not exist YET — at startup, or before the GitTarget's first reconcile — is not an answer: the request stays pending in phase WaitingForWorker and the controller polls again, within its safety window. Resolving NoOpenWindow there would end a save before the worker that could collect its writes, or record its CommitEmpty message, had started. It is reported as pending and NOT as an error, because the GitTarget did resolve: the controller reads a service error as "the target never resolved" and would count the request's eventual failure without its GitTarget labels. A GitTarget that cannot be read is that error.

func (*EventRouter) UnregisterGitTargetEventStream

func (r *EventRouter) UnregisterGitTargetEventStream(gitDest types.ResourceReference)

UnregisterGitTargetEventStream removes a GitTargetEventStream from the router. This is called during GitTarget deletion cleanup.

func (*EventRouter) WithdrawCommitRequest added in v0.51.0

func (r *EventRouter) WithdrawCommitRequest(
	ctx context.Context,
	attach git.AttachCommitRequest,
) (git.FinalizeResult, bool, error)

WithdrawCommitRequest is the controller's fail-closed seam: it cancels a request the worker has not acted on, and reports the worker's answer, so the controller never fails a request the worker could still commit.

  • The worker that holds the request answers for it, even when its GitTarget is gone or the worker is being retired. The withdraw rides its FIFO behind every attach already sent, and is a no-op for a request it holds. Until it is handled, resolved=false and Phase says where the request stands; a held phase means the controller keeps waiting.
  • Nothing holds it, and the GitTarget is gone or names no worker: nothing can commit the request, so it resolves at once as withdrawn. The controller sends no attach after it starts withdrawing, so a worker that starts later never sees it.

type GVR

type GVR struct {
	Group    string
	Version  string
	Resource string
	Scope    configv1alpha3.ResourceScope
}

GVR represents a concrete Group/Version/Resource target with a scope. This is used to plan dynamic informer creation from active rules.

type GitPathAcceptanceStatus

type GitPathAcceptanceStatus struct {
	Accepted             bool
	Reason               string
	Message              string
	At                   metav1.Time
	RefusedCollection    types.CollectionKey
	RefusedCollectionSet bool
}

GitPathAcceptanceStatus is the whole-target write-safety status for a GitTarget path.

type Manager

type Manager struct {
	// Client provides cluster access.
	Client client.Client
	// Log is the logger to use.
	Log logr.Logger
	// RuleStore gives access to compiled WatchRule/ClusterWatchRule.
	RuleStore *rulestore.RuleStore
	// EventRouter dispatches per-type reconciles/sweeps and field-patch events to branch workers.
	EventRouter *EventRouter
	// AuthorResolver optionally names the commit author for a live watch event by
	// joining the audit attribution index (RV/UID match, bounded grace window). Nil
	// is configured-author mode (no audit/Redis): every event commits as the committer.
	AuthorResolver AuthorResolver
	// FactStreams is the process-wide subscription set the attribution fact follower reads. Each
	// running target watch holds one reference on the (audit route, group/resource) it covers, so
	// the process follows a type while at least one watch needs it and stops following it when the
	// last one goes away — which is the whole point of the per-type fan-out: facts for a type nobody
	// watches are appended and never received. Nil is configured-author mode: no follower runs, so
	// no subscription is taken.
	FactStreams *queue.FactStreamSet
	// WatchCursorStore optionally persists per-watch resourceVersion cursors so
	// reconnects can resume without replaying the full type snapshot.
	WatchCursorStore CursorStore
	// SensitiveResources is the startup-configured policy classifying which types must
	// use the encrypted Git write path. It is applied when the followability registry
	// builds its observations, so each TypeRecord carries the right Sensitive fact. The
	// zero value still treats core Secrets as sensitive.
	SensitiveResources types.SensitiveResourcePolicy

	// SourceClusters resolves a GitTarget's source cluster — a ClusterProvider NAME — into a
	// rest.Config, reading the kubeconfig Secret the provider names from the config plane. It is
	// required for any GitTarget to mirror, single-cluster installs included: a source cluster is
	// always a ClusterProvider, and only this resolver can say whether that provider is in-cluster
	// (kubeConfig omitted) or remote. Nil leaves every source cluster unresolvable; only the config
	// plane, which needs no provider, still works.
	SourceClusters SourceClusterResolver
	// contains filtered or unexported fields
}

Manager is a controller-runtime Runnable that keeps the followability registry and the per-GitTarget watched-type tables fresh, and owns every target watch. It holds no object informers and no object cache: each stream is a raw watch, opened per claimed (GVR, scope) against the GitTarget's own source cluster. The only API touch on a schedule is the discovery refresh that keeps the catalogs current. See docs/architecture.md.

func (*Manager) ClusterTypeLookup added in v0.38.0

func (m *Manager) ClusterTypeLookup(clusterID string) typeset.Lookup

ClusterTypeLookup returns the GVK->GVR resolver the git writer scans a folder's manifests with, scoped to ONE source cluster — its own registry. A folder is owned by exactly one GitTarget (one materialization), so the writer resolves each document against that GitTarget's cluster, never a union: two clusters can validly serve one GVK under different GVRs/scopes, and a first-wins union would mis-file or delete manifests. In a single-cluster install every target resolves against the one local registry, unchanged. An unknown cluster id yields the (possibly unready) context registry, which fails closed via the acceptance gate.

func (*Manager) CollectionOverlapForClusterWatchRule added in v0.52.0

func (m *Manager) CollectionOverlapForClusterWatchRule(rule configv1alpha3.ClusterWatchRule) (bool, string)

CollectionOverlapForClusterWatchRule is CollectionOverlapForWatchRule for a ClusterWatchRule.

func (*Manager) CollectionOverlapForWatchRule added in v0.52.0

func (m *Manager) CollectionOverlapForWatchRule(rule configv1alpha3.WatchRule) (bool, string)

CollectionOverlapForWatchRule reports whether the rule is refused because one of its collections overlaps a different collection on the same GitTarget, and why. It reads the resolution the running plan was built from, refreshing it first.

func (*Manager) ComputeRequestedGVRs

func (m *Manager) ComputeRequestedGVRs() []GVR

ComputeRequestedGVRs aggregates the watched GVRs from the active RuleStore: the union of every GitTarget's watched types, read from the resident tables.

func (*Manager) DeclareForGitTarget

func (m *Manager) DeclareForGitTarget(
	gitDest types.ResourceReference,
	clusterID string,
	auditRoute string,
	pruneMode v1alpha3.PruneMode,
	forceRecheck ...bool,
)

DeclareForGitTarget submits the GitTarget's declaration to the watch-plane owner and returns. It does no work on the caller's goroutine.

clusterID is (api/v1alpha3).GitTarget.SourceCluster() — the referenced ClusterProvider's name, "default" for the cluster the operator runs in. It is captured with the UID and the audit route, because none of the three can be derived from the rule-derived watch tables the owner plans from.

pruneMode is (api/v1alpha3).GitTarget.EffectivePruneMode(). Unlike the other two it is mutable, and widening it to a sweeping mode forces a fresh replay — see prune_declaration.go for why the edge, and only that edge, has to be the trigger.

It returns no error, and that is the contract: the pass runs after the target has been quiet for the settle window, so there is no result to hand back here. The controller reads DeclareStatusForGitTarget for whether the declaration has landed and how the last pass ended. A GitTarget applied together with its WatchRules is ONE piece of configuration, and waiting for the silence window is what makes the first plan it declares the one the user wrote rather than an empty one superseded four times as each rule arrives.

func (*Manager) DeclareStatusForGitTarget added in v0.42.0

func (m *Manager) DeclareStatusForGitTarget(gitDest types.ResourceReference) DeclareStatus

DeclareStatusForGitTarget reports whether the owner still owes this GitTarget a pass and how the last one ended. A target whose passes keep timing out must not read as idle.

func (*Manager) FollowableTypeRecords

func (m *Manager) FollowableTypeRecords() []typeset.TypeRecord

FollowableTypeRecords returns the LOCAL cluster's currently-followable type records (verdict followable or retained), sorted by identity. It is the inventory the status and visibility surfaces read; it never recomputes followability.

func (*Manager) ForcedRecheckTargetsForTest added in v0.50.0

func (m *Manager) ForcedRecheckTargetsForTest() []string

ForcedRecheckTargetsForTest lists the declared GitTargets whose next pass will re-anchor their streams. It exists so a test in another package can assert which targets a recovery reached; nothing in production calls it.

func (*Manager) ForgetGitTargetDeclaration

func (m *Manager) ForgetGitTargetDeclaration(gitDest types.ResourceReference)

ForgetGitTargetDeclaration drops in-memory watch state for a deleted GitTarget, and tears down its source cluster's context when it was the last GitTarget mirroring from it.

The pending trigger is dropped synchronously, in the same step that queues the teardown: a trigger that outlived the delete would have the next pass re-create what the delete tore down. The teardown itself carries the UID it was issued for, so a GitTarget deleted and recreated under the same namespace and name cannot have its successor's watches torn down by its predecessor's delete.

func (*Manager) GitPathAcceptanceForGitTarget

func (m *Manager) GitPathAcceptanceForGitTarget(gitDest types.ResourceReference) GitPathAcceptanceStatus

GitPathAcceptanceForGitTarget returns the latest acceptance status for the GitTarget. Missing state means no refusal has been observed, so the path is accepted.

func (*Manager) GitPathEvents

func (m *Manager) GitPathEvents() <-chan event.GenericEvent

GitPathEvents returns the channel the GitTarget controller wires via source.Channel so a GitPath acceptance transition enqueues the owning GitTarget. It is lazily created so a zero-value Manager (tests) and the cmd-wired Manager share one channel.

func (*Manager) LayoutForGitTarget added in v0.43.0

func (m *Manager) LayoutForGitTarget(gitDest types.ResourceReference) (git.LayoutReport, bool)

LayoutForGitTarget returns the most recently resolved layout for a GitTarget, and whether one has been resolved at all. Absent means no scan has reported yet — which is different from a folder that resolved to nothing, and the controller has to distinguish the two: the first leaves LayoutResolved Unknown, the second sets it with reason None.

func (*Manager) MarkTargetGitPathAccepted

func (m *Manager) MarkTargetGitPathAccepted(gitDest types.ResourceReference)

MarkTargetGitPathAccepted clears any prior refusal for the GitTarget path, write and scan alike. Scoped resyncs should call MarkTargetGitPathScopeAccepted so they only clear the refusal they proved.

func (*Manager) MarkTargetGitPathRefused

func (m *Manager) MarkTargetGitPathRefused(gitDest types.ResourceReference, reason, message string)

MarkTargetGitPathRefused records that the GitTarget path failed the structure-only acceptance gate. The refusal is target-wide, not stream-specific.

It is a report from a branch-worker drain goroutine. Only a real TRANSITION republishes the snapshot and enqueues a reconcile, so the happy-path resync stream does not enqueue one per event.

func (*Manager) MarkTargetGitPathScanAccepted added in v0.50.0

func (m *Manager) MarkTargetGitPathScanAccepted(gitDest types.ResourceReference)

MarkTargetGitPathScanAccepted records that a read of the folder found nothing wrong.

It clears ONLY the scan verdict. A structural read sees the files; it cannot see that a write may not touch one of them, or that a new document has no single root to go into, so clearing a write refusal here would report a target as writable that is not.

func (*Manager) MarkTargetGitPathScanRefused added in v0.50.0

func (m *Manager) MarkTargetGitPathScanRefused(gitDest types.ResourceReference, reason, message string)

MarkTargetGitPathScanRefused records that a READ of the folder failed the structure-only gate.

It touches only the scan verdict. A write refusal standing beside it is untouched evidence: it was raised by something that had work in hand, and nothing a read can do proves it resolved.

func (*Manager) MarkTargetGitPathScopeAccepted added in v0.45.1

func (m *Manager) MarkTargetGitPathScopeAccepted(gitDest types.ResourceReference, collection types.CollectionKey)

MarkTargetGitPathScopeAccepted clears a write refusal only when the successful resync covered the same collection that produced it. A successful replay of secrets must not hide a still impossible configmaps path, because the GitTarget status is the operator's only durable clue.

It clears the SCAN verdict unconditionally, and that asymmetry is deliberate: the resync ran the same structure-only gate the scan runs, over the same folder, and passed. That is strictly stronger evidence than a read, so a scan refusal left standing beside it would be stale.

func (*Manager) MarkTargetGitPathScopeRefused added in v0.45.1

func (m *Manager) MarkTargetGitPathScopeRefused(
	gitDest types.ResourceReference,
	collection types.CollectionKey,
	reason string,
	message string,
)

MarkTargetGitPathScopeRefused records that a scoped writer pass failed the structure-only acceptance gate. The status remains target-level, but the remembered scope prevents an unrelated successful resync from clearing it.

func (*Manager) MarkTargetRenderFidelityDiverged added in v0.37.0

func (m *Manager) MarkTargetRenderFidelityDiverged(
	target types.ResourceReference,
	divergence manifestanalyzer.RenderDivergence,
)

MarkTargetRenderFidelityDiverged closes normal writes immediately when a live window hits the same boundary outside a scoped replay. Restarting every scope is the only recovery route.

func (*Manager) MarkTargetRenderFidelityScopeClean added in v0.37.0

func (m *Manager) MarkTargetRenderFidelityScopeClean(
	target types.ResourceReference,
	revision uint64,
	collection types.CollectionKey,
)

MarkTargetRenderFidelityScopeClean records one complete clean replay result from the collection's current stream. A stale cancellation tail carries the retired stream's revision, so the gate ignores it and it cannot reopen a failed target.

func (*Manager) MarkTargetRenderFidelityScopeDiverged added in v0.37.0

func (m *Manager) MarkTargetRenderFidelityScopeDiverged(
	target types.ResourceReference,
	revision uint64,
	collection types.CollectionKey,
	divergence manifestanalyzer.RenderDivergence,
)

MarkTargetRenderFidelityScopeDiverged records a replay refusal caused by a rendered token.

func (*Manager) MarkTargetRetention added in v0.39.0

func (m *Manager) MarkTargetRetention(
	gitDest types.ResourceReference,
	collection types.CollectionKey,
	revision uint64,
	mode v1alpha3.PruneMode,
	retained int,
)

MarkTargetRetention records what one scope's resync retained.

Scope lifecycle is handled by the watch plan rather than by eviction here: retainTargetRetentionScopes installs the selected collections and the revision each one's stream reports under, so a collection that leaves the plan takes its count with it, and a stale in-flight reply from a cancelled watch — which carries the retired stream's revision — cannot resurrect or overwrite a count. A report for a collection the plan does not hold is dropped for the same reason.

Zero is recorded as actively as any other number: it is the converged signal.

func (*Manager) NeedLeaderElection

func (m *Manager) NeedLeaderElection() bool

NeedLeaderElection ensures only the elected leader runs the watch manager.

func (*Manager) RenderFidelityForGitTarget added in v0.37.0

func (m *Manager) RenderFidelityForGitTarget(target types.ResourceReference) RenderFidelityStatus

RenderFidelityForGitTarget returns the latest condition projection. Missing state means the target has not installed watches yet and remains writable for compatibility.

func (*Manager) ReportGitPathRefusal

func (m *Manager) ReportGitPathRefusal(
	gitDest types.ResourceReference,
	collection types.CollectionKey,
	refused *manifestanalyzer.AcceptanceRefusedError,
)

ReportGitPathRefusal records a write plan the branch worker refused on a live-event path, where no result channel carries the error back to the router. It is installed on the WorkerManager (git.GitPathRefusalReporter) at startup, and applies the same reason mapping the resync path uses, so a refusal reaches the user as GitPathAccepted=False / Stalled=True whether it was a live write or a background resync that hit it.

func (*Manager) ReportGitPathScan added in v0.50.0

func (m *Manager) ReportGitPathScan(
	gitDest types.ResourceReference,
	refused *manifestanalyzer.AcceptanceRefusedError,
)

ReportGitPathScan publishes what a read-only scan of the folder found: a structural refusal nobody tried to write, or its recovery when refused is nil.

It is the refresher's channel, and it is separate from ReportGitPathRefusal because a scan has no collection: it reads the whole folder, so its verdict is target-wide and cannot take part in the scoped recovery that write refusals depend on.

Structure-only acceptance cannot produce a render-fidelity issue — that one needs the live objects, which a scan does not have — so unlike ReportGitPathRefusal there is no fidelity case to route away here.

func (*Manager) ReportLayoutResolved added in v0.43.0

func (m *Manager) ReportLayoutResolved(gitDest types.ResourceReference, report git.LayoutReport)

ReportLayoutResolved records what a scan resolved about a GitTarget's folder. It is installed on the WorkerManager (git.LayoutReporter) at startup, and it is the only writer of the layouts projection.

Every scan calls it, including the ones that resolve exactly what the last one did — the branch worker has no memory across scans and should not grow one. The transition test lives here, so a steady-state target republishes nothing and enqueues no reconcile, and a folder whose shape actually changed does both.

func (*Manager) RequestRecheckForGitTarget added in v0.50.0

func (m *Manager) RequestRecheckForGitTarget(gitDest types.ResourceReference)

RequestRecheckForGitTarget forces this target's next pass to re-anchor its streams, which is what makes each one replay and drive the mark-and-sweep that rebuilds its folder.

It exists for the branch worker replacement. The declaration is level-triggered and its inputs do not change when the repository underneath it does, so without this the pass is a no-op: the streams keep running, nothing replays, and an idle target would leave the new repository empty indefinitely. The force flag is the same one a refused Git path raises, and it is sticky on the intent, so it survives until a pass actually consumes it.

A target that has not declared yet needs nothing: its first pass starts its streams, and a stream that starts replays.

func (*Manager) ResolveClusterWatchRuleResources

func (m *Manager) ResolveClusterWatchRuleResources(
	_ context.Context,
	rule configv1alpha3.ClusterWatchRule,
) (bool, string)

ResolveClusterWatchRuleResources reports one ClusterWatchRule's resource-resolution status for controller feedback. See resolveRuleResourceStatus. A ClusterWatchRule is cluster-scoped, so its gitTargetRef names both the GitTarget and its namespace; the status resolves against that GitTarget's source cluster.

func (*Manager) ResolveWatchRuleResources

func (m *Manager) ResolveWatchRuleResources(
	_ context.Context,
	rule configv1alpha3.WatchRule,
) (bool, string)

ResolveWatchRuleResources reports one WatchRule's resource-resolution status for controller feedback. See resolveRuleResourceStatus. A WatchRule's GitTarget lives in the WatchRule's own namespace, and the status resolves against THAT GitTarget's source cluster — so a remote-only CRD is reported as watched, and a local-only CRD selected by a remote target is not, instead of both being answered from the local registry.

func (*Manager) RetentionForGitTarget added in v0.39.0

func (m *Manager) RetentionForGitTarget(gitDest types.ResourceReference) RetentionSummary

RetentionForGitTarget returns the roll-up across the target's currently tracked scopes.

func (*Manager) SetupWithManager

func (m *Manager) SetupWithManager(mgr ctrl.Manager) error

SetupWithManager is a placeholder to enable kubebuilder RBAC marker scanning. The Manager is manually added to the controller-runtime manager in main.go as a Runnable, but this method allows kubebuilder's controller-gen to discover and process the RBAC markers.

func (*Manager) SourceClusterReachable added in v0.38.0

func (m *Manager) SourceClusterReachable(clusterID string) SourceClusterReachableStatus

SourceClusterReachable projects a source cluster's runtime reachability for a GitTarget's SourceClusterReachable condition. The local cluster is always reachable; a remote is Unknown until the data plane's first discovery attempt, then True or False with a classified reason.

func (*Manager) Start

func (m *Manager) Start(ctx context.Context) error

Start begins the watch ingestion manager and blocks until context cancellation. Everything the watch plane does happens on the loop it enters: controllers submit intent, this owns the state.

func (*Manager) StreamStateEvents added in v0.42.0

func (m *Manager) StreamStateEvents() <-chan event.GenericEvent

StreamStateEvents returns a channel carrying a GenericEvent for the GitTarget whose per-collection stream readiness just changed, so a controller projecting that readiness re-reconciles on the transition instead of on its next periodic requeue.

Unlike GitPathEvents this is a FAN-OUT: every call registers a new subscriber and returns its own channel, because a Go channel has one consumer and three controllers project this state (the GitTarget and both rule kinds). Sending is best-effort into each subscriber's buffer.

Why it exists: a stream reaching Streaming is the last thing that has to happen before a rule or a target can honestly report StreamsRunning=True, and it was the one report on the watch plane that notified nobody. The data plane would converge in about two seconds and the status would follow up to ten seconds later, on RequeueStreamSettleInterval, because nothing said so.

func (*Manager) StreamSummaryForClusterWatchRule

func (m *Manager) StreamSummaryForClusterWatchRule(rule configv1alpha3.ClusterWatchRule) StreamSummary

StreamSummaryForClusterWatchRule reports stream readiness for one ClusterWatchRule, resolved against the source cluster its GitTarget mirrors from. It always matches cluster-scoped records, because a ClusterWatchRule is cluster-scope-only.

func (*Manager) StreamSummaryForGitTarget

func (m *Manager) StreamSummaryForGitTarget(gitDest types.ResourceReference) StreamSummary

StreamSummaryForGitTarget reports the GitTarget stream-readiness roll-up.

func (*Manager) StreamSummaryForWatchRule

func (m *Manager) StreamSummaryForWatchRule(rule configv1alpha3.WatchRule) StreamSummary

StreamSummaryForWatchRule reports stream readiness for one namespaced WatchRule, resolved against the source cluster its GitTarget mirrors from.

It reads the COMPILED rule, not the spec. A rule's watched namespaces can no longer be derived from its spec at all: a `sourceNamespace: "*"` item's set exists only after resolution against the GitTarget's policy and the source-cluster snapshot. Rebuilding the keys from the spec would look for streams under keys that were never opened, so a perfectly healthy wildcard rule would report permanently not-ready while its streams run — the same class of bug the singular field already hit once, one level up.

A rule that is not compiled expects no streams, which is correct: the gate refused it, or the store has not been seeded yet.

func (*Manager) TriggerAllRuleChange added in v0.42.0

func (m *Manager) TriggerAllRuleChange()

TriggerAllRuleChange marks every declared GitTarget dirty. It is the deletion path only: a WatchRule that is already gone cannot be read for the target it named, so the owner cannot narrow the invalidation and does not pretend to.

func (*Manager) TriggerRuleChange added in v0.42.0

func (m *Manager) TriggerRuleChange(gitDest types.ResourceReference)

TriggerRuleChange marks the GitTarget a rule names as needing a new plan pass.

It posts intent and returns, where the inline call sites it replaced did a discovery call, a namespace list, a re-projection and a replan of EVERY running GitTarget synchronously.

It deliberately does NOT also enqueue the GitTarget: the pass itself enqueues on a render-fidelity change, the one moment its answer differs. Enqueueing here would fire before anything it would report has changed.

func (*Manager) TypeRecords

func (m *Manager) TypeRecords() []typeset.TypeRecord

TypeRecords returns every known type record — followable, retained, and refused — for the LOCAL cluster, for inventory and "why is this type not picked up?" views.

func (*Manager) TypeRegistry

func (m *Manager) TypeRegistry() *typeset.Registry

TypeRegistry returns the LOCAL cluster's followability registry. Retained for the git writer's manager-wide mapper wiring (cmd/main.go); the cluster-scoped writer lookup uses ClusterTypeLookup instead (see gvr.go / Step 4).

type ObjectSelectorError added in v0.50.0

type ObjectSelectorError struct {
	// Message names the offending item and why its selector is invalid.
	Message string
}

ObjectSelectorError is the compile refusal for an invalid spec.rules[].objectSelector. It is an error rather than a decision because the compile functions already return one for a transient failure, and the callers must tell the two apart: this one is terminal, and the compiled rule is already out of the store when it is returned.

func (*ObjectSelectorError) Error added in v0.50.0

func (e *ObjectSelectorError) Error() string

type RenderFidelityStatus added in v0.37.0

type RenderFidelityStatus = git.RenderFidelityStatus

RenderFidelityStatus is the GitTarget render-vs-live condition state shared with the writer.

type RetentionSummary added in v0.39.0

type RetentionSummary struct {
	// Reported distinguishes "no resync has reported yet" from "a resync reported zero". Both are
	// legitimate states and they mean opposite things: the first is unknown, the second is the
	// converged signal, which is half the value of publishing this at all.
	Reported bool
	// Mode is the effective spec.prune.mode the most recent contributing resync ran under. It
	// travels WITH the count rather than being read from the spec at projection time, so the two
	// always describe the same observation — a target switched to `always` does not briefly
	// publish `always` beside a count that a retaining policy produced.
	Mode v1alpha3.PruneMode
	// RetainedDocuments is the sum over the target's currently tracked scopes.
	RetainedDocuments int
	// LastChangedAt is when this roll-up last changed. It advances only in the mutation that
	// moves the published count or mode, never on a re-report of the same numbers, so it dates the
	// RESULT rather than the last scan. Non-zero whenever Reported is true.
	LastChangedAt time.Time
}

RetentionSummary is the per-GitTarget roll-up the controller projects onto status.

type SourceClusterReachableStatus added in v0.38.0

type SourceClusterReachableStatus struct {
	State   string
	Reason  string
	Message string
}

SourceClusterReachableStatus is the kstatus-shaped projection of a source cluster's reachability, for the GitTarget controller to set as the SourceClusterReachable condition. State is "True" | "False" | "Unknown"; the controller maps it to metav1.ConditionStatus.

type SourceClusterResolver added in v0.38.0

type SourceClusterResolver interface {
	// ResolveSourceCluster returns the rest.Config for a ClusterProvider name, and an opaque
	// version token that changes when the resolved config changes (the provider generation and
	// the kubeconfig Secret's resourceVersion). An unknown or unreadable name is an error:
	// mirroring the wrong cluster into a folder is worse than mirroring none.
	//
	// A NIL config with a nil error means the provider omits spec.kubeConfig and therefore names
	// the operator's OWN cluster — the in-cluster answer, available to every provider name. This
	// is the ONLY authority on whether a source cluster is in-cluster; nothing keys that off the
	// provider's name.
	ResolveSourceCluster(ctx context.Context, providerName string) (cfg *rest.Config, version string, err error)
}

SourceClusterResolver turns a source-cluster NAME (a ClusterProvider's name) into a rest.Config by looking up the ClusterProvider and reading the kubeconfig Secret it names from the operator namespace. It is an interface so the watch manager grows no Kubernetes client of its own for this, and so tests can stand up a remote cluster without a Secret. The concrete implementation lives in source_cluster_resolver.go.

func NewSecretSourceClusterResolver added in v0.38.0

func NewSecretSourceClusterResolver(
	c client.Client,
	operatorNamespace string,
	safety kubeconfig.SafetyPolicy,
) SourceClusterResolver

NewSecretSourceClusterResolver builds the production source-cluster resolver.

type StreamState

type StreamState string

StreamState names the per-type watch readiness state.

const (
	// StreamStateReplaying means the initial-events replay is still being folded.
	StreamStateReplaying StreamState = "Replaying"
	// StreamStateStreaming means the watch is routing live, attributable events.
	StreamStateStreaming StreamState = "Streaming"
	// StreamStateBlocked means the watch cannot currently run.
	StreamStateBlocked StreamState = "Blocked"
)

type StreamSummary

type StreamSummary struct {
	Total         int
	Ready         int
	Replaying     int
	Blocked       int
	Reason        string
	Message       string
	PendingSample []string
}

StreamSummary is a bounded status roll-up for a target or rule.

func (StreamSummary) StreamsRunning

func (s StreamSummary) StreamsRunning() bool

StreamsRunning reports whether all resolved streams are Streaming.

func (StreamSummary) Summary

func (s StreamSummary) Summary() string

Summary returns the display ratio stored in status.streams.summary.

type WatchedType

type WatchedType struct {
	GVK           schema.GroupVersionKind
	GVR           schema.GroupVersionResource
	Namespaced    bool
	Scope         configv1alpha3.ResourceScope
	ServedVersion string
	Preferred     bool

	// NamespaceScopes maps each namespace this type is watched in to the canonical object
	// selector that scope is selected with ("" selects every object). The empty-string key is a
	// cluster-wide collection: a cluster-scoped resource, or a namespaced resource a WatchRule
	// follows across every namespace. One selector per scope is enough because two collections on
	// one type and namespace overlap, and the resolver refuses a second selector over an
	// overlapping collection (see refuseCollectionOverlaps).
	NamespaceScopes map[string]string
}

WatchedType is one followable type a GitTarget watches: a (GVK, GVR, scope) triple plus the namespace scope and served-version metadata, projected straight from the type registry's followable set. The registry owns identity (GVK<->GVR is 1:1 there), followability, and the removal grace, so a WatchedType is a copy of a registry fact, never a re-decision.

func (WatchedType) ClusterWide

func (t WatchedType) ClusterWide() bool

ClusterWide reports whether this type is gathered under a cluster-wide scope: true for a cluster-scoped resource and for a namespaced resource a ClusterWatchRule follows across all namespaces. It reports the presence of that scope, NOT that it is the only one — a namespaced type may carry the cluster-wide scope alongside named ones, and each is streamed in its own right. Use WatchScopes to enumerate them.

func (WatchedType) WatchScopes added in v0.39.0

func (t WatchedType) WatchScopes() []string

WatchScopes returns the distinct namespace scopes this type is gathered under — one per stream — in a stable order. The empty string is the cluster-wide scope (a cluster-scoped resource, or a namespaced resource a ClusterWatchRule follows across every namespace) and sorts first.

A cluster-wide selection does NOT suppress co-resident named namespaces. A WatchRule scoped to one namespace and a ClusterWatchRule scoped cluster-wide, on the same GVR and the same GitTarget, stay two scopes here. This previously collapsed to a single cluster-wide scope, which silently widened the named rule's stream to every namespace the credential could read — a gate bypass once a WatchRule declares the source namespaces it is authorized for.

Every read site must project the same scope set, because a gather's scope becomes the mark-and-sweep's scope: a gather wider than the stream that triggered it deletes managed documents that were never in scope (see git.ResyncScope).

type WatchedTypeTable

type WatchedTypeTable struct {
	GitDest types.ResourceReference
	// Dest is the GitTarget's write destination fingerprint (provider/branch/path),
	// carried so the effective-plan hash can be derived from the table alone.
	Dest       string
	Types      []WatchedType
	ResolvedAt uint64
}

WatchedTypeTable is a GitTarget's resident, resolved-once set of watched types: the subset of the type registry's followable set its WatchRules and ClusterWatchRules select. It is re-resolved only on a deliberate trigger (a rule-set change or a catalog/registry generation bump) and read by the snapshot, informer, and plan-hash paths instead of each re-resolving inline.

Jump to

Keyboard shortcuts

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