git

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: 67 Imported by: 0

Documentation

Overview

Package git provides Git repository operations and abstractions for the GitOps Reverser controller.

Index

Constants

View Source
const (
	// DefaultCommitWindow is the default idle timeout that closes a commit window, used when
	// GitTarget.spec.commit.window.idleTimeout is unset or the target cannot be read.
	DefaultCommitWindow = 5 * time.Second

	// DefaultCommitWindowMaxDuration is the default maximum duration of a commit window, used when
	// GitTarget.spec.commit.window.maxDuration is unset or the target cannot be read. It keeps
	// continuous activity from postponing a commit until the memory limit forces one.
	DefaultCommitWindowMaxDuration = time.Minute

	// PushCooldown is the minimum interval between successful pushes. The cooldown
	// is intentionally fixed: commit cadence is a user concern (spec.commit.window on
	// the CRD); push cadence is an implementation/politeness concern.
	PushCooldown = 5 * time.Second
)
View Source
const (
	// SigningKeyDataKey is the Secret data key for the PEM-encoded SSH private signing key.
	SigningKeyDataKey = "signing.key"
	// SigningPublicKeyDataKey is the Secret data key for the authorized_keys-format public key.
	SigningPublicKeyDataKey = "signing.pub"
	// SigningPassphraseDataKey is the Secret data key for an optional key passphrase.
	SigningPassphraseDataKey = "passphrase"
)
View Source
const (
	// UnresolvedAuthorUsername is the stable machine token for an unresolved attribution.
	UnresolvedAuthorUsername = "attribution-unresolved"
	// UnresolvedAuthorDisplayName is the human-facing git author name.
	UnresolvedAuthorDisplayName = "unknown (attribution unresolved)"
	// UnresolvedAuthorEmail is a reserved-invalid address (RFC 2606).
	UnresolvedAuthorEmail = "attribution-unresolved@gitops-reverser.invalid"
)
View Source
const (
	// DefaultCommitterName matches the default operator identity in Git history.
	DefaultCommitterName = "GitOps Reverser"
	// DefaultCommitterEmail matches the default operator email in Git history.
	DefaultCommitterEmail = "noreply@configbutler.ai"
	// DefaultReconcileCommitMessageTemplate names the synced type AND, when the run covered one,
	// the namespace, so the otherwise indistinguishable per-collection reconciles one GitTarget
	// produces are self-describing: a target watching configmaps in team-a and in team-b would
	// otherwise write two byte-identical subjects. Plural resource alone for readability; add
	// {{.APIVersion}} when plural collisions matter.
	//
	// The {{if}} guards fall back to "chore: reconcile N resources" for a whole-target reconcile,
	// so the subject never degrades to an identity-less "chore: reconcile N ". The namespace guard
	// in particular must stay an {{if}} rather than a sentinel: an empty Namespace here means the
	// run was not namespace-scoped, which covers BOTH an all-namespaces sweep of a namespaced type
	// and a cluster-scoped type having no namespaces at all. No single word is true of both, so
	// the honest rendering of "no namespace to name" is to say nothing. (Contrast ResourceRef,
	// which describes ONE resource, where empty has exactly one meaning and carries the
	// types.ClusterScopeSegment sentinel.)
	DefaultReconcileCommitMessageTemplate = "chore: reconcile {{.Count}} " +
		"{{if .Resource}}{{.Resource}}{{else}}resources{{end}}" +
		"{{if .Namespace}} in {{.Namespace}}{{end}}" +
		"{{if .ResourceVersion}} (last resourceVersion: {{.ResourceVersion}}){{end}}"
	// DefaultLiveCommitMessageTemplate describes retained input resources.
	DefaultLiveCommitMessageTemplate = "chore: sync {{.Count}} resource{{if ne .Count 1}}s{{end}}\n\n" +
		"{{range .Resources -}}" +
		"- [{{.Operation}}] {{.APIVersion}}/{{.Resource}}/{{.Namespace}}/{{.Name}}\n" +
		"{{end -}}"
)
View Source
const DefaultBranchBufferMaxBytes int64 = 8 * 1024 * 1024

DefaultBranchBufferMaxBytes is the default cap on a worker's combined event buffer + unpushed-events memory. Operators override this via --branch-buffer-max-size (8Mi by default).

View Source
const DefaultBranchWorkerQueueDepth = 1000

DefaultBranchWorkerQueueDepth is the default depth of each branch worker's event queue. Operators override it via --branch-worker-queue-depth.

It is sized so that a bounded administrative burst cannot overrun it, because a burst that stays under the queue depth cannot drop at ALL, whatever its arrival shape. The sizing input is not an event rate: a slot holds one WriteRequest, so what must fit is the number of concurrent write requests a burst can produce, roughly (concurrent writers) x (GitTargets sharing this branch worker). Note the second factor: workers are keyed by (provider namespace, provider name, branch), so pointing another GitTarget at a branch divides the per-target headroom of a worker already in use.

1000 replaces an earlier 100, which a realistic workload cleared and a scripted one did not: a 200-participant demo across two GitTargets peaked at depth 44, and then deleting those 203 objects unpaced -- hundreds of requests per second rather than the ballots' ~3/s -- dropped 20 writes. Convergence still healed the mirror, but a dropped write is a LIVE, ATTRIBUTED commit that never happened, so the history is missing something the end state cannot show.

View Source
const (
	// EncryptionProviderSOPS is the only supported provider in this increment.
	EncryptionProviderSOPS = "sops"
)
View Source
const WorkerSweepInterval = time.Minute

WorkerSweepInterval is the floor under the orphan sweep. It is a FLOOR and not the mechanism: a deleted GitTarget's reconcile sweeps immediately, and this catches what that path cannot see.

Variables

View Source
var (
	ErrRemoteRefNotFound          = errors.New("remote ref not found")
	ErrRemoteRefNotFoundEmptyRepo = errors.New("remote ref not found (empty repo)")
)
View Source
var ErrBranchWorkerStopped = errors.New("the GitTarget's branch worker stopped before it finished the CommitRequest")

ErrBranchWorkerStopped is the outcome of a request whose branch worker stopped before it could finish it. Once the loop has exited nothing can publish the request, so this answer is final.

View Source
var ErrCommitRequestWithdrawn = errors.New("the CommitRequest was withdrawn before the worker acted on it")

ErrCommitRequestWithdrawn is the outcome of a request the controller withdrew before the worker acted on it. The worker records it, so a late re-send of the attach can never register the request again, and the controller's failure and the worker's answer are the same answer.

View Source
var ErrDefaultBranchUnresolved = errors.New("the remote's default branch does not resolve to a branch")

ErrDefaultBranchUnresolved reports a repository that is not empty but whose default branch does not resolve to a branch it carries, while no parent is configured and the target branch is absent. Starting the target branch there would make an orphan, so it is refused exactly like a missing configured parent. DefaultBranchUnresolvedError names what HEAD points at.

View Source
var ErrFinalizeQueueFull = errors.New("branch worker event queue full; item dropped")

ErrFinalizeQueueFull is reported when a work item cannot be enqueued because the worker's event queue is saturated.

View Source
var ErrParentBranchNotFound = errors.New("parent branch not found on the remote")

ErrParentBranchNotFound reports that a configured parent branch is absent on the remote while the target branch is absent too, so there is nothing to create the target branch from. It is never reported for an omitted parent: that falls back to the remote's default branch, and to an unborn branch in an empty repository.

View Source
var ErrResyncSuperseded = errors.New("resync superseded by a newer request for the same scope")

ErrResyncSuperseded replies to a resync that a newer request for the same GitTarget and scope replaced while it was still queued. It is NOT a failure: the newer request carries at least as fresh a desired set and runs in the superseded one's place, so a caller must not count it as a resync that did not happen.

Functions

func AuthFromSecretData

func AuthFromSecretData(
	ctx context.Context,
	k8sClient client.Client,
	provider *v1alpha3.GitProvider,
	secret *corev1.Secret,
	hostKeys SSHHostKeyConfig,
) ([]gitclient.Option, error)

AuthFromSecretData resolves go-git transport options from an already-fetched Git credentials Secret. It is the thin wrapper over CredentialFromSecretData; callers that need to know what kind of credential was produced should use that instead.

func ConstructSafeEmail

func ConstructSafeEmail(username string, domain string) string

ConstructSafeEmail takes a raw username and a domain and creates a valid git-compliant email address.

func CredentialOptionsForProvider added in v0.45.1

func CredentialOptionsForProvider(
	provider *v1alpha3.GitProvider,
	cred Credential,
	policy CredentialTransportPolicy,
) ([]gitclient.Option, error)

CredentialOptionsForProvider applies transport policy to a resolved credential.

func GenerateSSHSigningKeyPair

func GenerateSSHSigningKeyPair(passphrase []byte) ([]byte, []byte, error)

GenerateSSHSigningKeyPair creates an ed25519 SSH signing keypair.

func GetCommitSigner

func GetCommitSigner(
	ctx context.Context,
	k8sClient client.Client,
	provider *v1alpha3.GitProvider,
) (gogit.Signer, error)

GetCommitSigner fetches commit signing material from the specified secret.

func GetCurrentBranch

func GetCurrentBranch(r *git.Repository) (plumbing.ReferenceName, plumbing.Hash, error)

GetCurrentBranch gets the branch that is active.

func IsValidTargetPath

func IsValidTargetPath(p string) bool

IsValidTargetPath reports whether p is a path the writer can safely materialize into: the repository root (empty or "."), or a clean relative path. Paths the writer rejects as unsafe — absolute (leading "/"), Windows separators, or ".." traversal — are invalid and can own nothing. It mirrors sanitizePath, the write-path guard, so the overlap/admission check and the writer agree on what a target legitimately owns.

func LoadSSHCommitSigner

func LoadSSHCommitSigner(secret *corev1.Secret) (gogit.Signer, error)

LoadSSHCommitSigner loads a git-compatible SSH signer from the provided Secret.

func PinExplicitSigningPolicy added in v0.41.0

func PinExplicitSigningPolicy(repo *git.Repository) error

PinExplicitSigningPolicy records in the repository's own config that commits are not signed unless this operator signs them.

go-git v6 consults commit.gpgSign — merged across system, global and local scope — whenever CommitOptions.Signer is nil, and refuses the commit outright when the setting is true and no signer is registered ("cannot auto-sign commit"). v5 ignored the setting entirely.

Our signing policy comes from the GitProvider's signing Secret and is passed as CommitOptions.Signer, so an ambient commit.gpgSign — a developer's ~/.gitconfig, a mounted config, a future base image — must not be able to decide it for us. Writing the local value false makes the intent explicit and takes precedence over the wider scopes. Where we do sign, Signer is non-nil and this setting is never consulted.

func SSHAuthorizedPublicKeyFromSecret

func SSHAuthorizedPublicKeyFromSecret(secret *corev1.Secret) (string, error)

SSHAuthorizedPublicKeyFromSecret derives the authorized_keys-form public key from a signing Secret.

func SmartFetch

func SmartFetch(
	ctx context.Context,
	repo *git.Repository,
	target plumbing.ReferenceName,
	auth []gitclient.Option,
) (plumbing.ReferenceName, error)

SmartFetch performs a network sync and returns the best available LOCAL branch reference. It prioritizes the target branch but always fetches the default branch as a safety net.

Return values (example with target="refs/heads/feature"): - "refs/heads/feature", nil: Target found on remote, fetched, ready to checkout. - "refs/heads/main", nil: Target missing on remote, fell back to default branch. - "", nil: The repository is empty (see advertisesNoRefs). - "", ErrDefaultBranchUnresolved: Not empty, and no default branch to fall back to.

func SmartFetchFrom added in v0.51.0

func SmartFetchFrom(
	ctx context.Context,
	repo *git.Repository,
	target plumbing.ReferenceName,
	parent string,
	auth []gitclient.Option,
) (plumbing.ReferenceName, error)

