change

package
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Jun 11, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

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 is kept ready so downstream waits succeed. dependsOn is intentionally excluded: it's a reconcile-ordering signal, not a content dependency.

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

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).

  1. Every resource whose source file changed is kept.
  2. 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.
  3. Ancestor Kustomizations (shorter-prefix spec.path matches) are also kept so parent-injected patches / postBuild.substituteFrom land before the leaf renders. See #58.
  4. BFS over chart sources, sourceRef, and valuesFrom to pull in upstream dependencies. dependsOn is intentionally excluded.
  5. 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 manifest.NamedResource, child manifest.BaseManifest)

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 the child's sourceRef / chartRef / valuesFrom edges recursively (issue #260) and marks every newly-added entry primary so their own future emissions cascade correctly. Those edges are read straight off the child manifest (transitiveDepsOf), NOT via a Store lookup — a render-emitted child is kept here BEFORE its Store.AddObject (the ordering contract below), so it isn't yet visible to the Store, and its namespace- stamped chart source (e.g. a shared OCIRepository) would otherwise go unkept and unfetched.

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) Enabled

func (f *Filter) Enabled() bool

Enabled reports whether change-based filtering is active.

func (*Filter) KeepNames

func (f *Filter) KeepNames() []string

KeepNames returns the resolved keep-set as sorted strings for logs.

func (*Filter) KeepNamespaces

func (f *Filter) KeepNamespaces() map[string]struct{}

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).

func (*Filter) Size

func (f *Filter) Size() int

Size returns the number of resources in the resolved keep set.

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

func Detect(before, after string) (*Set, error)

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 NewSet

func NewSet(paths []string) *Set

NewSet constructs a Set from an iterable of relative paths.

func (*Set) Contains

func (s *Set) Contains(rel string) bool

Contains reports whether rel is in the change set. rel is expected to be filepath.ToSlash-normalized.

func (*Set) Len

func (s *Set) Len() int

Len reports how many files differ.

func (*Set) Paths

func (s *Set) Paths() []string

Paths returns the changed files as a sorted slice.

func (*Set) Reroot

func (s *Set) Reroot(prefix string) *Set

Reroot returns a copy of s with prefix prepended to every entry — used to lift a change set produced from a subdir-relative diff up into the repo-relative coordinate system that SourceFiles uses.

Jump to

Keyboard shortcuts

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