Documentation
¶
Index ¶
- Constants
- func ValidateKeyPrefix(prefix string) (string, error)
- type AttributionIndex
- func (a *AttributionIndex) LookupAuthor(ctx context.Context, auditRoute string, gvr schema.GroupVersionResource, ...) (AuthorFact, bool)
- func (a *AttributionIndex) LookupAuthorResolution(ctx context.Context, auditRoute string, gvr schema.GroupVersionResource, ...) AuthorResolution
- func (a *AttributionIndex) RecordDeleteCollectionFacts(ctx context.Context, auditRoute string, event auditv1.Event) error
- func (a *AttributionIndex) RecordFact(ctx context.Context, auditRoute string, event auditv1.Event) error
- type AttributionResult
- type AuthorFact
- type AuthorResolution
- type CommandAuthor
- type CommandAuthorStore
- type RedisStore
- func (s *RedisStore) AttributionIndex(factTTL time.Duration) *AttributionIndex
- func (s *RedisStore) CommandAuthorStore() *CommandAuthorStore
- func (s *RedisStore) KeyPrefix() string
- func (s *RedisStore) LookupWatchCursor(ctx context.Context, gitTargetUID string, gvr schema.GroupVersionResource, ...) (string, bool)
- func (s *RedisStore) Ping(ctx context.Context) error
- func (s *RedisStore) RecordWatchCursor(ctx context.Context, gitTargetUID string, gvr schema.GroupVersionResource, ...) error
- type RedisStoreConfig
Constants ¶
const DefaultAttributionFactTTL = 10 * time.Minute
DefaultAttributionFactTTL is how long an attribution fact is retained in Redis waiting for the matching watch event to join it. Facts are never object state, so they expire on their own — nothing deletes them. After it elapses a miss is simply "absent": the v3 schema keeps no tombstone, so an aged-out fact is indistinguishable from one that never arrived. Configurable via --author-attribution-ttl.
const DefaultKeyPrefix = "gitops-reverser"
DefaultKeyPrefix is the root namespace every Redis key (cursors, facts, and command author records alike) carries when --redis-key-prefix is not set. It is also the value every release before the flag existed used, so the default is a no-op upgrade.
Variables ¶
This section is empty.
Functions ¶
func ValidateKeyPrefix ¶
ValidateKeyPrefix checks a --redis-key-prefix value and returns its normalized form.
Two independent constraints shape the allowed character set:
- The attribution telemetry gauge SCANs "<prefix>:author:v1:audit:*". Redis glob metacharacters (*, ?, [, ], \) in the prefix would silently make that pattern match the wrong keyspace, so they are rejected rather than escaped — a prefix is an operator-chosen identifier, not user data.
- Key fields (uid, resourceVersion, namespace) are ':'-delimited and %-escaped by escapeKeyField. '%' in the prefix would make an escaped key ambiguous with an unescaped one, so it is rejected too.
':' is allowed, because a prefix like "cell-a:tenant-7" is a natural nesting and every suffix constant already begins with ':'. A trailing ':' is normalized away so "tenant-7:" and "tenant-7" name the same keyspace rather than two that differ by an empty segment.
An empty prefix is rejected: an unprefixed keyspace collides with Redis's own key namespace conventions and, more importantly, silently un-namespaces an install that meant to set the flag and passed the empty string. Use DefaultKeyPrefix to opt out.
Types ¶
type AttributionIndex ¶
type AttributionIndex struct {
// contains filtered or unexported fields
}
AttributionIndex is the optional Redis-backed lookup table that names a commit author from audit facts. It is built from a RedisStore (sharing its connection) only when author attribution is enabled, and stores only attribution facts keyed for a join against watch events — never object state, and never the resume cursors (those belong to RedisStore, which is required regardless of this index).
func (*AttributionIndex) LookupAuthor ¶
func (a *AttributionIndex) LookupAuthor( ctx context.Context, auditRoute string, gvr schema.GroupVersionResource, uid types.UID, rv string, exactCapable bool, ) (AuthorFact, bool)
LookupAuthor finds the strongest attribution fact for a watch event. ok=false means no fact matched (yet) — the caller ships as committer. exactCapable selects the join policy: see LookupAuthorResolution.
func (*AttributionIndex) LookupAuthorResolution ¶
func (a *AttributionIndex) LookupAuthorResolution( ctx context.Context, auditRoute string, gvr schema.GroupVersionResource, uid types.UID, rv string, exactCapable bool, ) AuthorResolution
LookupAuthorResolution finds the strongest attribution fact and classifies the match. It is event-kind-aware:
- An exact-capable event (ADDED / MODIFIED) tries only the immutable exact key object:<uid>:<rv> and the rv-only escape hatch; it never falls through to the last-writer-wins :last pointer, because that pointer may name a different, older author than the create/update this event represents.
- A known RV-mismatch event (DELETED, deletecollection-expanded removal) additionally consults object:<uid>:last, whose RV deliberately never matches.
A miss returns AttributionAbsent; there is no tombstone and so no expired outcome.
func (*AttributionIndex) RecordDeleteCollectionFacts ¶
func (a *AttributionIndex) RecordDeleteCollectionFacts( ctx context.Context, auditRoute string, event auditv1.Event, ) error
RecordDeleteCollectionFacts expands a deletecollection response body into one uid-latest (:last) attribution fact per listed object, joined by UID against the per-object removal watch event. It is a no-op for any other verb, or when the body is absent, hollow, or unparseable — an aggregated / metadata-only deletecollection then degrades to a committer-authored removal.
It writes ONLY the :last key: the body item's resourceVersion is the pre-delete RV, which no watch removal event ever presents, so the exact and rv-only keys would be dead. Finalizer-pending items are NOT skipped — under the deletion-as-intent rule a deletionTimestamp already removes the file, so the actor who ran the collection delete is credited with that removal even while Kubernetes finalization is still in flight. See docs/spec/deletecollection-attribution-expander.md.
func (*AttributionIndex) RecordFact ¶
func (a *AttributionIndex) RecordFact(ctx context.Context, auditRoute string, event auditv1.Event) error
RecordFact stores the attribution fact for one accepted, mutating audit event. A UID-bearing fact writes the immutable exact key (uid+rv) and overwrites the :last pointer; a fact that has an RV but no UID writes the type-scoped rv-only key instead. It is a no-op for events without an objectRef, a resolvable name, or a user — those can never name an author. The caller (the audit handler) has already rejected reads, failures, dry-runs, and non-ResponseComplete stages.
type AttributionResult ¶
type AttributionResult string
AttributionResult is the bounded resolver outcome recorded for each watch event.
const ( // AttributionExactUser is an exact UID+resourceVersion match for a human user. AttributionExactUser AttributionResult = "exact_user" // AttributionExactServiceAccount is an exact UID+resourceVersion match for a named service account. AttributionExactServiceAccount AttributionResult = "exact_serviceaccount" // AttributionWeak is a non-exact match: the uid-latest :last pointer or the rv-only // escape hatch, used by known RV-mismatch events and no-UID facts respectively. AttributionWeak AttributionResult = "weak" // AttributionExactDeleteCollectionItem is a match to a fact expanded from a // deletecollection response body — a precise per-object credit for one member of a // collection delete, joined by UID via :last (the body item's RV is the pre-delete // RV and never matches the removal event's RV). The reason is driven by the value's // verb, not by which key matched. AttributionExactDeleteCollectionItem AttributionResult = "exact_deletecollection_item" // AttributionAbsent means no usable author fact matched before the grace elapsed. AttributionAbsent AttributionResult = "absent" )
type AuthorFact ¶
type AuthorFact struct {
GroupResource string `json:"groupResource,omitempty"`
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
UID string `json:"uid,omitempty"`
Author string `json:"author"`
DisplayName string `json:"displayName,omitempty"`
Email string `json:"email,omitempty"`
Verb string `json:"verb,omitempty"`
Subresource string `json:"subresource,omitempty"`
AuditID string `json:"auditID,omitempty"`
ResourceVersion string `json:"resourceVersion,omitempty"`
StageTimestamp string `json:"stageTimestamp,omitempty"`
IsServiceAccount bool `json:"isServiceAccount,omitempty"`
}
AuthorFact is the minimal attribution fact stored per accepted, mutating audit event and read back by the watch-event resolver. It names an author candidate and carries the evidence needed to decide confidence; it is never object state. v3 moves the object identity (group-resource, namespace, name, uid) off the key and into the value, so the fact is self-describing.
type AuthorResolution ¶
type AuthorResolution struct {
Fact AuthorFact
Result AttributionResult
}
AuthorResolution is the structured result of an attribution lookup.
type CommandAuthor ¶
type CommandAuthor struct {
Author string `json:"author"`
DisplayName string `json:"displayName,omitempty"`
Email string `json:"email,omitempty"`
RequestedAt string `json:"requestedAt,omitempty"` // RFC3339Nano, for lag metrics/debug
}
CommandAuthor is the minimal authorship captured at admission for one command object. It carries only what a git commit author needs — no RV, no auditID, no conflict bit: this is a 1:1 command capture, not a post-persist join.
type CommandAuthorStore ¶
type CommandAuthorStore struct {
// contains filtered or unexported fields
}
CommandAuthorStore records and reads command authorship. It shares RedisStore's connection but is wired whenever the validate-operator-types webhook is enabled — independent of --author-attribution, which only governs mirrored-resource attribution.
func (*CommandAuthorStore) LookupCommandAuthor ¶
func (s *CommandAuthorStore) LookupCommandAuthor( ctx context.Context, uid types.UID, ) (CommandAuthor, bool)
LookupCommandAuthor is the controller-side read, keyed by the persisted object's UID. ok=false means no record was captured — the validate-operator-types webhook is not configured (or a best-effort write missed) — and the controller finalizes as the committer, immediately.
func (*CommandAuthorStore) RecordCommandAuthor ¶
func (s *CommandAuthorStore) RecordCommandAuthor( ctx context.Context, uid types.UID, author CommandAuthor, ) error
RecordCommandAuthor is the admission-side write: capture the authenticated submitter the instant a command CREATE is admitted, before it persists. Last-write-wins (a CREATE fires admission once; a retried admission re-asserts the same user).
type RedisStore ¶
type RedisStore struct {
// contains filtered or unexported fields
}
RedisStore is the required Redis/Valkey-backed store. It owns the connection and persists each GitTarget watch shard's resume cursor (state continuity / work re-pickup), and the readiness gate pings it. It is a hard dependency in every mode and knows nothing about attribution: author attribution is an optional layer built on the same connection via AttributionIndex.
func NewRedisStore ¶
func NewRedisStore(cfg RedisStoreConfig) (*RedisStore, error)
NewRedisStore opens the Redis/Valkey connection that backs the resume cursors.
func (*RedisStore) AttributionIndex ¶
func (s *RedisStore) AttributionIndex(factTTL time.Duration) *AttributionIndex
AttributionIndex builds the optional author-attribution fact index on this store's connection. Call it only when author attribution is enabled — the store itself, and the resume cursors it holds, never depend on it.
func (*RedisStore) CommandAuthorStore ¶
func (s *RedisStore) CommandAuthorStore() *CommandAuthorStore
CommandAuthorStore builds the command-authorship store on this connection. Wire it when the validate-operator-types webhook is enabled; it does not depend on attribution. The record lives in the same top-level author domain as audit facts but in the separate command subfamily, with its own fixed cleanup TTL.
func (*RedisStore) KeyPrefix ¶
func (s *RedisStore) KeyPrefix() string
KeyPrefix returns the root namespace every key this store's family owns is written under. Reported at startup so an operator can confirm which keyspace a pod claims.
func (*RedisStore) LookupWatchCursor ¶
func (s *RedisStore) LookupWatchCursor( ctx context.Context, gitTargetUID string, gvr schema.GroupVersionResource, namespace string, ) (string, bool)
LookupWatchCursor returns the last resourceVersion durably processed for one GitTarget watch shard. A miss means the watch must rebuild from a fresh replay.
func (*RedisStore) Ping ¶
func (s *RedisStore) Ping(ctx context.Context) error
Ping checks liveness of the underlying Redis/Valkey connection. The readiness gate uses it so the pod does not report ready before its resume-cursor store is reachable.
func (*RedisStore) RecordWatchCursor ¶
func (s *RedisStore) RecordWatchCursor( ctx context.Context, gitTargetUID string, gvr schema.GroupVersionResource, namespace, rv string, ) error
RecordWatchCursor stores the last resourceVersion durably processed for one GitTarget watch shard, refreshing watchCursorTTL on each write. The cursor is keyed by GitTarget UID and bounded by the TTL, so it never needs explicit deletion: a live watch keeps it fresh, and a dead one's cursor simply expires.
type RedisStoreConfig ¶
type RedisStoreConfig struct {
Addr string
Username string
AuthValue string
DB int
TLSEnabled bool
// KeyPrefix is the root namespace for every key this connection's stores own. Empty
// means DefaultKeyPrefix. Several reversers can then share one Redis/Valkey without
// sharing a logical database — Redis offers only 16 of those, and --redis-db was the
// only separator this operator provided.
KeyPrefix string
}
RedisStoreConfig configures the Redis/Valkey connection that backs the required watch-resume cursor store.