SmartFetchFrom is SmartFetch with an explicit parent: the branch to fall back to when the target branch is absent. Empty means the remote's default branch. A configured parent that is absent is an error rather than a fallback, so a typo can never start an orphan branch; it does not matter while the target branch exists.

func ValidateCommitConfig

func ValidateCommitConfig(config CommitConfig) error

ValidateCommitConfig checks that commit templates are syntactically valid.

func ValidateLiteralCommitMessage added in v0.44.0

func ValidateLiteralCommitMessage(message string) error

ValidateLiteralCommitMessage checks a literal override without altering its bytes. Empty means omission internally; the API schema rejects an explicitly empty field.

Types

type AttachCommitRequest

type AttachCommitRequest struct {
	// Namespace, Name, UID identify the CommitRequest. UID may be empty (a
	// Metadata-level audit policy can omit it); identity then keys on
	// namespace/name only.
	Namespace string
	Name      string
	UID       string

	// Author is the effective user that requested the finalize, captured from
	// validating admission. Only a window whose author
	// matches is attached; this binds "the open window" to "the requesting
	// author's open window".
	Author string
	// Attribution is the outcome of attributing THIS CommitRequest, from the command-authorship
	// path. It is matched alongside Author rather than inferred from it, because an empty Author
	// alone cannot say whether an actor was sought: it is both "attribution is off" and
	// "attribution ran and named nobody". Only the NamesActor half is compared against the
	// window's outcome — see matchesWindow for why the enums themselves must not be.
	Attribution AttributionOutcome
	// GitTargetName / GitTargetNamespace scope the finalize to one GitTarget.
	GitTargetName      string
	GitTargetNamespace string

	// Message is the verbatim commit message to attach to the window. Empty keeps
	// the generated grouped-commit message.
	Message string
	// Attach selects the window: the author's current one or the next, or only the next.
	Attach v1alpha3.AttachPolicy
	// AttachTimeout bounds the wait for a window, counted from the worker's registration.
	AttachTimeout time.Duration
	// IdleTimeout closes the attached window after this much silence; nil means no idle close.
	IdleTimeout *time.Duration
	// MaxDuration closes the attached window this long after the attach.
	MaxDuration time.Duration
	// CommitEmpty records Message in an empty commit when the request ends with nothing to
	// commit: whenNothingToCommit: CommitEmpty.
	CommitEmpty bool
}

AttachCommitRequest is the "attach this CommitRequest to the author's window, with its message and its timers" work item. It rides the same per-worker FIFO event queue as resource events, so by audit-stream ordering it is processed after every earlier write for that worker. Re-sends are idempotent: the worker keys pending requests by identity and keeps everything the first delivery set.

type AttributionOutcome added in v0.39.0

type AttributionOutcome string

AttributionOutcome records what happened when the operator tried to name the actor behind a change. It is carried EXPLICITLY rather than inferred from the author identity, because the author string is load-bearing in several places (window grouping, commit-message templates, the author_kind metric) and overloading it to also mean "attribution failed" made a silent failure indistinguishable from correct configured-author behaviour. See docs/architecture.md#author-and-committer-identity-in-git.

const (
	// AttributionNotAttempted is configured-author mode: attribution is switched off, so the
	// committer legitimately IS the author and no actor was ever sought.
	//
	// Deliberately the EMPTY string so it is also the ZERO VALUE: the many paths that never assign
	// Attribution (reconcile, resync, bootstrap) all mean exactly this. Any other value makes the
	// zero value a silent fourth state, which is the bug that stopped every CommitRequest
	// attaching. TestAttributionZeroValueIsNotAttempted pins it.
	AttributionNotAttempted AttributionOutcome = ""
	// AttributionResolved means an audit fact named the actor.
	AttributionResolved AttributionOutcome = "resolved"
	// AttributionUnresolved means attribution ran and did not arrive at an actor.
	//
	// "Unresolved", not "failed": no fact produced (correct; not every change has a human actor),
	// a cancelled wait, a read error and a malformed value all return the same not-found, so
	// calling it a failure would assert a fault the operator cannot prove.
	AttributionUnresolved AttributionOutcome = "unresolved"
)

func (AttributionOutcome) NamesActor added in v0.39.0

func (o AttributionOutcome) NamesActor() bool

NamesActor reports whether the outcome carries an actor to compare against.

The ONLY distinction that survives across subsystem boundaries: --author-attribution and --admission-webhook are configured independently, so two producers can disagree about the enum while agreeing about whether there is an actor. Comparing enums across that boundary couples the two flags; comparing NamesActor does not. Within one subsystem the enum IS compared directly.

type BranchInfo

type BranchInfo struct {
	ShortName string // e.g., "main"
	Sha       string // commit hash, normally the tip of the default branch. But will be empty ("") for an unborn branch that is going to be orphaned branch (if the default branch does not exist)
	Unborn    bool   // Is true for branches that don't have commits yet: only HEAD is configured to it
}

BranchInfo contains information about a Git branch.

type BranchKey

type BranchKey struct {
	// RepoNamespace is the namespace containing the GitProvider.
	RepoNamespace string
	// RepoName is the name of the GitProvider.
	RepoName string
	// Branch is the Git branch name.
	Branch string
}

BranchKey uniquely identifies a (GitProvider, Branch) combination. This is the unit of worker ownership to prevent merge conflicts. Multiple GitTargets can share the same BranchKey (same provider+branch) but write to different paths within that branch.

func (BranchKey) String

func (k BranchKey) String() string

String returns a string representation for logging and debugging. Format: "namespace/provider-name/branch".

type BranchWorker

type BranchWorker struct {
	// Identity (immutable after creation)
	GitProviderRef       string
	GitProviderNamespace string
	Branch               string

	// Dependencies
	Client client.Client
	Log    logr.Logger
	// contains filtered or unexported fields
}

BranchWorker processes events for a single (GitProvider, Branch) combination. It can serve multiple GitTargets that write to different paths in the same branch. This design ensures serialized commits per branch, preventing merge conflicts.

func NewBranchWorker

func NewBranchWorker(
	client client.Client,
	log logr.Logger,
	providerName, providerNamespace string,
	branch string,
	repo RepoIdentity,
	writer *contentWriter,
	limits BranchWorkerLimits,
) *BranchWorker

NewBranchWorker creates a worker for a (provider, branch) combination. A zero BranchWorkerLimits gets the defaults for both knobs; see BranchWorkerLimits.

func (*BranchWorker) BaseTrustedForTest added in v0.49.0

func (w *BranchWorker) BaseTrustedForTest() bool

BaseTrustedForTest exposes the flag to tests in another package.

func (*BranchWorker) Enqueue

func (w *BranchWorker) Enqueue(event Event) bool

Enqueue adds a single live event to this worker's queue. It reports whether the event entered the FIFO; a false return means the queue was full and the event was dropped, so a caller advancing a durable watch cursor past this event must not treat the drop as success (see reconcile.GitTargetEventStream.OnWatchEvent).

func (*BranchWorker) EnqueueAttach

func (w *BranchWorker) EnqueueAttach(req *AttachCommitRequest)

EnqueueAttach adds a CommitRequest attach to this worker's queue. Riding the same queue as resource events is what makes it process in audit order, after every earlier write. The attach is fire-and-forget: the controller polls the outcome via LookupCommitRequestOutcome and re-sends idempotently, so a queue- full drop is recovered by the next poll rather than a synchronous reply.

func (*BranchWorker) EnqueueRefresh added in v0.50.0

func (w *BranchWorker) EnqueueRefresh(req *RefreshRequest)

EnqueueRefresh queues a refresh, dropping it if the queue is full.

The drop is safe in a way it is not for a write: the next reconcile asks again, and a refresh must never occupy the slot a write needs. It is still counted, because a refresher that has stopped running is otherwise invisible.

func (*BranchWorker) EnqueueResync

func (w *BranchWorker) EnqueueResync(request *ResyncRequest) bool

EnqueueResync adds a resync request to this worker's queue. Like a finalize signal it rides the same queue as resource events, so it is applied in order with live events: a resync enqueued during the snapshot window lands before the buffered live events that follow it. If the queue is full the request is refused and its caller is told immediately via the result channel, so it can gather the scope again.

It reports whether the request actually entered the FIFO. A caller gating downstream state on the resync's ordering must not treat a refusal as success: it would mark the target reconciled-through-Hc with no reconcile ever queued.

func (*BranchWorker) EnqueueWithdraw added in v0.51.0

func (w *BranchWorker) EnqueueWithdraw(req *AttachCommitRequest)

EnqueueWithdraw asks the loop to cancel a CommitRequest it has not acted on. It never blocks: a full queue or a stopping worker drops it, and the controller sends it again on its next poll. A worker whose loop has exited answers at once, because nothing is left that could act on it.

func (*BranchWorker) EnsurePathBootstrapped

func (w *BranchWorker) EnsurePathBootstrapped(path, targetName, targetNamespace string) error

EnsurePathBootstrapped prepares bootstrap templates locally for a path. Existing files are preserved, and only missing template files are added. The files are staged in the local worktree but never committed or pushed here.

func (*BranchWorker) IntakePaused added in v0.52.0

func (w *BranchWorker) IntakePaused() <-chan struct{}

IntakePaused reports a branch that has paused intake: it returns a channel closed when intake reopens, or nil while it is open. A producer refused while the branch is paused waits on it instead of offering its work again on a timer, and checks again once woken, because the branch can pause again before it gets there.

func (*BranchWorker) LastRemoteObservation added in v0.50.0

func (w *BranchWorker) LastRemoteObservation() (RemoteObservation, bool)

LastRemoteObservation returns what the worker last proved about the target branch, and whether it has proved anything at all. It reads no repository and opens no connection.

func (*BranchWorker) LookupCommitRequestOutcome

func (w *BranchWorker) LookupCommitRequestOutcome(namespace, name, uid string) (FinalizeResult, bool)

