Documentation
¶
Overview ¶
Package change computes file-level differences between two filesystem trees and maps them onto the Flux resources they affect.
It is the core of flate's "changed-only" mode (--path-orig on any command): walk both trees in parallel, SHA-256 every regular file, emit the relative paths that differ. Callers then ask Filter whether a given resource — identified by the file it was loaded from — has changed.
The filter cascades through references: when a HelmRelease's file changed, its chart source (OCIRepository / HelmRepository / GitRepository) is also marked needed so the actual chart still gets downloaded. Likewise, a Kustomization's sourceRef and dependsOn ancestors are kept ready so downstream waits succeed.
The cascade also runs in reverse: when the changed file IS a source resource, every HelmRelease/Kustomization that references it is kept so its render re-runs against the new source spec. This catches the centralized-source layout where an OCIRepository lives in its own Kustomization tree, separate from the HelmReleases that chartRef it — bumping the OCIRepository's tag must still re-render those consumers.
Index ¶
- type Filter
- func (f *Filter) AddEmitted(emitter, child manifest.NamedResource)
- func (f *Filter) Enabled() bool
- func (f *Filter) KeepNames() []string
- func (f *Filter) KeepNamespaces() map[string]struct{}
- func (f *Filter) ProducersFor(id manifest.NamedResource) []manifest.NamedResource
- func (f *Filter) ShouldReconcile(id manifest.NamedResource) bool
- func (f *Filter) Size() int
- type ObjectLister
- type Set
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Filter ¶
type Filter struct {
// OnAdd, when non-nil, fires for every id newly added to the
// keep set by AddEmitted / Add (including transitive-dep
// recursion). The orchestrator wires this to refire
// EventObjectAdded for source-kind ids whose listener already
// short-circuited via PreGate before the consuming KS joined
// keep. Issue #260.
//
// Set BEFORE controllers start. The Filter calls OnAdd outside
// its internal lock so callbacks are free to take other locks.
OnAdd func(manifest.NamedResource)
// contains filtered or unexported fields
}
Filter answers "should I reconcile this resource?" by checking against a keep-set resolved from a file-level diff. Construct via NewFilter — the zero value is the "no filtering" sentinel and returns true from ShouldReconcile for every id.
The keep set has two tiers:
- "keep" entries reconcile. resolve() seeds this from file changes + ancestors of changed files (#58) + structural parents of owner KSes (#103).
- "primary" is the subset whose render output likely differs from baseline: file-change owners, their siblings under the same owner, transitive deps walked from a primary entry, and runtime entries inserted by AddEmitted from a primary emitter. Ancestor-only entries are explicitly NOT primary.
The keep set extends at runtime via AddEmitted when a primary parent KS renders and emits a child the file-walk couldn't see (kustomize component + replacement patterns generate Flux Kustomizations on the fly — see #204). Ancestor-only emitters don't propagate keep to file-loaded children, which prevents a one-file change from cascading the entire tree into keep.
func NewFilter ¶
func NewFilter(changes *Set, sourceFiles map[manifest.NamedResource]string, repoRoot string, objs ObjectLister) *Filter
NewFilter constructs a fully-resolved Filter in one shot. It walks the file-level Changes set, attributes each change to the most specific Flux Kustomization that owns it, then expands transitive dependencies (chart sources, sourceRef, valuesFrom). Pass a nil changes argument to construct a disabled filter (ShouldReconcile returns true for everything).
- Every resource whose source file changed is kept.
- For each changed file, the most-specific Flux Kustomization that owns it (longest matching spec.path, including spec.components) is kept — along with every resource whose source file shares the same owner.
- Ancestor Kustomizations (shorter-prefix spec.path matches) are also kept so parent-injected patches / postBuild.substituteFrom land before the leaf renders. See #58.
- BFS over chart sources, sourceRef, and valuesFrom to pull in upstream dependencies. dependsOn is intentionally excluded.
- Reverse edge: when a changed file IS itself a source resource (OCIRepository / HelmRepository / GitRepository / Bucket / ExternalArtifact / HelmChart), every HelmRelease and Kustomization that references it is kept primary so its render re-runs against the new source spec — e.g. an OCIRepository spec.ref.tag bump pulls a different chart version. Only fires for a source whose OWN file changed (not one merely pulled in as a forward dep of step 4), so a single HR edit can't reverse- cascade into every sibling sharing its source.
func NewFilterWithCache ¶
func NewFilterWithCache(changes *Set, sourceFiles map[manifest.NamedResource]string, repoRoot string, objs ObjectLister, cache *manifest.ComponentCache, consumerRefs map[manifest.NamedResource][]manifest.NamedResource) *Filter
NewFilterWithCache is NewFilter with a shared *manifest.ComponentCache threaded into resolve()'s buildOwnership call. The orchestrator instantiates one cache per Bootstrap and passes the same pointer here and to the loader so a single kustomization.yaml's `components:` field is read once across the entire Bootstrap (vs. once per consumer: loader's parent index, discovery's orphan promotion, and the Filter's ownership index all previously re-read disk). Pass nil cache to fall back to a per-resolve local cache.
consumerRefs maps each consumer (HelmRelease / Kustomization) to the source resources it references; discovery supplies it because HelmReleases are absent from the Store at resolve() time. It drives the reverse edge (step 5 above). Pass nil to disable reverse propagation (the default for tests that don't exercise it).
func (*Filter) AddEmitted ¶
func (f *Filter) AddEmitted(emitter, child manifest.NamedResource)
AddEmitted extends the keep set with child when emitter is a "primary" keep entry — i.e. one whose own render output differs from baseline (its source file changed, it's a sibling of a changed file under a shared owner KS, or it was itself emitted by another primary parent at runtime). Ancestor-only emitters (kept so their patches/substituteFrom apply to descendants per #58) DON'T propagate keep to file-loaded children: their render output for unrelated siblings is identical to baseline, and cascading those siblings through the keep set turns a one-file change into a full-tree reconcile.
child inherits the emitter's primacy: AddEmitted walks transitiveDeps recursively for sourceRef / chartRef / valuesFrom (issue #260) and marks every newly-added entry primary so their own future emissions cascade correctly.
Used by the KS controller when a parent KS in the keep set renders and emits id as a child. Covers the kustomize component+replacement pattern (parent emits render-only per-app Kustomization from a CM-driven replacement, see #204) AND the patch-propagation chain (primary parent emits patched file-loaded child whose render-with-new-patches differs from baseline).
Newly-added ids are passed to OnAdd (when configured) so the orchestrator can refire dependent listeners (e.g. retrigger the source controller's fetch for a source whose PreGate-skip happened before its consumer joined keep).
Ordering contract for embedders: call AddEmitted(parent, child) BEFORE the emitting Store.AddObject(child). Store events fire synchronously on the calling goroutine, so the controller's listener invokes PreGate (and thus ShouldReconcile) inside that AddObject — if AddEmitted ran after, the listener sees the old keep set and short-circuits to Ready/"unchanged".
No-op when the filter is disabled. Safe for concurrent use.
func (*Filter) KeepNamespaces ¶
KeepNamespaces returns the namespaces represented in the keep-set, or nil when no scope can be derived (disabled, empty, or cluster-scoped only).
func (*Filter) ProducersFor ¶
func (f *Filter) ProducersFor(id manifest.NamedResource) []manifest.NamedResource
ProducersFor returns Flux Kustomizations that render the file-backed data resource id. Populated only for Enabled filters; used by controllers to add ordering edges for data deps that arrive via another KS's render — e.g. a postBuild.substituteFrom ConfigMap rendered by an unchanged producer KS in changed-only mode. See #418.
Returns a copy — callers append-without-aliasing the filter's internal state.
func (*Filter) ShouldReconcile ¶
func (f *Filter) ShouldReconcile(id manifest.NamedResource) bool
ShouldReconcile reports whether the controller for id should do work (true when filtering is disabled). The (Kind, Name) fallback below bridges parent-Kustomization targetNamespace inheritance: a resource loaded from disk with no namespace (entry kept with Namespace="") and queried later with the inherited namespace (lookup with Namespace=X) refers to the same logical resource.
The fallback ONLY indexes keep-entries whose Namespace is empty — so a fully-namespaced lookup never matches an unrelated fully- namespaced entry that happens to share (Kind, Name). Without this asymmetry the keep set would silently expand across namespaces (e.g. a kept `Kustomization/cluster-infra/external-secrets` dragging an unrelated `Kustomization/database/external-secrets` into scope on name match alone).
type ObjectLister ¶
type ObjectLister interface {
GetObject(manifest.NamedResource) manifest.BaseManifest
ListObjects(kind string) []manifest.BaseManifest
}
ObjectLister is the Store surface filter resolution needs.
type Set ¶
type Set struct {
// contains filtered or unexported fields
}
Set is the immutable result of Detect — the set of file paths (relative to the scan roots) whose contents differ.
func Detect ¶
Detect returns the set of repo-relative file paths that differ between before and after.
Fast path: when git is on $PATH, `git diff --no-index --name-status -z` does the comparison in C. For a 50k-file tree with one changed file, this finishes in ~10–50ms vs ~200ms–3s for the Go walker — because git's tree-walk + content-compare is implemented in C and uses index-style optimizations the Go walker can't match. The Go path remains as a fallback for: (a) systems without git installed, (b) git invocations that fail with an unexpected exit code, and (c) paths where git refuses to operate.
Slow path: walks before and after concurrently, then hashes every same-sized file pair. The previous (size, mtime) fast-path was removed — on coarse-granularity filesystems (HFS+ 1s, fresh `git checkout` clock-stamping) two distinct same-sized files written in the same second produce indistinguishable mtimes, so trusting them as identical silently dropped real changes. Always hashing is the only correctness-preserving option on the fallback path; the git path doesn't need it because content comparison is intrinsic to git's diff machinery.
Directories whose name begins with "." (e.g. .git, .flate-cache) and well-known noise dirs (node_modules, vendor) are skipped on both paths.
func (*Set) Contains ¶
Contains reports whether rel is in the change set. rel is expected to be filepath.ToSlash-normalized.