LookupCommitRequestOutcome returns a resolved CommitRequest outcome, or ok=false when the request is still in flight (or already GC'd). The controller polls this after sending its AttachCommitRequest.

func (*BranchWorker) LookupCommitRequestPhase added in v0.51.0

func (w *BranchWorker) LookupCommitRequestPhase(namespace, name, uid string) CommitRequestPhase

LookupCommitRequestPhase returns where an unresolved request stands on this worker, or "" when the worker has not registered it (yet) or it is resolved.

func (*BranchWorker) ParentBranch added in v0.51.0

func (w *BranchWorker) ParentBranch() string

ParentBranch is the configured parent branch, or empty for the remote's default branch.

func (*BranchWorker) ParentRecovery added in v0.51.0

func (w *BranchWorker) ParentRecovery() (bool, bool)

ParentRecovery reports whether the worker still owes work decided while the parent branch was missing (the first result), and whether the parent has been found since (the second).

func (*BranchWorker) Publication added in v0.52.0

func (w *BranchWorker) Publication() PublicationStatus

Publication is the worker's current report. It reads memory only, and costs no connection.

func (*BranchWorker) RecordMissingParentForTest added in v0.51.0

func (w *BranchWorker) RecordMissingParentForTest(parent string)

RecordMissingParentForTest records the observation a fetch makes when neither the write branch nor its configured parent exists, for tests in another package.

func (*BranchWorker) RecordUnresolvedDefaultBranchForTest added in v0.51.0

func (w *BranchWorker) RecordUnresolvedDefaultBranchForTest(head string)

RecordUnresolvedDefaultBranchForTest records the observation a fetch makes when the write branch is absent, no parent is configured, and the remote's HEAD names head, which it does not carry (empty: no HEAD at all), for tests in another package.

func (*BranchWorker) SeedRemoteObservationForTest added in v0.50.0

func (w *BranchWorker) SeedRemoteObservationForTest(revision string, at time.Time, by string)

SeedRemoteObservationForTest marks the base trusted and records an observation made at `at`. It exists so a test in another package can build a worker in a known state; nothing in production calls it.

func (*BranchWorker) SetParentBranch added in v0.51.0

func (w *BranchWorker) SetParentBranch(name string) bool

SetParentBranch configures the parent branch, and reports whether the value changed. A change raises the generation, which is what every later decision compares against: trust earned under the old one no longer counts, a cycle planned under it is rebuilt before its push is admitted, and retained writes are replayed on the new parent. The worker and its clone are kept, and an existing write branch, which a parent cannot move, keeps its trust at no cost. spec.parentBranch is immutable, so a change reaches a live worker only when the targets on its branch were replaced.

func (*BranchWorker) Start

func (w *BranchWorker) Start(parentCtx context.Context) error

Start begins processing events.

func (*BranchWorker) Stop

func (w *BranchWorker) Stop()

Stop gracefully shuts down the worker.

type BranchWorkerLimits added in v0.47.0

type BranchWorkerLimits struct {
	// MaxBufferBytes caps totalRetainedBytes: the open commit window plus the writes
	// committed locally and retained for replay until a push succeeds. Tripping it
	// finalizes the window early, ignoring the commit cadence. While a failed attempt waits
	// for its retry it is also the intake budget, covering queued work too. See intake.go.
	MaxBufferBytes int64

	// QueueDepth is the depth of the event queue, and it is a HARD drop boundary: the
	// enqueue is deliberately non-blocking, so a full queue refuses the item and counts
	// git_queue_refusals_total rather than stalling the watch path behind a slow remote. The
	// producer keeps it and offers it again: the watch redelivers from its unadvanced cursor.
	//
	// On a healthy branch MaxBufferBytes does NOT cover this queue: the window cap is accounted
	// only once the loop DEQUEUES an item, so whatever is still on the channel is bounded by count
	// alone and costs memory ON TOP of it. During an outage the intake gate charges queued
	// payload against MaxBufferBytes at enqueue (intake.go), in serialized bytes. The channel itself is trivial (a WorkItem is three
	// pointers); what it retains is not, because each live-event item holds a sanitized
	// object as an unstructured map, measured at roughly SIX times the bytes it serializes
	// to. Budget QueueDepth x serialized size x ~6 per SATURATED worker: ~5MiB at the
	// default depth for ~800-byte resources, but the multiplier rides the payload, so a
	// folder mirroring megabyte-sized ConfigMaps reaches gigabytes at the same depth.
	QueueDepth int
}

BranchWorkerLimits bounds one branch worker's memory. The two knobs cover different stages of the same pipeline and neither substitutes for the other, which is why they travel together.

type CommitConfig

type CommitConfig struct {
	Committer CommitterConfig
	Message   CommitMessageConfig
}

CommitConfig is the resolved commit behavior used by the git writer.

func ResolveCommitConfig

func ResolveCommitConfig(spec *v1alpha3.CommitSpec) CommitConfig

ResolveCommitConfig resolves a GitProvider's commit settings into runtime defaults.

It reads the COMMITTER only. Message templates are a GitTarget concern (GitTarget.spec.commit.message) and are overlaid by WithTargetMessage, so a provider that still carries a stored spec.commit.message has no effect here — that provider is refused outright by its own reconciler rather than half-honoured.

func (CommitConfig) WithTargetMessage added in v0.43.0

func (c CommitConfig) WithTargetMessage(spec *v1alpha3.CommitMessageSpec) CommitConfig

WithTargetMessage overlays a GitTarget's spec.commit.message onto a resolved config, leaving any template the target does not set at its built-in default. A nil spec changes nothing, so a target that configures no messages commits under the same wording it always did.

type CommitFile

type CommitFile struct {
	Path    string
	Content []byte
}

CommitFile represents a single file to be committed.

type CommitMessageConfig

type CommitMessageConfig struct {
	LiveTemplate      string
	ReconcileTemplate string
	// RequestTemplate frames a CommitRequest's message. Empty — the default — commits that
	// message verbatim, which is the behaviour every target had before the field existed.
	RequestTemplate string
}

CommitMessageConfig contains the resolved live and reconcile templates.

type CommitMode

type CommitMode string

CommitMode defines how a write request should be committed.

const (
	// CommitModePerEvent streams request events through the live commit window.
	// With commitWindow=0 each event finalizes immediately; otherwise events
	// coalesce by author, target, and quiet-window boundaries.
	CommitModePerEvent CommitMode = "per_event"
	// CommitModeAtomic creates one commit for all events in the request.
	CommitModeAtomic CommitMode = "atomic"
)

type CommitRequestPhase added in v0.51.0

type CommitRequestPhase string

CommitRequestPhase is where an unresolved CommitRequest stands on the worker. It is what the controller reports while it waits, and it comes from the worker because sending an attach does not prove the request was registered, attached or committed.

const (
	// PhaseWaitingForWorker is a request whose GitTarget has no branch worker yet, at startup or
	// before the target's first reconcile. It is reported by the router, since there is no worker to
	// report it, and the request is sent again on the controller's next poll.
	PhaseWaitingForWorker CommitRequestPhase = "WaitingForWorker"
	// PhaseWaitingForWindow is a registered request waiting for a window to attach to.
	PhaseWaitingForWindow CommitRequestPhase = "WaitingForWindow"
	// PhaseCollectingWindow is an attached request collecting writes until the window's timers close it.
	PhaseCollectingWindow CommitRequestPhase = "CollectingWindow"
	// PhaseWaitingForPush is a request whose write is decided and not yet confirmed by the remote.
	PhaseWaitingForPush CommitRequestPhase = "WaitingForPush"
)

func (CommitRequestPhase) Held added in v0.51.0

func (p CommitRequestPhase) Held() bool

Held reports whether the worker has acted on a request in this phase: it is attached to a window or committed. From there only the worker can say how it ends, so the controller waits for it however long that takes, instead of failing the request on a clock of its own.

type CommitterConfig

type CommitterConfig struct {
	Name  string
	Email string
}

CommitterConfig defines the operator identity used as the git committer.

type Credential added in v0.41.0

type Credential struct {
	SSH    *sshpkg.KeyAuth
	Basic  *gogithttp.BasicAuth
	Bearer *gogithttp.TokenAuth
}

Credential is the concrete credential a Secret yields, before it is wrapped into go-git v6's opaque transport client options. At most one field is non-nil; all nil means anonymous access to a public repository.

This type exists because v6 removed transport.AuthMethod: authentication is now supplied as functional options, which are closures and therefore cannot be inspected. Keeping the concrete value on the way past preserves the ability to assert which Secret key maps to which auth field, which is the contract CredentialFromSecretData is actually responsible for.

func CredentialFromSecretData added in v0.41.0

func CredentialFromSecretData(
	ctx context.Context,
	k8sClient client.Client,
	provider *v1alpha3.GitProvider,
	secret *corev1.Secret,
	hostKeys SSHHostKeyConfig,
) (Credential, error)

CredentialFromSecretData resolves a credential from an already-fetched Git credentials Secret, accepting the Kubernetes-native, Flux, and Argo CD key dialects (the credentials Secret is the one portable artifact across those ecosystems). provider supplies the namespace and the optional knownHostsRef for SSH host trust; hostKeys supplies the install-level default and the dev escape hatch. Auth precedence is: SSH key (if present) → HTTP basic (username+password) → bearer token.

func (Credential) Options added in v0.41.0

func (c Credential) Options() []gitclient.Option

Options renders the credential as go-git v6 transport client options. A zero Credential yields nil, which go-git treats as anonymous.

type CredentialTransportPolicy added in v0.45.1

type CredentialTransportPolicy struct {
	// AllowInsecureGitHTTP permits credentials with http:// GitProvider URLs. It is intended for
	// trusted in-cluster development Git servers only.
	AllowInsecureGitHTTP bool
}

CredentialTransportPolicy controls unsafe Git credential transports.

type DefaultBranchUnresolvedError added in v0.51.0

type DefaultBranchUnresolvedError struct {
	// Head is the short name of the branch HEAD points at; empty when there is no symbolic HEAD.
	Head string
}

DefaultBranchUnresolvedError is ErrDefaultBranchUnresolved with the evidence: the branch the remote's HEAD names, or empty when the advertisement carries no symbolic HEAD at all.

The resolution itself follows go-git. Remote.List rewrites a hash-only HEAD (a server without the symref capability, or a detached HEAD) into a symbolic one before returning: to master when master has that hash, otherwise to the alphabetically first branch that has it. So a HEAD this error reports is one even that heuristic could not place.

func (*DefaultBranchUnresolvedError) Error added in v0.51.0

func (*DefaultBranchUnresolvedError) Is added in v0.51.0

func (e *DefaultBranchUnresolvedError) Is(target error) bool

Is makes the error match ErrDefaultBranchUnresolved.

type Encryptor

type Encryptor interface {
	Encrypt(ctx context.Context, plain []byte, meta ResourceMeta) ([]byte, error)
}

Encryptor transforms plaintext bytes into encrypted bytes.

type Event

type Event struct {
	// Object is the sanitized Kubernetes object. Exactly one of Object or
	// FieldPatch is set for a resource mutation; a control or DELETE event may
	// carry neither.
	Object *unstructured.Unstructured

	// FieldPatch, when set, replaces Object with a bounded in-place edit of an
	// existing parent manifest (subresource audit resolution). It is mutually
	// exclusive with Object.
	FieldPatch *FieldPatch

	// Identifier contains resource identification information.
	Identifier types.ResourceIdentifier

	// ResourceVersion is the metadata.resourceVersion of the observed state this event
	// describes. It is PROVENANCE, never content: sanitize strips resourceVersion from Object
	// on purpose (a version in a committed manifest makes every observation a byte change), so
	// this field carries the fact BESIDE the object rather than inside it. Never write it back
	// onto Object.
	//
	// Stamped where the object is observed — the live watch holds the unsanitized object there
	// and nowhere downstream does. Producers with no observed object (reconcile, bootstrap)
	// leave it empty.
	//
	// Unlike the object-derived fields, a DELETE CAN carry one: the watch Deleted frame
	// delivers the final object, so this names the last version that existed.
	ResourceVersion string

	// Generation is the metadata.generation of the observed state — the DESIRED state's counter,
	// where ResourceVersion counts every write. Same provenance-not-content rule: sanitize strips
	// generation from Object one line after resourceVersion, and for the same reason.
	//
	// 0 means "no generation", which covers both "this producer observed nothing" and "this kind
	// has none": a ConfigMap or a Secret never carries one, only types with a spec do.
	Generation int64

	// Operation is the admission operation (CREATE, UPDATE, DELETE).
	Operation string

	// UserInfo contains user information for commit messages.
	UserInfo UserInfo

	// Attribution is the authority for author rendering, the author_kind metric, and
	// CommitRequest window matching. None may infer the outcome from UserInfo: an empty username
	// cannot distinguish "attribution is off" from "it ran and found nothing". attachAuthor is
	// the only assignment outside tests.
	Attribution AttributionOutcome

	// Path is the POSIX-like relative path prefix for this event's files.
	// This comes from the GitTarget that triggered this event.
	// Empty string means write to repository root.
	Path string

	// GitTargetName is the target owning this event.
	GitTargetName string

	// GitTargetNamespace is the namespace of the target owning this event.
	GitTargetNamespace string

	// SourceCluster is the NAME of the source cluster this object was watched on —
	// (api/v1alpha3).GitTarget.SourceCluster(), the referenced ClusterProvider's name
	// ("default" for the in-cluster provider). The writer resolves this document's GVK->GVR
	// against that cluster's type registry, so a folder mirroring a remote is never indexed
	// against the local cluster's mapping.
	SourceCluster string

	// BootstrapOptions controls path-scoped bootstrap file staging for this event.
	BootstrapOptions pathBootstrapOptions

	// SourceCollection names the target-watch collection that produced this event. Zero for every
	// non-stream producer (reconcile, bootstrap, the admission path). Diagnostic only:
	// nothing filters the queue on it. See source_collection.go.
	SourceCollection types.CollectionKey
	// contains filtered or unexported fields
}

Event represents a resource change event to be processed by a branch worker. Branch comes from the worker context (not stored in event). Path comes from the GitTarget that created this event.

func (Event) IsFieldPatch

func (e Event) IsFieldPatch() bool

IsFieldPatch reports whether the event carries a bounded field patch instead of a full object. It is the single predicate the pipeline branches on to route a patch to the in-place writer rather than the object writer.

type FieldPatch

type FieldPatch struct {
	// Assignments are the (path, value) pairs to set on the parent manifest. Paths
	// are disjoint; each owns only its own subtree, so the patch is additive and
	// leaves every unmentioned field in Git untouched.
	Assignments []manifestedit.FieldAssignment
	// Source is a bounded origin label for commit messages and metrics, e.g.
	// "deployments/scale". Never the request URI.
	//
	// The parent Kind is NOT carried: the audit objectRef gives only the GVR, and the subresource
	// body's own Kind ("Scale") is not the parent's. The writer resolves the parent through the
	// same resource-identity inventory the GVR-only delete uses.
	Source string
}

FieldPatch is a bounded set of field assignments to an existing parent manifest, carried in place of a full Object. It is how an author-preserving subresource mutation (e.g. deployments/scale) reaches Git: set exactly the audited field paths on the already committed parent, never reconstructing the whole object. See docs/spec/scale-subresource-audit-rehydration.md.

type FinalizeOutcome

type FinalizeOutcome string

FinalizeOutcome is the terminal result of resolving a CommitRequest.

const (
	// FinalizeCommitted means an open commit window was finalized into a commit.
	FinalizeCommitted FinalizeOutcome = "Committed"
	// FinalizeNoOpenWindow means the request's attach timeout ran out before a matching
	// same-author window was there to attach to, so nothing was committed for it.
	FinalizeNoOpenWindow FinalizeOutcome = "NoOpenWindow"
	// FinalizeWindowMismatch means a window was open during the request's wait that belonged to
	// a different author or GitTarget, so it was left untouched and the wait ran out without a
	// window this request could attach to. It is a refusal a human can see: the author's own edits
	// went into somebody else's commit, under a generated message rather than theirs.
	//
	// Raised at expiry from pendingCommitRequest.sawForeignWindow, never at attach time: under
	// eager attach a request WAITS for a matching window rather than being refused on sight, so
	// "a foreign window is open right now" is not yet an outcome.
	FinalizeWindowMismatch FinalizeOutcome = "WindowMismatch"
	// FinalizeAlreadyPresent means a matching window was finalized but its events
	// produced no diff — the change already matches the remote, so no commit was
	// made (loop prevention). Resolved at finalize, never waiting on a push.
	FinalizeAlreadyPresent FinalizeOutcome = "AlreadyPresent"
)

type FinalizeResult

type FinalizeResult struct {
	// Outcome is set when Err is nil.
	Outcome FinalizeOutcome
	// Commit is the resulting commit Commit when Outcome is FinalizeCommitted.
	Commit string
	// Branch is the branch the worker operates on.
	Branch string
	// Err is set when the request could not be completed.
	Err error
	// Phase is where an UNRESOLVED request stands on the worker; empty once resolved, and empty
	// before the worker has registered it.
	Phase CommitRequestPhase
	// Held is why a request the worker holds has not reached the remote yet: the worker's
	// publication report while it cannot publish, empty otherwise. See PublicationStatus.
	Held string
}

FinalizeResult carries the resolved outcome of a CommitRequest back to the controller, polled via LookupCommitRequestOutcome.

type LayoutReport added in v0.43.0

type LayoutReport struct {
	manifestanalyzer.LayoutResolution

	// Commit is the commit hash the scan read; ResolvedAt is when it ran.
	Commit     string
	ResolvedAt time.Time
}

LayoutReport is one scan's answer to "what does this folder's shape imply about where new documents go" — the resolution plus the revision and moment it was read at, so a consumer can tell a fresh answer from one taken before the folder changed.

type LayoutReporter added in v0.43.0

type LayoutReporter func(target itypes.ResourceReference, report LayoutReport)

LayoutReporter publishes a folder's resolved layout to the layer that owns GitTarget status.

It is the twin of PathRefusalReporter, and it exists for the same structural reason: the scan happens on a branch-worker goroutine with no result channel back to the controller, so without a hook the resolution would be computed and dropped. The watch Manager supplies it (WorkerManager.SetLayoutReporter), which is where the projection onto status lives.

Every scan reports, including one that changes nothing and one on a SUSPENDED target: a stopped valve that also stopped looking would freeze status.placement at whatever the folder looked like when someone panicked, which is exactly when a stale answer costs the most. That is only possible because the report is independent of anything being written. The consumer is responsible for republishing only on a transition.

type LiveCommitMessageData added in v0.44.0

type LiveCommitMessageData struct {
	// Author is the verbatim event.UserInfo.Username for the group.
	Author string
	// GitTarget is the single target this commit is bound to.
	GitTarget string
	// Count is the number of retained resource entries before comparison with Git.
	Count int
	// Operations counts retained entries by operation kind (CREATE/UPDATE/DELETE).
	Operations map[string]int
	// Resources is the per-resource list, deduplicated by file path so the
	// final state is what's being committed.
	Resources []ResourceRef
	// RequestMessage is the message an attached CommitRequest supplied, unaltered. It is empty for
	// every window no request attached to, which is the ordinary case and why liveTemplate may
	// reference it freely.
	//
	// It lives on this struct rather than on a parallel request-only context for the reason
	// ResourceRef's own comment gives about the placement vocabulary: a reader should not have to
	// learn two vocabularies to describe one commit. One sample builder then serves both renders.
	RequestMessage string
}

LiveCommitMessageData is the template context for every live commit message, whatever the window retained: one resource, many, or a 0s window. Each live commit covers exactly one (author, gitTarget) tuple (see docs/spec/commit-window-refactor.md).

func (LiveCommitMessageData) LabelValue added in v0.46.0

func (d LiveCommitMessageData) LabelValue(key string) string

LabelValue is the one value every resource in this commit agrees on for key, and "" unless all of them carry it with that value. It is what a SUBJECT line wants: naming the team a commit belongs to is only honest when the commit is one team's.

chore: sync {{.Count}} resources{{with .LabelValue "team"}} for {{.}}{{end}}

A resource that does not carry the label DISAGREES; it does not abstain. That is why this cannot be LabelValues with a length check: that set skips the unlabeled, so one "team: payments" resource committed next to an unlabeled one would name the whole commit "for payments" and hide the resource nobody can attribute. The same rule leaves a commit containing a DELETE unnamed, since a DELETE carries no object and so no labels (see ResourceRef.Labels) — the deleted resource's team is not something the window can know.

func (LiveCommitMessageData) LabelValues added in v0.46.0

func (d LiveCommitMessageData) LabelValues(key string) []string

LabelValues is the sorted, distinct set of values this commit's resources carry for one label key, skipping every resource that does not set it (so the result is empty, never a list with a blank in it).

A label is single-valued for a path and set-valued for a commit message: placement asks the question of one resource, a commit asks it of the n resources in the window. Printing the slice renders Go's "[a b]" form, so a template usually ranges over it:

{{range .LabelValues "team"}}{{.}} {{end}}

type ObservationSource added in v0.50.0

type ObservationSource string

ObservationSource names what proved an observation. It separates "this revision is our own work, and the server took it" from "we went and looked, and this is what was there", which is the first question asked of a branch that moved unexpectedly.

const (
	// ObservedByFetch is a fetch that was followed by a reset onto the fetched tip.
	ObservedByFetch ObservationSource = "Fetch"
	// ObservedByPush is a push session the server did not reject.
	ObservedByPush ObservationSource = "Push"
)

type ParentState added in v0.51.0

type ParentState string

ParentState is what a branch the remote does not carry would be created from.

const (
	// ParentFound means the parent branch exists, and the branch's first commit builds on its tip.
	ParentFound ParentState = "Found"
	// ParentMissing means the parent is not on the remote either, so the branch cannot be created
	// and nothing is written until one of them exists: the configured parent branch is absent, or,
	// with none configured, the repository is not empty but its default branch does not resolve.
	ParentMissing ParentState = "Missing"
	// ParentUnborn means the repository is empty, with no hash refs at all (tags included), and
	// no parent was configured, so the first commit starts the branch with no history.
	ParentUnborn ParentState = "Unborn"
)

type PathRefusalReporter

type PathRefusalReporter func(
	target itypes.ResourceReference,
	collection itypes.CollectionKey,
	refused *manifestanalyzer.AcceptanceRefusedError,
)

PathRefusalReporter surfaces a refused write plan to the layer that owns GitTarget status. A refusal is not a transient write fault: the acceptance gate or a write-boundary precondition aborted the flush before any byte was written, nothing was committed, and only a human editing the Git path can clear it — so it must reach the user as GitPathAccepted=False / Stalled=True rather than being logged and dropped.

The resync path already carries its refusal back on ResyncResult.Err, where the watch layer classifies it. The live-event paths have no result channel — a window is finalized on a timer, and its failure used to be logged and dropped — so they report through this hook instead. The watch Manager supplies it (WorkerManager.SetPathRefusalReporter), which is why the reason mapping lives there and not here.

type PendingWrite

type PendingWrite struct {
	Kind               PendingWriteKind
	Events             []Event
	CommitMessage      string
	CommitConfig       CommitConfig
	Signer             gogit.Signer
	GitTargetName      string
	GitTargetNamespace string
	Targets            map[pendingTargetKey]ResolvedTargetMetadata
	ByteSize           int64

	// Desired is the complete desired resource snapshot, set only for a
	// PendingWriteResync. The worker folds it over the worktree's content-derived
	// store to produce the resync plan (upserts + mark-and-sweep drops).
	Desired []manifestanalyzer.DesiredResource
	// Scope, when set, restricts the resync's mark-and-sweep to one type's
	// (group, resource) and optionally to one namespace: the M12 per-type
	// reconcile/sweep. Desired then carries only that scope's objects (empty for a pure
	// sweep), and no sibling type's — nor, for a namespace-scoped resync, any sibling
	// namespace's — document is ever dropped. Nil is the whole-GitTarget resync.
	Scope *ResyncScope
	// ResourceVersion is the cluster snapshot's resourceVersion the desired set is pinned to
	// (the joined streaming-watch bookmark) — the COLLECTION's version, not any one object's.
	// Carried for diagnostics, logging, and the reconcile commit message.
	//
	// Not "Revision": in this codebase a revision is a Git commit (see LayoutReport.Revision).
	ResourceVersion string
	// ResyncStats, when non-nil, is populated during apply with the plan's
	// create/update/delete/skip counts so a synchronous caller can report them.
	ResyncStats *ResyncStats
	// Committed, when non-nil, is set true during apply iff the resync produced a
	// commit. A no-op resync (e.g. an empty initial snapshot) must not be retained or
	// pushed: doing so would advance the push cooldown and delay the next real
	// snapshot's push past its window.
	Committed *bool

	// AllowEmpty makes a grouped window commit even when its events changed nothing, because the
	// CommitRequest attached to it asked for whenNothingToCommit: CommitEmpty. A suspended target
	// still writes nothing.
	AllowEmpty bool
	// RequestAuthor and RequestAttribution author a PendingWriteRequestRecord, which has no
	// events to take an author from: they are the request submitter's, matched the same way a
	// window's author is.
	RequestAuthor      UserInfo
	RequestAttribution AttributionOutcome

	// CommitRequest, when set, is the CommitRequest attached to this write: it is
	// resolved Committed (with CommitSHA) once this write is pushed. It rides the write through the
	// push cooldown and the conflict rebase-replay, so the result follows the data.
	CommitRequest *commitRequestID

	// CommitSHA is the hash of the commit this write created, captured in
	// executePendingWrite and refreshed when the write is re-executed on a
	// rebase-replay (so it is never a stale pre-rebase hash). Zero when the write
	// produced no commit (no diff).
	CommitSHA plumbing.Hash
	// contains filtered or unexported fields
}

PendingWrite is the unit retained until a push succeeds.

func (PendingWrite) AttributionOutcome added in v0.39.0

func (p PendingWrite) AttributionOutcome() AttributionOutcome

AttributionOutcome returns the attribution outcome for commit-shaped pending writes. It mirrors AuthorUserInfo: the window is single-author, so the first event's outcome describes the whole write. Atomic and empty writes never attempt attribution.

func (PendingWrite) Author

func (p PendingWrite) Author() string

Author returns the grouped commit author username for commit-shaped pending writes. It is the stable identity used for window coalescing and the grouped commit message; see AuthorUserInfo for the full signing identity.

func (PendingWrite) AuthorUserInfo

func (p PendingWrite) AuthorUserInfo() UserInfo

AuthorUserInfo returns the full author identity for commit-shaped pending writes, including any OIDC display name and email. Atomic and empty writes have no per-user author and return the zero value.

func (PendingWrite) Target

Target returns the single resolved target metadata for this pending write.

type PendingWriteKind

type PendingWriteKind string

PendingWriteKind distinguishes the durable write shapes retained until push.

const (
	// PendingWriteCommit is one finalized commit-shaped live-event window.
	PendingWriteCommit PendingWriteKind = "grouped_window"
	// PendingWriteAtomic is a caller-defined atomic request, typically from
	// reconciliation.
	PendingWriteAtomic PendingWriteKind = "atomic"
	// PendingWriteResync is a streaming-snapshot resync (M8): it carries the COMPLETE
	// desired resource set for one GitTarget, and the worker materialises it with a
	// content-derived mark-and-sweep against the worktree (upsert every desired
	// resource, drop every watched managed document the snapshot did not contain).
	PendingWriteResync PendingWriteKind = "resync"
	// PendingWriteRefusalTouch carries no content at all. It exists to move the branch after a
	// refused write, so the reconciler re-applies the desired state and reverts the live edit a
	// refusal left in place. It is the only write kind that deliberately produces a commit with
	// an empty tree diff, and it is created only for a GitTarget with
	// spec.onRefusal: PushEmptyCommit.
	PendingWriteRefusalTouch PendingWriteKind = "refusal_touch"
	// PendingWriteRequestRecord carries no content either. It records a CommitRequest's message
	// in an empty commit when the request ran out of time to attach, and asked for that with
	// whenNothingToCommit: CommitEmpty. It is not a window: only writes open windows.
	PendingWriteRequestRecord PendingWriteKind = "request_record"
)

type PublicationStatus added in v0.52.0

type PublicationStatus struct {
	// Failing is set while a failed attempt waits for its retry.
	Failing bool
	// Since is when this run of failures began. It holds until a publication lands.
	Since time.Time
	// Cause is what the last attempt met.
	Cause string
	// ParentUnavailable is set when the cause is the parent branch a new write branch is created
	// from: the parent status already says so, more precisely.
	ParentUnavailable bool
	// IntakePaused is set while the branch refuses new writes, saves and resyncs.
	IntakePaused bool
}

PublicationStatus is a branch worker's report of its publication, for every GitTarget on the branch and every save it holds. The zero value is a branch with nothing failing.

func (PublicationStatus) Message added in v0.52.0

func (p PublicationStatus) Message() string

Message says what the report means, in words that change only when the report does.

type PullReport

type PullReport struct {
	ExistsOnRemote  bool // Branch exists on remote
	HEAD            BranchInfo
	IncomingChanges bool // SHA changed, requiring resource-level reconcile
	// ParentBranch is the branch checked out instead, when the target branch is absent: the parent
	// it would be created from. HEAD.Sha is then the parent's commit. Empty otherwise.
	ParentBranch string
}

PullReport provides detailed pull operation results.

func PrepareBranch

func PrepareBranch(
	ctx context.Context,
	repoURL, repoPath, targetBranchName string,
	auth []gitclient.Option,
) (*PullReport, error)

PrepareBranch clones repository immediately when GitDestination is created, optimized for single branch usage. It tries to fetch the useful branch: either target or default.

func PrepareBranchFrom added in v0.51.0

func PrepareBranchFrom(
	ctx context.Context,
	repoURL, repoPath, targetBranchName, parentBranch string,
	auth []gitclient.Option,
) (*PullReport, error)

PrepareBranchFrom is PrepareBranch with an explicit parent branch for a target branch the remote does not carry; empty means the remote's default branch. See SmartFetchFrom.

type PushOutcome added in v0.50.0

type PushOutcome struct {
	// Kind is which exit was taken.
	Kind PushOutcomeKind
	// Head is where the branch is on the remote now. Zero only for PushNoBranch.
	Head plumbing.Hash
}

PushOutcome is what the push session learned about the remote.

Every field is read from the ONE connection the push was making anyway, so none of it costs a round trip — and a push that returns without an error proves more than a fetch does. A fetch says "the branch was at X when I looked"; an accepted push says "it was at Old when I looked, and the server has just moved it to New on my authority", with no window between the read and the write for anyone to slip through.

The zero value is not a valid outcome: an error return means nothing was observed at all, and the caller must invalidate rather than record.

func PushAtomic

func PushAtomic(
	ctx context.Context,
	repo *git.Repository,
	rootHash plumbing.Hash,
	rootBranch plumbing.ReferenceName,
	auth []gitclient.Option,
) (PushOutcome, error)

type PushOutcomeKind added in v0.50.0

type PushOutcomeKind string

PushOutcomeKind names which of the three non-error exits a push session took.

const (
	// PushAccepted means the server took the ref update. The outcome's Head is the new tip.
	PushAccepted PushOutcomeKind = "Accepted"
	// PushUpToDate means the advertisement already carried the branch at our head, so nothing
	// was sent. Head is that hash.
	PushUpToDate PushOutcomeKind = "UpToDate"
	// PushNoBranch means the advertisement did not carry the branch and there was nothing local
	// to send. Head is the zero hash, and that is an observation rather than the absence of one:
	// a branch does not exist without a commit.
	PushNoBranch PushOutcomeKind = "NoBranch"
)

type ReconcileCommitMessageData

type ReconcileCommitMessageData struct {
	Count      int
	GitTarget  string
	Group      string
	Version    string
	Resource   string
	APIVersion string
	// ResourceVersion is THE COLLECTION'S metadata.resourceVersion — the LIST the desired set
	// was folded from, not any one object's. A reconcile describes a type, so the version it
	// can name is the snapshot's. (The per-object counterpart is
	// LiveCommitMessageData.Resources[i].ResourceVersion, which a live commit carries because
	// it names n resources.)
	//
	// Empty for a pure sweep. It was called Revision until v0.48.0; see the Revision tombstone
	// below for why the word moved.
	ResourceVersion string
	// Namespace is the single source namespace a namespace-scoped reconcile covered, and
	// is empty for a whole-target or all-namespaces reconcile.
	Namespace string
}

ReconcileCommitMessageData is the template context for reconcile commit messages.

Group, Version, Resource and APIVersion describe the reconciled type, and are populated only for a per-type reconcile. ResourceVersion is the version the desired set was pinned to. Any template referencing these must render cleanly when absent; the default guards both with {{if}}.

func (ReconcileCommitMessageData) Revision

func (ReconcileCommitMessageData) Revision() (string, error)

Revision is the retired name of ResourceVersion, kept ONLY so a stored template that still says {{.Revision}} is refused with a sentence instead of a text/template internal ("can't evaluate field Revision in type git.ReconcileCommitMessageData").

It is a method rather than a scan of the template source because a method catches every spelling — {{with .Revision}}, {{.Revision | printf "%s"}}, assignment to a variable — which a search for the literal string would not. Same argument validateRequestTemplate makes for probing behaviour rather than matching template text.

It deliberately does NOT return the value. Silently honouring the old name would keep the wrong word alive indefinitely, and "accepted, and quietly carried on" is the upgrade failure mode docs/UPGRADING.md is written against. Reject, do not prune.

Delete one minor release after v0.48.0. By then {{.Revision}} can go back to failing as a plain unknown field, which is all an unknown field deserves.

type RefreshRequest added in v0.50.0

type RefreshRequest struct {
	// Target is the GitTarget this refresh is for, and the one its observation is reported
	// against. A worker serves every target on its (provider, branch); this names the one in
	// hand.
	Target itypes.ResourceReference
	// Path is the GitTarget's spec.path, so a refresh can re-read that folder and republish
	// what its shape resolves to. Empty skips the re-scan.
	Path string
	// MaxAge is how old an observation may be before it is topped up. A younger one makes the
	// refresh a no-op with no connection at all, which is what keeps an actively-publishing
	// branch free: its pushes renew the record continuously.
	MaxAge time.Duration
}

RefreshRequest asks a worker to re-prove where its branch is on the remote, unless something has proved it recently enough.

It is enqueued from the GitTarget reconcile — a scheduled tick that already happens — and never called inline: the reconcile must not block on a network round trip, and the work belongs on the one goroutine allowed to touch the repository.

type RemoteMovedError added in v0.49.0

type RemoteMovedError struct {
	// Branch is the cycle's root branch, the one whose advertised hash was compared.
	Branch plumbing.ReferenceName
	// Expected is the hash the local commits were based on (the cycle's root hash).
	Expected plumbing.Hash
	// Advertised is where the remote says that branch is now.
	Advertised plumbing.Hash
	// Missing records that the branch was absent from the advertisement rather than sitting at a
	// different hash. Advertised is then the zero hash, which is the honest answer: there is
	// nothing there.
	//
	// A deleted branch IS the remote moving, and typing it as one is what lets the worker recover
	// from it. Reported as an untyped error, it fell through to the fallback probe — which reads
	// refs/remotes/origin/<branch>, a ref SmartFetch cannot prune because it builds no refspec for
	// a branch the remote no longer has. The probe therefore answered with the branch's old hash,
	// that hash matched the cycle's root, and the worker concluded the remote had not moved. No
	// replay followed, and the retained writes then made every later cycle skip its fetch as well,
	// so the worker pushed the same doomed commits forever.
	Missing bool
}

RemoteMovedError reports a compare-and-swap that cannot proceed: the cycle's root branch is no longer at the hash the local commits were based on.

It carries Advertised, which is the hash the push session's OWN advertisement named. That is the point of the type. The caller has to know where the remote is before it can replay, and without this it learned the same number a second time over the network — a whole SmartFetch, which is two more requests to the Git host, for a fact the rejected push already had in hand.

The message is unchanged from the error this replaces, deliberately: it is the string in the logs operators have been reading, and the type is the new information, not the wording.

func (*RemoteMovedError) Error added in v0.49.0

func (e *RemoteMovedError) Error() string

type RemoteObservation added in v0.50.0

type RemoteObservation struct {
	// Commit is the hash the branch is at on the remote. EMPTY means the advertisement did not
	// carry the branch: a branch does not exist without a commit, so "no revision" and "no
	// branch" are the same fact and do not need two fields.
	Commit string
	// At is when the observation was made.
	At time.Time
	// By is what proved it.
	By ObservationSource
	// Repo is the repository the observation is ABOUT: the identity of the worker that proved it.
	//
	// A revision means nothing without it. Workers are keyed by (provider, branch) while a
	// GitProvider can be repointed at another repository, so the layer that publishes this can be
	// holding a revision from a repository its GitTarget no longer uses — and if the new one is
	// unreachable, nothing else would ever correct it. Carrying the identity lets that be DERIVED
	// at the moment of publication, by comparing it against what the GitProvider names now,
	// instead of latched by whoever happened to notice the change.
	//
	// It is zero for a worker that has no identity of its own: the CLI, and tests that never reach
	// a remote. A comparison against an unknown identity proves nothing, so it is not made.
	Repo RepoIdentity
	// ParentState is what the remote offers an absent branch to be created from, observed
	// together with its absence. Empty while the branch exists.
	ParentState ParentState
	// ParentBranch and ParentCommit name that parent and where it is. ParentCommit is empty
	// unless ParentState is ParentFound; ParentBranch is empty for ParentUnborn. For an omitted
	// parent that is ParentMissing, ParentBranch is the branch the remote's HEAD names, or empty
	// when it names none.
	ParentBranch string
	ParentCommit string
	// ParentRequested is spec.parentBranch as the observing operation read it: empty for the
	// remote's default branch. It is set on ParentMissing, so a reader can tell whether the
	// observation is about the parent configured now.
	ParentRequested string
}

RemoteObservation is what a branch worker last PROVED about the target branch on the remote, and when.

It is not a cache of a fetch: a successful compare-and-swap push proves the same fact more strongly, because it names the hash the server accepted rather than one it advertised, so both producers write here. The trio this replaced (branchExists, lastCommitSHA, lastFetchTime) was stamped by fetches alone, so it went arbitrarily stale on exactly the targets in continuous contact with the remote.

An observation is a fact about a MOMENT — somebody can push a millisecond after the server acks ours, as they can after any fetch. The cost of a stale belief is one rejected advertisement, never a bad write, and it is why anything published from this carries the timestamp beside the revision.

func (RemoteObservation) Age added in v0.50.0

Age is how long ago the observation was made, against now.

type RemoteReporter added in v0.50.0

type RemoteReporter func(observed RemoteObservation)

RemoteReporter delivers a confirmed observation of a branch's remote state to the layer that keeps it for the whole branch. Every worker gets one, bound to its own BranchKey, because the observation is made on a branch-worker goroutine with no result channel back to the controller and would otherwise be learned and dropped.

DELIVERY IS NOT PUBLICATION, and this is the delivery half. "Where is branch B" is ONE fact, proved once — by a push or by a fetch — and shared by every GitTarget on that branch at no cost: it is a map write, and distributing a fact is not re-proving it. What each GitTarget then WRITES to its status is a separate, bounded decision, made on its own reconcile. See publishRemote.

This is why the worker keeps no list of targets. It used to report against whichever targets were in hand — the ones a push's writes named, or the one a refresh was serving — so a fact about the branch reached some of its targets and not others, and the layer above had to keep a copy per target of a value that was the same for all of them.

type RenderFidelityGate added in v0.37.0

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

RenderFidelityGate is the concurrency-safe ownership point for the RenderMatchesLive state machine. A restarted scope closes writes until it reports clean again. A single divergence latches False for that scope's revision; a later success from another scope cannot reopen it.

Revisions are PER SCOPE rather than per target, because the watch plan is applied per collection: a collection whose stream is left running across a plan change keeps its result and its revision, and only the collections that were started or restarted go back to pending. A target-wide epoch would have marked every collection pending on every plan edit — closing writes on a target whose streams never moved — and would have cleared a divergence that nothing re-measured.

func NewRenderFidelityGate added in v0.37.0

func NewRenderFidelityGate() *RenderFidelityGate

NewRenderFidelityGate creates an empty gate. Targets absent from it remain writable for backwards-compatible callers until their watch manager reconciles a plan.

func (*RenderFidelityGate) AllowsWrites added in v0.37.0

func (g *RenderFidelityGate) AllowsWrites(target types.ResourceReference) bool

AllowsWrites reports whether a target may accept a normal live or atomic write. Resync work is deliberately not gated here: it is how the current plan measures and repairs the Git tree.

func (*RenderFidelityGate) Fail added in v0.37.0

Fail closes a target immediately when a steady-state write discovers a divergence. It does not invent a successful scope result, so recovery still requires every scope to be re-measured after the failure.

func (*RenderFidelityGate) Forget added in v0.37.0

func (g *RenderFidelityGate) Forget(target types.ResourceReference)

Forget removes a deleted GitTarget's state.

func (*RenderFidelityGate) Invalidate added in v0.50.0

func (g *RenderFidelityGate) Invalidate(target types.ResourceReference)

Invalidate says that everything this target has proved was proved about a repository it no longer writes to, and that nothing may be written until it has been measured again.

It is what a branch worker's replacement needs, and Forget is not. Forgetting a target makes it UNREGISTERED, and an unregistered target is treated as writable — so the gate opened the moment the repository changed, before anything had looked at the new one. It also reset the revision counter, so the next plan issued revision 1 again and a result still in flight from the old repository, carrying that same number, was accepted as proof about the new one.

So the state is kept and turned back to pending instead: every scope is given a fresh revision, which no running stream holds, and the target is held Unknown — including the vacuous case of a target with no scopes at all, where an empty plan would otherwise reduce to True — until a pass restarts every scope and measures the new repository.

func (*RenderFidelityGate) Reconcile added in v0.42.0

Reconcile installs target's current scope set and returns the revision every scope must report under, alongside the resulting status.

A scope is one independently replayed target-watch collection, so the namespace is part of it: a GitTarget can watch one type in more than one namespace, and each reports its own result.

  • a scope in restarted is given a FRESH revision and goes back to pending;
  • a scope in scopes but not in restarted keeps its revision and its result, so a stream that was left running is not asked to prove itself again;
  • a scope absent from scopes is dropped, and any result still in flight for it is stale by construction.

Restarting every scope also clears a divergence a live write latched, since that is a complete fresh measurement of the target. An empty scope set does not: it restarts every scope only vacuously.

It returns Unknown while scopes are pending, or True for the vacuous zero-scope case.

func (*RenderFidelityGate) RecordScopeClean added in v0.37.0

func (g *RenderFidelityGate) RecordScopeClean(
	target types.ResourceReference,
	revision uint64,
	scope types.CollectionKey,
) (RenderFidelityStatus, bool)

RecordScopeClean records a completed clean result. It ignores a result carrying any revision but the scope's current one, and a result for a scope the current plan no longer contains, returning applied=false in either case.

func (*RenderFidelityGate) RecordScopeDivergence added in v0.37.0

func (g *RenderFidelityGate) RecordScopeDivergence(
	target types.ResourceReference,
	revision uint64,
	scope types.CollectionKey,
	divergence manifestanalyzer.RenderDivergence,
) (RenderFidelityStatus, bool)

RecordScopeDivergence records a render-vs-live mismatch for one completed scope. It latches the target False until that scope is restarted with a fresh revision.

func (*RenderFidelityGate) Status added in v0.37.0

Status returns the current status. An unregistered target is treated as True so adding the gate does not change callers that have no target watch lifecycle.

type RenderFidelityState added in v0.37.0

type RenderFidelityState string

RenderFidelityState is the three-state result of a complete render-vs-live epoch.

const (
	RenderFidelityUnknown RenderFidelityState = "Unknown"
	RenderFidelityTrue    RenderFidelityState = "True"
	RenderFidelityFalse   RenderFidelityState = "False"
)

type RenderFidelityStatus added in v0.37.0

type RenderFidelityStatus struct {
	// Revision is the highest scope revision this target has issued. It moves whenever any
	// scope is restarted, so a status projection can tell one plan from the next.
	Revision    uint64
	State       RenderFidelityState
	Reason      string
	Message     string
	Divergence  *manifestanalyzer.RenderDivergence
	ScopeCount  int
	CleanScopes int
}

RenderFidelityStatus is the target-level reduction of all scope results. Unknown means some scope has not reported under its current revision; callers must not write live events while it is Unknown or False.

type RepoIdentity added in v0.50.0

type RepoIdentity struct {
	// ProviderUID is the GitProvider object's metadata.uid.
	ProviderUID k8stypes.UID
	// URL is the GitProvider's spec.url, exactly as it is written there.
	URL string
}

RepoIdentity is WHICH REPOSITORY a branch worker's clone, base trust and observations are about. It is what BranchKey does not say: every field of that key comes off the GitTarget, and none of them mentions the repository.

The UID carries the identity because spec.url is immutable, so within the lifetime of one GitProvider object the repository cannot change: a new object is the only way to reach a new one, and that mints a new UID. The URL rides along as a safety net for an immutability rule that was bypassed — a CRD reinstalled without the CEL rule, or a write straight to etcd. It is compared EXACTLY, with no normalization, because a re-spelled URL can only arrive with a recreate, which the UID has already caught.

It is deliberately NOT part of BranchKey. That key is derivable from the GitTarget alone, which is what lets the event router resolve a worker with a map read and no GitProvider in hand; a key naming the repository would put a provider read on the per-event path, and would let two workers for one (provider, branch) report under one set of metric labels while both were live.

func (RepoIdentity) IsZero added in v0.50.0

func (r RepoIdentity) IsZero() bool

IsZero reports that nothing is known about the repository. The CLI and tests that never touch a remote build workers this way, and a comparison against an unknown identity proves nothing.

func (RepoIdentity) String added in v0.50.0

func (r RepoIdentity) String() string

String is for logs: enough to tell two repositories apart without printing a credential.

spec.url is validated for length and nothing else, so it can carry userinfo (https://user:token@host/repo.git), and a replacement logs both identities at default verbosity. The userinfo is stripped rather than the whole URL withheld, because telling two repositories apart is the entire job of this line. A URL that will not parse is printed as written: it reached no remote, so it holds no credential a remote accepted.

type RepoInfo

type RepoInfo struct {
	DefaultBranch     *BranchInfo
	RemoteBranchCount int
}

RepoInfo represents high-level repository information.

func CheckRepo

func CheckRepo(ctx context.Context, repoURL string, auth []gitclient.Option) (*RepoInfo, error)

CheckRepo performs lightweight connectivity checks and gathers repository metadata.

type RepositoryNotEmptyError added in v0.51.0

type RepositoryNotEmptyError struct {
	// Refs is how many hash refs the advertisement carried.
	Refs int
}

RepositoryNotEmptyError reports a push planned against an empty repository that is no longer empty: the cycle's root is the zero hash, the write branch is not advertised, and the advertisement carries other refs. Creating the branch now would make an orphan beside them, so the push is refused and the worker replays onto what discovery finds.

It is its own type rather than a RemoteMovedError, whose Branch and Advertised mean "the root, compared": filling them with another branch's hash would make them lie.

func (*RepositoryNotEmptyError) Error added in v0.51.0

func (e *RepositoryNotEmptyError) Error() string

type ResolvedEncryptionConfig

type ResolvedEncryptionConfig struct {
	Provider      string
	AgeRecipients []string
}

ResolvedEncryptionConfig contains runtime encryption settings resolved from GitTarget spec.

It carries public age recipients only. The write path encrypts, it never decrypts, so no private age identity is retained, written to disk, or passed to the sops process. See docs/rbac.md.

func ResolveTargetEncryption

func ResolveTargetEncryption(
	ctx context.Context,
	k8sClient client.Client,
	target *v1alpha3.GitTarget,
) (*ResolvedEncryptionConfig, error)

ResolveTargetEncryption resolves and validates GitTarget encryption configuration.

type ResolvedTargetMetadata

type ResolvedTargetMetadata struct {
	Name             string
	Namespace        string
	Path             string
	BootstrapOptions pathBootstrapOptions
	EncryptionConfig *ResolvedEncryptionConfig
	// Placement is the GitTarget's declared new-file placement policy, resolved
	// from spec.placement. Nil when the GitTarget declares none, in which case new
	// resources are placed beside the folder's one kustomize root, if it has exactly one,
	// and otherwise at the built-in path.
	Placement *manifestanalyzer.PlacementPolicy
	// Namespaces is the GitTarget's declared namespace behavior — spec.serializeNamespace and the
	// source namespaces reaching the target — which decides whether the documents this target
	// writes carry metadata.namespace, and which namespace a namespace-free one belongs to. It is
	// resolved with the rest of the metadata so a replayed write honours the policy it was planned
	// under, exactly as PruneMode and Suspend are.
	Namespaces namespacePolicy
	// CommitMessage is the GitTarget's spec.commit.message, verbatim and possibly nil. It is
	// overlaid onto the provider-resolved CommitConfig so a commit is phrased by the folder it
	// writes to rather than by the connection it travels over. Resolved with the rest of the
	// target's mutable state, so a write replayed after a rebase is phrased by the policy it was
	// planned under.
	CommitMessage *v1alpha3.CommitMessageSpec

	// PruneMode is the GitTarget's EFFECTIVE spec.prune.mode — always a concrete value,
	// because it is resolved through EffectivePruneMode and an omitted policy is onEvent.
	// It gates both deletion paths: the resync mark-and-sweep (through the planner's
	// SweepMode) and the steady-state DELETE-event writer.
	//
	// Retained on the pending write so a replay after a rebase is not re-planned under a LOOSER
	// policy than it was planned against. Not frozen, though: tightenPendingPruneModes lowers it
	// when the current policy is stricter, because tightening exists to stop deletions that have
	// not landed yet.
	PruneMode v1alpha3.PruneMode
	// SourceCluster is the NAME of the source cluster the GitTarget mirrors from —
	// (api/v1alpha3).GitTarget.SourceCluster(), the referenced ClusterProvider's name
	// ("default" for the in-cluster provider). The resync mark-and-sweep resolves this subtree's
	// documents' GVK->GVR against that cluster's registry, so a folder mirroring a remote is swept
	// against the right cluster's mapping.
	SourceCluster string

	// Suspend suppresses the WRITE only; the scan still runs, which keeps a suspended target's
	// status fresh. Being CAPTURED defines the cutover: a suspension arriving after this write was
	// planned does not retract it, and a commit already made locally is still pushed. Reading the
	// live GitTarget at push time would strand that commit, to resurface out of order on resume.
	Suspend bool
}

ResolvedTargetMetadata is the target-scoped planning data retained with a pending write so replay does not re-fetch mutable GitTarget state.

type ResourceMeta

type ResourceMeta struct {
	Identifier      itypes.ResourceIdentifier
	UID             string
	ResourceVersion string
	Generation      int64
}

ResourceMeta is passed to encryptors for context and diagnostics.

type ResourceRef

type ResourceRef struct {
	Operation  string
	APIVersion string
	Group      string
	Version    string
	Resource   string
	// Kind is the manifest kind, the commit-message spelling of the {kind} placement
	// variable. It is read off the event's object, so it is EMPTY for a DELETE: the watcher
	// carries no object for one, because by then the object is gone from the cluster.
	Kind string
	// Namespace is the resource's namespace, or the literal "_cluster" when it is
	// cluster-scoped — types.ClusterScopeSegment, the same word the built-in Git path and the
	// {namespace} placement variable use. A cluster-scoped resource HAS a scope name; it is
	// simply not a namespace, and "_cluster" is a name no real namespace can collide with
	// (DNS-1123 forbids "_"). A template therefore never has to guard this field.
	Namespace string
	Name      string
	// Labels is the resource's metadata.labels as the writer commits them: the SANITIZED
	// object's labels, so one internal/sanitize strips is already absent, exactly as for the
	// "{label:key}" placement variable. It is nil for a DELETE (no object) and for a resource
	// carrying none.
	//
	// Read it with .Label, not with .Labels.key — see Label.
	Labels map[string]string
	// ResourceVersion is the metadata.resourceVersion of the observed state this commit wrote,
	// and "" for a producer that observed none (reconcile, bootstrap). Guard it with
	// {{with .ResourceVersion}} — empty here has one meaning, "this producer observed no
	// version", and no sentinel states that better than silence.
	//
	// It is NOT the object's current version, and the gap is deliberate: an update whose
	// git-writable content is unchanged (a /status-only write) is never routed, so this value
	// lags the cluster's. Compare it for EQUALITY — equal means nothing is pending — never by
	// subtraction: resourceVersion is opaque by contract and is the global store revision in
	// practice, so a gap measures other objects' writes, not our own drops. The census
	// counts those: watch_events_total{outcome="unchanged"}.
	//
	// Unlike Kind and Labels, a DELETE DOES carry one — see Event.ResourceVersion.
	ResourceVersion string
	// Generation is the metadata.generation of the state this commit wrote, and 0 when there is
	// none. It moves only when the DESIRED state changes, so unlike ResourceVersion it is worth
	// comparing: it is per object, starts at 1, and advances once per spec write, so a gap
	// between two commits really does mean spec changes that were not committed separately.
	//
	// Two blind spots keep it from replacing ResourceVersion. A ConfigMap, a Secret, and any
	// other kind without a spec never carry one, so this stays 0 for much of what a target
	// mirrors. And a label- or annotation-only edit changes what gets committed WITHOUT moving
	// it, so an unchanged Generation does not mean an unchanged commit.
	//
	// Guard it with {{with .Generation}}, which renders nothing for 0.
	Generation int64
}

ResourceRef is the lightweight resource identifier emitted to grouped commit templates via LiveCommitMessageData.Resources.

Its fields are deliberately the placement template language's variables under Go template casing: {namespace} is .Namespace, {kind} is .Kind, and so on. The two renderers are separate on purpose (a path must be statically checkable, a message must be able to range over n resources), but a reader should not have to learn two vocabularies to describe the same resource. See docs/configuration.md.

func (ResourceRef) Label added in v0.46.0

func (r ResourceRef) Label(key string) string

Label is the value of one label on this resource, and "" when it does not carry the label.

Use it rather than indexing Labels directly. These templates render with missingkey=error, so "{{.Labels.team}}" does not render empty for a resource that has no "team" label: it fails the render, and a failed render fails the whole commit (the window's events are lost until the next resync). "{{.Label \"team\"}}" renders empty instead, which is this language's counterpart of the "_unlabeled" bucket a path falls back to.

func (ResourceRef) String

func (r ResourceRef) String() string

String renders the ref as group/version/resource/namespace/name, where the namespace segment is "_cluster" for a cluster-scoped resource (Namespace never renders blank). Templates that {{range}} over Resources and print each entry get that form.

type ResyncRequest

type ResyncRequest struct {
	Desired            []manifestanalyzer.DesiredResource
	ResourceVersion    string
	GitTargetName      string
	GitTargetNamespace string
	// Scope, when set, makes this a per-type (M12) reconcile/sweep: the mark-and-sweep is
	// restricted to the named type — and, when the scope names a namespace, to that
	// namespace — while Desired carries only that scope's objects (empty = pure sweep of a
	// removed type). Nil is a whole-GitTarget resync. See ResyncScope for the invariant
	// binding this to Desired.
	Scope *ResyncScope
	// Heal marks a non-urgent drift-correcting resync the worker DEFERS while a commit window is
	// open. One worker serves N GitTargets and the window is a worker singleton, so a
	// force-finalizing heal can steal a DIFFERENT GitTarget's held CommitRequest window. Waiting
	// for idle recurs on every silence timeout, so it never starves. A first-sync backfill is NOT
	// a heal: it must establish initial state promptly.
	Heal bool
	// SourceCollection names the target-watch collection that gathered this snapshot. Zero for a
	// whole-GitTarget resync, which speaks for no single collection. Diagnostic only: nothing
	// filters the queue on it. See source_collection.go.
	SourceCollection types.CollectionKey
	// Result receives exactly one reply. It is buffered (cap 1) by the emitter so
	// the worker never blocks delivering it.
	Result chan ResyncResult
	// contains filtered or unexported fields
}

ResyncRequest is a synchronous resync of one GitTarget against a complete, version-pinned desired snapshot (M8). It rides the worker queue so the single git-mutating goroutine applies it in order with live events, and replies on Result once the local commit is created. The desired set is the whole watched resource state at ResourceVersion; the worker's content-derived mark-and-sweep drops any managed document the snapshot did not contain.

type ResyncResult

type ResyncResult struct {
	Stats ResyncStats
	Err   error
}

ResyncResult is the reply to a ResyncRequest: the plan's change counts, or an error if the resync could not be applied (in which case nothing was committed).

type ResyncScope added in v0.39.0

type ResyncScope struct {
	// Collection is the sweep boundary and the scope's identity: group, resource, namespace.
	Collection types.CollectionKey
	// Version is the served version the desired set was gathered at. It is DATA, not
	// identity: it renders the reconcile commit message's {{.APIVersion}} and names the
	// version a snapshot came from in logs, and it is deliberately absent from the collection
	// key, so a scope always round-trips to the boundary it sweeps (types.CollectionKey).
	Version string
}

ResyncScope restricts a resync's mark-and-sweep to the slice of the mirror the desired snapshot was actually gathered over: one collection, and the served version that collection was gathered at.

The invariant: THE SWEEP SCOPE MUST BE EXACTLY THE SCOPE THE DESIRED SET WAS GATHERED OVER. Narrower deletes documents that were never in scope; wider silently leaves documents unmanaged. The namespace lives inside the collection so a per-namespace replay cannot reach the sweep carrying only its type — a replay of one namespace once swept every other namespace's documents.

func ResyncScopeFor added in v0.42.0

func ResyncScopeFor(gvr schema.GroupVersionResource, namespace string) ResyncScope

ResyncScopeFor builds a scope from the served GVR a snapshot was gathered with and the namespace it was gathered in. It is the only constructor: going through it is what keeps the version on the data side of the type and out of the identity.

func (*ResyncScope) GVR added in v0.39.0

GVR reconstructs the served GroupVersionResource this scope was gathered with, for the callers that must talk to the API machinery in its own terms.

func (*ResyncScope) Matches added in v0.39.0

func (s *ResyncScope) Matches(ri types.ResourceIdentifier) bool

Matches reports whether a resolved resource identity falls inside this scope. A nil scope matches everything (whole-GitTarget resync). An empty namespace matches every namespace for the type.

func (*ResyncScope) Overlaps added in v0.50.0

func (s *ResyncScope) Overlaps(other *ResyncScope) bool

Overlaps reports whether two scopes' sweep boundaries can hold the same document. A nil scope is the whole GitTarget and overlaps every scope.

func (*ResyncScope) String added in v0.39.0

func (s *ResyncScope) String() string

String renders the scope for logs and for the deferred-heal key. It is nil-safe: a nil scope is the whole-GitTarget resync and renders empty.

type ResyncStats

type ResyncStats struct {
	Created          int
	Updated          int
	Deleted          int
	Skipped          int
	PlacementSkipped int
	// Retained is how many managed documents this resync's prune policy kept that a converged
	// mirror would have dropped. It is the ONE count here that does not describe something the
	// resync did: a suppressed drop produces no action, no commit, and no other stat, so without
	// it nothing downstream can tell a converged mirror from a deliberately retaining one. It
	// rides the reply channel to the drain, which rolls it up onto GitTarget status.
	Retained int
	// PruneMode stamps Retained with the effective policy that produced it, so the count and the
	// reason for it travel together. Reading the mode from the spec at projection time instead
	// would let a target that has just been switched publish a new mode beside a count the old
	// one produced.
	PruneMode v1alpha3.PruneMode
}

ResyncStats summarises what a resync changed. Skipped is documents present but not safely editable; PlacementSkipped is new resources the writer refused to place fail-safe. Both are counted and logged per-resource rather than swallowed, so a not-mirrored resource is visible in the summary. Neither has a dedicated status condition yet.

type SOPSEncryptor

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

SOPSEncryptor encrypts YAML by invoking the external sops binary.

func NewSOPSEncryptor

func NewSOPSEncryptor(binaryPath, configPath string) *SOPSEncryptor

NewSOPSEncryptor creates an Encryptor that shells out to sops.

func NewSOPSEncryptorWithEnv

func NewSOPSEncryptorWithEnv(binaryPath, configPath, workDir string, env map[string]string) *SOPSEncryptor

NewSOPSEncryptorWithEnv creates an Encryptor that shells out to sops with additional environment variables.

func (*SOPSEncryptor) Encrypt

func (e *SOPSEncryptor) Encrypt(ctx context.Context, plain []byte, meta ResourceMeta) ([]byte, error)

Encrypt streams plaintext YAML to sops over stdin and returns encrypted YAML bytes.

type SSHHostKeyConfig

type SSHHostKeyConfig struct {
	// ControllerNamespace is the namespace the controller runs in; it scopes the install-level
	// default known-hosts ConfigMap.
	ControllerNamespace string

	// DefaultKnownHostsConfigMap names an optional install-level ConfigMap in ControllerNamespace
	// that supplies known_hosts when neither the credentials Secret nor the GitProvider supplies
	// it. Empty disables this layer.
	DefaultKnownHostsConfigMap string

	// AllowMissingKnownHosts permits SSH only when NO host-key source produced any known_hosts at
	// all (the controller's --insecure-allow-missing-known-hosts flag). A known_hosts that is
	// present but unparseable is always a hard error.
	AllowMissingKnownHosts bool
}

SSHHostKeyConfig configures where SSH known_hosts (host-trust material) are sourced and the dev-only escape hatch for a host with no pinned key. It is set once at startup and threaded to every credentials read. Its zero value fails closed: no install-level default and no missing-key opt-out.

type ScanAcceptanceReporter added in v0.50.0

type ScanAcceptanceReporter func(
	target itypes.ResourceReference,
	refused *manifestanalyzer.AcceptanceRefusedError,
)

ScanAcceptanceReporter publishes what a READ-ONLY scan of a GitTarget's folder found: a structural refusal, or its recovery when refused is nil.

It is the refresher's twin of PathRefusalReporter and deliberately a separate hook, because the two are not the same fact. A refusal reported here was found by READING, with nobody writing and nothing lost yet; one reported through PathRefusalReporter aborted a write that had work in hand. They also recover differently — see Manager.MarkTargetGitPathScanAccepted — and a single hook would have had to carry a collection that a whole-folder scan does not have.

type UserInfo

type UserInfo struct {
	Username string
	UID      string
	// DisplayName is the human-readable name from the OIDC "name" claim, when
	// the audit event carries it. Empty means "fall back to Username".
	DisplayName string
	// Email is the address from the OIDC "email" claim, when the audit event
	// carries it. Empty means "fall back to ConstructSafeEmail(Username)".
	Email string
}

UserInfo contains relevant user information for commit messages.

func UnresolvedAuthor added in v0.39.0

func UnresolvedAuthor() UserInfo

UnresolvedAuthor is the identity written to the Git AUTHOR HEADER when attribution ran and did not resolve an actor. It exists so an unresolved attribution is visible in `git log` instead of being indistinguishable from a configured-author commit.

Scope: the git author header and nothing else. DERIVED at the write path from the carried outcome, never stamped onto an Event, so it does NOT reach window grouping, message bodies, or {{.Username}} templates — pushing a magic token there would change commit text in every existing deployment and force user templates to handle a value they never had.

Three strings because the header needs all three: Username is the greppable machine token, DisplayName is what a human reads, Email uses the RFC 2606 .invalid TLD so it never routes mail.

type WorkItem

type WorkItem struct {
	// Request is a resource-write request.
	Request *WriteRequest
	// Attach is a CommitRequest attach: bind a message to the author's window and
	// let its timers close that window.
	Attach *AttachCommitRequest
	// Resync is the FIFO position of a streaming-snapshot resync request (M8): a synchronous
	// request/reply that materialises a GitTarget's complete desired set.
	Resync *resyncMarker
	// Refresh asks the worker to re-prove where its branch is on the remote. It is the only
	// work item that never writes anything: see RefreshRequest.
	Refresh *RefreshRequest
	// Withdraw cancels a CommitRequest the worker has not acted on yet. It rides the same FIFO as
	// the attach, so it is always handled after every attach sent before it.
	Withdraw *AttachCommitRequest
	// contains filtered or unexported fields
}

WorkItem is the unit of work in the BranchWorker queue. Exactly one of Request, Attach, Resync, or Refresh is set.

type WorkerManager

type WorkerManager struct {
	Client client.Client
	Log    logr.Logger
	// contains filtered or unexported fields
}

WorkerManager manages BranchWorkers. Creates workers per (repo, branch), shared by multiple GitDestinations. Implements controller-runtime's Runnable interface for lifecycle management.

func NewWorkerManager

func NewWorkerManager(
	client client.Client,
	log logr.Logger,
	limits BranchWorkerLimits,
	sensitiveResources types.SensitiveResourcePolicy,
) *WorkerManager

NewWorkerManager creates a new worker manager. limits bounds every worker this manager creates; a zero value of either knob takes that knob's default.

func (*WorkerManager) AcknowledgeReplacement added in v0.50.0

func (m *WorkerManager) AcknowledgeReplacement(key BranchKey)

AcknowledgeReplacement clears a pending recovery, and must be called only once the targets on the branch have actually been told. Anything less leaves the request standing, which is the whole point of keeping it here: a failed delivery is retried by the next reconcile of any GitTarget on the branch, and by the periodic sweep that follows them.

func (*WorkerManager) CommitRequestOwner added in v0.51.0

func (m *WorkerManager) CommitRequestOwner(namespace, name, uid string) (*BranchWorker, bool)

CommitRequestOwner returns the worker that holds a CommitRequest: the one that accepted its attach and has not given it back. It is found whatever has happened to the request's GitTarget since, including a worker retired and still finishing its last push, so a caller that finds no owner knows that no worker can still commit the request.

func (*WorkerManager) EnsureWorker

func (m *WorkerManager) EnsureWorker(
	_ context.Context,
	providerName, providerNamespace string,
	branch string,
	repo RepoIdentity,
	parentBranch string,
) error

EnsureWorker ensures a worker exists for the given (provider, branch), and that it is the worker for the repository the GitProvider names NOW.

repo is the caller's answer to "which repository is this"; the reconcile has already read the GitProvider one gate earlier, so it costs no API call. A slot holding a worker for a DIFFERENT repository is not corrected field by field — the clone, the base trust, the observation and the retained writes are all statements about the old one — so that worker is stopped, its checkout reclaimed, and a fresh one takes the slot.

A replacement is not complete when it returns: the new worker has no clone and no queue, so every GitTarget on that branch has to re-establish its folder against the new repository. That is recorded as a PENDING RECOVERY rather than returned, because arranging it is the caller's job and the caller can fail. See ReplacementPending; the delivery is the GitTarget reconcile's worker wiring gate.

parentBranch is the GitTarget's spec.parentBranch. It is immutable on a GitTarget, so a live worker sees a different one only when every target on its branch was replaced before the worker sweep ran. That is not a replacement: the worker keeps its clone, and SetParentBranch raises the parent generation, so nothing planned under the old parent is pushed and retained writes are rebuilt on the new one.

Worker creation/start is protected by the manager lock.

func (*WorkerManager) GetWorkerForTarget

func (m *WorkerManager) GetWorkerForTarget(
	providerName, providerNamespace string,
	branch string,
) (*BranchWorker, bool)

GetWorkerForTarget finds the worker for a target's (provider, branch). Returns the worker and true if found, nil and false otherwise. This is used by EventRouter to dispatch events to the correct worker.

func (*WorkerManager) NeedLeaderElection

func (m *WorkerManager) NeedLeaderElection() bool

NeedLeaderElection ensures only the elected leader manages workers. This prevents multiple pods from managing the same workers.

func (*WorkerManager) PublicationEvents added in v0.52.0

func (m *WorkerManager) PublicationEvents() <-chan event.GenericEvent

PublicationEvents carries a GitTarget whose branch worker's publication report changed, for the GitTarget controller to re-project its status within one reconcile instead of at its next requeue. Nil for a manager not built by NewWorkerManager.

func (*WorkerManager) ReconcileWorkers

func (m *WorkerManager) ReconcileWorkers(ctx context.Context) error

ReconcileWorkers stops every worker no live GitTarget still needs.

It is the ONLY thing that removes a worker before shutdown, and it is driven from the GitTarget reconcile's deleted-object path: that reconcile is what the delete watch event produces, so the sweep runs exactly when the set of needed workers can have shrunk. A worker that outlives its last GitTarget is not free — it holds a goroutine, an event queue and an on-disk clone until the process restarts.

It decides from the API rather than from a count of registrations, which is what makes it correct for a branch SHARED by several GitTargets: deleting one of them leaves the others listed, so the worker they share is still needed and is left alone.

It fails safe. A List that errors stops nothing: the alternative — treating "I could not read the targets" as "there are no targets" — would take down every live worker in the process.

func (*WorkerManager) RemoteForBranch added in v0.50.0

func (m *WorkerManager) RemoteForBranch(key BranchKey) (RemoteObservation, bool)

RemoteForBranch is what this branch was last proved to be at, and whether anything has looked at all. It opens no connection and reads no repository.

It answers for the BRANCH, which is the whole of the delivery half: two GitTargets writing different folders of one branch share a worker, so they share its observation and the moment it was proved. Absent means nothing has looked yet, which is different from a present observation with no revision: that one means the branch is not on the remote.

func (*WorkerManager) RenderFidelityGate added in v0.37.0

func (m *WorkerManager) RenderFidelityGate() *RenderFidelityGate

RenderFidelityGate returns the manager-wide target gate used by branch workers. The gate is safe for concurrent watch and worker access.

func (*WorkerManager) ReplacementPending added in v0.50.0

func (m *WorkerManager) ReplacementPending(key BranchKey) bool

ReplacementPending reports whether this branch's GitTargets still have to be re-established after their worker was replaced.

It answers for the BRANCH rather than for the caller, so whichever GitTarget reconciles next picks the recovery up — including the siblings, which have no reason of their own to notice that the repository changed.

func (*WorkerManager) SetClusterMapper added in v0.38.0

func (m *WorkerManager) SetClusterMapper(resolver func(clusterID string) typeset.Lookup)

SetClusterMapper injects the per-source-cluster GVK->GVR resolver used by every worker's store scan when a GitTarget names a source cluster. Like SetMapper, it is called once at startup before any worker is created, so each worker created by EnsureWorker carries it.

func (*WorkerManager) SetCredentialTransportPolicy added in v0.45.1

func (m *WorkerManager) SetCredentialTransportPolicy(policy CredentialTransportPolicy)

SetCredentialTransportPolicy injects Git credential transport policy into every worker.

func (*WorkerManager) SetLayoutReporter added in v0.43.0

func (m *WorkerManager) SetLayoutReporter(reporter LayoutReporter)

SetLayoutReporter injects the hook every worker calls after a scan resolves a target's folder layout, so status.placement and LayoutResolved reflect the folder rather than being computed and dropped. Like SetPathRefusalReporter, it is called once at startup before any worker is created.

func (*WorkerManager) SetMapper

func (m *WorkerManager) SetMapper(mapper typeset.Lookup)

SetMapper injects the GVK->GVR resolver used by every worker's store scan. It is called once at startup, before any GitTarget registers a worker, so each worker created by EnsureWorker carries it.

func (*WorkerManager) SetPathRefusalReporter

func (m *WorkerManager) SetPathRefusalReporter(reporter PathRefusalReporter)

SetPathRefusalReporter injects the hook every worker calls when a live write plan is refused, so the refusal reaches GitTarget status instead of being logged and dropped. Like SetMapper, it is called once at startup before any worker is created.

func (*WorkerManager) SetSSHHostKeyConfig

func (m *WorkerManager) SetSSHHostKeyConfig(cfg SSHHostKeyConfig)

SetSSHHostKeyConfig injects the SSH host-key resolution config used by every worker's credential reads. Like SetMapper, it is called once at startup before any worker is created.

func (*WorkerManager) SetScanAcceptanceReporter added in v0.50.0

func (m *WorkerManager) SetScanAcceptanceReporter(reporter ScanAcceptanceReporter)

SetScanAcceptanceReporter injects the hook the refresher calls after it re-reads a target's folder, so a structural refusal that arrived in Git surfaces before anything tries to write it. Like SetLayoutReporter, it is called once at startup before any worker is created.

func (*WorkerManager) Start

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

Start implements manager.Runnable interface. This is called by controller-runtime when the manager starts.

type WriteRequest

type WriteRequest struct {
	Events             []Event
	CommitMessage      string
	CommitConfig       *CommitConfig
	Signer             gogit.Signer
	GitTargetName      string
	GitTargetNamespace string
	BootstrapOptions   pathBootstrapOptions
	CommitMode         CommitMode
}

WriteRequest is the unit of work queued and written by the BranchWorker.

Directories

Path Synopsis
Package manifestedit is an isolated proof of concept for the manifest-inventory "file-agnostic placement" feature.
Package manifestedit is an isolated proof of concept for the manifest-inventory "file-agnostic placement" feature.

Jump to

Keyboard shortcuts

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